diff --git a/.vscode/launch.json b/.vscode/launch.json index 985a6243db8..eadea56323a 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -57,26 +57,6 @@ ], "cwd": "${workspaceFolder}" }, - { - "type": "lldb", - "request": "launch", - "name": "executable 'stacks-node' -- mocknet", - "cargo": { - "args": [ - "build", - "--bin=stacks-node", - "--package=stacks-node" - ], - "filter": { - "name": "stacks-node", - "kind": "bin" - } - }, - "args": [ - "mocknet" - ], - "cwd": "${workspaceFolder}" - }, { "type": "lldb", "request": "launch", diff --git a/changelog.d/6408-remove-helium.removed b/changelog.d/6408-remove-helium.removed new file mode 100644 index 00000000000..aba0b6459ea --- /dev/null +++ b/changelog.d/6408-remove-helium.removed @@ -0,0 +1 @@ +Removed `helium` network mode and its legacy Stacks 2.x run loop. This included the `Node` struct, `Tenure` struct, `RunLoopCallbacks`, and related test infrastructure incompatible with Epoch 3 (Nakamoto). Shared genesis utilities relocated to `genesis.rs`. Use [Clarinet](https://github.com/hirosystems/clarinet) or `nakamoto-neon` mode for local development. diff --git a/changelog.d/6553-remove-mocknet.removed b/changelog.d/6553-remove-mocknet.removed new file mode 100644 index 00000000000..3683777f7bd --- /dev/null +++ b/changelog.d/6553-remove-mocknet.removed @@ -0,0 +1 @@ +Removed `mocknet` network mode. This used a simulated in-process burnchain incompatible with Epoch 3 (Nakamoto). Use [Clarinet](https://github.com/hirosystems/clarinet) or a private testnet (`nakamoto-neon` mode) for local development. diff --git a/contrib/stacks-inspect/README.md b/contrib/stacks-inspect/README.md index d24ef59d026..4759f080d2e 100644 --- a/contrib/stacks-inspect/README.md +++ b/contrib/stacks-inspect/README.md @@ -12,7 +12,7 @@ cargo build -p stacks-inspect ``` --config Path to stacks-node configuration file ---network-config Use a predefined network (helium, mainnet, mocknet, xenon) +--network-config Use a predefined network (mainnet, xenon) --version Show version information --help Show help ``` diff --git a/contrib/stacks-inspect/src/cli.rs b/contrib/stacks-inspect/src/cli.rs index f796558e111..a94178916f7 100644 --- a/contrib/stacks-inspect/src/cli.rs +++ b/contrib/stacks-inspect/src/cli.rs @@ -115,7 +115,7 @@ pub struct Cli { #[arg(long, global = true, value_name = "CONFIG_FILE")] pub config: Option, - /// Use a predefined network configuration (helium, mainnet, mocknet, xenon) + /// Use a predefined network configuration (mainnet, xenon) #[arg(long = "network-config", global = true, value_name = "NETWORK")] pub network_config: Option, diff --git a/contrib/stacks-inspect/src/main.rs b/contrib/stacks-inspect/src/main.rs index a08efb42dd3..8b7071b810f 100644 --- a/contrib/stacks-inspect/src/main.rs +++ b/contrib/stacks-inspect/src/main.rs @@ -322,9 +322,7 @@ fn build_common_opts(cli: &Cli) -> CommonOpts { // Handle --network-config option if let Some(ref network) = cli.network_config { let config_file = match network.to_lowercase().as_str() { - "helium" => ConfigFile::helium(), "mainnet" => ConfigFile::mainnet(), - "mocknet" => ConfigFile::mocknet(), "xenon" => ConfigFile::xenon(), other => { eprintln!("Unknown network choice `{other}`"); diff --git a/docs/follower.md b/docs/follower.md index a299e4677b1..2f7d5bdb718 100644 --- a/docs/follower.md +++ b/docs/follower.md @@ -69,17 +69,9 @@ events_keys = ["stackerdb", "block_proposal", "burn_blocks"] auth_token = "your-secret-token" ``` -## Local Development (Mocknet) +## Local Development -For local development without a Bitcoin node, use mocknet mode: - -```bash -stacks-node start --config=mocknet.toml -``` - -Mocknet runs a simulated burnchain in-process, removes execution cost limits, -and requires pre-funded test accounts via `[[ustx_balance]]` entries. -See [`mocknet.toml`](../sample/conf/mocknet.toml). +For local development, use [Clarinet](https://github.com/hirosystems/clarinet) for contract development, or run a private testnet with `nakamoto-neon` mode and a local bitcoind regtest node. ## Environment Variables @@ -99,7 +91,6 @@ These environment variables affect node behavior and cannot be set via TOML: | --- | --- | | [`mainnet-follower-conf.toml`](../sample/conf/mainnet-follower-conf.toml) | Mainnet follower | | [`testnet-follower-conf.toml`](../sample/conf/testnet-follower-conf.toml) | Testnet follower | -| [`mocknet.toml`](../sample/conf/mocknet.toml) | Local mocknet development | ## Further Reading diff --git a/docs/mining.md b/docs/mining.md index ff558e33682..d8323b2637a 100644 --- a/docs/mining.md +++ b/docs/mining.md @@ -60,7 +60,6 @@ signer configuration and the critical miner-signer coordination settings. | [`mainnet-follower-conf.toml`](../sample/conf/mainnet-follower-conf.toml) | Mainnet follower (read-only node) | | [`testnet-follower-conf.toml`](../sample/conf/testnet-follower-conf.toml) | Testnet follower | | [`testnet-signer.toml`](../sample/conf/testnet-signer.toml) | Testnet node-side signer config | -| [`mocknet.toml`](../sample/conf/mocknet.toml) | Local mocknet development | ## RBF Configuration diff --git a/sample/conf/mainnet-miner-conf.toml b/sample/conf/mainnet-miner-conf.toml index 5dadc9e4475..fa419e029ad 100644 --- a/sample/conf/mainnet-miner-conf.toml +++ b/sample/conf/mainnet-miner-conf.toml @@ -54,7 +54,7 @@ p2p_bind = "0.0.0.0:20444" # bootstrap_node = "02e...@seed.mainnet.hiro.so:20444" # Human-readable node name (used in logging). -# Default: "helium-node" +# Default: "stacks-node" # name = "my-stacks-miner" # Disable microblocks (not used in Nakamoto / Epoch 3.0+). diff --git a/sample/conf/mocknet.toml b/sample/conf/mocknet.toml deleted file mode 100644 index fee25e096db..00000000000 --- a/sample/conf/mocknet.toml +++ /dev/null @@ -1,50 +0,0 @@ -# ============================================================ -# STACKS MOCKNET - LOCAL DEVELOPMENT CONFIGURATION -# ============================================================ -# -# Mocknet runs a simulated burnchain entirely in-process. No Bitcoin -# node is required. Execution cost limits are removed. Use this for -# local development and testing. -# -# Start with: stacks-node start --config=mocknet.toml - -[node] -# working_dir = "/stacks-data/mocknet" -rpc_bind = "0.0.0.0:20443" -p2p_bind = "0.0.0.0:20444" -prometheus_bind = "0.0.0.0:9153" - -# Enable mining so the node produces blocks. -miner = true -mock_mining = true -seed = "0000000000000000000000000000000000000000000000000000000000000000" - -[burnchain] -mode = "mocknet" - -# How long to wait after a burnchain tip before building a block. -# Default: 5_000 -# Units: milliseconds -# commit_anchor_block_within = 5000 - -# Pre-funded test accounts (10M STX each). -[[ustx_balance]] -address = "ST2QKZ4FKHAH1NQKYKYAYZPY440FEPK7GZ1R5HBP2" -amount = 10000000000000000 - -[[ustx_balance]] -address = "ST319CF5WV77KYR1H3GT0GZ7B8Q4AQPY42ETP1VPF" -amount = 10000000000000000 - -[[ustx_balance]] -address = "ST221Z6TDTC5E0BYR2V624Q2ST6R0Q71T78WTAX6H" -amount = 10000000000000000 - -[[ustx_balance]] -address = "ST2TFVBMRPS5SSNP98DQKQ5JNB2B6NZM91C4K3P7B" -amount = 10000000000000000 - -# Optional: stacks-blockchain-api event observer. -# [[events_observer]] -# endpoint = "localhost:3700" -# events_keys = ["*"] diff --git a/sample/conf/signer/mainnet-signer-conf.toml b/sample/conf/signer/mainnet-signer-conf.toml index 12fcb9319ac..ccb54f7f360 100644 --- a/sample/conf/signer/mainnet-signer-conf.toml +++ b/sample/conf/signer/mainnet-signer-conf.toml @@ -39,7 +39,7 @@ node_host = "127.0.0.1:20443" endpoint = "0.0.0.0:30000" # REQUIRED: Network selection. -# Valid values: "mainnet", "testnet", "mocknet" +# Valid values: "mainnet", "testnet" network = "mainnet" # REQUIRED: Authorization password for the node's block proposal endpoint. diff --git a/stacks-node/Stacks.toml b/stacks-node/Stacks.toml deleted file mode 100644 index 3b091e72bd9..00000000000 --- a/stacks-node/Stacks.toml +++ /dev/null @@ -1,97 +0,0 @@ -[node] -name = "helium-node" -rpc_bind = "127.0.0.1:20443" - -## Settings for local testnet, relying on a local bitcoind server -## running with the following bitcoin.conf: -## -## chain=regtest -## disablewallet=0 -## txindex=1 -## server=1 -## rpcuser=helium-node -## rpcpassword=secret -## -# [burnchain] -# chain = "bitcoin" -# mode = "helium" -# peer_host = "127.0.0.1" -# peer_port = 18444 -# rpc_port = 18443 -# rpc_ssl = false -# username = "helium-node" -# password = "secret" -# timeout = 300 -# socket_timeout = 30 -# local_mining_public_key = "04ee0b1602eb18fef7986887a7e8769a30c9df981d33c8380d255edef003abdcd243a0eb74afdf6740e6c423e62aec631519a24cf5b1d62bf8a3e06ddc695dcb77" -# satoshis_per_byte = 50 -# commit_anchor_block_within = 3000 - -## Settings for public testnet, relying on a remote bitcoind server -## hosted by blockstack -## -# [burnchain] -# chain = "bitcoin" -# mode = "argon" -# peer_host = "argon.blockstack.org" -# satoshis_per_byte = 50 -# commit_anchor_block_within = 10000 -# rpc_port = 3000 -# peer_port = 18444 - -## Settings for a local testnet simulating a burnchain -## Best setup for smart contract development -## -[burnchain] -chain = "bitcoin" -mode = "mocknet" -commit_anchor_block_within = 5000 - -# These are addresses from the README.md -[[ustx_balance]] -# Private key: b8d99fd45da58038d630d9855d3ca2466e8e0f89d3894c4724f0efc9ff4b51f001 -address = "ST2ZRX0K27GW0SP3GJCEMHD95TQGJMKB7G9Y0X1MH" -amount = 100000000 - -[[ustx_balance]] -# Private key: 3a4e84abb8abe0c1ba37cef4b604e73c82b1fe8d99015cb36b029a65099d373601 -address = "ST26FVX16539KKXZKJN098Q08HRX3XBAP541MFS0P" -amount = 100000000 - -[[ustx_balance]] -# Private key: 052cc5b8f25b1e44a65329244066f76c8057accd5316c889f476d0ea0329632c01 -address = "ST3CECAKJ4BH08JYY7W53MC81BYDT4YDA5M7S5F53" -amount = 100000000 - -[[ustx_balance]] -# Private key: 9aef533e754663a453984b69d36f109be817e9940519cc84979419e2be00864801 -address = "ST31HHVBKYCYQQJ5AQ25ZHA6W2A548ZADDQ6S16GP" -amount = 100000000 - -## Event dispatcher -## The stacks blockchain can be observed by sidecar processes, notified through TCP socket, of events such as: -## - print -## - stx-transfer / stx-burn -## - ft-mint / ft-transfer -## - nft-mint / nft-transfer -## A demo is available here: https://github.com/blockstack/stacks-blockchain-sidecar -## -# [[events_observer]] -# port = 8080 -# address = "127.0.0.1" -# events_keys = [ -# "STGT7GSMZG7EA0TS6MVSKT5JC1DCDFGZWJJZXN8A.store::print", -# "STGT7GSMZG7EA0TS6MVSKT5JC1DCDFGZWJJZXN8A.contract.ft-token", -# "STGT7GSMZG7EA0TS6MVSKT5JC1DCDFGZWJJZXN8A.contract.nft-token", -# "stx" -# ] - -## Atlas database -## The Atlas database, which handles file attachments, can be configured here -## The values used in the example below are the minimum values which Atlas will accept -## -#[atlas] -#attachments_max_size = 1048576 -#max_uninstantiated_attachments = 10000 -#uninstantiated_attachments_expire_after = 3600 -#unresolved_attachment_instances_expire_after = 172800 diff --git a/stacks-node/src/burnchains/bitcoin_regtest_controller.rs b/stacks-node/src/burnchains/bitcoin_regtest_controller.rs index 9c051d3ddd7..acd074b5080 100644 --- a/stacks-node/src/burnchains/bitcoin_regtest_controller.rs +++ b/stacks-node/src/burnchains/bitcoin_regtest_controller.rs @@ -61,7 +61,9 @@ use stacks_common::deps_common::bitcoin::blockdata::transaction::{ use stacks_common::deps_common::bitcoin::network::serialize::{serialize, serialize_hex}; use stacks_common::deps_common::bitcoin::util::hash::Sha256dHash; use stacks_common::types::chainstate::BurnchainHeaderHash; -use stacks_common::util::hash::{hex_bytes, Hash160}; +#[cfg(test)] +use stacks_common::util::hash::hex_bytes; +use stacks_common::util::hash::Hash160; use stacks_common::util::secp256k1::Secp256k1PublicKey; use stacks_common::util::sleep_ms; @@ -571,7 +573,7 @@ impl BitcoinRegtestController { let coordinator_comms = match self.use_coordinator.as_ref() { Some(x) => x.clone(), None => { - // pre-PoX helium node + // no coordinator — fall back to direct indexer sync let tip = self.receive_blocks_helium(); let height = tip.block_snapshot.block_height; return Ok((tip, height)); @@ -1948,6 +1950,7 @@ impl BitcoinRegtestController { } /// Instruct a regtest Bitcoin node to build the next block. + #[cfg(test)] pub fn build_next_block(&self, num_blocks: u64) { debug!("Generate {num_blocks} block(s)"); let public_key_bytes = match &self.config.burnchain.local_mining_public_key { @@ -1960,25 +1963,9 @@ impl BitcoinRegtestController { .expect("FATAL: invalid public key bytes"); let address = self.get_miner_address(StacksEpochId::Epoch21, &public_key); - let result = self - .get_rpc_client() - .generate_to_address(num_blocks, &address); - /* - Temporary: not using `BitcoinRpcClientResultExt::ok_or_log_panic` (test code related), - because we need this logic available outside `#[cfg(test)]` due to Helium network. - - After the Helium cleanup (https://github.com/stacks-network/stacks-core/issues/6408), - we can: - - move `build_next_block` behind `#[cfg(test)]` - - simplify this match by using `ok_or_log_panic`. - */ - match result { - Ok(_) => {} - Err(e) => { - error!("Bitcoin RPC failure: error generating block {e:?}"); - panic!(); - } - } + self.get_rpc_client() + .generate_to_address(num_blocks, &address) + .ok_or_log_panic("error generating block"); } /// Instruct a regtest Bitcoin node to build an empty block. @@ -2365,14 +2352,8 @@ impl BurnchainController for BitcoinRegtestController { &mut self, target_block_height_opt: Option, ) -> Result<(BurnchainTip, u64), BurnchainControllerError> { - let (burnchain_tip, burnchain_height) = if self.config.burnchain.mode == "helium" { - // Helium: this node is responsible for mining new burnchain blocks - self.build_next_block(1); - self.receive_blocks(true, None)? - } else { - // Neon: this node is waiting on a block to be produced - self.receive_blocks(true, target_block_height_opt)? - }; + let (burnchain_tip, burnchain_height) = + self.receive_blocks(true, target_block_height_opt)?; // Evaluate process_exit_at_block_height setting if let Some(cap) = self.config.burnchain.process_exit_at_block_height { diff --git a/stacks-node/src/burnchains/mocknet_controller.rs b/stacks-node/src/burnchains/mocknet_controller.rs deleted file mode 100644 index 09e3fa219e4..00000000000 --- a/stacks-node/src/burnchains/mocknet_controller.rs +++ /dev/null @@ -1,317 +0,0 @@ -use std::collections::VecDeque; -use std::time::Instant; - -use clarity::vm::costs::ExecutionCost; -use stacks::burnchains::bitcoin::BitcoinBlock; -use stacks::burnchains::{ - Burnchain, BurnchainBlock, BurnchainBlockHeader, BurnchainStateTransitionOps, Txid, -}; -use stacks::chainstate::burn::db::sortdb::{SortitionDB, SortitionHandleTx}; -use stacks::chainstate::burn::operations::leader_block_commit::BURN_BLOCK_MINED_AT_MODULUS; -use stacks::chainstate::burn::operations::{ - BlockstackOperationType, DelegateStxOp, LeaderBlockCommitOp, LeaderKeyRegisterOp, PreStxOp, - StackStxOp, TransferStxOp, VoteForAggregateKeyOp, -}; -use stacks::chainstate::burn::BlockSnapshot; -use stacks::core::{ - EpochList, StacksEpoch, StacksEpochId, PEER_VERSION_EPOCH_2_0, PEER_VERSION_EPOCH_2_05, - PEER_VERSION_EPOCH_2_1, STACKS_EPOCH_MAX, -}; -use stacks_common::types::chainstate::{BurnchainHeaderHash, PoxId}; -use stacks_common::util::get_epoch_time_secs; -use stacks_common::util::hash::Sha256Sum; - -use super::super::operations::BurnchainOpSigner; -use super::super::Config; -use super::{BurnchainController, BurnchainTip, Error as BurnchainControllerError}; - -/// MocknetController is simulating a simplistic burnchain. -pub struct MocknetController { - config: Config, - burnchain: Burnchain, - db: Option, - chain_tip: Option, - queued_operations: VecDeque, -} - -impl MocknetController { - pub fn generic(config: Config) -> Box { - Box::new(Self::new(config)) - } - - fn new(config: Config) -> Self { - debug!("Opening Burnchain at {}", &config.get_burn_db_path()); - let burnchain = config.get_burnchain(); - - Self { - config, - burnchain, - db: None, - queued_operations: VecDeque::new(), - chain_tip: None, - } - } - - fn build_next_block_header(current_block: &BlockSnapshot) -> BurnchainBlockHeader { - let curr_hash = ¤t_block.burn_header_hash.to_bytes()[..]; - let next_hash = Sha256Sum::from_data(curr_hash); - - let block = BurnchainBlock::Bitcoin(BitcoinBlock::new( - current_block.block_height + 1, - &BurnchainHeaderHash::from_bytes(next_hash.as_bytes()).unwrap(), - ¤t_block.burn_header_hash, - vec![], - get_epoch_time_secs(), - )); - block.header() - } -} - -impl BurnchainController for MocknetController { - fn sortdb_ref(&self) -> &SortitionDB { - self.db.as_ref().expect("BUG: did not instantiate burn DB") - } - - fn sortdb_mut(&mut self) -> &mut SortitionDB { - match self.db { - Some(ref mut sortdb) => sortdb, - None => { - unreachable!(); - } - } - } - - fn get_chain_tip(&self) -> BurnchainTip { - match &self.chain_tip { - Some(chain_tip) => chain_tip.clone(), - None => { - unreachable!(); - } - } - } - - fn get_headers_height(&self) -> u64 { - match &self.chain_tip { - Some(chain_tip) => chain_tip.block_snapshot.block_height, - None => { - unreachable!(); - } - } - } - - fn get_stacks_epochs(&self) -> EpochList { - match &self.config.burnchain.epochs { - Some(epochs) => epochs.clone(), - None => EpochList::new(&[ - StacksEpoch { - epoch_id: StacksEpochId::Epoch20, - start_height: 0, - end_height: 1, - block_limit: ExecutionCost::max_value(), - network_epoch: PEER_VERSION_EPOCH_2_0, - }, - StacksEpoch { - epoch_id: StacksEpochId::Epoch2_05, - start_height: 1, - end_height: 2, - block_limit: ExecutionCost::max_value(), - network_epoch: PEER_VERSION_EPOCH_2_05, - }, - StacksEpoch { - epoch_id: StacksEpochId::Epoch21, - start_height: 2, - end_height: STACKS_EPOCH_MAX, - block_limit: ExecutionCost::max_value(), - network_epoch: PEER_VERSION_EPOCH_2_1, - }, - ]), - } - } - - fn start( - &mut self, - _ignored_target_height_opt: Option, - ) -> Result<(BurnchainTip, u64), BurnchainControllerError> { - // If `config` sets a value, use that. Otherwise, use a default. - let epoch_vector = self.get_stacks_epochs(); - let db = match SortitionDB::connect( - &self.config.get_burn_db_file_path(), - 0, - &BurnchainHeaderHash::zero(), - get_epoch_time_secs(), - &epoch_vector, - self.burnchain.pox_constants.clone(), - None, - true, - None, - ) { - Ok(db) => db, - Err(_) => panic!("Error while connecting to burnchain db"), - }; - let block_snapshot = SortitionDB::get_canonical_burn_chain_tip(db.conn()) - .expect("FATAL: failed to get canonical chain tip"); - - self.db = Some(db); - - let genesis_state = BurnchainTip { - block_snapshot, - state_transition: BurnchainStateTransitionOps::noop(), - received_at: Instant::now(), - }; - self.chain_tip = Some(genesis_state.clone()); - let block_height = genesis_state.block_snapshot.block_height; - Ok((genesis_state, block_height)) - } - - fn submit_operation( - &mut self, - _epoch_id: StacksEpochId, - operation: BlockstackOperationType, - _op_signer: &mut BurnchainOpSigner, - ) -> Result { - let txid = operation.txid(); - self.queued_operations.push_back(operation); - Ok(txid) - } - - fn sync( - &mut self, - _ignored_target_height_opt: Option, - ) -> Result<(BurnchainTip, u64), BurnchainControllerError> { - let chain_tip = self.get_chain_tip(); - - // Simulating mining - let next_block_header = Self::build_next_block_header(&chain_tip.block_snapshot); - let mut ops = vec![]; - - while let Some(payload) = self.queued_operations.pop_front() { - let op = match payload { - BlockstackOperationType::LeaderKeyRegister(payload) => { - BlockstackOperationType::LeaderKeyRegister(LeaderKeyRegisterOp { - consensus_hash: payload.consensus_hash, - public_key: payload.public_key, - memo: payload.memo, - txid: payload.txid, - vtxindex: payload.vtxindex, - block_height: next_block_header.block_height, - burn_header_hash: next_block_header.block_hash.clone(), - }) - } - BlockstackOperationType::LeaderBlockCommit(payload) => { - BlockstackOperationType::LeaderBlockCommit(LeaderBlockCommitOp { - treatment: vec![], - sunset_burn: 0, - block_header_hash: payload.block_header_hash, - new_seed: payload.new_seed, - parent_block_ptr: payload.parent_block_ptr, - parent_vtxindex: payload.parent_vtxindex, - key_block_ptr: payload.key_block_ptr, - key_vtxindex: payload.key_vtxindex, - memo: payload.memo, - burn_fee: payload.burn_fee, - apparent_sender: payload.apparent_sender, - input: payload.input, - commit_outs: payload.commit_outs, - txid: payload.txid, - vtxindex: payload.vtxindex, - block_height: next_block_header.block_height, - burn_parent_modulus: if next_block_header.block_height > 0 { - (next_block_header.block_height - 1) % BURN_BLOCK_MINED_AT_MODULUS - } else { - BURN_BLOCK_MINED_AT_MODULUS - 1 - } as u8, - burn_header_hash: next_block_header.block_hash.clone(), - }) - } - BlockstackOperationType::PreStx(payload) => { - BlockstackOperationType::PreStx(PreStxOp { - block_height: next_block_header.block_height, - burn_header_hash: next_block_header.block_hash.clone(), - ..payload - }) - } - BlockstackOperationType::TransferStx(payload) => { - BlockstackOperationType::TransferStx(TransferStxOp { - block_height: next_block_header.block_height, - burn_header_hash: next_block_header.block_hash.clone(), - ..payload - }) - } - BlockstackOperationType::StackStx(payload) => { - BlockstackOperationType::StackStx(StackStxOp { - block_height: next_block_header.block_height, - burn_header_hash: next_block_header.block_hash.clone(), - ..payload - }) - } - BlockstackOperationType::DelegateStx(payload) => { - BlockstackOperationType::DelegateStx(DelegateStxOp { - block_height: next_block_header.block_height, - burn_header_hash: next_block_header.block_hash.clone(), - ..payload - }) - } - BlockstackOperationType::VoteForAggregateKey(payload) => { - BlockstackOperationType::VoteForAggregateKey(VoteForAggregateKeyOp { - block_height: next_block_header.block_height, - burn_header_hash: next_block_header.block_hash.clone(), - ..payload - }) - } - }; - ops.push(op); - } - - // Include txs in a new block - let (block_snapshot, state_transition) = { - match self.db { - None => { - unreachable!(); - } - Some(ref mut burn_db) => { - let mut burn_tx = - SortitionHandleTx::begin(burn_db, &chain_tip.block_snapshot.sortition_id) - .unwrap(); - let new_chain_tip = burn_tx - .process_block_ops( - false, - &self.burnchain, - &chain_tip.block_snapshot, - &next_block_header, - ops, - None, - PoxId::stubbed(), - None, - 0, - ) - .unwrap(); - burn_tx.commit().unwrap(); - new_chain_tip - } - } - }; - - let state_transition = BurnchainStateTransitionOps { - accepted_ops: state_transition.accepted_ops, - consumed_leader_keys: state_transition.consumed_leader_keys, - }; - - // Transmit the new state - let new_state = BurnchainTip { - block_snapshot, - state_transition, - received_at: Instant::now(), - }; - self.chain_tip = Some(new_state.clone()); - - let block_height = new_state.block_snapshot.block_height; - Ok((new_state, block_height)) - } - - #[cfg(test)] - fn bootstrap_chain(&self, _num_blocks: u64) {} - - fn connect_dbs(&mut self) -> Result<(), BurnchainControllerError> { - Ok(()) - } -} diff --git a/stacks-node/src/burnchains/mod.rs b/stacks-node/src/burnchains/mod.rs index 71740661386..a193c45cff5 100644 --- a/stacks-node/src/burnchains/mod.rs +++ b/stacks-node/src/burnchains/mod.rs @@ -1,6 +1,5 @@ pub mod bitcoin; pub mod bitcoin_regtest_controller; -pub mod mocknet_controller; pub mod rpc; use std::time::Instant; @@ -14,7 +13,6 @@ use stacks::core::{EpochList, StacksEpochId}; use stacks_common::codec::Error as CodecError; pub use self::bitcoin_regtest_controller::{make_bitcoin_indexer, BitcoinRegtestController}; -pub use self::mocknet_controller::MocknetController; use super::operations::BurnchainOpSigner; #[derive(Debug, thiserror::Error)] diff --git a/stacks-node/src/genesis.rs b/stacks-node/src/genesis.rs new file mode 100644 index 00000000000..493699c3eb8 --- /dev/null +++ b/stacks-node/src/genesis.rs @@ -0,0 +1,112 @@ +use std::env; + +use stacks::chainstate::stacks::db::{ + ChainstateAccountBalance, ChainstateAccountLockup, ChainstateBNSName, ChainstateBNSNamespace, +}; + +pub use self::chain_tip::ChainTip; +use crate::genesis_data::USE_TEST_GENESIS_CHAINSTATE; +use crate::Config; + +mod chain_tip { + use stacks::chainstate::stacks::db::StacksHeaderInfo; + use stacks::chainstate::stacks::events::StacksTransactionReceipt; + use stacks::chainstate::stacks::StacksBlock; + use stacks_common::types::chainstate::{BurnchainHeaderHash, TrieHash}; + + #[derive(Debug, Clone)] + pub struct ChainTip { + pub metadata: StacksHeaderInfo, + pub block: StacksBlock, + pub receipts: Vec, + } + + impl ChainTip { + pub fn genesis( + first_burnchain_block_hash: &BurnchainHeaderHash, + first_burnchain_block_height: u64, + first_burnchain_block_timestamp: u64, + ) -> ChainTip { + ChainTip { + metadata: StacksHeaderInfo::genesis( + TrieHash([0u8; 32]), + first_burnchain_block_hash, + first_burnchain_block_height as u32, + first_burnchain_block_timestamp, + ), + block: StacksBlock::genesis_block(), + receipts: vec![], + } + } + } +} + +pub fn get_account_lockups( + use_test_chainstate_data: bool, +) -> Box> { + Box::new( + stx_genesis::GenesisData::new(use_test_chainstate_data) + .read_lockups() + .map(|item| ChainstateAccountLockup { + address: item.address, + amount: item.amount, + block_height: item.block_height, + }), + ) +} + +pub fn get_account_balances( + use_test_chainstate_data: bool, +) -> Box> { + Box::new( + stx_genesis::GenesisData::new(use_test_chainstate_data) + .read_balances() + .map(|item| ChainstateAccountBalance { + address: item.address, + amount: item.amount, + }), + ) +} + +pub fn get_namespaces( + use_test_chainstate_data: bool, +) -> Box> { + Box::new( + stx_genesis::GenesisData::new(use_test_chainstate_data) + .read_namespaces() + .map(|item| ChainstateBNSNamespace { + namespace_id: item.namespace_id, + importer: item.importer, + buckets: item.buckets, + base: item.base as u64, + coeff: item.coeff as u64, + nonalpha_discount: item.nonalpha_discount as u64, + no_vowel_discount: item.no_vowel_discount as u64, + lifetime: item.lifetime as u64, + }), + ) +} + +pub fn get_names(use_test_chainstate_data: bool) -> Box> { + Box::new( + stx_genesis::GenesisData::new(use_test_chainstate_data) + .read_names() + .map(|item| ChainstateBNSName { + fully_qualified_name: item.fully_qualified_name, + owner: item.owner, + zonefile_hash: item.zonefile_hash, + }), + ) +} + +/// Check if the small test genesis chainstate data should be used. +/// First check env var, then config file, then use default. +pub fn use_test_genesis_chainstate(config: &Config) -> bool { + if env::var("BLOCKSTACK_USE_TEST_GENESIS_CHAINSTATE") == Ok("1".to_string()) { + true + } else if let Some(use_test_genesis_chainstate) = config.node.use_test_genesis_chainstate { + use_test_genesis_chainstate + } else { + USE_TEST_GENESIS_CHAINSTATE + } +} diff --git a/stacks-node/src/main.rs b/stacks-node/src/main.rs index 9691b7a687a..fd4d59b7ad6 100644 --- a/stacks-node/src/main.rs +++ b/stacks-node/src/main.rs @@ -33,16 +33,15 @@ pub mod monitoring; pub mod burnchains; pub mod event_dispatcher; +pub mod genesis; pub mod genesis_data; pub mod globals; pub mod keychain; pub mod nakamoto_node; pub mod neon_node; -pub mod node; pub mod operations; pub mod run_loop; pub mod syncctl; -pub mod tenure; use std::collections::HashMap; use std::{env, panic, process}; @@ -61,14 +60,11 @@ use stacks_common::alloc_tracker::{tracking_allocator_installed, TrackingAllocat #[cfg(not(any(target_os = "macos", target_os = "windows", target_arch = "arm")))] use tikv_jemallocator::Jemalloc; -pub use self::burnchains::{ - BitcoinRegtestController, BurnchainController, BurnchainTip, MocknetController, -}; +pub use self::burnchains::{BitcoinRegtestController, BurnchainController, BurnchainTip}; pub use self::event_dispatcher::EventDispatcher; +pub use self::genesis::ChainTip; pub use self::keychain::Keychain; -pub use self::node::{ChainTip, Node}; -pub use self::run_loop::{helium, neon}; -pub use self::tenure::Tenure; +pub use self::run_loop::neon; use crate::neon_node::{BlockMinerThread, TipCandidate}; use crate::run_loop::boot_nakamoto; @@ -338,14 +334,6 @@ fn main() { } let config_file = match subcommand.as_str() { - "mocknet" => { - args.finish(); - ConfigFile::mocknet() - } - "helium" => { - args.finish(); - ConfigFile::helium() - } "testnet" => { args.finish(); ConfigFile::xenon() @@ -466,14 +454,7 @@ fn main() { send_pending_event_payloads(&conf); - let num_round: u64 = 0; // Infinite number of rounds - - if conf.burnchain.mode == "helium" || conf.burnchain.mode == "mocknet" { - let mut run_loop = helium::RunLoop::new(conf); - if let Err(e) = run_loop.start(num_round) { - warn!("Helium runloop exited: {e}"); - } - } else if conf.burnchain.mode == "neon" + if conf.burnchain.mode == "neon" || conf.burnchain.mode == "nakamoto-neon" || conf.burnchain.mode == "xenon" || conf.burnchain.mode == "krypton" @@ -512,17 +493,6 @@ SUBCOMMANDS: mainnet\t\tStart a node that will join and stream blocks from the public mainnet. -mocknet\t\tStart a node based on a fast local setup emulating a burnchain. Ideal for smart contract development. - -helium\t\tStart a node based on a local setup relying on a local instance of bitcoind. -\t\tThe following bitcoin.conf is expected: -\t\t chain=regtest -\t\t disablewallet=0 -\t\t txindex=1 -\t\t server=1 -\t\t rpcuser=helium -\t\t rpcpassword=helium - testnet\t\tStart a node that will join and stream blocks from the public testnet, relying on Bitcoin Testnet. start\t\tStart a node with a config of your own. Can be used for joining a network, starting new chain, etc. diff --git a/stacks-node/src/node.rs b/stacks-node/src/node.rs deleted file mode 100644 index 1553e8c4852..00000000000 --- a/stacks-node/src/node.rs +++ /dev/null @@ -1,1110 +0,0 @@ -// Copyright (C) 2013-2020 Blockstack PBC, a public benefit corporation -// Copyright (C) 2020-2026 Stacks Open Internet Foundation -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . - -use std::collections::{HashMap, HashSet}; -use std::net::SocketAddr; -use std::thread::JoinHandle; -use std::{env, thread, time}; - -use rand::RngCore; -use stacks::burnchains::bitcoin::BitcoinNetworkType; -use stacks::burnchains::db::BurnchainDB; -use stacks::burnchains::{PoxConstants, Txid}; -use stacks::chainstate::burn::db::sortdb::SortitionDB; -use stacks::chainstate::burn::operations::leader_block_commit::{ - RewardSetInfo, BURN_BLOCK_MINED_AT_MODULUS, -}; -use stacks::chainstate::burn::operations::{ - BlockstackOperationType, LeaderBlockCommitOp, LeaderKeyRegisterOp, -}; -use stacks::chainstate::burn::ConsensusHash; -use stacks::chainstate::stacks::address::PoxAddress; -use stacks::chainstate::stacks::db::{ - ChainStateBootData, ChainstateAccountBalance, ChainstateAccountLockup, ChainstateBNSName, - ChainstateBNSNamespace, ClarityTx, StacksChainState, StacksEpochReceipt, StacksHeaderInfo, -}; -use stacks::chainstate::stacks::events::{ - StacksTransactionEvent, StacksTransactionReceipt, TransactionOrigin, -}; -use stacks::chainstate::stacks::{ - CoinbasePayload, StacksBlock, StacksMicroblock, StacksTransaction, StacksTransactionSigner, - TransactionAnchorMode, TransactionPayload, TransactionVersion, -}; -use stacks::core::mempool::MemPoolDB; -use stacks::core::{EpochList, STACKS_EPOCH_2_1_MARKER}; -use stacks::cost_estimates::metrics::UnitMetric; -use stacks::cost_estimates::UnitEstimator; -use stacks::net::atlas::{AtlasConfig, AtlasDB, AttachmentInstance}; -use stacks::net::db::PeerDB; -use stacks::net::p2p::PeerNetwork; -use stacks::net::stackerdb::StackerDBs; -use stacks::net::RPCHandlerArgs; -use stacks::util_lib::strings::UrlString; -use stacks_common::types::chainstate::{BlockHeaderHash, BurnchainHeaderHash, TrieHash, VRFSeed}; -use stacks_common::types::net::PeerAddress; -use stacks_common::util::get_epoch_time_secs; -use stacks_common::util::hash::Sha256Sum; -use stacks_common::util::secp256k1::Secp256k1PrivateKey; -use stacks_common::util::vrf::VRFPublicKey; - -use super::{BurnchainController, BurnchainTip, Config, EventDispatcher, Keychain, Tenure}; -use crate::burnchains::make_bitcoin_indexer; -use crate::genesis_data::USE_TEST_GENESIS_CHAINSTATE; -use crate::run_loop; -use crate::run_loop::RegisteredKey; - -#[derive(Debug, Clone)] -pub struct ChainTip { - pub metadata: StacksHeaderInfo, - pub block: StacksBlock, - pub receipts: Vec, -} - -impl ChainTip { - pub fn genesis( - first_burnchain_block_hash: &BurnchainHeaderHash, - first_burnchain_block_height: u64, - first_burnchain_block_timestamp: u64, - ) -> ChainTip { - ChainTip { - metadata: StacksHeaderInfo::genesis( - TrieHash([0u8; 32]), - first_burnchain_block_hash, - first_burnchain_block_height as u32, - first_burnchain_block_timestamp, - ), - block: StacksBlock::genesis_block(), - receipts: vec![], - } - } -} - -/// Node is a structure modelising an active node working on the stacks chain. -pub struct Node { - pub chain_state: StacksChainState, - pub config: Config, - active_registered_key: Option, - bootstraping_chain: bool, - pub burnchain_tip: Option, - pub chain_tip: Option, - keychain: Keychain, - last_sortitioned_block: Option, - event_dispatcher: EventDispatcher, - nonce: u64, - leader_key_registers: HashSet, - block_commits: HashSet, -} - -pub fn get_account_lockups( - use_test_chainstate_data: bool, -) -> Box> { - Box::new( - stx_genesis::GenesisData::new(use_test_chainstate_data) - .read_lockups() - .map(|item| ChainstateAccountLockup { - address: item.address, - amount: item.amount, - block_height: item.block_height, - }), - ) -} - -pub fn get_account_balances( - use_test_chainstate_data: bool, -) -> Box> { - Box::new( - stx_genesis::GenesisData::new(use_test_chainstate_data) - .read_balances() - .map(|item| ChainstateAccountBalance { - address: item.address, - amount: item.amount, - }), - ) -} - -pub fn get_namespaces( - use_test_chainstate_data: bool, -) -> Box> { - Box::new( - stx_genesis::GenesisData::new(use_test_chainstate_data) - .read_namespaces() - .map(|item| ChainstateBNSNamespace { - namespace_id: item.namespace_id, - importer: item.importer, - buckets: item.buckets, - base: item.base as u64, - coeff: item.coeff as u64, - nonalpha_discount: item.nonalpha_discount as u64, - no_vowel_discount: item.no_vowel_discount as u64, - lifetime: item.lifetime as u64, - }), - ) -} - -pub fn get_names(use_test_chainstate_data: bool) -> Box> { - Box::new( - stx_genesis::GenesisData::new(use_test_chainstate_data) - .read_names() - .map(|item| ChainstateBNSName { - fully_qualified_name: item.fully_qualified_name, - owner: item.owner, - zonefile_hash: item.zonefile_hash, - }), - ) -} - -// This function is called for helium and mocknet. -#[allow(clippy::too_many_arguments)] -fn spawn_peer( - is_mainnet: bool, - chain_id: u32, - mut this: PeerNetwork, - p2p_sock: &SocketAddr, - rpc_sock: &SocketAddr, - burn_db_path: String, - stacks_chainstate_path: String, - pox_consts: PoxConstants, - event_dispatcher: EventDispatcher, - exit_at_block_height: Option, - genesis_chainstate_hash: Sha256Sum, - poll_timeout: u64, - config: Config, -) -> JoinHandle<()> { - this.bind(p2p_sock, rpc_sock).unwrap(); - let server_thread = thread::spawn(move || { - // create estimators, metric instances for RPC handler - let cost_estimator = config - .make_cost_estimator() - .unwrap_or_else(|| Box::new(UnitEstimator)); - let metric = config - .make_cost_metric() - .unwrap_or_else(|| Box::new(UnitMetric)); - let fee_estimator = config.make_fee_estimator(); - - let handler_args = RPCHandlerArgs { - exit_at_block_height, - cost_estimator: Some(cost_estimator.as_ref()), - cost_metric: Some(metric.as_ref()), - fee_estimator: fee_estimator.as_ref().map(|x| x.as_ref()), - genesis_chainstate_hash, - ..RPCHandlerArgs::default() - }; - - loop { - let sortdb = match SortitionDB::open( - &burn_db_path, - false, - pox_consts.clone(), - Some(config.node.get_marf_opts()), - ) { - Ok(x) => x, - Err(e) => { - warn!("Error while connecting burnchain db in peer loop: {e}"); - thread::sleep(time::Duration::from_secs(1)); - continue; - } - }; - let (mut chainstate, _) = match StacksChainState::open( - is_mainnet, - chain_id, - &stacks_chainstate_path, - Some(config.node.get_marf_opts()), - ) { - Ok(x) => x, - Err(e) => { - warn!("Error while connecting chainstate db in peer loop: {e}"); - thread::sleep(time::Duration::from_secs(1)); - continue; - } - }; - - let estimator = Box::new(UnitEstimator); - let metric = Box::new(UnitMetric); - - let mut mem_pool = match MemPoolDB::open( - is_mainnet, - chain_id, - &stacks_chainstate_path, - estimator, - metric, - ) { - Ok(x) => x, - Err(e) => { - warn!("Error while connecting to mempool db in peer loop: {e}"); - thread::sleep(time::Duration::from_secs(1)); - continue; - } - }; - - let indexer = make_bitcoin_indexer(&config, None); - - let net_result = this - .run( - &indexer, - &sortdb, - &mut chainstate, - &mut mem_pool, - None, - false, - false, - poll_timeout, - &handler_args, - config.node.txindex, - ) - .unwrap(); - if net_result.has_transactions() { - event_dispatcher.process_new_mempool_txs(net_result.transactions()) - } - // Dispatch retrieved attachments, if any. - if net_result.has_attachments() { - event_dispatcher.process_new_attachments(&net_result.attachments); - } - } - }); - server_thread -} - -// Check if the small test genesis chainstate data should be used. -// First check env var, then config file, then use default. -pub fn use_test_genesis_chainstate(config: &Config) -> bool { - if env::var("BLOCKSTACK_USE_TEST_GENESIS_CHAINSTATE") == Ok("1".to_string()) { - true - } else if let Some(use_test_genesis_chainstate) = config.node.use_test_genesis_chainstate { - use_test_genesis_chainstate - } else { - USE_TEST_GENESIS_CHAINSTATE - } -} - -impl Node { - /// Instantiate and initialize a new node, given a config - pub fn new(config: Config, boot_block_exec: Box) -> Self { - let use_test_genesis_data = if config.burnchain.mode == "mocknet" { - use_test_genesis_chainstate(&config) - } else { - USE_TEST_GENESIS_CHAINSTATE - }; - - let keychain = Keychain::default(config.node.seed.clone()); - - let initial_balances = config - .initial_balances - .iter() - .map(|e| (e.address.clone(), e.amount)) - .collect(); - let pox_constants = match config.burnchain.get_bitcoin_network() { - (_, BitcoinNetworkType::Mainnet) => PoxConstants::mainnet_default(), - (_, BitcoinNetworkType::Testnet) => PoxConstants::testnet_default(), - (_, BitcoinNetworkType::Regtest) => PoxConstants::regtest_default(), - }; - - let mut boot_data = ChainStateBootData { - initial_balances, - first_burnchain_block_hash: BurnchainHeaderHash::zero(), - first_burnchain_block_height: 0, - first_burnchain_block_timestamp: 0, - pox_constants, - post_flight_callback: Some(boot_block_exec), - get_bulk_initial_lockups: Some(Box::new(move || { - get_account_lockups(use_test_genesis_data) - })), - get_bulk_initial_balances: Some(Box::new(move || { - get_account_balances(use_test_genesis_data) - })), - get_bulk_initial_namespaces: Some(Box::new(move || { - get_namespaces(use_test_genesis_data) - })), - get_bulk_initial_names: Some(Box::new(move || get_names(use_test_genesis_data))), - }; - - let chain_state_result = StacksChainState::open_and_exec( - config.is_mainnet(), - config.burnchain.chain_id, - &config.get_chainstate_path_str(), - Some(&mut boot_data), - Some(config.node.get_marf_opts()), - ); - - let (chain_state, receipts) = match chain_state_result { - Ok(res) => res, - Err(err) => panic!( - "Error while opening chain state at path {}: {err:?}", - config.get_chainstate_path_str() - ), - }; - - let estimator = Box::new(UnitEstimator); - let metric = Box::new(UnitMetric); - - // avoid race to create condition on mempool db - let _mem_pool = MemPoolDB::open( - config.is_mainnet(), - config.burnchain.chain_id, - &chain_state.root_path, - estimator, - metric, - ) - .expect("FATAL: failed to initiate mempool"); - - let mut event_dispatcher = EventDispatcher::new_with_custom_queue_size( - config.get_working_dir(), - config.node.effective_event_dispatcher_queue_size(), - ); - - for observer in &config.events_observers { - event_dispatcher.register_observer(observer); - } - - let burnchain_config = config.get_burnchain(); - - // instantiate DBs - let _burnchain_db = BurnchainDB::connect( - &burnchain_config.get_burnchaindb_path(), - &burnchain_config, - true, - ) - .expect("FATAL: failed to connect to burnchain DB"); - - run_loop::announce_boot_receipts( - &mut event_dispatcher, - &chain_state, - &burnchain_config.pox_constants, - &receipts, - ); - - Self { - active_registered_key: None, - bootstraping_chain: false, - chain_state, - chain_tip: None, - keychain, - last_sortitioned_block: None, - config, - burnchain_tip: None, - nonce: 0, - event_dispatcher, - leader_key_registers: HashSet::new(), - block_commits: HashSet::new(), - } - } - - fn make_atlas_config() -> AtlasConfig { - AtlasConfig::new(false) - } - - pub fn make_atlas_db(&self) -> AtlasDB { - AtlasDB::connect( - Self::make_atlas_config(), - &self.config.get_atlas_db_file_path(), - true, - ) - .unwrap() - } - - // This function is used for helium and mocknet. - pub fn spawn_peer_server(&mut self) { - // we can call _open_ here rather than _connect_, since connect is first called in - // make_genesis_block - let burnchain = self.config.get_burnchain(); - let sortdb = SortitionDB::open( - &self.config.get_burn_db_file_path(), - true, - burnchain.pox_constants.clone(), - Some(self.config.node.get_marf_opts()), - ) - .expect("Error while instantiating burnchain db"); - - let epochs_vec = SortitionDB::get_stacks_epochs(sortdb.conn()) - .expect("Error while loading stacks epochs"); - let epochs = EpochList::new(&epochs_vec); - - Config::assert_valid_epoch_settings(&burnchain, &epochs); - - let view = { - let sortition_tip = SortitionDB::get_canonical_burn_chain_tip(sortdb.conn()) - .expect("Failed to get sortition tip"); - SortitionDB::get_burnchain_view(&sortdb.index_conn(), &burnchain, &sortition_tip) - .unwrap() - }; - - // create a new peerdb - let data_url = UrlString::try_from(self.config.node.data_url.to_string()).unwrap(); - - let initial_neighbors = self.config.node.bootstrap_node.clone(); - - println!("BOOTSTRAP WITH {initial_neighbors:?}"); - - let rpc_sock: SocketAddr = - self.config.node.rpc_bind.parse().unwrap_or_else(|_| { - panic!("Failed to parse socket: {}", &self.config.node.rpc_bind) - }); - let p2p_sock: SocketAddr = - self.config.node.p2p_bind.parse().unwrap_or_else(|_| { - panic!("Failed to parse socket: {}", &self.config.node.p2p_bind) - }); - let p2p_addr: SocketAddr = self.config.node.p2p_address.parse().unwrap_or_else(|_| { - panic!("Failed to parse socket: {}", &self.config.node.p2p_address) - }); - let node_privkey = { - let mut re_hashed_seed = self.config.node.local_peer_seed.clone(); - let my_private_key = loop { - match Secp256k1PrivateKey::from_slice(&re_hashed_seed[..]) { - Ok(sk) => break sk, - Err(_) => { - re_hashed_seed = Sha256Sum::from_data(&re_hashed_seed[..]) - .as_bytes() - .to_vec() - } - } - }; - my_private_key - }; - - let mut peerdb = PeerDB::connect( - &self.config.get_peer_db_file_path(), - true, - self.config.burnchain.chain_id, - burnchain.network_id, - Some(node_privkey), - self.config.connection_options.private_key_lifetime, - PeerAddress::from_socketaddr(&p2p_addr), - p2p_sock.port(), - data_url, - &[], - Some(&initial_neighbors), - &[], - ) - .unwrap(); - - println!("DENY NEIGHBORS {:?}", &self.config.node.deny_nodes); - { - let tx = peerdb.tx_begin().unwrap(); - for denied in self.config.node.deny_nodes.iter() { - PeerDB::set_deny_peer( - &tx, - denied.addr.network_id, - &denied.addr.addrbytes, - denied.addr.port, - get_epoch_time_secs() + 24 * 365 * 3600, - ) - .unwrap(); - } - tx.commit().unwrap(); - } - let atlasdb = self.make_atlas_db(); - - let stackerdbs = - StackerDBs::connect(&self.config.get_stacker_db_file_path(), true).unwrap(); - - let local_peer = match PeerDB::get_local_peer(peerdb.conn()) { - Ok(local_peer) => local_peer, - _ => panic!("Unable to retrieve local peer"), - }; - - let event_dispatcher = self.event_dispatcher.clone(); - let exit_at_block_height = self.config.burnchain.process_exit_at_block_height; - let burnchain_db = burnchain - .open_burnchain_db(false) - .expect("Failed to open burnchain DB"); - - let p2p_net = PeerNetwork::new( - peerdb, - atlasdb, - stackerdbs, - burnchain_db, - local_peer, - self.config.burnchain.peer_version, - burnchain.clone(), - view, - self.config.connection_options.clone(), - HashMap::new(), - epochs, - ); - let _join_handle = spawn_peer( - self.config.is_mainnet(), - self.config.burnchain.chain_id, - p2p_net, - &p2p_sock, - &rpc_sock, - self.config.get_burn_db_file_path(), - self.config.get_chainstate_path_str(), - burnchain.pox_constants, - event_dispatcher, - exit_at_block_height, - Sha256Sum::from_hex(stx_genesis::GENESIS_CHAINSTATE_HASH).unwrap(), - 1000, - self.config.clone(), - ); - - info!("Start HTTP server on: {}", &self.config.node.rpc_bind); - info!("Start P2P server on: {}", &self.config.node.p2p_bind); - } - - pub fn setup(&mut self, burnchain_controller: &mut Box) { - // Register a new key - let burnchain_tip = burnchain_controller.get_chain_tip(); - let (vrf_pk, _) = self - .keychain - .make_vrf_keypair(burnchain_tip.block_snapshot.block_height); - let consensus_hash = burnchain_tip.block_snapshot.consensus_hash; - - let burnchain = self.config.get_burnchain(); - - let sortdb = SortitionDB::open( - &self.config.get_burn_db_file_path(), - true, - burnchain.pox_constants.clone(), - Some(self.config.node.get_marf_opts()), - ) - .expect("Error while opening sortition db"); - - let epochs = SortitionDB::get_stacks_epochs(sortdb.conn()) - .expect("FATAL: failed to read sortition DB"); - - Config::assert_valid_epoch_settings(&burnchain, &epochs); - - let cur_epoch = - SortitionDB::get_stacks_epoch(sortdb.conn(), burnchain_tip.block_snapshot.block_height) - .expect("FATAL: failed to read sortition DB") - .expect("FATAL: no epoch defined"); - - let key_reg_op = self.generate_leader_key_register_op(vrf_pk, consensus_hash); - let mut op_signer = self.keychain.generate_op_signer(); - let key_txid = burnchain_controller - .submit_operation(cur_epoch.epoch_id, key_reg_op, &mut op_signer) - .expect("FATAL: failed to submit leader key register operation"); - - self.leader_key_registers.insert(key_txid); - } - - /// Process an state coming from the burnchain, by extracting the validated KeyRegisterOp - /// and inspecting if a sortition was won. - pub fn process_burnchain_state( - &mut self, - burnchain_tip: &BurnchainTip, - ) -> (Option, bool) { - let mut new_key = None; - let mut last_sortitioned_block = None; - let mut won_sortition = false; - let ops = &burnchain_tip.state_transition.accepted_ops; - - for op in ops.iter() { - match op { - BlockstackOperationType::LeaderKeyRegister(ref op) => { - if self.leader_key_registers.contains(&op.txid) { - // Registered key has been mined - new_key = Some(RegisteredKey { - vrf_public_key: op.public_key.clone(), - block_height: op.block_height, - op_vtxindex: op.vtxindex, - target_block_height: op.block_height - 1, - memo: op.memo.clone(), - }); - } - } - BlockstackOperationType::LeaderBlockCommit(ref op) => { - if op.txid == burnchain_tip.block_snapshot.winning_block_txid { - last_sortitioned_block = Some(burnchain_tip.clone()); - if self.block_commits.contains(&op.txid) { - won_sortition = true; - } - } - } - _ => { - // no-op, ops are not supported / produced at this point. - } - } - } - - // Update the active key so we use the latest registered key. - if new_key.is_some() { - self.active_registered_key = new_key; - } - - // Update last_sortitioned_block so we keep a reference to the latest - // block including a sortition. - if last_sortitioned_block.is_some() { - self.last_sortitioned_block = last_sortitioned_block; - } - - // Keep a pointer of the burnchain's chain tip. - self.burnchain_tip = Some(burnchain_tip.clone()); - - (self.last_sortitioned_block.clone(), won_sortition) - } - - /// Prepares the node to run a tenure consisting in bootstraping the chain. - /// - /// Will internally call initiate_new_tenure(). - pub fn initiate_genesis_tenure(&mut self, burnchain_tip: &BurnchainTip) -> Option { - // Set the `bootstraping_chain` flag, that will be unset once the - // bootstraping tenure ran successfully (process_tenure). - self.bootstraping_chain = true; - - self.last_sortitioned_block = Some(burnchain_tip.clone()); - - self.initiate_new_tenure() - } - - /// Constructs and returns an instance of Tenure, that can be run - /// on an isolated thread and discarded or canceled without corrupting the - /// chain state of the node. - pub fn initiate_new_tenure(&mut self) -> Option { - // Get the latest registered key - let registered_key = match &self.active_registered_key { - None => { - // We're continuously registering new keys, as such, this branch - // should be unreachable. - unreachable!() - } - Some(ref key) => key, - }; - - let burnchain = self.config.get_burnchain(); - let sortdb = SortitionDB::open( - &self.config.get_burn_db_file_path(), - true, - burnchain.pox_constants, - Some(self.config.node.get_marf_opts()), - ) - .expect("Error while opening sortition db"); - let tip = SortitionDB::get_canonical_burn_chain_tip(sortdb.conn()) - .expect("FATAL: failed to query canonical burn chain tip"); - - // Generates a proof out of the sortition hash provided in the params. - let Some(vrf_proof) = self.keychain.generate_proof( - registered_key.target_block_height, - tip.sortition_hash.as_bytes(), - ) else { - warn!("Failed to generate VRF proof, will be unable to initiate new tenure"); - return None; - }; - - // Generates a new secret key for signing the trail of microblocks - // of the upcoming tenure. - let microblock_secret_key = self.keychain.get_microblock_key(tip.block_height); - - // Get the stack's chain tip - let chain_tip = match self.bootstraping_chain { - true => ChainTip::genesis(&BurnchainHeaderHash::zero(), 0, 0), - false => match &self.chain_tip { - Some(chain_tip) => chain_tip.clone(), - None => unreachable!(), - }, - }; - - let estimator = self - .config - .make_cost_estimator() - .unwrap_or_else(|| Box::new(UnitEstimator)); - let metric = self - .config - .make_cost_metric() - .unwrap_or_else(|| Box::new(UnitMetric)); - - let mem_pool = MemPoolDB::open( - self.config.is_mainnet(), - self.config.burnchain.chain_id, - &self.chain_state.root_path, - estimator, - metric, - ) - .expect("FATAL: failed to open mempool"); - - // Construct the coinbase transaction - 1st txn that should be handled and included in - // the upcoming tenure. - let coinbase_tx = self.generate_coinbase_tx(self.config.is_mainnet()); - - let burn_fee_cap = self.config.burnchain.burn_fee_cap; - - let block_to_build_upon = match &self.last_sortitioned_block { - None => unreachable!(), - Some(block) => block.clone(), - }; - - // Construct the upcoming tenure - let tenure = Tenure::new( - chain_tip, - coinbase_tx, - self.config.clone(), - mem_pool, - microblock_secret_key, - block_to_build_upon, - vrf_proof, - burn_fee_cap, - ); - - Some(tenure) - } - - pub fn commit_artifacts( - &mut self, - anchored_block_from_ongoing_tenure: &StacksBlock, - burnchain_tip: &BurnchainTip, - burnchain_controller: &mut Box, - burn_fee: u64, - ) { - if self.active_registered_key.is_some() { - let registered_key = self.active_registered_key.clone().unwrap(); - - let Some(vrf_proof) = self.keychain.generate_proof( - registered_key.target_block_height, - burnchain_tip.block_snapshot.sortition_hash.as_bytes(), - ) else { - warn!("Failed to generate VRF proof, will be unable to mine commits"); - return; - }; - - let op = self.generate_block_commit_op( - anchored_block_from_ongoing_tenure.header.block_hash(), - burn_fee, - ®istered_key, - burnchain_tip, - VRFSeed::from_proof(&vrf_proof), - ); - - let burnchain = self.config.get_burnchain(); - let sortdb = SortitionDB::open( - &self.config.get_burn_db_file_path(), - true, - burnchain.pox_constants, - Some(self.config.node.get_marf_opts()), - ) - .expect("Error while opening sortition db"); - - let cur_epoch = SortitionDB::get_stacks_epoch( - sortdb.conn(), - burnchain_tip.block_snapshot.block_height, - ) - .expect("FATAL: failed to read sortition DB") - .expect("FATAL: no epoch defined"); - - let mut op_signer = self.keychain.generate_op_signer(); - let txid = burnchain_controller - .submit_operation(cur_epoch.epoch_id, op, &mut op_signer) - .expect("FATAL: failed to submit block-commit"); - - self.block_commits.insert(txid); - } else { - warn!("No leader key active!"); - } - } - - /// Process artifacts from the tenure. - /// At this point, we're modifying the chainstate, and merging the artifacts from the previous tenure. - pub fn process_tenure( - &mut self, - anchored_block: &StacksBlock, - consensus_hash: &ConsensusHash, - microblocks: Vec, - db: &mut SortitionDB, - atlas_db: &mut AtlasDB, - ) -> ChainTip { - let _parent_consensus_hash = { - // look up parent consensus hash - let ic = db.index_conn(); - let parent_consensus_hash = StacksChainState::get_parent_consensus_hash( - &ic, - &anchored_block.header.parent_block, - consensus_hash, - ) - .unwrap_or_else(|_| { - panic!( - "BUG: could not query chainstate to find parent consensus hash of {consensus_hash}/{}", - &anchored_block.block_hash() - ) - }) - .unwrap_or_else(|| { - panic!( - "BUG: no such parent of block {consensus_hash}/{}", - &anchored_block.block_hash() - ) - }); - - // Preprocess the anchored block - self.chain_state - .preprocess_anchored_block( - &ic, - consensus_hash, - anchored_block, - &parent_consensus_hash, - 0, - ) - .unwrap(); - - // Preprocess the microblocks - for microblock in microblocks.iter() { - let res = self - .chain_state - .preprocess_streamed_microblock( - consensus_hash, - &anchored_block.block_hash(), - microblock, - ) - .unwrap(); - if !res { - warn!( - "Unhandled error while pre-processing microblock {}", - microblock.header.block_hash() - ); - } - } - - parent_consensus_hash - }; - - let atlas_config = Self::make_atlas_config(); - let mut processed_blocks = vec![]; - loop { - let mut process_blocks_at_tip = { - let tx = db.tx_begin_at_tip(); - self.chain_state - .process_blocks(tx, 1, Some(&self.event_dispatcher)) - }; - match process_blocks_at_tip { - Err(e) => panic!("Error while processing block - {e:?}"), - Ok(ref mut blocks) => { - if blocks.is_empty() { - break; - } else { - for block in blocks.iter() { - if let (Some(epoch_receipt), _) = block { - let attachments_instances = - self.get_attachment_instances(epoch_receipt, &atlas_config); - if !attachments_instances.is_empty() { - for new_attachment in attachments_instances.into_iter() { - if let Err(e) = - atlas_db.queue_attachment_instance(&new_attachment) - { - warn!( - "Atlas: Error writing attachment instance to DB"; - "err" => ?e, - "index_block_hash" => %new_attachment.index_block_hash, - "contract_id" => %new_attachment.contract_id, - "attachment_index" => %new_attachment.attachment_index, - ); - } - } - } - } - } - - processed_blocks.append(blocks); - } - } - } - } - - // todo(ludo): yikes but good enough in the context of helium: - // we only expect 1 block. - let processed_block = processed_blocks[0].clone().0.unwrap(); - - let mut cost_estimator = self.config.make_cost_estimator(); - let mut fee_estimator = self.config.make_fee_estimator(); - - let stacks_epoch = - SortitionDB::get_stacks_epoch_by_epoch_id(db.conn(), &processed_block.evaluated_epoch) - .expect("FATAL: could not query sortition DB for epochs") - .expect("Could not find a stacks epoch."); - if let Some(estimator) = cost_estimator.as_mut() { - estimator.notify_block( - &processed_block.tx_receipts, - &stacks_epoch.block_limit, - &stacks_epoch.epoch_id, - ); - } - - if let Some(estimator) = fee_estimator.as_mut() { - if let Err(e) = estimator.notify_block(&processed_block, &stacks_epoch.block_limit) { - warn!("FeeEstimator failed to process block receipt"; - "stacks_block_hash" => %processed_block.header.anchored_header.block_hash(), - "stacks_height" => %processed_block.header.stacks_block_height, - "error" => %e); - } - } - - // Handle events - let receipts = processed_block.tx_receipts; - let metadata = processed_block.header; - let block: StacksBlock = { - let block_path = StacksChainState::get_block_path( - &self.chain_state.blocks_path, - &metadata.consensus_hash, - &metadata.anchored_header.block_hash(), - ) - .unwrap(); - StacksChainState::consensus_load(&block_path).unwrap() - }; - - let chain_tip = ChainTip { - metadata, - block, - receipts, - }; - self.chain_tip = Some(chain_tip.clone()); - - // Unset the `bootstraping_chain` flag. - if self.bootstraping_chain { - self.bootstraping_chain = false; - } - - chain_tip - } - - pub fn get_attachment_instances( - &self, - epoch_receipt: &StacksEpochReceipt, - atlas_config: &AtlasConfig, - ) -> HashSet { - let mut attachments_instances = HashSet::new(); - for receipt in epoch_receipt.tx_receipts.iter() { - if let TransactionOrigin::Stacks(ref transaction) = receipt.transaction { - if let TransactionPayload::ContractCall(ref contract_call) = transaction.payload { - let contract_id = contract_call.to_clarity_contract_id(); - if atlas_config.contracts.contains(&contract_id) { - for event in receipt.events.iter() { - if let StacksTransactionEvent::SmartContractEvent(ref event_data) = - event - { - let res = AttachmentInstance::try_new_from_value( - &event_data.value, - &contract_id, - epoch_receipt.header.index_block_hash(), - epoch_receipt.header.stacks_block_height, - receipt.transaction.txid(), - self.chain_tip - .as_ref() - .map(|t| t.metadata.stacks_block_height), - ); - if let Some(attachment_instance) = res { - attachments_instances.insert(attachment_instance); - } - } - } - } - } - } - } - attachments_instances - } - - /// Constructs and returns a LeaderKeyRegisterOp out of the provided params - fn generate_leader_key_register_op( - &mut self, - vrf_public_key: VRFPublicKey, - consensus_hash: ConsensusHash, - ) -> BlockstackOperationType { - let mut txid_bytes = [0u8; 32]; - let mut rng = rand::thread_rng(); - rng.fill_bytes(&mut txid_bytes); - let txid = Txid(txid_bytes); - - BlockstackOperationType::LeaderKeyRegister(LeaderKeyRegisterOp { - public_key: vrf_public_key, - memo: vec![], - consensus_hash, - vtxindex: 1, - txid, - block_height: 0, - burn_header_hash: BurnchainHeaderHash::zero(), - }) - } - - /// Constructs and returns a LeaderBlockCommitOp out of the provided params - fn generate_block_commit_op( - &mut self, - block_header_hash: BlockHeaderHash, - burn_fee: u64, - key: &RegisteredKey, - burnchain_tip: &BurnchainTip, - vrf_seed: VRFSeed, - ) -> BlockstackOperationType { - let winning_tx_vtindex = burnchain_tip.get_winning_tx_index().unwrap_or(0); - - let (parent_block_ptr, parent_vtxindex) = match self.bootstraping_chain { - true => (0, 0), // parent_block_ptr and parent_vtxindex should both be 0 on block #1 - false => ( - burnchain_tip.block_snapshot.block_height as u32, - winning_tx_vtindex as u16, - ), - }; - - let burnchain = self.config.get_burnchain(); - let commit_outs = if burnchain_tip.block_snapshot.block_height + 1 - < burnchain.pox_constants.sunset_end - && !burnchain.is_in_prepare_phase(burnchain_tip.block_snapshot.block_height + 1) - { - RewardSetInfo::into_commit_outs(None, self.config.is_mainnet()) - } else { - vec![PoxAddress::standard_burn_address(self.config.is_mainnet())] - }; - - let burn_parent_modulus = - (burnchain_tip.block_snapshot.block_height % BURN_BLOCK_MINED_AT_MODULUS) as u8; - - let mut txid_bytes = [0u8; 32]; - let mut rng = rand::thread_rng(); - rng.fill_bytes(&mut txid_bytes); - let txid = Txid(txid_bytes); - - BlockstackOperationType::LeaderBlockCommit(LeaderBlockCommitOp { - treatment: vec![], - sunset_burn: 0, - block_header_hash, - burn_fee, - input: (Txid([0; 32]), 0), - apparent_sender: self.keychain.get_burnchain_signer(), - key_block_ptr: key.block_height as u32, - key_vtxindex: key.op_vtxindex as u16, - memo: vec![STACKS_EPOCH_2_1_MARKER], - new_seed: vrf_seed, - parent_block_ptr, - parent_vtxindex, - vtxindex: 2, - txid, - commit_outs, - block_height: 0, - burn_header_hash: BurnchainHeaderHash::zero(), - burn_parent_modulus, - }) - } - - // Constructs a coinbase transaction - fn generate_coinbase_tx(&mut self, is_mainnet: bool) -> StacksTransaction { - let mut tx_auth = self.keychain.get_transaction_auth().unwrap(); - tx_auth.set_origin_nonce(self.nonce); - - let version = if is_mainnet { - TransactionVersion::Mainnet - } else { - TransactionVersion::Testnet - }; - let mut tx = StacksTransaction::new( - version, - tx_auth, - TransactionPayload::Coinbase(CoinbasePayload([0u8; 32]), None, None), - ); - tx.chain_id = self.config.burnchain.chain_id; - tx.anchor_mode = TransactionAnchorMode::OnChainOnly; - let mut tx_signer = StacksTransactionSigner::new(&tx); - self.keychain.sign_as_origin(&mut tx_signer); - - // Increment nonce - self.nonce += 1; - - tx_signer.get_tx().unwrap() - } -} diff --git a/stacks-node/src/run_loop/helium.rs b/stacks-node/src/run_loop/helium.rs deleted file mode 100644 index c61581553cc..00000000000 --- a/stacks-node/src/run_loop/helium.rs +++ /dev/null @@ -1,235 +0,0 @@ -use stacks::chainstate::stacks::db::ClarityTx; -use stacks_common::types::chainstate::BurnchainHeaderHash; - -use super::RunLoopCallbacks; -use crate::burnchains::Error as BurnchainControllerError; -use crate::{ - BitcoinRegtestController, BurnchainController, ChainTip, Config, MocknetController, Node, -}; - -/// RunLoop is coordinating a simulated burnchain and some simulated nodes -/// taking turns in producing blocks. -pub struct RunLoop { - config: Config, - pub node: Node, - pub callbacks: RunLoopCallbacks, -} - -impl RunLoop { - pub fn new(config: Config) -> Self { - RunLoop::new_with_boot_exec(config, Box::new(|_| {})) - } - - /// Sets up a runloop and node, given a config. - pub fn new_with_boot_exec(config: Config, boot_exec: Box) -> Self { - // Build node based on config - let node = Node::new(config.clone(), boot_exec); - - Self { - config, - node, - callbacks: RunLoopCallbacks::new(), - } - } - - /// Starts the testnet runloop. - /// - /// This function will block by looping infinitely. - /// It will start the burnchain (separate thread), set-up a channel in - /// charge of coordinating the new blocks coming from the burnchain and - /// the nodes, taking turns on tenures. - pub fn start(&mut self, expected_num_rounds: u64) -> Result<(), BurnchainControllerError> { - // Initialize and start the burnchain. - let mut burnchain: Box = match &self.config.burnchain.mode[..] { - "helium" => Box::new(BitcoinRegtestController::new(self.config.clone(), None)), - "mocknet" => MocknetController::generic(self.config.clone()), - _ => unreachable!(), - }; - - self.callbacks.invoke_burn_chain_initialized(&mut burnchain); - - let (initial_state, _) = burnchain.start(None)?; - - // Update each node with the genesis block. - self.node.process_burnchain_state(&initial_state); - - // make first non-genesis block, with initial VRF keys - self.node.setup(&mut burnchain); - - // Waiting on the 1st block (post-genesis) from the burnchain, containing the first key registrations - // that will be used for bootstraping the chain. - let mut round_index: u64 = 0; - - // Sync and update node with this new block. - let (burnchain_tip, _) = burnchain.sync(None)?; - self.node.process_burnchain_state(&burnchain_tip); // todo(ludo): should return genesis? - let mut chain_tip = ChainTip::genesis(&BurnchainHeaderHash::zero(), 0, 0); - - self.node.spawn_peer_server(); - - // Bootstrap the chain: node will start a new tenure, - // using the sortition hash from block #1 for generating a VRF. - let leader = &mut self.node; - let mut first_tenure = match leader.initiate_genesis_tenure(&burnchain_tip) { - Some(res) => res, - None => panic!("Error while initiating genesis tenure"), - }; - - self.callbacks.invoke_new_tenure( - round_index, - &burnchain_tip, - &chain_tip, - &mut first_tenure, - ); - - // TODO (hack) instantiate db - let _ = burnchain.sortdb_mut(); - - // Run the tenure, keep the artifacts - let artifacts_from_1st_tenure = match first_tenure.run( - &burnchain - .sortdb_ref() - .index_handle(&burnchain_tip.block_snapshot.sortition_id), - ) { - Some(res) => res, - None => panic!("Error while running 1st tenure"), - }; - - // Tenures are instantiating their own chainstate, so that nodes can keep a clean chainstate, - // while having the option of running multiple tenures concurrently and try different strategies. - // As a result, once the tenure ran and we have the artifacts (anchored_blocks, microblocks), - // we have the 1st node (leading) updating its chainstate with the artifacts from its own tenure. - leader.commit_artifacts( - &artifacts_from_1st_tenure.anchored_block, - &artifacts_from_1st_tenure.parent_block, - &mut burnchain, - artifacts_from_1st_tenure.burn_fee, - ); - - let (mut burnchain_tip, _) = burnchain.sync(None)?; - - self.callbacks - .invoke_new_burn_chain_state(round_index, &burnchain_tip, &chain_tip); - - let mut leader_tenure = None; - - let (last_sortitioned_block, won_sortition) = - match self.node.process_burnchain_state(&burnchain_tip) { - (Some(sortitioned_block), won_sortition) => (sortitioned_block, won_sortition), - (None, _) => panic!("Node should have a sortitioned block"), - }; - - // Have the node process its own tenure. - // We should have some additional checks here, and ensure that the previous artifacts are legit. - let mut atlas_db = self.node.make_atlas_db(); - - chain_tip = self.node.process_tenure( - &artifacts_from_1st_tenure.anchored_block, - &last_sortitioned_block.block_snapshot.consensus_hash, - artifacts_from_1st_tenure.microblocks.clone(), - burnchain.sortdb_mut(), - &mut atlas_db, - ); - - self.callbacks.invoke_new_stacks_chain_state( - round_index, - &burnchain_tip, - &chain_tip, - &mut self.node.chain_state, - &burnchain - .sortdb_ref() - .index_handle(&burnchain_tip.block_snapshot.sortition_id), - ); - - // If the node we're looping on won the sortition, initialize and configure the next tenure - if won_sortition { - leader_tenure = self.node.initiate_new_tenure(); - } - - // Start the runloop - round_index = 1; - loop { - if expected_num_rounds == round_index { - return Ok(()); - } - - // Run the last initialized tenure - let artifacts_from_tenure = match leader_tenure { - Some(mut tenure) => { - self.callbacks.invoke_new_tenure( - round_index, - &burnchain_tip, - &chain_tip, - &mut tenure, - ); - tenure.run( - &burnchain - .sortdb_ref() - .index_handle(&burnchain_tip.block_snapshot.sortition_id), - ) - } - None => None, - }; - - if let Some(artifacts) = &artifacts_from_tenure { - // Have each node receive artifacts from the current tenure - self.node.commit_artifacts( - &artifacts.anchored_block, - &artifacts.parent_block, - &mut burnchain, - artifacts.burn_fee, - ); - } - - let (new_burnchain_tip, _) = burnchain.sync(None)?; - burnchain_tip = new_burnchain_tip; - - self.callbacks - .invoke_new_burn_chain_state(round_index, &burnchain_tip, &chain_tip); - - leader_tenure = None; - - // Have each node process the new block, that can include, or not, a sortition. - let (last_sortitioned_block, won_sortition) = - match self.node.process_burnchain_state(&burnchain_tip) { - (Some(sortitioned_block), won_sortition) => (sortitioned_block, won_sortition), - (None, _) => panic!("Node should have a sortitioned block"), - }; - - match artifacts_from_tenure { - // Pass if we're missing the artifacts from the current tenure. - None => continue, - Some(ref artifacts) => { - // Have the node process its tenure. - // We should have some additional checks here, and ensure that the previous artifacts are legit. - let mut atlas_db = self.node.make_atlas_db(); - - chain_tip = self.node.process_tenure( - &artifacts.anchored_block, - &last_sortitioned_block.block_snapshot.consensus_hash, - artifacts.microblocks.clone(), - burnchain.sortdb_mut(), - &mut atlas_db, - ); - - self.callbacks.invoke_new_stacks_chain_state( - round_index, - &burnchain_tip, - &chain_tip, - &mut self.node.chain_state, - &burnchain - .sortdb_ref() - .index_handle(&burnchain_tip.block_snapshot.sortition_id), - ); - } - }; - - // If won sortition, initialize and configure the next tenure - if won_sortition { - leader_tenure = self.node.initiate_new_tenure(); - } - - round_index += 1; - } - } -} diff --git a/stacks-node/src/run_loop/mod.rs b/stacks-node/src/run_loop/mod.rs index 5a903aec113..a935409e5ed 100644 --- a/stacks-node/src/run_loop/mod.rs +++ b/stacks-node/src/run_loop/mod.rs @@ -1,159 +1,18 @@ pub mod boot_nakamoto; -pub mod helium; pub mod nakamoto; pub mod neon; use clarity::vm::costs::ExecutionCost; -use clarity::vm::database::BurnStateDB; use stacks::burnchains::{PoxConstants, Txid}; use stacks::chainstate::stacks::db::StacksChainState; use stacks::chainstate::stacks::events::StacksTransactionReceipt; -use stacks::chainstate::stacks::{ - StacksBlock, TransactionAuth, TransactionPayload, TransactionSpendingCondition, -}; +use stacks::chainstate::stacks::StacksBlock; use stacks_common::types::chainstate::StacksBlockId; use stacks_common::util::vrf::VRFPublicKey; use crate::stacks::chainstate::coordinator::BlockEventDispatcher; use crate::stacks::chainstate::stacks::index::ClarityMarfTrieId; -use crate::{BurnchainController, BurnchainTip, ChainTip, EventDispatcher, Tenure}; - -macro_rules! info_blue { - ($($arg:tt)*) => ({ - eprintln!("\x1b[0;96m{}\x1b[0m", format!($($arg)*)); - }) -} - -#[allow(unused_macros)] -macro_rules! info_yellow { - ($($arg:tt)*) => ({ - eprintln!("\x1b[0;33m{}\x1b[0m", format!($($arg)*)); - }) -} - -macro_rules! info_green { - ($($arg:tt)*) => ({ - eprintln!("\x1b[0;32m{}\x1b[0m", format!($($arg)*)); - }) -} - -#[allow(clippy::type_complexity)] -pub struct RunLoopCallbacks { - on_burn_chain_initialized: Option)>, - on_new_burn_chain_state: Option, - on_new_stacks_chain_state: - Option, - on_new_tenure: Option, -} - -impl Default for RunLoopCallbacks { - fn default() -> Self { - Self::new() - } -} - -impl RunLoopCallbacks { - pub fn new() -> RunLoopCallbacks { - RunLoopCallbacks { - on_burn_chain_initialized: None, - on_new_burn_chain_state: None, - on_new_stacks_chain_state: None, - on_new_tenure: None, - } - } - - pub fn on_burn_chain_initialized(&mut self, callback: fn(&mut Box)) { - self.on_burn_chain_initialized = Some(callback); - } - - pub fn on_new_burn_chain_state(&mut self, callback: fn(u64, &BurnchainTip, &ChainTip)) { - self.on_new_burn_chain_state = Some(callback); - } - - pub fn on_new_stacks_chain_state( - &mut self, - callback: fn(u64, &BurnchainTip, &ChainTip, &mut StacksChainState, &dyn BurnStateDB), - ) { - self.on_new_stacks_chain_state = Some(callback); - } - - pub fn on_new_tenure(&mut self, callback: fn(u64, &BurnchainTip, &ChainTip, &mut Tenure)) { - self.on_new_tenure = Some(callback); - } - - pub fn invoke_burn_chain_initialized(&self, burnchain: &mut Box) { - if let Some(cb) = self.on_burn_chain_initialized { - cb(burnchain); - } - } - - pub fn invoke_new_burn_chain_state( - &self, - round: u64, - burnchain_tip: &BurnchainTip, - chain_tip: &ChainTip, - ) { - info_blue!( - "Burnchain block #{} ({}) was produced with sortition #{}", - burnchain_tip.block_snapshot.block_height, - burnchain_tip.block_snapshot.burn_header_hash, - burnchain_tip.block_snapshot.sortition_hash - ); - - if let Some(cb) = self.on_new_burn_chain_state { - cb(round, burnchain_tip, chain_tip); - } - } - - pub fn invoke_new_stacks_chain_state( - &self, - round: u64, - burnchain_tip: &BurnchainTip, - chain_tip: &ChainTip, - chain_state: &mut StacksChainState, - burn_dbconn: &dyn BurnStateDB, - ) { - info_green!( - "Stacks block #{} ({}) successfully produced, including {} transactions", - chain_tip.metadata.stacks_block_height, - chain_tip.metadata.index_block_hash(), - chain_tip.block.txs.len() - ); - for tx in chain_tip.block.txs.iter() { - match &tx.auth { - TransactionAuth::Standard(TransactionSpendingCondition::Singlesig(auth)) => { - println!( - "-> Tx issued by {:?} (fee: {}, nonce: {})", - auth.signer, auth.tx_fee, auth.nonce - ) - } - _ => println!("-> Tx {:?}", tx.auth), - } - match &tx.payload { - TransactionPayload::Coinbase(..) => println!(" Coinbase"), - TransactionPayload::SmartContract(contract, ..) => println!(" Publish smart contract\n**************************\n{:?}\n**************************", contract.code_body), - TransactionPayload::TokenTransfer(recipent, amount, _) => println!(" Transfering {amount} µSTX to {recipent}"), - _ => println!(" {:?}", tx.payload) - } - } - - if let Some(cb) = self.on_new_stacks_chain_state { - cb(round, burnchain_tip, chain_tip, chain_state, burn_dbconn); - } - } - - pub fn invoke_new_tenure( - &self, - round: u64, - burnchain_tip: &BurnchainTip, - chain_tip: &ChainTip, - tenure: &mut Tenure, - ) { - if let Some(cb) = self.on_new_tenure { - cb(round, burnchain_tip, chain_tip, tenure); - } - } -} +use crate::EventDispatcher; #[derive(Clone, Debug, Serialize, Deserialize)] pub struct RegisteredKey { diff --git a/stacks-node/src/run_loop/nakamoto.rs b/stacks-node/src/run_loop/nakamoto.rs index b29bbd08685..86dcdb4a6b4 100644 --- a/stacks-node/src/run_loop/nakamoto.rs +++ b/stacks-node/src/run_loop/nakamoto.rs @@ -37,14 +37,14 @@ use stacks_common::util::{get_epoch_time_secs, sleep_ms}; use stx_genesis::GenesisData; use crate::burnchains::make_bitcoin_indexer; +use crate::genesis::{ + get_account_balances, get_account_lockups, get_names, get_namespaces, + use_test_genesis_chainstate, +}; use crate::globals::Globals as GenericGlobals; use crate::monitoring::{start_serving_monitoring_metrics, MonitoringError}; use crate::nakamoto_node::{self, StacksNode, BLOCK_PROCESSOR_STACK_SIZE, RELAYER_MAX_BUFFER}; use crate::neon_node::LeaderKeyRegistrationState; -use crate::node::{ - get_account_balances, get_account_lockups, get_names, get_namespaces, - use_test_genesis_chainstate, -}; use crate::run_loop::boot_nakamoto::Neon2NakaData; use crate::run_loop::neon; use crate::run_loop::neon::Counters; diff --git a/stacks-node/src/run_loop/neon.rs b/stacks-node/src/run_loop/neon.rs index e9f7eb2bab4..010220ab5fa 100644 --- a/stacks-node/src/run_loop/neon.rs +++ b/stacks-node/src/run_loop/neon.rs @@ -45,17 +45,16 @@ use stacks_common::types::PublicKey; use stacks_common::util::hash::Hash160; use stx_genesis::GenesisData; -use super::RunLoopCallbacks; use crate::burnchains::{make_bitcoin_indexer, Error}; +use crate::genesis::{ + get_account_balances, get_account_lockups, get_names, get_namespaces, + use_test_genesis_chainstate, +}; use crate::globals::NeonGlobals as Globals; use crate::monitoring::{start_serving_monitoring_metrics, MonitoringError}; use crate::neon_node::{ LeaderKeyRegistrationState, StacksNode, BLOCK_PROCESSOR_STACK_SIZE, RELAYER_MAX_BUFFER, }; -use crate::node::{ - get_account_balances, get_account_lockups, get_names, get_namespaces, - use_test_genesis_chainstate, -}; use crate::run_loop::boot_nakamoto::Neon2NakaData; use crate::syncctl::{PoxSyncWatchdog, PoxSyncWatchdogComms}; use crate::{ @@ -286,7 +285,6 @@ impl Counters { /// Coordinating a node running in neon mode. pub struct RunLoop { config: Config, - pub callbacks: RunLoopCallbacks, globals: Option, counters: Counters, coordinator_channels: Option<(CoordinatorReceivers, CoordinatorChannels)>, @@ -342,7 +340,6 @@ impl RunLoop { config, globals: None, coordinator_channels: Some(channels), - callbacks: RunLoopCallbacks::new(), counters: Counters::default(), should_keep_running, event_dispatcher, diff --git a/stacks-node/src/tenure.rs b/stacks-node/src/tenure.rs deleted file mode 100644 index 7361dca2a5c..00000000000 --- a/stacks-node/src/tenure.rs +++ /dev/null @@ -1,144 +0,0 @@ -use std::thread; -use std::time::{Duration, Instant}; - -#[cfg(test)] -use stacks::burnchains::PoxConstants; -#[cfg(test)] -use stacks::chainstate::burn::db::sortdb::SortitionDB; -use stacks::chainstate::burn::db::sortdb::SortitionHandleConn; -use stacks::chainstate::stacks::db::StacksChainState; -use stacks::chainstate::stacks::miner::BlockBuilderSettings; -use stacks::chainstate::stacks::{ - StacksBlock, StacksBlockBuilder, StacksMicroblock, StacksPrivateKey, StacksPublicKey, - StacksTransaction, -}; -use stacks::core::mempool::MemPoolDB; -use stacks_common::types::chainstate::VRFSeed; -use stacks_common::util::hash::Hash160; -use stacks_common::util::vrf::VRFProof; - -/// Only used by the Helium (Mocknet) node -use super::node::ChainTip; -use super::{BurnchainTip, Config}; - -pub struct TenureArtifacts { - pub anchored_block: StacksBlock, - pub microblocks: Vec, - pub parent_block: BurnchainTip, - pub burn_fee: u64, -} - -pub struct Tenure { - coinbase_tx: StacksTransaction, - config: Config, - pub burnchain_tip: BurnchainTip, - pub parent_block: ChainTip, - pub mem_pool: MemPoolDB, - pub vrf_seed: VRFSeed, - burn_fee_cap: u64, - vrf_proof: VRFProof, - microblock_pubkeyhash: Hash160, - parent_block_total_burn: u64, -} - -impl Tenure { - #[allow(clippy::too_many_arguments)] - pub fn new( - parent_block: ChainTip, - coinbase_tx: StacksTransaction, - config: Config, - mem_pool: MemPoolDB, - microblock_secret_key: StacksPrivateKey, - burnchain_tip: BurnchainTip, - vrf_proof: VRFProof, - burn_fee_cap: u64, - ) -> Tenure { - let mut microblock_pubkey = StacksPublicKey::from_private(µblock_secret_key); - microblock_pubkey.set_compressed(true); - let microblock_pubkeyhash = Hash160::from_node_public_key(µblock_pubkey); - - let parent_block_total_burn = burnchain_tip.block_snapshot.total_burn; - - Self { - coinbase_tx, - config, - burnchain_tip, - mem_pool, - parent_block, - vrf_seed: VRFSeed::from_proof(&vrf_proof), - vrf_proof, - burn_fee_cap, - microblock_pubkeyhash, - parent_block_total_burn, - } - } - - pub fn run(&mut self, burn_dbconn: &SortitionHandleConn) -> Option { - info!("Node starting new tenure with VRF {:?}", self.vrf_seed); - - let duration_left: u128 = self.config.burnchain.commit_anchor_block_within as u128; - let mut elapsed = Instant::now().duration_since(self.burnchain_tip.received_at); - while duration_left.saturating_sub(elapsed.as_millis()) > 0 { - thread::sleep(Duration::from_millis(1000)); - elapsed = Instant::now().duration_since(self.burnchain_tip.received_at); - } - - let (chain_state, _) = StacksChainState::open( - self.config.is_mainnet(), - self.config.burnchain.chain_id, - &self.config.get_chainstate_path_str(), - Some(self.config.node.get_marf_opts()), - ) - .unwrap(); - - let (anchored_block, _, _) = StacksBlockBuilder::build_anchored_block( - &chain_state, - burn_dbconn, - &mut self.mem_pool, - &self.parent_block.metadata, - self.parent_block_total_burn, - &self.vrf_proof, - &self.microblock_pubkeyhash, - &self.coinbase_tx, - BlockBuilderSettings::limited(), - None, - &self.config.get_burnchain(), - ) - .unwrap(); - - info!("Finish tenure: {}", anchored_block.block_hash()); - - let artifact = TenureArtifacts { - anchored_block, - microblocks: vec![], - parent_block: self.burnchain_tip.clone(), - burn_fee: self.burn_fee_cap, - }; - Some(artifact) - } - - #[cfg(test)] - pub fn open_chainstate(&self) -> StacksChainState { - use stacks::core::CHAIN_ID_TESTNET; - - let (chain_state, _) = StacksChainState::open( - false, - CHAIN_ID_TESTNET, - &self.config.get_chainstate_path_str(), - Some(self.config.node.get_marf_opts()), - ) - .unwrap(); - chain_state - } - - #[cfg(test)] - pub fn open_fake_sortdb(&self) -> SortitionDB { - SortitionDB::open( - &self.config.get_burn_db_file_path(), - true, - PoxConstants::testnet_default(), - None, - ) - .unwrap() - } -} diff --git a/stacks-node/src/tests/integrations.rs b/stacks-node/src/tests/integrations.rs deleted file mode 100644 index 993dca370ed..00000000000 --- a/stacks-node/src/tests/integrations.rs +++ /dev/null @@ -1,2447 +0,0 @@ -// Copyright (C) 2020-2026 Stacks Open Internet Foundation -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . - -use std::cmp::Ordering; -use std::collections::HashMap; -use std::fmt::Write; -use std::sync::Mutex; - -use clarity::vm::analysis::contract_interface_builder::{ - build_contract_interface, ContractInterface, -}; -use clarity::vm::analysis::mem_type_check; -use clarity::vm::costs::ExecutionCost; -use clarity::vm::types::{ - QualifiedContractIdentifier, ResponseData, StacksAddressExtensions, TupleData, -}; -use clarity::vm::{ClarityName, ClarityVersion, ContractName, Value}; -use lazy_static::lazy_static; -use pinny::tag; -use reqwest; -use serde_json::json; -use stacks::burnchains::Address; -use stacks::chainstate::stacks::db::blocks::{MemPoolRejection, MINIMUM_TX_FEE_RATE_PER_BYTE}; -use stacks::chainstate::stacks::db::StacksChainState; -use stacks::chainstate::stacks::{ - StacksBlockHeader, StacksPrivateKey, StacksTransaction, TokenTransferMemo, - TransactionContractCall, TransactionPayload, -}; -use stacks::clarity_vm::clarity::ClarityConnection; -use stacks::codec::StacksMessageCodec; -use stacks::config::InitialBalance; -use stacks::core::mempool::MAXIMUM_MEMPOOL_TX_CHAINING; -use stacks::core::test_util::{ - make_contract_call, make_contract_publish, make_sponsored_stacks_transfer_on_testnet, - make_stacks_transfer_serialized, to_addr, -}; -use stacks::core::{ - EpochList, StacksEpoch, StacksEpochId, CHAIN_ID_TESTNET, PEER_VERSION_EPOCH_2_0, - PEER_VERSION_EPOCH_2_05, PEER_VERSION_EPOCH_2_1, -}; -use stacks::net::api::callreadonly::CallReadOnlyRequestBody; -use stacks::net::api::getaccount::AccountEntryResponse; -use stacks::net::api::getcontractsrc::ContractSrcResponse; -use stacks::net::api::getistraitimplemented::GetIsTraitImplementedResponse; -use stacks_common::types::chainstate::{StacksAddress, StacksBlockId, VRFSeed}; -use stacks_common::util::hash::{hex_bytes, to_hex, Sha256Sum}; - -use super::{new_test_conf, ADDR_4, SK_1, SK_2, SK_3}; -use crate::helium::RunLoop; - -const OTHER_CONTRACT: &str = " - (define-data-var x uint u0) - (define-public (f1) - (ok (var-get x))) - (define-public (f2 (val uint)) - (ok (var-set x val))) -"; - -const CALL_READ_CONTRACT: &str = " - (define-public (public-no-write) - (ok (contract-call? .other f1))) - (define-public (public-write) - (ok (contract-call? .other f2 u5))) -"; - -const GET_INFO_CONTRACT: &str = " - (define-map block-data - { height: uint } - { stacks-hash: (buff 32), - id-hash: (buff 32), - btc-hash: (buff 32), - vrf-seed: (buff 32), - burn-block-time: uint, - stacks-miner: principal }) - (define-private (test-1) (get-block-info? time u1)) - (define-private (test-2) (get-block-info? time block-height)) - (define-private (test-3) (get-block-info? time u100000)) - (define-private (test-4 (x uint)) (get-block-info? header-hash x)) - (define-private (test-5) (get-block-info? header-hash (- block-height u1))) - (define-private (test-6) (get-block-info? burnchain-header-hash u1)) - (define-private (test-7) (get-block-info? vrf-seed u1)) - (define-private (test-8) (get-block-info? miner-address u1)) - (define-private (test-9) (get-block-info? miner-address block-height)) - (define-private (test-10) (get-block-info? miner-address u100000)) - (define-private (test-11) burn-block-height) - - (define-private (get-block-id-hash (height uint)) (unwrap-panic - (get id-hash (map-get? block-data { height: height })))) - - ;; should always return true! - ;; evaluates 'block-height' at the block in question. - ;; NOTABLY, this would fail if the MARF couldn't figure out - ;; the height of the 'current chain tip'. - (define-private (exotic-block-height (height uint)) - (is-eq (at-block (get-block-id-hash height) block-height) - height)) - (define-read-only (get-exotic-data-info (height uint)) - (unwrap-panic (map-get? block-data { height: height }))) - - (define-read-only (get-exotic-data-info? (height uint)) - (unwrap-panic (map-get? block-data { height: height }))) - - (define-private (exotic-data-checks (height uint)) - (let ((block-to-check (unwrap-panic (get-block-info? id-header-hash height))) - (block-info (unwrap-panic (map-get? block-data { height: (- height u1) })))) - (and (is-eq (print (unwrap-panic (at-block block-to-check (get-block-info? id-header-hash (- block-height u1))))) - (print (get id-hash block-info))) - (is-eq (print (unwrap-panic (at-block block-to-check (get-block-info? header-hash (- block-height u1))))) - (print (unwrap-panic (get-block-info? header-hash (- height u1)))) - (print (get stacks-hash block-info))) - (is-eq (print (unwrap-panic (at-block block-to-check (get-block-info? vrf-seed (- block-height u1))))) - (print (unwrap-panic (get-block-info? vrf-seed (- height u1)))) - (print (get vrf-seed block-info))) - (is-eq (print (unwrap-panic (at-block block-to-check (get-block-info? burnchain-header-hash (- block-height u1))))) - (print (unwrap-panic (get-block-info? burnchain-header-hash (- height u1)))) - (print (get btc-hash block-info))) - (is-eq (print (unwrap-panic (at-block block-to-check (get-block-info? time (- block-height u1))))) - (print (unwrap-panic (get-block-info? time (- height u1)))) - (print (get burn-block-time block-info))) - (is-eq (print (unwrap-panic (at-block block-to-check (get-block-info? miner-address (- block-height u1))))) - (print (unwrap-panic (get-block-info? miner-address (- height u1)))) - (print (get stacks-miner block-info)))))) - - (define-private (inner-update-info (height uint)) - (let ((value (tuple - (stacks-hash (unwrap-panic (get-block-info? header-hash height))) - (id-hash (unwrap-panic (get-block-info? id-header-hash height))) - (btc-hash (unwrap-panic (get-block-info? burnchain-header-hash height))) - (vrf-seed (unwrap-panic (get-block-info? vrf-seed height))) - (burn-block-time (unwrap-panic (get-block-info? time height))) - (stacks-miner (unwrap-panic (get-block-info? miner-address height)))))) - (ok (map-set block-data { height: height } value)))) - - (define-public (update-info) - (begin - (unwrap-panic (inner-update-info (- block-height u2))) - (inner-update-info (- block-height u1)))) - - (define-trait trait-1 ( - (foo-exec (int) (response int int)))) - - (define-trait trait-2 ( - (get-1 (uint) (response uint uint)) - (get-2 (uint) (response uint uint)))) - - (define-trait trait-3 ( - (fn-1 (uint) (response uint uint)) - (fn-2 (uint) (response uint uint)))) - "; - -const IMPL_TRAIT_CONTRACT: &str = " - ;; explicit trait compliance for trait 1 - (impl-trait .get-info.trait-1) - (define-private (test-height) burn-block-height) - (define-public (foo-exec (a int)) (ok 1)) - - ;; implicit trait compliance for trait-2 - (define-public (get-1 (x uint)) (ok u1)) - (define-public (get-2 (x uint)) (ok u1)) - - ;; invalid trait compliance for trait-3 - (define-public (fn-1 (x uint)) (ok u1)) - "; - -lazy_static! { - static ref HTTP_BINDING: Mutex> = Mutex::new(None); -} - -#[tag(slow)] -#[test] -#[ignore] -fn integration_test_get_info() { - let mut conf = new_test_conf(); - let spender_addr = to_addr(&StacksPrivateKey::from_hex(SK_3).unwrap()).into(); - let principal_sk = StacksPrivateKey::from_hex(SK_2).unwrap(); - let contract_sk = StacksPrivateKey::from_hex(SK_1).unwrap(); - - conf.initial_balances.push(InitialBalance { - address: spender_addr, - amount: 100300, - }); - conf.initial_balances.push(InitialBalance { - address: to_addr(&principal_sk).into(), // contract-publish - amount: 1000, - }); - conf.initial_balances.push(InitialBalance { - address: to_addr(&contract_sk).into(), - amount: 1000, - }); - - conf.burnchain.commit_anchor_block_within = 5000; - conf.miner.first_attempt_time_ms = i64::MAX as u64; - conf.miner.subsequent_attempt_time_ms = i64::MAX as u64; - - let num_rounds = 5; - - let rpc_bind = conf.node.rpc_bind.clone(); - let mut run_loop = RunLoop::new(conf); - - { - let mut http_opt = HTTP_BINDING.lock().unwrap(); - http_opt.replace(format!("http://{rpc_bind}")); - } - - run_loop - .callbacks - .on_new_tenure(|round, _burnchain_tip, chain_tip, tenure| { - let mut chainstate_copy = tenure.open_chainstate(); - let sortdb = tenure.open_fake_sortdb(); - - let contract_sk = StacksPrivateKey::from_hex(SK_1).unwrap(); - let principal_sk = StacksPrivateKey::from_hex(SK_2).unwrap(); - let spender_sk = StacksPrivateKey::from_hex(SK_3).unwrap(); - let header_hash = chain_tip.block.block_hash(); - let consensus_hash = &chain_tip.metadata.consensus_hash; - - if round == 1 { - // block-height = 2 - eprintln!("Tenure in 1 started!"); - let publish_tx = make_contract_publish( - &contract_sk, - 0, - 10, - CHAIN_ID_TESTNET, - "get-info", - GET_INFO_CONTRACT, - ); - tenure - .mem_pool - .submit_raw( - &mut chainstate_copy, - &sortdb, - consensus_hash, - &header_hash, - publish_tx, - &ExecutionCost::max_value(), - &StacksEpochId::Epoch21, - ) - .unwrap(); - let publish_tx = make_contract_publish( - &contract_sk, - 1, - 10, - CHAIN_ID_TESTNET, - "other", - OTHER_CONTRACT, - ); - tenure - .mem_pool - .submit_raw( - &mut chainstate_copy, - &sortdb, - consensus_hash, - &header_hash, - publish_tx, - &ExecutionCost::max_value(), - &StacksEpochId::Epoch21, - ) - .unwrap(); - let publish_tx = make_contract_publish( - &contract_sk, - 2, - 10, - CHAIN_ID_TESTNET, - "main", - CALL_READ_CONTRACT, - ); - tenure - .mem_pool - .submit_raw( - &mut chainstate_copy, - &sortdb, - consensus_hash, - &header_hash, - publish_tx, - &ExecutionCost::max_value(), - &StacksEpochId::Epoch21, - ) - .unwrap(); - - // store this for later, because we can't just do it in a refcell or any outer - // variable because this is a function pointer type, and thus can't access anything - // outside its scope :( - let tmppath = "/tmp/integration_test_get_info-old-tip"; - let old_tip = StacksBlockId::new(consensus_hash, &header_hash); - use std::fs; - use std::io::Write; - if fs::metadata(tmppath).is_ok() { - fs::remove_file(tmppath).unwrap(); - } - let mut f = fs::File::create(tmppath).unwrap(); - f.write_all(&old_tip.serialize_to_vec()).unwrap(); - } else if round == 2 { - // block-height = 3 - let publish_tx = make_contract_publish( - &contract_sk, - 3, - 10, - CHAIN_ID_TESTNET, - "impl-trait-contract", - IMPL_TRAIT_CONTRACT, - ); - eprintln!("Tenure in 2 started!"); - tenure - .mem_pool - .submit_raw( - &mut chainstate_copy, - &sortdb, - consensus_hash, - &header_hash, - publish_tx, - &ExecutionCost::max_value(), - &StacksEpochId::Epoch21, - ) - .unwrap(); - } else if round >= 3 { - // block-height > 3 - let tx = make_contract_call( - &principal_sk, - round - 3, - 10, - CHAIN_ID_TESTNET, - &to_addr(&contract_sk), - "get-info", - "update-info", - &[], - ); - eprintln!("update-info submitted"); - tenure - .mem_pool - .submit_raw( - &mut chainstate_copy, - &sortdb, - consensus_hash, - &header_hash, - tx, - &ExecutionCost::max_value(), - &StacksEpochId::Epoch21, - ) - .unwrap(); - } - - if round >= 1 { - let tx_xfer = make_stacks_transfer_serialized( - &spender_sk, - round - 1, - 10, - CHAIN_ID_TESTNET, - &StacksAddress::from_string(ADDR_4).unwrap().into(), - 100, - ); - tenure - .mem_pool - .submit_raw( - &mut chainstate_copy, - &sortdb, - consensus_hash, - &header_hash, - tx_xfer, - &ExecutionCost::max_value(), - &StacksEpochId::Epoch21, - ) - .unwrap(); - } - }); - - run_loop.callbacks.on_new_stacks_chain_state(|round, _burnchain_tip, chain_tip, chain_state, burn_dbconn| { - let contract_addr = to_addr(&StacksPrivateKey::from_hex(SK_1).unwrap()); - let contract_identifier = - QualifiedContractIdentifier::parse(&format!("{contract_addr}.get-info")).unwrap(); - let impl_trait_contract_identifier = - QualifiedContractIdentifier::parse(&format!("{contract_addr}.impl-trait-contract")).unwrap(); - - let http_origin = { - HTTP_BINDING.lock().unwrap().clone().unwrap() - }; - - match round { - 1 => { - // - Chain length should be 2. - let blocks = StacksChainState::list_blocks(chain_state.db()).unwrap(); - assert!(chain_tip.metadata.stacks_block_height == 2); - - // Block #1 should have 5 txs - assert_eq!(chain_tip.block.txs.len(), 5); - - let parent = &chain_tip.block.header.parent_block; - let bhh = &chain_tip.metadata.index_block_hash(); - eprintln!("Current Block: {bhh} Parent Block: {parent}"); - let parent_val = Value::buff_from(parent.as_bytes().to_vec()).unwrap(); - - // find header metadata - let mut headers = vec![]; - for block in blocks.iter() { - let header = StacksChainState::get_anchored_block_header_info(chain_state.db(), &block.0, &block.1).unwrap().unwrap(); - eprintln!("{}/{}: {header:?}", &block.0, &block.1); - headers.push(header); - } - - let _tip_header_info = headers.last().unwrap(); - - // find miner metadata - let mut miners = vec![]; - for block in blocks.iter() { - let miner = StacksChainState::get_miner_info(chain_state.db(), &block.0, &block.1).unwrap().unwrap(); - miners.push(miner); - } - - let _tip_miner = miners.last().unwrap(); - - assert_eq!( - chain_state.clarity_eval_read_only( - burn_dbconn, bhh, &contract_identifier, "block-height"), - Value::UInt(2)); - - assert_eq!( - chain_state.clarity_eval_read_only( - burn_dbconn,bhh, &contract_identifier, "(test-1)"), - Value::some(Value::UInt(headers[0].burn_header_timestamp as u128)).unwrap()); - - assert_eq!( - chain_state.clarity_eval_read_only( - burn_dbconn, bhh, &contract_identifier, "(test-2)"), - Value::none()); - - assert_eq!( - chain_state.clarity_eval_read_only( - burn_dbconn, bhh, &contract_identifier, "(test-3)"), - Value::none()); - - assert_eq!( - chain_state.clarity_eval_read_only( - burn_dbconn, bhh, &contract_identifier, "(test-4 u1)"), - Value::some(parent_val.clone()).unwrap()); - - assert_eq!( - chain_state.clarity_eval_read_only( - burn_dbconn, bhh, &contract_identifier, "(test-5)"), - Value::some(parent_val).unwrap()); - - // test-6 and test-7 return the block at height 1's VRF-seed, - // which in this integration test, should be blocks[0] - let last_tip = &blocks[0]; - eprintln!("Last block info: stacks: {}, burn: {}", last_tip.1, last_tip.0); - let last_block = StacksChainState::load_block(&chain_state.blocks_path, &last_tip.0, &last_tip.1).unwrap().unwrap(); - assert_eq!(parent, &last_block.header.block_hash()); - - let last_vrf_seed = VRFSeed::from_proof(&last_block.header.proof).as_bytes().to_vec(); - let last_burn_header = headers[0].burn_header_hash.as_bytes().to_vec(); - - assert_eq!( - chain_state.clarity_eval_read_only( - burn_dbconn, bhh, &contract_identifier, "(test-6)"), - Value::some(Value::buff_from(last_burn_header).unwrap()).unwrap()); - assert_eq!( - chain_state.clarity_eval_read_only( - burn_dbconn, bhh, &contract_identifier, "(test-7)"), - Value::some(Value::buff_from(last_vrf_seed).unwrap()).unwrap()); - - // verify that we can get the block miner - assert_eq!( - chain_state.clarity_eval_read_only( - burn_dbconn, bhh, &contract_identifier, "(test-8)"), - Value::some(Value::Principal(miners[0].address.to_account_principal())).unwrap()); - - assert_eq!( - chain_state.clarity_eval_read_only( - burn_dbconn, bhh, &contract_identifier, "(test-9)"), - Value::none()); - - assert_eq!( - chain_state.clarity_eval_read_only( - burn_dbconn, bhh, &contract_identifier, "(test-10)"), - Value::none()); - - // verify we can read the burn block height - assert_eq!( - chain_state.clarity_eval_read_only( - burn_dbconn, bhh, &contract_identifier, "(test-11)"), - Value::UInt(2)); - - }, - 2 => { - // Chain height should be 3 - let bhh = &chain_tip.metadata.index_block_hash(); - assert_eq!( - chain_state.clarity_eval_read_only( - burn_dbconn, bhh, &impl_trait_contract_identifier, "(test-height)"), - Value::UInt(3)); - } - 4 => { - let bhh = &chain_tip.metadata.index_block_hash(); - - assert_eq!(Value::Bool(true), chain_state.clarity_eval_read_only( - burn_dbconn, bhh, &contract_identifier, "(exotic-block-height u2)")); - assert_eq!(Value::Bool(true), chain_state.clarity_eval_read_only( - burn_dbconn, bhh, &contract_identifier, "(exotic-block-height u3)")); - assert_eq!(Value::Bool(true), chain_state.clarity_eval_read_only( - burn_dbconn, bhh, &contract_identifier, "(exotic-block-height u4)")); - - assert_eq!(Value::Bool(true), chain_state.clarity_eval_read_only( - burn_dbconn, bhh, &contract_identifier, "(exotic-data-checks u3)")); - assert_eq!(Value::Bool(true), chain_state.clarity_eval_read_only( - burn_dbconn, bhh, &contract_identifier, "(exotic-data-checks u4)")); - - let client = reqwest::blocking::Client::new(); - let path = format!("{http_origin}/v2/map_entry/{contract_addr}/get-info/block-data"); - - let key: Value = TupleData::from_data(vec![(ClarityName::from_literal("height"), Value::UInt(3))]) - .unwrap().into(); - - eprintln!("Test: POST {path}"); - let res = client.post(&path) - .json(&key.serialize_to_hex().unwrap()) - .send() - .unwrap().json::>().unwrap(); - let result_data = Value::try_deserialize_hex_untyped(&res["data"][2..]).unwrap(); - let expected_data = chain_state.clarity_eval_read_only(burn_dbconn, bhh, &contract_identifier, - "(some (get-exotic-data-info u3))"); - assert!(res.contains_key("proof")); - - assert_eq!(result_data, expected_data); - - let key: Value = TupleData::from_data(vec![(ClarityName::from_literal("height"), Value::UInt(100))]) - .unwrap().into(); - - eprintln!("Test: POST {path}"); - let res = client.post(&path) - .json(&key.serialize_to_hex().unwrap()) - .send() - .unwrap().json::>().unwrap(); - let result_data = Value::try_deserialize_hex_untyped(&res["data"][2..]).unwrap(); - assert_eq!(result_data, Value::none()); - - let sender_addr = to_addr(&StacksPrivateKey::from_hex(SK_3).unwrap()); - - // now, let's use a query string to get data without a proof - let path = format!("{http_origin}/v2/map_entry/{contract_addr}/get-info/block-data?proof=0"); - - let key: Value = TupleData::from_data(vec![(ClarityName::from_literal("height"), Value::UInt(3))]) - .unwrap().into(); - - eprintln!("Test: POST {path}"); - let res = client.post(&path) - .json(&key.serialize_to_hex().unwrap()) - .send() - .unwrap().json::>().unwrap(); - - assert!(!res.contains_key("proof")); - let result_data = Value::try_deserialize_hex_untyped(&res["data"][2..]).unwrap(); - let expected_data = chain_state.clarity_eval_read_only(burn_dbconn, bhh, &contract_identifier, - "(some (get-exotic-data-info u3))"); - eprintln!("{}", serde_json::to_string(&res).unwrap()); - - assert_eq!(result_data, expected_data); - - // now, let's use a query string to get data _with_ a proof - let path = format!("{http_origin}/v2/map_entry/{contract_addr}/get-info/block-data?proof=1"); - - let key: Value = TupleData::from_data(vec![(ClarityName::from_literal("height"), Value::UInt(3))]) - .unwrap().into(); - - eprintln!("Test: POST {path}"); - let res = client.post(&path) - .json(&key.serialize_to_hex().unwrap()) - .send() - .unwrap().json::>().unwrap(); - - assert!(res.contains_key("proof")); - let result_data = Value::try_deserialize_hex_untyped(&res["data"][2..]).unwrap(); - let expected_data = chain_state.clarity_eval_read_only(burn_dbconn, bhh, &contract_identifier, - "(some (get-exotic-data-info u3))"); - eprintln!("{}", serde_json::to_string(&res).unwrap()); - - assert_eq!(result_data, expected_data); - - // account with a nonce entry + a balance entry - let path = format!("{http_origin}/v2/accounts/{sender_addr}"); - eprintln!("Test: GET {path}"); - let res = client.get(&path).send().unwrap().json::().unwrap(); - assert_eq!(u128::from_str_radix(&res.balance[2..], 16).unwrap(), 99860); - assert_eq!(res.nonce, 4); - assert!(res.nonce_proof.is_some()); - assert!(res.balance_proof.is_some()); - - // account with a nonce entry but not a balance entry - let path = format!("{http_origin}/v2/accounts/{contract_addr}"); - eprintln!("Test: GET {path}"); - let res = client.get(&path).send().unwrap().json::().unwrap(); - assert_eq!(u128::from_str_radix(&res.balance[2..], 16).unwrap(), 960); - assert_eq!(res.nonce, 4); - assert!(res.nonce_proof.is_some()); - assert!(res.balance_proof.is_some()); - - // account with a balance entry but not a nonce entry - let path = format!("{http_origin}/v2/accounts/{ADDR_4}"); - eprintln!("Test: GET {path}"); - let res = client.get(&path).send().unwrap().json::().unwrap(); - assert_eq!(u128::from_str_radix(&res.balance[2..], 16).unwrap(), 400); - assert_eq!(res.nonce, 0); - assert!(res.nonce_proof.is_some()); - assert!(res.balance_proof.is_some()); - - // account with neither! - let path = format!("{http_origin}/v2/accounts/{contract_addr}.get-info"); - eprintln!("Test: GET {path}"); - let res = client.get(&path).send().unwrap().json::().unwrap(); - assert_eq!(u128::from_str_radix(&res.balance[2..], 16).unwrap(), 0); - assert_eq!(res.nonce, 0); - assert!(res.nonce_proof.is_some()); - assert!(res.balance_proof.is_some()); - - let path = format!("{http_origin}/v2/accounts/{ADDR_4}?proof=0"); - eprintln!("Test: GET {path}"); - let res = client.get(&path).send().unwrap().json::().unwrap(); - assert_eq!(u128::from_str_radix(&res.balance[2..], 16).unwrap(), 400); - assert_eq!(res.nonce, 0); - assert!(res.nonce_proof.is_none()); - assert!(res.balance_proof.is_none()); - - let path = format!("{http_origin}/v2/accounts/{ADDR_4}?proof=1"); - eprintln!("Test: GET {path}"); - let res = client.get(&path).send().unwrap().json::().unwrap(); - assert_eq!(u128::from_str_radix(&res.balance[2..], 16).unwrap(), 400); - assert_eq!(res.nonce, 0); - assert!(res.nonce_proof.is_some()); - assert!(res.balance_proof.is_some()); - - // let's try getting the transfer cost - let path = format!("{http_origin}/v2/fees/transfer"); - eprintln!("Test: GET {path}"); - let res = client.get(&path).send().unwrap().json::().unwrap(); - assert!(res > 0); - - // let's get a contract ABI - - let path = format!("{http_origin}/v2/contracts/interface/{contract_addr}/get-info"); - eprintln!("Test: GET {path}"); - let res = client.get(&path).send().unwrap().json::().unwrap(); - - let contract_analysis = mem_type_check(GET_INFO_CONTRACT, ClarityVersion::Clarity2, StacksEpochId::Epoch21).unwrap().1; - let expected_interface = build_contract_interface(&contract_analysis).unwrap(); - - eprintln!("{}", serde_json::to_string(&expected_interface).unwrap()); - - assert_eq!(res, expected_interface); - - // a missing one? - - let path = format!("{http_origin}/v2/contracts/interface/{contract_addr}/not-there"); - eprintln!("Test: GET {path}"); - assert_eq!(client.get(&path).send().unwrap().status(), 404); - - // let's get a contract SRC - - let path = format!("{http_origin}/v2/contracts/source/{contract_addr}/get-info"); - eprintln!("Test: GET {path}"); - let res = client.get(&path).send().unwrap().json::().unwrap(); - - assert_eq!(res.source, GET_INFO_CONTRACT); - assert_eq!(res.publish_height, 2); - assert!(res.marf_proof.is_some()); - - - let path = format!("{http_origin}/v2/contracts/source/{contract_addr}/get-info?proof=0"); - eprintln!("Test: GET {path}"); - let res = client.get(&path).send().unwrap().json::().unwrap(); - - assert_eq!(res.source, GET_INFO_CONTRACT); - assert_eq!(res.publish_height, 2); - assert!(res.marf_proof.is_none()); - - // a missing one? - - let path = format!("{http_origin}/v2/contracts/source/{contract_addr}/not-there"); - eprintln!("Test: GET {path}"); - assert_eq!(client.get(&path).send().unwrap().status(), 404); - - - // how about a read-only function call! - let path = format!("{http_origin}/v2/contracts/call-read/{contract_addr}/get-info/get-exotic-data-info"); - eprintln!("Test: POST {path}"); - - let body = CallReadOnlyRequestBody { - sender: "'SP139Q3N9RXCJCD1XVA4N5RYWQ5K9XQ0T9PKQ8EE5".into(), - sponsor: None, - arguments: vec![Value::UInt(3).serialize_to_hex().unwrap()] - }; - - let res = client.post(&path) - .json(&body) - .send() - .unwrap().json::().unwrap(); - assert!(res.get("cause").is_none()); - assert!(res["okay"].as_bool().unwrap()); - - let result_data = Value::try_deserialize_hex_untyped(&res["result"].as_str().unwrap()[2..]).unwrap(); - let expected_data = chain_state.clarity_eval_read_only(burn_dbconn, bhh, &contract_identifier, - "(get-exotic-data-info u3)"); - assert_eq!(result_data, expected_data); - - // how about a non read-only function call which does not modify anything - let path = format!("{http_origin}/v2/contracts/call-read/{contract_addr}/main/public-no-write"); - eprintln!("Test: POST {path}"); - - let body = CallReadOnlyRequestBody { - sender: "'SP139Q3N9RXCJCD1XVA4N5RYWQ5K9XQ0T9PKQ8EE5".into(), - sponsor: None, - arguments: vec![] - }; - - let res = client.post(&path) - .json(&body) - .send() - .unwrap().json::().unwrap(); - assert!(res.get("cause").is_none()); - assert!(res["okay"].as_bool().unwrap()); - - let result_data = Value::try_deserialize_hex_untyped(&res["result"].as_str().unwrap()[2..]).unwrap(); - let expected_data = Value::Response(ResponseData { - committed: true, - data: Box::new(Value::Response(ResponseData { - committed: true, - data: Box::new(Value::UInt(0)) - })) - }); - assert_eq!(result_data, expected_data); - - // how about a non read-only function call which does modify something and should fail - let path = format!("{http_origin}/v2/contracts/call-read/{contract_addr}/main/public-write"); - eprintln!("Test: POST {path}"); - - let body = CallReadOnlyRequestBody { - sender: "'SP139Q3N9RXCJCD1XVA4N5RYWQ5K9XQ0T9PKQ8EE5".into(), - sponsor: None, - arguments: vec![] - }; - - let res = client.post(&path) - .json(&body) - .send() - .unwrap().json::().unwrap(); - assert!(res.get("cause").is_some()); - assert!(!res["okay"].as_bool().unwrap()); - assert!(res["cause"].as_str().unwrap().contains("NotReadOnly")); - - // let's try a call with a url-encoded string. - let path = format!("{http_origin}/v2/contracts/call-read/{contract_addr}/get-info/get-exotic-data-info%3F"); - eprintln!("Test: POST {path}"); - - let body = CallReadOnlyRequestBody { - sender: "'SP139Q3N9RXCJCD1XVA4N5RYWQ5K9XQ0T9PKQ8EE5".into(), - sponsor: None, - arguments: vec![Value::UInt(3).serialize_to_hex().unwrap()] - }; - - let res = client.post(&path) - .json(&body) - .send() - .unwrap() - .json::().unwrap(); - assert!(res.get("cause").is_none()); - assert!(res["okay"].as_bool().unwrap()); - - let result_data = Value::try_deserialize_hex_untyped(&res["result"].as_str().unwrap()[2..]).unwrap(); - let expected_data = chain_state.clarity_eval_read_only(burn_dbconn, bhh, &contract_identifier, - "(get-exotic-data-info? u3)"); - assert_eq!(result_data, expected_data); - - // let's have a runtime error! - let path = format!("{http_origin}/v2/contracts/call-read/{contract_addr}/get-info/get-exotic-data-info"); - eprintln!("Test: POST {path}"); - - let body = CallReadOnlyRequestBody { - sender: "'SP139Q3N9RXCJCD1XVA4N5RYWQ5K9XQ0T9PKQ8EE5".into(), - sponsor: None, - arguments: vec![Value::UInt(100).serialize_to_hex().unwrap()] - }; - - let res = client.post(&path) - .json(&body) - .send() - .unwrap().json::().unwrap(); - - assert!(res.get("result").is_none()); - assert!(!res["okay"].as_bool().unwrap()); - assert!(res["cause"].as_str().unwrap().contains("UnwrapFailure")); - - // let's have a runtime error! - let path = format!("{http_origin}/v2/contracts/call-read/{contract_addr}/get-info/update-info"); - eprintln!("Test: POST {path}"); - - let body = CallReadOnlyRequestBody { - sender: "'SP139Q3N9RXCJCD1XVA4N5RYWQ5K9XQ0T9PKQ8EE5".into(), - sponsor: None, - arguments: vec![] - }; - - let res = client.post(&path) - .json(&body) - .send() - .unwrap().json::().unwrap(); - - eprintln!("{:#?}", res["cause"].as_str().unwrap()); - assert!(res.get("result").is_none()); - assert!(!res["okay"].as_bool().unwrap()); - assert!(res["cause"].as_str().unwrap().contains("NotReadOnly")); - - // let's submit a valid transaction! - let spender_sk = StacksPrivateKey::from_hex(SK_3).unwrap(); - let path = format!("{http_origin}/v2/transactions"); - eprintln!("Test: POST {path} (valid)"); - - // tx_xfer is 180 bytes long - let tx_xfer = make_stacks_transfer_serialized( - &spender_sk, - round, - 200, - CHAIN_ID_TESTNET, - &StacksAddress::from_string(ADDR_4).unwrap().into(), - 123); - - let res: String = client.post(&path) - .header("Content-Type", "application/octet-stream") - .body(tx_xfer.clone()) - .send() - .unwrap() - .json() - .unwrap(); - - assert_eq!(res, format!("{}", StacksTransaction::consensus_deserialize(&mut &tx_xfer[..]).unwrap().txid())); - - // let's test a posttransaction call that fails to deserialize, - let tx_hex = "80800000000400f942874ce525e87f21bbe8c121b12fac831d02f4000000000000000000000000000003e80001031734446f0870af42bb0cafad27f405e5d9eba441375eada8607a802b875fbb7ba7c4da3474f2bfd76851fb6314a48fe98b57440b8ccec6c9b8362c843a89f303020000000001047465737400000007282b2031203129"; - let tx_xfer_invalid = hex_bytes(tx_hex).unwrap(); - - let res = client.post(&path) - .header("Content-Type", "application/octet-stream") - .body(tx_xfer_invalid) - .send() - .unwrap().json::().unwrap(); - - eprintln!("{res}"); - assert_eq!(res.get("error").unwrap().as_str().unwrap(), "transaction rejected"); - assert!(res.get("reason").is_some()); - - // let's submit an invalid transaction! - let path = format!("{http_origin}/v2/transactions"); - eprintln!("Test: POST {path} (invalid)"); - - // tx_xfer_invalid is 180 bytes long - // bad nonce - let tx_xfer_invalid = make_stacks_transfer_serialized(&spender_sk, round + 30, 200, CHAIN_ID_TESTNET, - &StacksAddress::from_string(ADDR_4).unwrap().into(), 456); - - let tx_xfer_invalid_tx = StacksTransaction::consensus_deserialize(&mut &tx_xfer_invalid[..]).unwrap(); - - let res = client.post(&path) - .header("Content-Type", "application/octet-stream") - .body(tx_xfer_invalid) - .send() - .unwrap() - .json::() - .unwrap(); - - eprintln!("{res}"); - assert_eq!(res.get("txid").unwrap().as_str().unwrap(), format!("{}", tx_xfer_invalid_tx.txid())); - assert_eq!(res.get("error").unwrap().as_str().unwrap(), "transaction rejected"); - assert!(res.get("reason").is_some()); - - // testing /v2/trait// - // trait does not exist - let path = format!("{http_origin}/v2/traits/{contract_addr}/get-info/{contract_addr}/get-info/dummy-trait"); - eprintln!("Test: GET {path}"); - assert_eq!(client.get(&path).send().unwrap().status(), 404); - - // explicit trait compliance - let path = format!("{http_origin}/v2/traits/{contract_addr}/impl-trait-contract/{contract_addr}/get-info/trait-1"); - let res = client.get(&path).send().unwrap().json::().unwrap(); - eprintln!("Test: GET {path}"); - assert!(res.is_implemented); - - // No trait found - let path = format!("{http_origin}/v2/traits/{contract_addr}/impl-trait-contract/{contract_addr}/get-info/trait-4"); - eprintln!("Test: GET {path}"); - assert_eq!(client.get(&path).send().unwrap().status(), 404); - - // implicit trait compliance - let path = format!("{http_origin}/v2/traits/{contract_addr}/impl-trait-contract/{contract_addr}/get-info/trait-2"); - let res = client.get(&path).send().unwrap().json::().unwrap(); - eprintln!("Test: GET {path}"); - assert!(res.is_implemented); - - - // invalid trait compliance - let path = format!("{http_origin}/v2/traits/{contract_addr}/impl-trait-contract/{contract_addr}/get-info/trait-3"); - let res = client.get(&path).send().unwrap().json::().unwrap(); - eprintln!("Test: GET {path}"); - assert!(!res.is_implemented); - - // test query parameters for v2/trait endpoint - // evaluate check for explicit compliance against the chain tip of the first block (contract DNE at that block) - - // Recover the stored tip - let tmppath = "/tmp/integration_test_get_info-old-tip"; - use std::fs; - use std::io::Read; - let mut f = fs::File::open(tmppath).unwrap(); - let mut buf = vec![]; - f.read_to_end(&mut buf).unwrap(); - let old_tip = StacksBlockId::consensus_deserialize(&mut &buf[..]).unwrap(); - - let path = format!("{http_origin}/v2/traits/{contract_addr}/impl-trait-contract/{contract_addr}/get-info/trait-1?tip={old_tip}"); - - let res = client.get(&path).send().unwrap(); - eprintln!("Test: GET {path}"); - assert_eq!(res.text().unwrap(), "No contract analysis found or trait definition not found"); - - // evaluate check for explicit compliance where tip is the chain tip of the first block (contract DNE at that block), but tip is "latest" - let path = format!("{http_origin}/v2/traits/{contract_addr}/impl-trait-contract/{contract_addr}/get-info/trait-1?tip=latest"); - let res = client.get(&path).send().unwrap().json::().unwrap(); - eprintln!("Test: GET {path}"); - assert!(res.is_implemented); - - // perform some tests of the fee rate interface - let path = format!("{http_origin}/v2/fees/transaction"); - - let tx_payload = - TransactionPayload::TokenTransfer(contract_addr.clone().into(), 10_000_000, TokenTransferMemo([0; 34])); - - let payload_data = tx_payload.serialize_to_vec(); - let payload_hex = format!("0x{}", to_hex(&payload_data)); - - eprintln!("Test: POST {path}"); - - let body = json!({ "transaction_payload": payload_hex }); - - let res = client.post(&path) - .json(&body) - .send() - .expect("Should be able to post") - .json::() - .expect("Failed to parse result into JSON"); - - eprintln!("{res}"); - - // destruct the json result - // estimated_cost for transfers should be 0 -- their cost is just in their length - let estimated_cost = res.get("estimated_cost").expect("Response should have estimated_cost field"); - assert_eq!(estimated_cost.get("read_count").unwrap().as_u64().unwrap(), 0); - assert_eq!(estimated_cost.get("read_length").unwrap().as_u64().unwrap(), 0); - assert_eq!(estimated_cost.get("write_count").unwrap().as_u64().unwrap(), 0); - assert_eq!(estimated_cost.get("write_length").unwrap().as_u64().unwrap(), 0); - assert_eq!(estimated_cost.get("runtime").unwrap().as_u64().unwrap(), 0); - - // the estimated scalar should still be non-zero, because the length of the tx goes into this field. - assert!(res.get("estimated_cost_scalar").unwrap().as_u64().unwrap() > 0); - - let estimations = res.get("estimations").expect("Should have an estimations field") - .as_array() - .expect("Fees should be array"); - - let estimated_fee_rates = estimations - .iter() - .map(|x| x.get("fee_rate").expect("Should have fee_rate field")); - let estimated_fees = estimations - .iter() - .map(|x| x.get("fee").expect("Should have fee field")); - - assert_eq!(estimated_fee_rates.count(), 3, "Fee rates should be length 3 array"); - assert_eq!(estimated_fees.count(), 3, "Fees should be length 3 array"); - - let tx_payload = TransactionPayload::from(TransactionContractCall { - address: contract_addr.clone(), - contract_name: ContractName::from_literal("get-info"), - function_name: ClarityName::from_literal("update-info"), - function_args: vec![], - }); - - let payload_data = tx_payload.serialize_to_vec(); - let payload_hex = to_hex(&payload_data); - - eprintln!("Test: POST {path}"); - - let body = json!({ "transaction_payload": payload_hex }); - - let res = client.post(&path) - .json(&body) - .send() - .expect("Should be able to post") - .json::() - .expect("Failed to parse result into JSON"); - - eprintln!("{res}"); - - // destruct the json result - // estimated_cost for transfers should be non-zero - let estimated_cost = res.get("estimated_cost").expect("Response should have estimated_cost field"); - assert!(estimated_cost.get("read_count").unwrap().as_u64().unwrap() > 0); - assert!(estimated_cost.get("read_length").unwrap().as_u64().unwrap() > 0); - assert!(estimated_cost.get("write_count").unwrap().as_u64().unwrap() > 0); - assert!(estimated_cost.get("write_length").unwrap().as_u64().unwrap() > 0); - assert!(estimated_cost.get("runtime").unwrap().as_u64().unwrap() > 0); - - let estimated_cost_scalar = res.get("estimated_cost_scalar").unwrap().as_u64().unwrap(); - assert!(estimated_cost_scalar > 0); - - let estimations = res.get("estimations").expect("Should have an estimations field") - .as_array() - .expect("Fees should be array"); - - let estimated_fee_rates = estimations - .iter() - .map(|x| x.get("fee_rate").expect("Should have fee_rate field")); - let estimated_fees: Vec<_> = estimations - .iter() - .map(|x| x.get("fee").expect("Should have fee field")) - .collect(); - - assert_eq!(estimated_fee_rates.count(), 3, "Fee rates should be length 3 array"); - assert_eq!(estimated_fees.len(), 3, "Fees should be length 3 array"); - - let tx_payload = TransactionPayload::from(TransactionContractCall { - address: contract_addr, - contract_name: ContractName::from_literal("get-info"), - function_name: ClarityName::from_literal("update-info"), - function_args: vec![], - }); - - let payload_data = tx_payload.serialize_to_vec(); - let payload_hex = to_hex(&payload_data); - - let estimated_len = 1550; - let body = json!({ "transaction_payload": payload_hex, "estimated_len": estimated_len }); - info!("POST body\n {body}"); - - let res = client.post(&path) - .json(&body) - .send() - .expect("Should be able to post") - .json::() - .expect("Failed to parse result into JSON"); - - info!("{res}"); - - // destruct the json result - // estimated_cost for transfers should be non-zero - let estimated_cost = res.get("estimated_cost").expect("Response should have estimated_cost field"); - assert!(estimated_cost.get("read_count").unwrap().as_u64().unwrap() > 0); - assert!(estimated_cost.get("read_length").unwrap().as_u64().unwrap() > 0); - assert!(estimated_cost.get("write_count").unwrap().as_u64().unwrap() > 0); - assert!(estimated_cost.get("write_length").unwrap().as_u64().unwrap() > 0); - assert!(estimated_cost.get("runtime").unwrap().as_u64().unwrap() > 0); - - let new_estimated_cost_scalar = res.get("estimated_cost_scalar").unwrap().as_u64().unwrap(); - assert!(estimated_cost_scalar > 0); - assert!(new_estimated_cost_scalar > estimated_cost_scalar, "New scalar estimate should be higher because of the tx length increase"); - - let new_estimations = res.get("estimations").expect("Should have an estimations field") - .as_array() - .expect("Fees should be array"); - - let new_estimated_fees: Vec<_> = new_estimations - .iter() - .map(|x| x.get("fee").expect("Should have fee field")) - .collect(); - - let minimum_relay_fee = estimated_len * MINIMUM_TX_FEE_RATE_PER_BYTE; - - assert!(new_estimated_fees[2].as_u64().unwrap() >= estimated_fees[2].as_u64().unwrap(), - "Supplying an estimated tx length should increase the estimated fees"); - assert!(new_estimated_fees[0].as_u64().unwrap() >= estimated_fees[0].as_u64().unwrap(), - "Supplying an estimated tx length should increase the estimated fees"); - assert!(new_estimated_fees[1].as_u64().unwrap() >= estimated_fees[1].as_u64().unwrap(), - "Supplying an estimated tx length should increase the estimated fees"); - for estimate in new_estimated_fees.iter() { - assert!(estimate.as_u64().unwrap() >= minimum_relay_fee, - "The estimated fees must always be greater than minimum_relay_fee"); - } - }, - _ => {}, - } - }); - - run_loop.start(num_rounds).unwrap(); -} - -const FAUCET_CONTRACT: &str = " - (define-public (spout) - (let ((recipient tx-sender)) - (print (as-contract (stx-transfer? u1 .faucet recipient))))) -"; - -#[tag(slow)] -#[test] -fn contract_stx_transfer() { - let mut conf = new_test_conf(); - - let contract_sk = StacksPrivateKey::from_hex(SK_1).unwrap(); - let sk_3 = StacksPrivateKey::from_hex(SK_3).unwrap(); - let addr_3 = to_addr(&sk_3); - - conf.burnchain.commit_anchor_block_within = 5000; - conf.add_initial_balance(addr_3.to_string(), 100000); - conf.add_initial_balance( - to_addr(&StacksPrivateKey::from_hex(SK_2).unwrap()).to_string(), - 1000, - ); - conf.add_initial_balance(to_addr(&contract_sk).to_string(), 1000); - - let num_rounds = 5; - - let mut run_loop = RunLoop::new(conf); - - run_loop - .callbacks - .on_new_tenure(|round, _burnchain_tip, chain_tip, tenure| { - let mut chainstate_copy = tenure.open_chainstate(); - let sortdb = tenure.open_fake_sortdb(); - - let contract_sk = StacksPrivateKey::from_hex(SK_1).unwrap(); - let sk_2 = StacksPrivateKey::from_hex(SK_2).unwrap(); - let sk_3 = StacksPrivateKey::from_hex(SK_3).unwrap(); - let header_hash = chain_tip.block.block_hash(); - let consensus_hash = &chain_tip.metadata.consensus_hash; - - let contract_identifier = QualifiedContractIdentifier::parse(&format!( - "{}.faucet", - to_addr(&StacksPrivateKey::from_hex(SK_1).unwrap()) - )) - .unwrap(); - - if round == 1 { - // block-height = 2 - let xfer_to_contract = make_stacks_transfer_serialized( - &sk_3, - 0, - 10, - CHAIN_ID_TESTNET, - &contract_identifier.into(), - 1000, - ); - tenure - .mem_pool - .submit_raw( - &mut chainstate_copy, - &sortdb, - consensus_hash, - &header_hash, - xfer_to_contract, - &ExecutionCost::max_value(), - &StacksEpochId::Epoch21, - ) - .unwrap(); - } else if round == 2 { - // block-height > 2 - let publish_tx = make_contract_publish( - &contract_sk, - 0, - 10, - CHAIN_ID_TESTNET, - "faucet", - FAUCET_CONTRACT, - ); - tenure - .mem_pool - .submit_raw( - &mut chainstate_copy, - &sortdb, - consensus_hash, - &header_hash, - publish_tx, - &ExecutionCost::max_value(), - &StacksEpochId::Epoch21, - ) - .unwrap(); - } else if round == 3 { - // try to publish again - let publish_tx = make_contract_publish( - &contract_sk, - 1, - 10, - CHAIN_ID_TESTNET, - "faucet", - FAUCET_CONTRACT, - ); - - let (consensus_hash, block_hash) = ( - &tenure.parent_block.metadata.consensus_hash, - &tenure.parent_block.metadata.anchored_header.block_hash(), - ); - tenure - .mem_pool - .submit_raw( - &mut chainstate_copy, - &sortdb, - consensus_hash, - block_hash, - publish_tx, - &ExecutionCost::max_value(), - &StacksEpochId::Epoch21, - ) - .unwrap(); - - let tx = make_contract_call( - &sk_2, - 0, - 10, - CHAIN_ID_TESTNET, - &to_addr(&contract_sk), - "faucet", - "spout", - &[], - ); - tenure - .mem_pool - .submit_raw( - &mut chainstate_copy, - &sortdb, - consensus_hash, - &header_hash, - tx, - &ExecutionCost::max_value(), - &StacksEpochId::Epoch21, - ) - .unwrap(); - } else if round == 4 { - // let's testing "chaining": submit MAXIMUM_MEMPOOL_TX_CHAINING - 1 txs, which should succeed - for i in 0..MAXIMUM_MEMPOOL_TX_CHAINING { - let xfer_to_contract = make_stacks_transfer_serialized( - &sk_3, - 1 + i, - 200, - CHAIN_ID_TESTNET, - &contract_identifier.clone().into(), - 1000, - ); - let xfer_to_contract = - StacksTransaction::consensus_deserialize(&mut &xfer_to_contract[..]) - .unwrap(); - tenure - .mem_pool - .submit( - &mut chainstate_copy, - &sortdb, - consensus_hash, - &header_hash, - &xfer_to_contract, - None, - &ExecutionCost::max_value(), - &StacksEpochId::Epoch21, - ) - .unwrap(); - } - // this one should fail because the nonce is already in the mempool - let xfer_to_contract = make_stacks_transfer_serialized( - &sk_3, - 3, - 190, - CHAIN_ID_TESTNET, - &contract_identifier.into(), - 1000, - ); - let xfer_to_contract = - StacksTransaction::consensus_deserialize(&mut &xfer_to_contract[..]).unwrap(); - match tenure - .mem_pool - .submit( - &mut chainstate_copy, - &sortdb, - consensus_hash, - &header_hash, - &xfer_to_contract, - None, - &ExecutionCost::max_value(), - &StacksEpochId::Epoch21, - ) - .unwrap_err() - { - MemPoolRejection::ConflictingNonceInMempool => (), - e => panic!("{e:?}"), - }; - } - }); - - run_loop.callbacks.on_new_stacks_chain_state( - |round, _burnchain_tip, chain_tip, chain_state, burn_dbconn| { - let contract_identifier = QualifiedContractIdentifier::parse(&format!( - "{}.faucet", - to_addr(&StacksPrivateKey::from_hex(SK_1).unwrap()) - )) - .unwrap(); - - match round { - 1 => { - assert_eq!(chain_tip.metadata.stacks_block_height, 2); - // Block #1 should have 2 txs -- coinbase + transfer - assert_eq!(chain_tip.block.txs.len(), 2); - - let cur_tip = ( - &chain_tip.metadata.consensus_hash, - chain_tip.metadata.anchored_header.block_hash(), - ); - // check that 1000 stx _was_ transferred to the contract principal - assert_eq!( - chain_state - .with_read_only_clarity_tx( - burn_dbconn, - &StacksBlockHeader::make_index_block_hash(cur_tip.0, &cur_tip.1), - |conn| { - conn.with_clarity_db_readonly(|db| { - db.get_account_stx_balance( - &contract_identifier.clone().into(), - ) - .unwrap() - .amount_unlocked() - }) - } - ) - .unwrap(), - 1000 - ); - // check that 1000 stx _was_ debited from SK_3 - let sk_3 = StacksPrivateKey::from_hex(SK_3).unwrap(); - let addr_3 = to_addr(&sk_3).into(); - assert_eq!( - chain_state - .with_read_only_clarity_tx( - burn_dbconn, - &StacksBlockHeader::make_index_block_hash(cur_tip.0, &cur_tip.1), - |conn| { - conn.with_clarity_db_readonly(|db| { - db.get_account_stx_balance(&addr_3) - .unwrap() - .amount_unlocked() - }) - } - ) - .unwrap(), - 98990 - ); - } - 2 => { - assert_eq!(chain_tip.metadata.stacks_block_height, 3); - // Block #2 should have 2 txs -- coinbase + publish - assert_eq!(chain_tip.block.txs.len(), 2); - } - 3 => { - assert_eq!(chain_tip.metadata.stacks_block_height, 4); - // Block #3 should have 2 txs -- coinbase + contract-call, - // the second publish _should have been rejected_ - assert_eq!(chain_tip.block.txs.len(), 2); - - // check that 1 stx was transferred to SK_2 via the contract-call - let cur_tip = ( - &chain_tip.metadata.consensus_hash, - chain_tip.metadata.anchored_header.block_hash(), - ); - - let sk_2 = StacksPrivateKey::from_hex(SK_2).unwrap(); - let addr_2 = to_addr(&sk_2).into(); - assert_eq!( - chain_state - .with_read_only_clarity_tx( - burn_dbconn, - &StacksBlockHeader::make_index_block_hash(cur_tip.0, &cur_tip.1), - |conn| { - conn.with_clarity_db_readonly(|db| { - db.get_account_stx_balance(&addr_2) - .unwrap() - .amount_unlocked() - }) - } - ) - .unwrap(), - 991 - ); - - assert_eq!( - chain_state - .with_read_only_clarity_tx( - burn_dbconn, - &StacksBlockHeader::make_index_block_hash(cur_tip.0, &cur_tip.1), - |conn| { - conn.with_clarity_db_readonly(|db| { - db.get_account_stx_balance( - &contract_identifier.clone().into(), - ) - .unwrap() - .amount_unlocked() - }) - } - ) - .unwrap(), - 999 - ); - } - 4 => { - assert_eq!(chain_tip.metadata.stacks_block_height, 5); - assert_eq!( - chain_tip.block.txs.len() as u64, - MAXIMUM_MEMPOOL_TX_CHAINING + 1, - "Should have 1 coinbase tx and MAXIMUM_MEMPOOL_TX_CHAINING transfers" - ); - - let cur_tip = ( - &chain_tip.metadata.consensus_hash, - chain_tip.metadata.anchored_header.block_hash(), - ); - - // check that 1000 stx were sent to the contract - assert_eq!( - chain_state - .with_read_only_clarity_tx( - burn_dbconn, - &StacksBlockHeader::make_index_block_hash(cur_tip.0, &cur_tip.1), - |conn| { - conn.with_clarity_db_readonly(|db| { - db.get_account_stx_balance( - &contract_identifier.clone().into(), - ) - .unwrap() - .amount_unlocked() - }) - } - ) - .unwrap(), - 25999 - ); - // check that 1000 stx _was_ debited from SK_3 - let sk_3 = StacksPrivateKey::from_hex(SK_3).unwrap(); - let addr_3 = to_addr(&sk_3).into(); - assert_eq!( - chain_state - .with_read_only_clarity_tx( - burn_dbconn, - &StacksBlockHeader::make_index_block_hash(cur_tip.0, &cur_tip.1), - |conn| { - conn.with_clarity_db_readonly(|db| { - db.get_account_stx_balance(&addr_3) - .unwrap() - .amount_unlocked() - }) - } - ) - .unwrap(), - 68990 - ); - } - - _ => {} - } - }, - ); - - run_loop.start(num_rounds).unwrap(); -} - -#[test] -fn mine_transactions_out_of_order() { - let mut conf = new_test_conf(); - - let sk = StacksPrivateKey::from_hex(SK_3).unwrap(); - let addr = to_addr(&sk); - conf.burnchain.commit_anchor_block_within = 5000; - conf.add_initial_balance(addr.to_string(), 100000); - - let num_rounds = 5; - let mut run_loop = RunLoop::new(conf); - - run_loop - .callbacks - .on_new_tenure(|round, _burnchain_tip, chain_tip, tenure| { - let mut chainstate_copy = tenure.open_chainstate(); - let sortdb = tenure.open_fake_sortdb(); - - let sk = StacksPrivateKey::from_hex(SK_3).unwrap(); - let header_hash = chain_tip.block.block_hash(); - let consensus_hash = &chain_tip.metadata.consensus_hash; - - let contract_identifier = QualifiedContractIdentifier::parse(&format!( - "{}.faucet", - to_addr(&StacksPrivateKey::from_hex(SK_1).unwrap()) - )) - .unwrap(); - - if round == 1 { - // block-height = 2 - let xfer_to_contract = make_stacks_transfer_serialized( - &sk, - 1, - 10, - CHAIN_ID_TESTNET, - &contract_identifier.into(), - 1000, - ); - tenure - .mem_pool - .submit_raw( - &mut chainstate_copy, - &sortdb, - consensus_hash, - &header_hash, - xfer_to_contract, - &ExecutionCost::max_value(), - &StacksEpochId::Epoch21, - ) - .unwrap(); - } else if round == 2 { - // block-height > 2 - let publish_tx = - make_contract_publish(&sk, 2, 10, CHAIN_ID_TESTNET, "faucet", FAUCET_CONTRACT); - tenure - .mem_pool - .submit_raw( - &mut chainstate_copy, - &sortdb, - consensus_hash, - &header_hash, - publish_tx, - &ExecutionCost::max_value(), - &StacksEpochId::Epoch21, - ) - .unwrap(); - } else if round == 3 { - let xfer_to_contract = make_stacks_transfer_serialized( - &sk, - 3, - 10, - CHAIN_ID_TESTNET, - &contract_identifier.into(), - 1000, - ); - tenure - .mem_pool - .submit_raw( - &mut chainstate_copy, - &sortdb, - consensus_hash, - &header_hash, - xfer_to_contract, - &ExecutionCost::max_value(), - &StacksEpochId::Epoch21, - ) - .unwrap(); - } else if round == 4 { - let xfer_to_contract = make_stacks_transfer_serialized( - &sk, - 0, - 10, - CHAIN_ID_TESTNET, - &contract_identifier.into(), - 1000, - ); - tenure - .mem_pool - .submit_raw( - &mut chainstate_copy, - &sortdb, - consensus_hash, - &header_hash, - xfer_to_contract, - &ExecutionCost::max_value(), - &StacksEpochId::Epoch21, - ) - .unwrap(); - } - }); - - run_loop.callbacks.on_new_stacks_chain_state( - |round, _burnchain_tip, chain_tip, chain_state, burn_dbconn| { - let contract_identifier = QualifiedContractIdentifier::parse(&format!( - "{}.faucet", - to_addr(&StacksPrivateKey::from_hex(SK_1).unwrap()) - )) - .unwrap(); - - match round { - 1 => { - assert_eq!(chain_tip.metadata.stacks_block_height, 2); - assert_eq!(chain_tip.block.txs.len(), 1); - } - 2 => { - assert_eq!(chain_tip.metadata.stacks_block_height, 3); - assert_eq!(chain_tip.block.txs.len(), 1); - } - 3 => { - assert_eq!(chain_tip.metadata.stacks_block_height, 4); - assert_eq!(chain_tip.block.txs.len(), 1); - } - 4 => { - assert_eq!(chain_tip.metadata.stacks_block_height, 5); - assert_eq!(chain_tip.block.txs.len(), 5); - - // check that 1000 stx _was_ transferred to the contract principal - let curr_tip = ( - &chain_tip.metadata.consensus_hash, - chain_tip.metadata.anchored_header.block_hash(), - ); - assert_eq!( - chain_state - .with_read_only_clarity_tx( - burn_dbconn, - &StacksBlockHeader::make_index_block_hash(curr_tip.0, &curr_tip.1), - |conn| { - conn.with_clarity_db_readonly(|db| { - db.get_account_stx_balance( - &contract_identifier.clone().into(), - ) - .unwrap() - .amount_unlocked() - }) - } - ) - .unwrap(), - 3000 - ); - } - _ => {} - } - }, - ); - - run_loop.start(num_rounds).unwrap(); -} - -/// Test mining a smart contract twice (in non-sequential blocks) -/// this can happen in the testnet leader if they get "behind" -/// the burnchain and a previously mined block doesn't get included -/// in the block it was processed for. Tests issue #1540 -#[test] -fn mine_contract_twice() { - let mut conf = new_test_conf(); - let contract_sk = StacksPrivateKey::from_hex(SK_1).unwrap(); - - conf.burnchain.commit_anchor_block_within = 1000; - conf.add_initial_balance(to_addr(&contract_sk).to_string(), 1000); - - let num_rounds = 3; - - let mut run_loop = RunLoop::new(conf); - - run_loop - .callbacks - .on_new_tenure(|round, _burnchain_tip, _chain_tip, tenure| { - let mut chainstate_copy = tenure.open_chainstate(); - let sortdb = tenure.open_fake_sortdb(); - let contract_sk = StacksPrivateKey::from_hex(SK_1).unwrap(); - - if round == 1 { - // block-height = 2 - let publish_tx = make_contract_publish( - &contract_sk, - 0, - 10, - CHAIN_ID_TESTNET, - "faucet", - FAUCET_CONTRACT, - ); - let (consensus_hash, block_hash) = ( - &tenure.parent_block.metadata.consensus_hash, - &tenure.parent_block.metadata.anchored_header.block_hash(), - ); - tenure - .mem_pool - .submit_raw( - &mut chainstate_copy, - &sortdb, - consensus_hash, - block_hash, - publish_tx, - &ExecutionCost::max_value(), - &StacksEpochId::Epoch21, - ) - .unwrap(); - - // throw an extra "run" in. - // tenure.run().unwrap(); - } - }); - - run_loop.callbacks.on_new_stacks_chain_state( - |round, _burnchain_tip, chain_tip, chain_state, burn_dbconn| { - let contract_identifier = QualifiedContractIdentifier::parse(&format!( - "{}.faucet", - to_addr(&StacksPrivateKey::from_hex(SK_1).unwrap()) - )) - .unwrap(); - - if round == 2 { - let cur_tip = ( - &chain_tip.metadata.consensus_hash, - chain_tip.metadata.anchored_header.block_hash(), - ); - // check that the contract published! - assert_eq!( - &chain_state - .with_read_only_clarity_tx( - burn_dbconn, - &StacksBlockHeader::make_index_block_hash(cur_tip.0, &cur_tip.1), - |conn| { - conn.with_clarity_db_readonly(|db| { - db.get_contract_src(&contract_identifier).unwrap() - }) - } - ) - .unwrap(), - FAUCET_CONTRACT - ); - } - }, - ); - - run_loop.start(num_rounds).unwrap(); -} - -#[test] -fn bad_contract_tx_rollback() { - let mut conf = new_test_conf(); - - let contract_sk = StacksPrivateKey::from_hex(SK_1).unwrap(); - let sk_2 = StacksPrivateKey::from_hex(SK_2).unwrap(); - let sk_3 = StacksPrivateKey::from_hex(SK_3).unwrap(); - let addr_3 = to_addr(&sk_3); - - conf.burnchain.commit_anchor_block_within = 5000; - conf.add_initial_balance(addr_3.to_string(), 100000); - conf.add_initial_balance(to_addr(&contract_sk).to_string(), 1000); - conf.add_initial_balance(to_addr(&sk_2).to_string(), 1000); - - let num_rounds = 4; - - let mut run_loop = RunLoop::new(conf); - - run_loop - .callbacks - .on_new_tenure(|round, _burnchain_tip, _chain_tip, tenure| { - let mut chainstate_copy = tenure.open_chainstate(); - let sortdb = tenure.open_fake_sortdb(); - - let contract_sk = StacksPrivateKey::from_hex(SK_1).unwrap(); - let sk_2 = StacksPrivateKey::from_hex(SK_2).unwrap(); - let sk_3 = StacksPrivateKey::from_hex(SK_3).unwrap(); - let addr_2: StacksAddress = to_addr(&sk_2); - - let contract_identifier = QualifiedContractIdentifier::parse(&format!( - "{}.faucet", - to_addr(&StacksPrivateKey::from_hex(SK_1).unwrap()) - )) - .unwrap(); - - if round == 1 { - // block-height = 2 - let xfer_to_contract = make_stacks_transfer_serialized( - &sk_3, - 0, - 10, - CHAIN_ID_TESTNET, - &contract_identifier.into(), - 1000, - ); - let (consensus_hash, block_hash) = ( - &tenure.parent_block.metadata.consensus_hash, - &tenure.parent_block.metadata.anchored_header.block_hash(), - ); - tenure - .mem_pool - .submit_raw( - &mut chainstate_copy, - &sortdb, - consensus_hash, - block_hash, - xfer_to_contract, - &ExecutionCost::max_value(), - &StacksEpochId::Epoch21, - ) - .unwrap(); - } else if round == 2 { - // block-height = 3 - let xfer_to_contract = make_stacks_transfer_serialized( - &sk_3, - 1, - 10, - CHAIN_ID_TESTNET, - &addr_2.clone().into(), - 1000, - ); - let (consensus_hash, block_hash) = ( - &tenure.parent_block.metadata.consensus_hash, - &tenure.parent_block.metadata.anchored_header.block_hash(), - ); - tenure - .mem_pool - .submit_raw( - &mut chainstate_copy, - &sortdb, - consensus_hash, - block_hash, - xfer_to_contract, - &ExecutionCost::max_value(), - &StacksEpochId::Epoch21, - ) - .unwrap(); - - // doesn't consistently get mined by the StacksBlockBuilder, because order matters! - let xfer_to_contract = make_stacks_transfer_serialized( - &sk_3, - 2, - 10, - CHAIN_ID_TESTNET, - &addr_2.into(), - 3000, - ); - tenure - .mem_pool - .submit_raw( - &mut chainstate_copy, - &sortdb, - consensus_hash, - block_hash, - xfer_to_contract, - &ExecutionCost::max_value(), - &StacksEpochId::Epoch21, - ) - .unwrap(); - - let publish_tx = make_contract_publish( - &contract_sk, - 0, - 10, - CHAIN_ID_TESTNET, - "faucet", - FAUCET_CONTRACT, - ); - tenure - .mem_pool - .submit_raw( - &mut chainstate_copy, - &sortdb, - consensus_hash, - block_hash, - publish_tx, - &ExecutionCost::max_value(), - &StacksEpochId::Epoch21, - ) - .unwrap(); - - let publish_tx = make_contract_publish( - &contract_sk, - 1, - 10, - CHAIN_ID_TESTNET, - "faucet", - FAUCET_CONTRACT, - ); - tenure - .mem_pool - .submit_raw( - &mut chainstate_copy, - &sortdb, - consensus_hash, - block_hash, - publish_tx, - &ExecutionCost::max_value(), - &StacksEpochId::Epoch21, - ) - .unwrap(); - } - }); - - run_loop.callbacks.on_new_stacks_chain_state( - |round, _burnchain_tip, chain_tip, chain_state, burn_dbconn| { - let contract_identifier = QualifiedContractIdentifier::parse(&format!( - "{}.faucet", - to_addr(&StacksPrivateKey::from_hex(SK_1).unwrap()) - )) - .unwrap(); - - match round { - 1 => { - assert!(chain_tip.metadata.stacks_block_height == 2); - // Block #1 should have 2 txs -- coinbase + transfer - assert_eq!(chain_tip.block.txs.len(), 2); - - let cur_tip = ( - &chain_tip.metadata.consensus_hash, - chain_tip.metadata.anchored_header.block_hash(), - ); - // check that 1000 stx _was_ transferred to the contract principal - assert_eq!( - chain_state - .with_read_only_clarity_tx( - burn_dbconn, - &StacksBlockHeader::make_index_block_hash(cur_tip.0, &cur_tip.1), - |conn| { - conn.with_clarity_db_readonly(|db| { - db.get_account_stx_balance( - &contract_identifier.clone().into(), - ) - .unwrap() - .amount_unlocked() - }) - } - ) - .unwrap(), - 1000 - ); - // check that 1000 stx _was_ debited from SK_3 - let sk_3 = StacksPrivateKey::from_hex(SK_3).unwrap(); - let addr_3 = to_addr(&sk_3).into(); - assert_eq!( - chain_state - .with_read_only_clarity_tx( - burn_dbconn, - &StacksBlockHeader::make_index_block_hash(cur_tip.0, &cur_tip.1), - |conn| { - conn.with_clarity_db_readonly(|db| { - db.get_account_stx_balance(&addr_3) - .unwrap() - .amount_unlocked() - }) - } - ) - .unwrap(), - 98990 - ); - } - 2 => { - assert_eq!(chain_tip.metadata.stacks_block_height, 3); - // Block #2 should have 4 txs -- coinbase + 2 transfer + 1 publish - assert_eq!(chain_tip.block.txs.len(), 4); - } - 3 => { - assert_eq!(chain_tip.metadata.stacks_block_height, 4); - // Block #2 should have 1 txs -- coinbase - assert_eq!(chain_tip.block.txs.len(), 1); - } - _ => {} - } - }, - ); - - run_loop.start(num_rounds).unwrap(); -} - -lazy_static! { - static ref EXPENSIVE_CONTRACT: String = make_expensive_contract( - "(define-private (inner-loop (x int)) (begin - (map sha256 list-9) - 0))", - "" - ); -} - -fn make_expensive_contract(inner_loop: &str, other_decl: &str) -> String { - let mut contract = "(define-constant list-0 (list 0))".to_string(); - - for i in 0..10 { - contract.push('\n'); - contract.push_str(&format!( - "(define-constant list-{} (concat list-{i} list-{i}))", - i + 1, - )); - } - - contract.push('\n'); - contract.push_str(other_decl); - contract.push('\n'); - contract.push_str(inner_loop); - - write!( - contract, - "\n(define-private (outer-loop) (map inner-loop list-5))\n" - ) - .unwrap(); - write!( - contract, - "(define-public (do-it) (begin (outer-loop) (ok 1)))" - ) - .unwrap(); - - contract -} - -fn make_keys(seed: &str, count: u64) -> Vec { - let mut seed = { - let secret_state = seed.as_bytes().to_vec(); - Sha256Sum::from_data(&secret_state) - }; - - let mut ret = vec![]; - while (ret.len() as u64) < count { - if let Ok(sk) = StacksPrivateKey::from_slice(seed.as_bytes()) { - ret.push(sk); - } - seed = Sha256Sum::from_data(seed.as_bytes()); - } - ret -} - -#[test] -fn block_limit_runtime_test() { - let mut conf = new_test_conf(); - - conf.burnchain.epochs = Some(EpochList::new(&[ - StacksEpoch { - epoch_id: StacksEpochId::Epoch10, - start_height: 0, - end_height: 0, - block_limit: ExecutionCost { - write_length: 150000000, - write_count: 50000, - read_length: 1000000000, - read_count: 50000, - // use a shorter runtime limit. the current runtime limit - // is _painfully_ slow in a opt-level=0 build (i.e., `cargo test`) - runtime: 1_000_000_000, - }, - network_epoch: PEER_VERSION_EPOCH_2_0, - }, - StacksEpoch { - epoch_id: StacksEpochId::Epoch20, - start_height: 0, - end_height: 0, - block_limit: ExecutionCost { - write_length: 150000000, - write_count: 50000, - read_length: 1000000000, - read_count: 50000, - // use a shorter runtime limit. the current runtime limit - // is _painfully_ slow in a opt-level=0 build (i.e., `cargo test`) - runtime: 1_000_000_000, - }, - network_epoch: PEER_VERSION_EPOCH_2_0, - }, - StacksEpoch { - epoch_id: StacksEpochId::Epoch2_05, - start_height: 0, - end_height: 0, - block_limit: ExecutionCost { - write_length: 150000000, - write_count: 50000, - read_length: 1000000000, - read_count: 50000, - // use a shorter runtime limit. the current runtime limit - // is _painfully_ slow in a opt-level=0 build (i.e., `cargo test`) - runtime: 1_000_000_000, - }, - network_epoch: PEER_VERSION_EPOCH_2_05, - }, - StacksEpoch { - epoch_id: StacksEpochId::Epoch21, - start_height: 0, - end_height: 9223372036854775807, - block_limit: ExecutionCost { - write_length: 150000000, - write_count: 50000, - read_length: 1000000000, - read_count: 50000, - // use a shorter runtime limit. the current runtime limit - // is _painfully_ slow in a opt-level=0 build (i.e., `cargo test`) - runtime: 2_665_574 * 3, - }, - network_epoch: PEER_VERSION_EPOCH_2_1, - }, - ])); - conf.burnchain.commit_anchor_block_within = 5000; - - let contract_sk = StacksPrivateKey::from_hex(SK_1).unwrap(); - conf.add_initial_balance(to_addr(&contract_sk).to_string(), 1000); - - let seed = "a948904f2f0f479b8f8197694b30184b0d2ed1c1cd2a1ec0fb85d299a192a447"; - let spender_sks = make_keys(seed, 500); - for sk in spender_sks.iter() { - conf.add_initial_balance(to_addr(sk).to_string(), 1000); - } - - let num_rounds = 6; - let mut run_loop = RunLoop::new(conf); - - run_loop - .callbacks - .on_new_tenure(|round, _burnchain_tip, _chain_tip, tenure| { - let mut chainstate_copy = tenure.open_chainstate(); - let sortdb = tenure.open_fake_sortdb(); - - let contract_sk = StacksPrivateKey::from_hex(SK_1).unwrap(); - let _contract_identifier = QualifiedContractIdentifier::parse(&format!( - "{}.hello-contract", - to_addr(&contract_sk) - )) - .unwrap(); - let (consensus_hash, block_hash) = ( - &tenure.parent_block.metadata.consensus_hash, - &tenure.parent_block.metadata.anchored_header.block_hash(), - ); - - match round.cmp(&1) { - Ordering::Equal => { - let publish_tx = make_contract_publish( - &contract_sk, - 0, - 10, - CHAIN_ID_TESTNET, - "hello-contract", - EXPENSIVE_CONTRACT.as_str(), - ); - tenure - .mem_pool - .submit_raw( - &mut chainstate_copy, - &sortdb, - consensus_hash, - block_hash, - publish_tx, - &ExecutionCost::max_value(), - &StacksEpochId::Epoch21, - ) - .unwrap(); - } - Ordering::Greater => { - eprintln!("Begin Round: {round}"); - let to_submit = 2 * (round - 1); - - let seed = "a948904f2f0f479b8f8197694b30184b0d2ed1c1cd2a1ec0fb85d299a192a447"; - let spender_sks = make_keys(seed, 500); - - for i in 0..to_submit { - let sk = &spender_sks[(i + round * round) as usize]; - let tx = make_contract_call( - sk, - 0, - 10, - CHAIN_ID_TESTNET, - &to_addr(&contract_sk), - "hello-contract", - "do-it", - &[], - ); - tenure - .mem_pool - .submit_raw( - &mut chainstate_copy, - &sortdb, - consensus_hash, - block_hash, - tx, - &ExecutionCost::max_value(), - &StacksEpochId::Epoch21, - ) - .unwrap(); - } - } - Ordering::Less => {} - }; - }); - - run_loop.callbacks.on_new_stacks_chain_state( - |round, _chain_state, block, _chain_tip_info, _burn_dbconn| { - let contract_sk = StacksPrivateKey::from_hex(SK_1).unwrap(); - let _contract_identifier = QualifiedContractIdentifier::parse(&format!( - "{}.hello-contract", - to_addr(&contract_sk) - )) - .unwrap(); - - match round { - 2 => { - // Block #1 should have 3 txs -- coinbase + 2 contract calls... - assert_eq!(block.block.txs.len(), 3); - } - 3..=5 => { - // Block >= 2 should have 4 txs -- coinbase + 3 contract calls - // because the _subsequent_ transactions should never have been - // included. - assert_eq!(block.block.txs.len(), 4); - } - _ => {} - } - }, - ); - - run_loop.start(num_rounds).unwrap(); -} - -#[test] -fn mempool_errors() { - let mut conf = new_test_conf(); - - conf.burnchain.commit_anchor_block_within = 5000; - - let spender_addr = to_addr(&StacksPrivateKey::from_hex(SK_3).unwrap()).into(); - conf.initial_balances.push(InitialBalance { - address: spender_addr, - amount: 100300, - }); - - let contract_sk = StacksPrivateKey::from_hex(SK_1).unwrap(); - conf.add_initial_balance(to_addr(&contract_sk).to_string(), 1000); - - let num_rounds = 2; - - let rpc_bind = conf.node.rpc_bind.clone(); - - { - let mut http_opt = HTTP_BINDING.lock().unwrap(); - http_opt.replace(format!("http://{rpc_bind}")); - } - - let mut run_loop = RunLoop::new(conf); - - run_loop - .callbacks - .on_new_tenure(|round, _burnchain_tip, chain_tip, tenure| { - let contract_sk = StacksPrivateKey::from_hex(SK_1).unwrap(); - let mut chainstate_copy = tenure.open_chainstate(); - let sortdb = tenure.open_fake_sortdb(); - - let header_hash = chain_tip.block.block_hash(); - let consensus_hash = &chain_tip.metadata.consensus_hash; - - if round == 1 { - // block-height = 2 - let publish_tx = make_contract_publish( - &contract_sk, - 0, - 10, - CHAIN_ID_TESTNET, - "get-info", - GET_INFO_CONTRACT, - ); - eprintln!("Tenure in 1 started!"); - tenure - .mem_pool - .submit_raw( - &mut chainstate_copy, - &sortdb, - consensus_hash, - &header_hash, - publish_tx, - &ExecutionCost::max_value(), - &StacksEpochId::Epoch21, - ) - .unwrap(); - } - }); - - run_loop.callbacks.on_new_stacks_chain_state( - |round, _chain_state, _block, _chain_tip_info, _burn_dbconn| { - let contract_sk = StacksPrivateKey::from_hex(SK_1).unwrap(); - let _contract_identifier = QualifiedContractIdentifier::parse(&format!( - "{}.hello-contract", - to_addr(&contract_sk) - )) - .unwrap(); - let http_origin = { HTTP_BINDING.lock().unwrap().clone().unwrap() }; - let client = reqwest::blocking::Client::new(); - let path = format!("{http_origin}/v2/transactions"); - let spender_sk = StacksPrivateKey::from_hex(SK_3).unwrap(); - let spender_addr = to_addr(&spender_sk); - - let send_to = StacksAddress::from_string(ADDR_4).unwrap().into(); - - if round == 1 { - // let's submit an invalid transaction! - eprintln!("Test: POST {path} (invalid)"); - let tx_xfer_invalid = make_stacks_transfer_serialized( - &spender_sk, - 30, // bad nonce -- too much chaining - 200, - CHAIN_ID_TESTNET, - &send_to, - 456, - ); - let tx_xfer_invalid_tx = - StacksTransaction::consensus_deserialize(&mut &tx_xfer_invalid[..]).unwrap(); - - let res = client - .post(&path) - .header("Content-Type", "application/octet-stream") - .body(tx_xfer_invalid) - .send() - .unwrap() - .json::() - .unwrap(); - - eprintln!("{res}"); - assert_eq!( - res.get("txid").unwrap().as_str().unwrap(), - tx_xfer_invalid_tx.txid().to_string() - ); - assert_eq!( - res.get("error").unwrap().as_str().unwrap(), - "transaction rejected" - ); - assert_eq!( - res.get("reason").unwrap().as_str().unwrap(), - "TooMuchChaining" - ); - let data = res.get("reason_data").unwrap(); - assert!(data.get("is_origin").unwrap().as_bool().unwrap()); - assert_eq!( - data.get("principal").unwrap().as_str().unwrap(), - &spender_addr.to_string() - ); - assert_eq!(data.get("expected").unwrap().as_i64().unwrap(), 26); - assert_eq!(data.get("actual").unwrap().as_i64().unwrap(), 30); - - let tx_xfer_invalid = make_stacks_transfer_serialized( - &spender_sk, - 0, - 1, // bad fee - CHAIN_ID_TESTNET, - &send_to, - 456, - ); - let tx_xfer_invalid_tx = - StacksTransaction::consensus_deserialize(&mut &tx_xfer_invalid[..]).unwrap(); - - let res = client - .post(&path) - .header("Content-Type", "application/octet-stream") - .body(tx_xfer_invalid) - .send() - .unwrap() - .json::() - .unwrap(); - - eprintln!("{res}"); - assert_eq!( - res.get("txid").unwrap().as_str().unwrap(), - tx_xfer_invalid_tx.txid().to_string() - ); - assert_eq!( - res.get("error").unwrap().as_str().unwrap(), - "transaction rejected" - ); - assert_eq!(res.get("reason").unwrap().as_str().unwrap(), "FeeTooLow"); - let data = res.get("reason_data").unwrap(); - assert_eq!(data.get("expected").unwrap().as_u64().unwrap(), 180); - assert_eq!(data.get("actual").unwrap().as_u64().unwrap(), 1); - - let tx_xfer_invalid = make_stacks_transfer_serialized( - &contract_sk, - 1, - 2000, // not enough funds! - CHAIN_ID_TESTNET, - &send_to, - 456, - ); - let tx_xfer_invalid_tx = - StacksTransaction::consensus_deserialize(&mut &tx_xfer_invalid[..]).unwrap(); - - let res = client - .post(&path) - .header("Content-Type", "application/octet-stream") - .body(tx_xfer_invalid) - .send() - .unwrap() - .json::() - .unwrap(); - - eprintln!("{res}"); - assert_eq!( - res.get("txid").unwrap().as_str().unwrap(), - tx_xfer_invalid_tx.txid().to_string() - ); - assert_eq!( - res.get("error").unwrap().as_str().unwrap(), - "transaction rejected" - ); - assert_eq!( - res.get("reason").unwrap().as_str().unwrap(), - "NotEnoughFunds" - ); - let data = res.get("reason_data").unwrap(); - assert_eq!( - data.get("expected").unwrap().as_str().unwrap(), - format!("0x{:032x}", 2456) - ); - assert_eq!( - data.get("actual").unwrap().as_str().unwrap(), - format!("0x{:032x}", 990) - ); - - let tx_xfer_invalid = make_sponsored_stacks_transfer_on_testnet( - &spender_sk, - &contract_sk, - 1 + MAXIMUM_MEMPOOL_TX_CHAINING, - 1, - 2000, - CHAIN_ID_TESTNET, - &send_to, - 1000, - ); - let tx_xfer_invalid_tx = - StacksTransaction::consensus_deserialize(&mut &tx_xfer_invalid[..]).unwrap(); - - let res = client - .post(&path) - .header("Content-Type", "application/octet-stream") - .body(tx_xfer_invalid) - .send() - .unwrap() - .json::() - .unwrap(); - - eprintln!("{res}"); - assert_eq!( - res.get("txid").unwrap().as_str().unwrap(), - tx_xfer_invalid_tx.txid().to_string() - ); - assert_eq!( - res.get("error").unwrap().as_str().unwrap(), - "transaction rejected" - ); - assert_eq!( - res.get("reason").unwrap().as_str().unwrap(), - "NotEnoughFunds" - ); - let data = res.get("reason_data").unwrap(); - assert_eq!( - data.get("expected").unwrap().as_str().unwrap(), - format!("0x{:032x}", 2000) - ); - assert_eq!( - data.get("actual").unwrap().as_str().unwrap(), - format!("0x{:032x}", 990) - ); - } - }, - ); - - run_loop.start(num_rounds).unwrap(); -} diff --git a/stacks-node/src/tests/mempool.rs b/stacks-node/src/tests/mempool.rs deleted file mode 100644 index 7a87ea5f090..00000000000 --- a/stacks-node/src/tests/mempool.rs +++ /dev/null @@ -1,1012 +0,0 @@ -// Copyright (C) 2020-2026 Stacks Open Internet Foundation -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . - -use std::sync::Mutex; - -use clarity::vm::costs::ExecutionCost; -use clarity::vm::database::NULL_BURN_STATE_DB; -use clarity::vm::representations::ContractName; -use clarity::vm::types::{PrincipalData, QualifiedContractIdentifier, StandardPrincipalData}; -use clarity::vm::Value; -use lazy_static::lazy_static; -use stacks::chainstate::stacks::db::blocks::MemPoolRejection; -use stacks::chainstate::stacks::{ - Error as ChainstateError, StacksBlockHeader, StacksMicroblockHeader, StacksPrivateKey, - StacksPublicKey, StacksTransaction, StacksTransactionSigner, TokenTransferMemo, - TransactionAnchorMode, TransactionAuth, TransactionPayload, TransactionSpendingCondition, - TransactionVersion, C32_ADDRESS_VERSION_MAINNET_SINGLESIG, -}; -use stacks::codec::StacksMessageCodec; -use stacks::core::mempool::MemPoolDB; -use stacks::core::test_util::{ - make_coinbase, make_contract_call, make_contract_publish, make_poison, - make_stacks_transfer_serialized, sign_standard_single_sig_tx_anchor_mode_version, to_addr, -}; -use stacks::core::{StacksEpochId, CHAIN_ID_TESTNET}; -use stacks::cost_estimates::metrics::UnitMetric; -use stacks::cost_estimates::UnitEstimator; -use stacks::net::Error as NetError; -use stacks_common::address::AddressHashMode; -use stacks_common::types::chainstate::{BlockHeaderHash, StacksAddress}; -use stacks_common::util::hash::*; -use stacks_common::util::secp256k1::*; - -use super::{SK_1, SK_2}; -use crate::helium::RunLoop; -use crate::Keychain; - -const FOO_CONTRACT: &str = "(define-public (foo) (ok 1)) - (define-public (bar (x uint)) (ok x))"; -const TRAIT_CONTRACT: &str = "(define-trait tr ((value () (response uint uint))))"; -const USE_TRAIT_CONTRACT: &str = "(use-trait tr-trait .trait-contract.tr) - (define-public (baz (abc )) (ok (contract-of abc)))"; -const IMPLEMENT_TRAIT_CONTRACT: &str = "(define-public (value) (ok u1))"; -const BAD_TRAIT_CONTRACT: &str = "(define-public (foo-bar) (ok u1))"; - -pub fn make_bad_stacks_transfer( - sender: &StacksPrivateKey, - nonce: u64, - tx_fee: u64, - recipient: &PrincipalData, - amount: u64, -) -> Vec { - let payload = - TransactionPayload::TokenTransfer(recipient.clone(), amount, TokenTransferMemo([0; 34])); - - let mut spending_condition = - TransactionSpendingCondition::new_singlesig_p2pkh(StacksPublicKey::from_private(sender)) - .expect("Failed to create p2pkh spending condition from public key."); - spending_condition.set_nonce(nonce); - spending_condition.set_tx_fee(tx_fee); - let auth = TransactionAuth::Standard(spending_condition); - - let mut unsigned_tx = StacksTransaction::new(TransactionVersion::Testnet, auth, payload); - unsigned_tx.chain_id = CHAIN_ID_TESTNET; - - let mut tx_signer = StacksTransactionSigner::new(&unsigned_tx); - - tx_signer.sign_origin(&StacksPrivateKey::random()).unwrap(); - - let mut buf = vec![]; - tx_signer - .get_tx() - .unwrap() - .consensus_serialize(&mut buf) - .unwrap(); - buf -} - -lazy_static! { - static ref CHAINSTATE_PATH: Mutex> = Mutex::new(None); -} - -#[test] -fn mempool_setup_chainstate() { - let mut conf = super::new_test_conf(); - - // force seeds to be the same - conf.node.seed = vec![0x00]; - - conf.burnchain.commit_anchor_block_within = 1500; - - let contract_sk = StacksPrivateKey::from_hex(SK_1).unwrap(); - let contract_addr = to_addr(&contract_sk); - conf.add_initial_balance(contract_addr.to_string(), 100000); - - { - CHAINSTATE_PATH - .lock() - .unwrap() - .replace(conf.get_chainstate_path_str()); - } - - let num_rounds = 4; - - let mut run_loop = RunLoop::new(conf.clone()); - - run_loop - .callbacks - .on_new_tenure(|round, _burnchain_tip, chain_tip, tenure| { - let mut chainstate_copy = tenure.open_chainstate(); - let contract_sk = StacksPrivateKey::from_hex(SK_1).unwrap(); - let header_hash = chain_tip.block.block_hash(); - let consensus_hash = &chain_tip.metadata.consensus_hash; - let sortdb = tenure.open_fake_sortdb(); - - if round == 1 { - eprintln!("Tenure in 1 started!"); - - let publish_tx1 = make_contract_publish( - &contract_sk, - 0, - 100, - CHAIN_ID_TESTNET, - "foo_contract", - FOO_CONTRACT, - ); - tenure - .mem_pool - .submit_raw( - &mut chainstate_copy, - &sortdb, - consensus_hash, - &header_hash, - publish_tx1, - &ExecutionCost::max_value(), - &StacksEpochId::Epoch20, - ) - .unwrap(); - - let publish_tx2 = make_contract_publish( - &contract_sk, - 1, - 100, - CHAIN_ID_TESTNET, - "trait-contract", - TRAIT_CONTRACT, - ); - tenure - .mem_pool - .submit_raw( - &mut chainstate_copy, - &sortdb, - consensus_hash, - &header_hash, - publish_tx2, - &ExecutionCost::max_value(), - &StacksEpochId::Epoch20, - ) - .unwrap(); - - let publish_tx3 = make_contract_publish( - &contract_sk, - 2, - 100, - CHAIN_ID_TESTNET, - "use-trait-contract", - USE_TRAIT_CONTRACT, - ); - tenure - .mem_pool - .submit_raw( - &mut chainstate_copy, - &sortdb, - consensus_hash, - &header_hash, - publish_tx3, - &ExecutionCost::max_value(), - &StacksEpochId::Epoch20, - ) - .unwrap(); - - let publish_tx4 = make_contract_publish( - &contract_sk, - 3, - 100, - CHAIN_ID_TESTNET, - "implement-trait-contract", - IMPLEMENT_TRAIT_CONTRACT, - ); - tenure - .mem_pool - .submit_raw( - &mut chainstate_copy, - &sortdb, - consensus_hash, - &header_hash, - publish_tx4, - &ExecutionCost::max_value(), - &StacksEpochId::Epoch20, - ) - .unwrap(); - - let publish_tx4 = make_contract_publish( - &contract_sk, - 4, - 100, - CHAIN_ID_TESTNET, - "bad-trait-contract", - BAD_TRAIT_CONTRACT, - ); - tenure - .mem_pool - .submit_raw( - &mut chainstate_copy, - &sortdb, - consensus_hash, - &header_hash, - publish_tx4, - &ExecutionCost::max_value(), - &StacksEpochId::Epoch20, - ) - .unwrap(); - } - }); - - run_loop.callbacks.on_new_stacks_chain_state( - |round, _burnchain_tip, chain_tip, chain_state, _burn_dbconn| { - let contract_sk = StacksPrivateKey::from_hex(SK_1).unwrap(); - let contract_addr = to_addr(&contract_sk); - - let other_sk = StacksPrivateKey::from_hex(SK_2).unwrap(); - let other_addr = to_addr(&other_sk).into(); - - let chainstate_path = { CHAINSTATE_PATH.lock().unwrap().clone().unwrap() }; - - let estimator = Box::new(UnitEstimator); - let metric = Box::new(UnitMetric); - - let _mempool = - MemPoolDB::open(false, CHAIN_ID_TESTNET, &chainstate_path, estimator, metric) - .unwrap(); - - if round == 3 { - let block_header = chain_tip.metadata.clone(); - let consensus_hash = &block_header.consensus_hash; - let block_hash = &block_header.anchored_header.block_hash(); - - let micro_pubkh = &block_header - .anchored_header - .as_stacks_epoch2() - .unwrap() - .microblock_pubkey_hash; - - // let's throw some transactions at it. - // first a couple valid ones: - let tx_bytes = make_contract_publish( - &contract_sk, - 5, - 1000, - CHAIN_ID_TESTNET, - "bar_contract", - FOO_CONTRACT, - ); - let tx = - StacksTransaction::consensus_deserialize(&mut tx_bytes.as_slice()).unwrap(); - chain_state - .will_admit_mempool_tx( - &NULL_BURN_STATE_DB, - consensus_hash, - block_hash, - &tx, - tx_bytes.len() as u64, - ) - .unwrap(); - - let tx_bytes = make_contract_call( - &contract_sk, - 5, - 200, - CHAIN_ID_TESTNET, - &contract_addr, - "foo_contract", - "bar", - &[Value::UInt(1)], - ); - let tx = - StacksTransaction::consensus_deserialize(&mut tx_bytes.as_slice()).unwrap(); - chain_state - .will_admit_mempool_tx( - &NULL_BURN_STATE_DB, - consensus_hash, - block_hash, - &tx, - tx_bytes.len() as u64, - ) - .unwrap(); - - let tx_bytes = make_stacks_transfer_serialized( - &contract_sk, - 5, - 200, - CHAIN_ID_TESTNET, - &other_addr, - 1000, - ); - let tx = - StacksTransaction::consensus_deserialize(&mut tx_bytes.as_slice()).unwrap(); - - // First, submit this transaction with a high-S signature. Even though that is technically - // a valid signature, the mempool should reject it. - let high_s_tx = tx.with_negated_s_in_signature(); - let mut high_s_tx_bytes = vec![]; - high_s_tx.consensus_serialize(&mut high_s_tx_bytes).unwrap(); - let e = chain_state - .will_admit_mempool_tx( - &NULL_BURN_STATE_DB, - consensus_hash, - block_hash, - &high_s_tx, - high_s_tx_bytes.len() as u64, - ) - .unwrap_err(); - eprintln!("Err: {e:?}"); - match e { - MemPoolRejection::FailedToValidate(ChainstateError::NetError( - NetError::VerifyingError(msg) - )) => assert_eq!(msg, "Invalid signature: high-S"), - _ => panic!("unexpected error {e:?} from mempool admittance check of high-s signature transaction") - } - - // Now, submit it with the original, low-S signature. This should be successful. - chain_state - .will_admit_mempool_tx( - &NULL_BURN_STATE_DB, - consensus_hash, - block_hash, - &tx, - tx_bytes.len() as u64, - ) - .unwrap(); - - // bad signature - let tx_bytes = make_bad_stacks_transfer(&contract_sk, 5, 200, &other_addr, 1000); - let tx = - StacksTransaction::consensus_deserialize(&mut tx_bytes.as_slice()).unwrap(); - let e = chain_state - .will_admit_mempool_tx( - &NULL_BURN_STATE_DB, - consensus_hash, - block_hash, - &tx, - tx_bytes.len() as u64, - ) - .unwrap_err(); - eprintln!("Err: {e:?}"); - assert!(matches!( - e, - MemPoolRejection::FailedToValidate(ChainstateError::NetError( - NetError::VerifyingError(_) - )) - )); - - // mismatched network on contract-call! - let bad_addr = StacksAddress::from_public_keys( - 18, - &AddressHashMode::SerializeP2PKH, - 1, - &vec![StacksPublicKey::from_private(&other_sk)], - ) - .unwrap(); - - let tx_bytes = make_contract_call( - &contract_sk, - 5, - 200, - CHAIN_ID_TESTNET, - &bad_addr, - "foo_contract", - "bar", - &[Value::UInt(1), Value::Int(2)], - ); - let tx = - StacksTransaction::consensus_deserialize(&mut tx_bytes.as_slice()).unwrap(); - let e = chain_state - .will_admit_mempool_tx( - &NULL_BURN_STATE_DB, - consensus_hash, - block_hash, - &tx, - tx_bytes.len() as u64, - ) - .unwrap_err(); - - assert!(matches!(e, MemPoolRejection::BadAddressVersionByte)); - - // mismatched network on transfer! - let bad_addr = StacksAddress::from_public_keys( - C32_ADDRESS_VERSION_MAINNET_SINGLESIG, - &AddressHashMode::SerializeP2PKH, - 1, - &vec![StacksPublicKey::from_private(&other_sk)], - ) - .unwrap() - .into(); - - let tx_bytes = make_stacks_transfer_serialized( - &contract_sk, - 5, - 200, - CHAIN_ID_TESTNET, - &bad_addr, - 1000, - ); - let tx = - StacksTransaction::consensus_deserialize(&mut tx_bytes.as_slice()).unwrap(); - let e = chain_state - .will_admit_mempool_tx( - &NULL_BURN_STATE_DB, - consensus_hash, - block_hash, - &tx, - tx_bytes.len() as u64, - ) - .unwrap_err(); - assert!(matches!(e, MemPoolRejection::BadAddressVersionByte)); - - // bad fees - let tx_bytes = make_stacks_transfer_serialized( - &contract_sk, - 5, - 0, - CHAIN_ID_TESTNET, - &other_addr, - 1000, - ); - let tx = - StacksTransaction::consensus_deserialize(&mut tx_bytes.as_slice()).unwrap(); - let e = chain_state - .will_admit_mempool_tx( - &NULL_BURN_STATE_DB, - consensus_hash, - block_hash, - &tx, - tx_bytes.len() as u64, - ) - .unwrap_err(); - eprintln!("Err: {e:?}"); - assert!(matches!(e, MemPoolRejection::FeeTooLow(0, _))); - - // bad nonce - let tx_bytes = make_stacks_transfer_serialized( - &contract_sk, - 0, - 200, - CHAIN_ID_TESTNET, - &other_addr, - 1000, - ); - let tx = - StacksTransaction::consensus_deserialize(&mut tx_bytes.as_slice()).unwrap(); - let e = chain_state - .will_admit_mempool_tx( - &NULL_BURN_STATE_DB, - consensus_hash, - block_hash, - &tx, - tx_bytes.len() as u64, - ) - .unwrap_err(); - eprintln!("Err: {e:?}"); - assert!(matches!(e, MemPoolRejection::BadNonces(_))); - - // not enough funds - let tx_bytes = make_stacks_transfer_serialized( - &contract_sk, - 5, - 110000, - CHAIN_ID_TESTNET, - &other_addr, - 1000, - ); - let tx = - StacksTransaction::consensus_deserialize(&mut tx_bytes.as_slice()).unwrap(); - let e = chain_state - .will_admit_mempool_tx( - &NULL_BURN_STATE_DB, - consensus_hash, - block_hash, - &tx, - tx_bytes.len() as u64, - ) - .unwrap_err(); - eprintln!("Err: {e:?}"); - assert!(matches!(e, MemPoolRejection::NotEnoughFunds(111000, 99500))); - - // sender == recipient - let contract_princ = PrincipalData::from(contract_addr.clone()); - let tx_bytes = make_stacks_transfer_serialized( - &contract_sk, - 5, - 300, - CHAIN_ID_TESTNET, - &contract_princ, - 1000, - ); - let tx = - StacksTransaction::consensus_deserialize(&mut tx_bytes.as_slice()).unwrap(); - let e = chain_state - .will_admit_mempool_tx( - &NULL_BURN_STATE_DB, - consensus_hash, - block_hash, - &tx, - tx_bytes.len() as u64, - ) - .unwrap_err(); - eprintln!("Err: {e:?}"); - assert!(if let MemPoolRejection::TransferRecipientIsSender(r) = e { - r == contract_princ - } else { - false - }); - - // recipient must be testnet - let testnet_recipient = to_addr(&other_sk); - let mainnet_recipient = StacksAddress::new( - C32_ADDRESS_VERSION_MAINNET_SINGLESIG, - testnet_recipient.destruct().1, - ) - .unwrap(); - let mainnet_princ = mainnet_recipient.into(); - let tx_bytes = make_stacks_transfer_serialized( - &contract_sk, - 5, - 300, - CHAIN_ID_TESTNET, - &mainnet_princ, - 1000, - ); - let tx = - StacksTransaction::consensus_deserialize(&mut tx_bytes.as_slice()).unwrap(); - let e = chain_state - .will_admit_mempool_tx( - &NULL_BURN_STATE_DB, - consensus_hash, - block_hash, - &tx, - tx_bytes.len() as u64, - ) - .unwrap_err(); - eprintln!("Err: {e:?}"); - assert!(matches!(e, MemPoolRejection::BadAddressVersionByte)); - - // tx version must be testnet - let contract_princ = PrincipalData::from(contract_addr.clone()); - let payload = TransactionPayload::TokenTransfer( - contract_princ, - 1000, - TokenTransferMemo([0; 34]), - ); - let tx = sign_standard_single_sig_tx_anchor_mode_version( - payload, - &contract_sk, - 5, - 300, - CHAIN_ID_TESTNET, - TransactionAnchorMode::OnChainOnly, - TransactionVersion::Mainnet, - ); - let mut tx_bytes = vec![]; - tx.consensus_serialize(&mut tx_bytes).unwrap(); - let e = chain_state - .will_admit_mempool_tx( - &NULL_BURN_STATE_DB, - consensus_hash, - block_hash, - &tx, - tx_bytes.len() as u64, - ) - .unwrap_err(); - eprintln!("Err: {e:?}"); - assert!(matches!(e, MemPoolRejection::BadTransactionVersion)); - - // send amount must be positive - let tx_bytes = make_stacks_transfer_serialized( - &contract_sk, - 5, - 300, - CHAIN_ID_TESTNET, - &other_addr, - 0, - ); - let tx = - StacksTransaction::consensus_deserialize(&mut tx_bytes.as_slice()).unwrap(); - let e = chain_state - .will_admit_mempool_tx( - &NULL_BURN_STATE_DB, - consensus_hash, - block_hash, - &tx, - tx_bytes.len() as u64, - ) - .unwrap_err(); - eprintln!("Err: {e:?}"); - assert!(matches!(e, MemPoolRejection::TransferAmountMustBePositive)); - - // not enough funds - let tx_bytes = make_stacks_transfer_serialized( - &contract_sk, - 5, - 110000, - CHAIN_ID_TESTNET, - &other_addr, - 1000, - ); - let tx = - StacksTransaction::consensus_deserialize(&mut tx_bytes.as_slice()).unwrap(); - let e = chain_state - .will_admit_mempool_tx( - &NULL_BURN_STATE_DB, - consensus_hash, - block_hash, - &tx, - tx_bytes.len() as u64, - ) - .unwrap_err(); - eprintln!("Err: {e:?}"); - assert!(matches!(e, MemPoolRejection::NotEnoughFunds(111000, 99500))); - - let tx_bytes = make_stacks_transfer_serialized( - &contract_sk, - 5, - 99700, - CHAIN_ID_TESTNET, - &other_addr, - 1000, - ); - let tx = - StacksTransaction::consensus_deserialize(&mut tx_bytes.as_slice()).unwrap(); - let e = chain_state - .will_admit_mempool_tx( - &NULL_BURN_STATE_DB, - consensus_hash, - block_hash, - &tx, - tx_bytes.len() as u64, - ) - .unwrap_err(); - eprintln!("Err: {e:?}"); - assert!(matches!(e, MemPoolRejection::NotEnoughFunds(100700, 99500))); - - let tx_bytes = make_contract_call( - &contract_sk, - 5, - 200, - CHAIN_ID_TESTNET, - &contract_addr, - "bar_contract", - "bar", - &[Value::UInt(1)], - ); - let tx = - StacksTransaction::consensus_deserialize(&mut tx_bytes.as_slice()).unwrap(); - let e = chain_state - .will_admit_mempool_tx( - &NULL_BURN_STATE_DB, - consensus_hash, - block_hash, - &tx, - tx_bytes.len() as u64, - ) - .unwrap_err(); - eprintln!("Err: {e:?}"); - assert!(matches!(e, MemPoolRejection::NoSuchContract)); - - let tx_bytes = make_contract_call( - &contract_sk, - 5, - 200, - CHAIN_ID_TESTNET, - &contract_addr, - "foo_contract", - "foobar", - &[Value::UInt(1)], - ); - let tx = - StacksTransaction::consensus_deserialize(&mut tx_bytes.as_slice()).unwrap(); - let e = chain_state - .will_admit_mempool_tx( - &NULL_BURN_STATE_DB, - consensus_hash, - block_hash, - &tx, - tx_bytes.len() as u64, - ) - .unwrap_err(); - eprintln!("Err: {e:?}"); - assert!(matches!(e, MemPoolRejection::NoSuchPublicFunction)); - - let tx_bytes = make_contract_call( - &contract_sk, - 5, - 200, - CHAIN_ID_TESTNET, - &contract_addr, - "foo_contract", - "bar", - &[Value::UInt(1), Value::Int(2)], - ); - let tx = - StacksTransaction::consensus_deserialize(&mut tx_bytes.as_slice()).unwrap(); - let e = chain_state - .will_admit_mempool_tx( - &NULL_BURN_STATE_DB, - consensus_hash, - block_hash, - &tx, - tx_bytes.len() as u64, - ) - .unwrap_err(); - eprintln!("Err: {e:?}"); - assert!(matches!(e, MemPoolRejection::BadFunctionArgument(_))); - - let tx_bytes = make_contract_publish( - &contract_sk, - 5, - 1000, - CHAIN_ID_TESTNET, - "foo_contract", - FOO_CONTRACT, - ); - let tx = - StacksTransaction::consensus_deserialize(&mut tx_bytes.as_slice()).unwrap(); - let e = chain_state - .will_admit_mempool_tx( - &NULL_BURN_STATE_DB, - consensus_hash, - block_hash, - &tx, - tx_bytes.len() as u64, - ) - .unwrap_err(); - eprintln!("Err: {e:?}"); - assert!(matches!(e, MemPoolRejection::ContractAlreadyExists(_))); - - let microblock_1 = StacksMicroblockHeader { - version: 0, - sequence: 0, - prev_block: BlockHeaderHash([0; 32]), - tx_merkle_root: Sha512Trunc256Sum::from_data(&[]), - signature: MessageSignature([1; 65]), - }; - - let microblock_2 = StacksMicroblockHeader { - version: 0, - sequence: 1, - prev_block: BlockHeaderHash([0; 32]), - tx_merkle_root: Sha512Trunc256Sum::from_data(&[]), - signature: MessageSignature([1; 65]), - }; - - let tx_bytes = make_poison( - &contract_sk, - 5, - 1000, - CHAIN_ID_TESTNET, - microblock_1, - microblock_2, - ); - let tx = - StacksTransaction::consensus_deserialize(&mut tx_bytes.as_slice()).unwrap(); - let e = chain_state - .will_admit_mempool_tx( - &NULL_BURN_STATE_DB, - consensus_hash, - block_hash, - &tx, - tx_bytes.len() as u64, - ) - .unwrap_err(); - eprintln!("Err: {e:?}"); - assert!(matches!(e, MemPoolRejection::Other(_))); - - let microblock_1 = StacksMicroblockHeader { - version: 0, - sequence: 0, - prev_block: block_hash.clone(), - tx_merkle_root: Sha512Trunc256Sum::from_data(&[]), - signature: MessageSignature([0; 65]), - }; - - let microblock_2 = StacksMicroblockHeader { - version: 0, - sequence: 0, - prev_block: block_hash.clone(), - tx_merkle_root: Sha512Trunc256Sum::from_data(&[1, 2, 3]), - signature: MessageSignature([0; 65]), - }; - - let tx_bytes = make_poison( - &contract_sk, - 5, - 1000, - CHAIN_ID_TESTNET, - microblock_1, - microblock_2, - ); - let tx = - StacksTransaction::consensus_deserialize(&mut tx_bytes.as_slice()).unwrap(); - let e = chain_state - .will_admit_mempool_tx( - &NULL_BURN_STATE_DB, - consensus_hash, - block_hash, - &tx, - tx_bytes.len() as u64, - ) - .unwrap_err(); - eprintln!("Err: {e:?}"); - assert!(matches!(e, MemPoolRejection::Other(_))); - - let mut microblock_1 = StacksMicroblockHeader { - version: 0, - sequence: 0, - prev_block: BlockHeaderHash([0; 32]), - tx_merkle_root: Sha512Trunc256Sum::from_data(&[]), - signature: MessageSignature([0; 65]), - }; - - let mut microblock_2 = StacksMicroblockHeader { - version: 0, - sequence: 0, - prev_block: BlockHeaderHash([0; 32]), - tx_merkle_root: Sha512Trunc256Sum::from_data(&[1, 2, 3]), - signature: MessageSignature([0; 65]), - }; - - microblock_1.sign(&other_sk).unwrap(); - microblock_2.sign(&other_sk).unwrap(); - - let tx_bytes = make_poison( - &contract_sk, - 5, - 1000, - CHAIN_ID_TESTNET, - microblock_1, - microblock_2, - ); - let tx = - StacksTransaction::consensus_deserialize(&mut tx_bytes.as_slice()).unwrap(); - let e = chain_state - .will_admit_mempool_tx( - &NULL_BURN_STATE_DB, - consensus_hash, - block_hash, - &tx, - tx_bytes.len() as u64, - ) - .unwrap_err(); - eprintln!("Err: {e:?}"); - assert!(matches!(e, MemPoolRejection::Other(_))); - - let tx_bytes = make_coinbase(&contract_sk, 5, 1000, CHAIN_ID_TESTNET); - let tx = - StacksTransaction::consensus_deserialize(&mut tx_bytes.as_slice()).unwrap(); - let e = chain_state - .will_admit_mempool_tx( - &NULL_BURN_STATE_DB, - consensus_hash, - block_hash, - &tx, - tx_bytes.len() as u64, - ) - .unwrap_err(); - eprintln!("Err: {e:?}"); - assert!(matches!(e, MemPoolRejection::NoCoinbaseViaMempool)); - - // find the correct priv-key - let mut secret_key = None; - let mut conf = super::new_test_conf(); - conf.node.seed = vec![0x00]; - - let keychain = Keychain::default(conf.node.seed); - for i in 0..4 { - let microblock_secret_key = keychain.get_microblock_key(1 + i); - let mut microblock_pubkey = - Secp256k1PublicKey::from_private(µblock_secret_key); - microblock_pubkey.set_compressed(true); - let pubkey_hash = StacksBlockHeader::pubkey_hash(µblock_pubkey); - if pubkey_hash == *micro_pubkh { - secret_key = Some(microblock_secret_key); - break; - } - } - - let secret_key = secret_key.expect("Failed to find the microblock secret key"); - - let mut microblock_1 = StacksMicroblockHeader { - version: 0, - sequence: 0, - prev_block: BlockHeaderHash([0; 32]), - tx_merkle_root: Sha512Trunc256Sum::from_data(&[]), - signature: MessageSignature([0; 65]), - }; - - let mut microblock_2 = StacksMicroblockHeader { - version: 0, - sequence: 0, - prev_block: BlockHeaderHash([0; 32]), - tx_merkle_root: Sha512Trunc256Sum::from_data(&[1, 2, 3]), - signature: MessageSignature([0; 65]), - }; - - microblock_1.sign(&secret_key).unwrap(); - microblock_2.sign(&secret_key).unwrap(); - - let tx_bytes = make_poison( - &contract_sk, - 5, - 1000, - CHAIN_ID_TESTNET, - microblock_1, - microblock_2, - ); - let tx = - StacksTransaction::consensus_deserialize(&mut tx_bytes.as_slice()).unwrap(); - let e = chain_state - .will_admit_mempool_tx( - &NULL_BURN_STATE_DB, - consensus_hash, - block_hash, - &tx, - tx_bytes.len() as u64, - ) - .unwrap_err(); - eprintln!("Err: {e:?}"); - assert!(matches!(e, MemPoolRejection::Other(_))); - - let contract_id = QualifiedContractIdentifier::new( - StandardPrincipalData::from(contract_addr.clone()), - ContractName::from_literal("implement-trait-contract"), - ); - let contract_principal = PrincipalData::Contract(contract_id); - - let tx_bytes = make_contract_call( - &contract_sk, - 5, - 250, - CHAIN_ID_TESTNET, - &contract_addr, - "use-trait-contract", - "baz", - &[Value::Principal(contract_principal)], - ); - let tx = - StacksTransaction::consensus_deserialize(&mut tx_bytes.as_slice()).unwrap(); - chain_state - .will_admit_mempool_tx( - &NULL_BURN_STATE_DB, - consensus_hash, - block_hash, - &tx, - tx_bytes.len() as u64, - ) - .unwrap(); - - let contract_id = QualifiedContractIdentifier::new( - StandardPrincipalData::from(contract_addr.clone()), - ContractName::from_literal("bad-trait-contract"), - ); - let contract_principal = PrincipalData::Contract(contract_id); - - let tx_bytes = make_contract_call( - &contract_sk, - 5, - 250, - CHAIN_ID_TESTNET, - &contract_addr, - "use-trait-contract", - "baz", - &[Value::Principal(contract_principal)], - ); - let tx = - StacksTransaction::consensus_deserialize(&mut tx_bytes.as_slice()).unwrap(); - let e = chain_state - .will_admit_mempool_tx( - &NULL_BURN_STATE_DB, - consensus_hash, - block_hash, - &tx, - tx_bytes.len() as u64, - ) - .unwrap_err(); - assert!(matches!(e, MemPoolRejection::BadFunctionArgument(_))); - } - }, - ); - - run_loop.start(num_rounds).unwrap(); -} diff --git a/stacks-node/src/tests/mod.rs b/stacks-node/src/tests/mod.rs index 6492b2e1916..59f301284b0 100644 --- a/stacks-node/src/tests/mod.rs +++ b/stacks-node/src/tests/mod.rs @@ -17,13 +17,10 @@ use std::collections::{HashMap, HashSet}; use std::sync::atomic::AtomicU64; use std::sync::{Arc, Mutex}; -use clarity::vm::costs::ExecutionCost; -use clarity::vm::events::STXEventType; use lazy_static::lazy_static; use neon_integrations::test_observer::EVENT_OBSERVER_PORT; use rand::Rng; use stacks::chainstate::burn::ConsensusHash; -use stacks::chainstate::stacks::events::StacksTransactionEvent; use stacks::chainstate::stacks::{ StacksPrivateKey, StacksPublicKey, StacksTransaction, TransactionPayload, }; @@ -38,7 +35,6 @@ use stacks_common::util::hash::{hex_bytes, to_hex}; use super::neon_node::{BlockMinerThread, TipCandidate}; use super::Config; -use crate::helium::RunLoop; use crate::tests::neon_integrations::{get_chain_info, next_block_and_wait}; use crate::BitcoinRegtestController; @@ -49,10 +45,8 @@ mod epoch_21; mod epoch_22; mod epoch_23; mod epoch_24; -mod integrations; mod marf; mod mem_abort; -mod mempool; pub mod nakamoto_integrations; pub mod neon_integrations; mod signer; @@ -237,467 +231,6 @@ pub fn run_until_burnchain_height( true } -#[test] -fn should_succeed_mining_valid_txs() { - let mut conf = new_test_conf(); - - conf.add_initial_balance( - to_addr( - &StacksPrivateKey::from_hex( - "043ff5004e3d695060fa48ac94c96049b8c14ef441c50a184a6a3875d2a000f3", - ) - .unwrap(), - ) - .to_string(), - 10000, - ); - conf.add_initial_balance( - to_addr( - &StacksPrivateKey::from_hex( - "b1cf9cee5083f421c84d7cb53be5edf2801c3c78d63d53917aee0bdc8bd160ee01", - ) - .unwrap(), - ) - .to_string(), - 100000, - ); - - let num_rounds = 6; - let mut run_loop = RunLoop::new(conf.clone()); - - // Use tenure's hook for submitting transactions - run_loop.callbacks.on_new_tenure(|round, _burnchain_tip, chain_tip, tenure| { - let header_hash = chain_tip.block.block_hash(); - let consensus_hash = &chain_tip.metadata.consensus_hash; - - let mut chainstate_copy = tenure.open_chainstate(); - let sortdb = tenure.open_fake_sortdb(); - - match round { - 1 => { - // On round 1, publish the KV contract - tenure.mem_pool.submit_raw(&mut chainstate_copy, &sortdb, consensus_hash, &header_hash, PUBLISH_CONTRACT.to_owned(), - &ExecutionCost::max_value(), - &StacksEpochId::Epoch21, - ).unwrap(); - }, - 2 => { - // On round 2, publish a "get:foo" transaction - // ./blockstack-cli --testnet contract-call 043ff5004e3d695060fa48ac94c96049b8c14ef441c50a184a6a3875d2a000f3 10 1 STGT7GSMZG7EA0TS6MVSKT5JC1DCDFGZWJJZXN8A store get-value -e \"foo\" - let get_foo = "8080000000040021a3c334fc0ee50359353799e8b2605ac6be1fe40000000000000001000000000000000a0100b7ff8b6c20c427b4f4f09c1ad7e50027e2b076b2ddc0ab55e64ef5ea3771dd4763a79bc5a2b1a79b72ce03dd146ccf24b84942d675a815819a8b85aa8065dfaa030200000000021a21a3c334fc0ee50359353799e8b2605ac6be1fe40573746f7265096765742d76616c7565000000010d00000003666f6f"; - tenure.mem_pool.submit_raw(&mut chainstate_copy, &sortdb, consensus_hash, &header_hash,hex_bytes(get_foo).unwrap().to_vec(), - &ExecutionCost::max_value(), - &StacksEpochId::Epoch21, - ).unwrap(); - }, - 3 => { - // On round 3, publish a "set:foo=bar" transaction - // ./blockstack-cli --testnet contract-call 043ff5004e3d695060fa48ac94c96049b8c14ef441c50a184a6a3875d2a000f3 10 2 STGT7GSMZG7EA0TS6MVSKT5JC1DCDFGZWJJZXN8A store set-value -e \"foo\" -e \"bar\" - let set_foo_bar = "8080000000040021a3c334fc0ee50359353799e8b2605ac6be1fe40000000000000002000000000000000a010142a01caf6a32b367664869182f0ebc174122a5a980937ba259d44cc3ebd280e769a53dd3913c8006ead680a6e1c98099fcd509ce94b0a4e90d9f4603b101922d030200000000021a21a3c334fc0ee50359353799e8b2605ac6be1fe40573746f7265097365742d76616c7565000000020d00000003666f6f0d00000003626172"; - tenure.mem_pool.submit_raw(&mut chainstate_copy, &sortdb, consensus_hash, &header_hash,hex_bytes(set_foo_bar).unwrap().to_vec(), - &ExecutionCost::max_value(), - &StacksEpochId::Epoch21, - ).unwrap(); - }, - 4 => { - // On round 4, publish a "get:foo" transaction - // ./blockstack-cli --testnet contract-call 043ff5004e3d695060fa48ac94c96049b8c14ef441c50a184a6a3875d2a000f3 10 3 STGT7GSMZG7EA0TS6MVSKT5JC1DCDFGZWJJZXN8A store get-value -e \"foo\" - let get_foo = "8080000000040021a3c334fc0ee50359353799e8b2605ac6be1fe40000000000000003000000000000000a010046c2c1c345231443fef9a1f64fccfef3e1deacc342b2ab5f97612bb3742aa799038b20aea456789aca6b883e52f84a31adfee0bc2079b740464877af8f2f87d2030200000000021a21a3c334fc0ee50359353799e8b2605ac6be1fe40573746f7265096765742d76616c7565000000010d00000003666f6f"; - tenure.mem_pool.submit_raw(&mut chainstate_copy, &sortdb, consensus_hash, &header_hash,hex_bytes(get_foo).unwrap().to_vec(), - &ExecutionCost::max_value(), - &StacksEpochId::Epoch21, - ).unwrap(); - }, - 5 => { - // On round 5, publish a stacks transaction - // ./blockstack-cli --testnet token-transfer b1cf9cee5083f421c84d7cb53be5edf2801c3c78d63d53917aee0bdc8bd160ee01 10 0 ST195Q2HPXY576N4CT2A0R94D7DRYSX54A5X3YZTH 1000 - let transfer_1000_stx = "80800000000400b71a091b4b8b7661a661c620966ab6573bc2dcd30000000000000000000000000000000a0000393810832bacd44cfc4024980876135de6b95429bdb610d5ce96a92c9ee9bfd81ec77ea0f1748c8515fc9a1589e51d8b92bf028e3e84ade1249682c05271d5b803020000000000051a525b8a36ef8a73548cd0940c248d3b71ecf4a45100000000000003e800000000000000000000000000000000000000000000000000000000000000000000"; - tenure.mem_pool.submit_raw(&mut chainstate_copy, &sortdb, consensus_hash, &header_hash,hex_bytes(transfer_1000_stx).unwrap().to_vec(), - &ExecutionCost::max_value(), - &StacksEpochId::Epoch21, - ).unwrap(); - }, - _ => {} - }; - }); - - // Use block's hook for asserting expectations - run_loop.callbacks.on_new_stacks_chain_state( - |round, _burnchain_tip, chain_tip, _chain_state, _burn_dbconn| { - match round { - 0 => { - // Inspecting the chain at round 0. - // - Chain length should be 1. - assert!(chain_tip.metadata.stacks_block_height == 1); - - // Block #1 should only have 0 txs - assert!(chain_tip.block.txs.len() == 1); - } - 1 => { - // Inspecting the chain at round 1. - // - Chain length should be 2. - assert!(chain_tip.metadata.stacks_block_height == 2); - - // Block #2 should only have 2 txs - assert!(chain_tip.block.txs.len() == 2); - - // Transaction #1 should be the coinbase from the leader - let coinbase_tx = &chain_tip.block.txs[0]; - assert!(coinbase_tx.chain_id == CHAIN_ID_TESTNET); - assert!(matches!( - coinbase_tx.payload, - TransactionPayload::Coinbase(..) - )); - - // Transaction #2 should be the smart contract published - let contract_tx = &chain_tip.block.txs[1]; - assert!(contract_tx.chain_id == CHAIN_ID_TESTNET); - assert!(matches!( - contract_tx.payload, - TransactionPayload::SmartContract(..) - )); - - // 0 event should have been produced - let events = chain_tip.receipts.iter().flat_map(|a| a.events.clone()); - assert!(events.count() == 0); - } - 2 => { - // Inspecting the chain at round 2. - // - Chain length should be 3. - assert!(chain_tip.metadata.stacks_block_height == 3); - - // Block #3 should only have 2 txs - assert!(chain_tip.block.txs.len() == 2); - - // Transaction #1 should be the coinbase from the leader - let coinbase_tx = &chain_tip.block.txs[0]; - assert!(coinbase_tx.chain_id == CHAIN_ID_TESTNET); - assert!(matches!( - coinbase_tx.payload, - TransactionPayload::Coinbase(..) - )); - - // Transaction #2 should be the get-value contract-call - let contract_tx = &chain_tip.block.txs[1]; - assert!(contract_tx.chain_id == CHAIN_ID_TESTNET); - assert!(matches!( - contract_tx.payload, - TransactionPayload::ContractCall(_) - )); - - // 2 lockup events should have been produced - let events = chain_tip.receipts.iter().flat_map(|a| a.events.clone()); - assert_eq!(events.count(), 2); - } - 3 => { - // Inspecting the chain at round 3. - // - Chain length should be 4. - assert!(chain_tip.metadata.stacks_block_height == 4); - - // Block #4 should only have 2 txs - assert!(chain_tip.block.txs.len() == 2); - - // Transaction #1 should be the coinbase from the leader - let coinbase_tx = &chain_tip.block.txs[0]; - assert!(coinbase_tx.chain_id == CHAIN_ID_TESTNET); - assert!(matches!( - coinbase_tx.payload, - TransactionPayload::Coinbase(..) - )); - - // Transaction #2 should be the set-value contract-call - let contract_tx = &chain_tip.block.txs[1]; - assert!(contract_tx.chain_id == CHAIN_ID_TESTNET); - assert!(matches!( - contract_tx.payload, - TransactionPayload::ContractCall(_) - )); - - // 2 lockup events + 1 contract event should have been produced - let events: Vec = chain_tip - .receipts - .iter() - .flat_map(|a| a.events.clone()) - .collect(); - assert_eq!(events.len(), 3); - assert!(match &events.last().unwrap() { - StacksTransactionEvent::SmartContractEvent(data) => { - format!("{}", data.key.0) - == "STGT7GSMZG7EA0TS6MVSKT5JC1DCDFGZWJJZXN8A.store" - && data.key.1 == "print" - && format!("{}", data.value) == "\"Setting key foo\"" - } - _ => false, - }); - } - 4 => { - // Inspecting the chain at round 4. - // - Chain length should be 5. - assert!(chain_tip.metadata.stacks_block_height == 5); - - // Block #5 should only have 2 txs - assert!(chain_tip.block.txs.len() == 2); - - // Transaction #1 should be the coinbase from the leader - let coinbase_tx = &chain_tip.block.txs[0]; - assert!(coinbase_tx.chain_id == CHAIN_ID_TESTNET); - assert!(matches!( - coinbase_tx.payload, - TransactionPayload::Coinbase(..) - )); - - // Transaction #2 should be the get-value contract-call - let contract_tx = &chain_tip.block.txs[1]; - assert!(contract_tx.chain_id == CHAIN_ID_TESTNET); - assert!(matches!( - contract_tx.payload, - TransactionPayload::ContractCall(_) - )); - - // 1 event should have been produced - let events: Vec = chain_tip - .receipts - .iter() - .flat_map(|a| a.events.clone()) - .collect(); - assert!(events.len() == 1); - assert!(match &events[0] { - StacksTransactionEvent::SmartContractEvent(data) => { - format!("{}", data.key.0) - == "STGT7GSMZG7EA0TS6MVSKT5JC1DCDFGZWJJZXN8A.store" - && data.key.1 == "print" - && format!("{}", data.value) == "\"Getting key foo\"" - } - _ => false, - }); - } - 5 => { - // Inspecting the chain at round 5. - // - Chain length should be 6. - assert!(chain_tip.metadata.stacks_block_height == 6); - - // Block #6 should only have 2 txs - assert!(chain_tip.block.txs.len() == 2); - - // Transaction #1 should be the coinbase from the leader - let coinbase_tx = &chain_tip.block.txs[0]; - assert!(coinbase_tx.chain_id == CHAIN_ID_TESTNET); - assert!(matches!( - coinbase_tx.payload, - TransactionPayload::Coinbase(..) - )); - - // Transaction #2 should be the STX transfer - let contract_tx = &chain_tip.block.txs[1]; - assert!(contract_tx.chain_id == CHAIN_ID_TESTNET); - - assert!(matches!( - contract_tx.payload, - TransactionPayload::TokenTransfer(_, _, _) - )); - - // 1 event should have been produced - let events: Vec = chain_tip - .receipts - .iter() - .flat_map(|a| a.events.clone()) - .collect(); - assert!(events.len() == 1); - assert!(match &events[0] { - StacksTransactionEvent::STXEvent(STXEventType::STXTransferEvent(event)) => { - format!("{}", event.recipient) - == "ST195Q2HPXY576N4CT2A0R94D7DRYSX54A5X3YZTH" - && format!("{}", event.sender) - == "ST2VHM28V9E5QCRD6C73215KAPSBKQGPWTEE5CMQT" - && event.amount == 1000 - } - _ => false, - }); - } - _ => {} - } - }, - ); - run_loop.start(num_rounds).unwrap(); -} - -#[test] -#[ignore] -fn should_succeed_handling_malformed_and_valid_txs() { - let mut conf = new_test_conf(); - let contract_sk = StacksPrivateKey::from_hex(SK_1).unwrap(); - conf.add_initial_balance( - to_addr( - &StacksPrivateKey::from_hex( - "043ff5004e3d695060fa48ac94c96049b8c14ef441c50a184a6a3875d2a000f3", - ) - .unwrap(), - ) - .to_string(), - 10000, - ); - conf.add_initial_balance(to_addr(&contract_sk).to_string(), 10000); - - let num_rounds = 4; - let mut run_loop = RunLoop::new(conf); - - // Use tenure's hook for submitting transactions - run_loop.callbacks.on_new_tenure(|round, _burnchain_tip, chain_tip, tenure| { - let header_hash = chain_tip.block.block_hash(); - let consensus_hash = &chain_tip.metadata.consensus_hash; - let mut chainstate_copy = tenure.open_chainstate(); - let sortdb = tenure.open_fake_sortdb(); - - match round { - 1 => { - // On round 1, publish the KV contract - let contract_sk = StacksPrivateKey::from_hex(SK_1).unwrap(); - let publish_contract = make_contract_publish(&contract_sk, 0, 10, CHAIN_ID_TESTNET, "store", STORE_CONTRACT); - tenure.mem_pool.submit_raw(&mut chainstate_copy, &sortdb, consensus_hash, &header_hash,publish_contract, - &ExecutionCost::max_value(), - &StacksEpochId::Epoch20, - ).unwrap(); - }, - 2 => { - // On round 2, publish a "get:foo" transaction (mainnet instead of testnet). - // Will not be mined - // ./blockstack-cli contract-call 043ff5004e3d695060fa48ac94c96049b8c14ef441c50a184a6a3875d2a000f3 10 1 STGT7GSMZG7EA0TS6MVSKT5JC1DCDFGZWJJZXN8A store get-value -e \"foo\" - let get_foo = "0000000001040021a3c334fc0ee50359353799e8b2605ac6be1fe40000000000000001000000000000000a0101ef2b00e7e55ee5cb7684d5313c7c49680c97e60cb29f0166798e6ffabd984a030cf0a7b919bcf5fa052efd5d9efd96b927213cb3af1cfb8d9c5a0be0fccda64d030200000000021a21a3c334fc0ee50359353799e8b2605ac6be1fe40573746f7265096765742d76616c7565000000010d00000003666f6f"; - tenure.mem_pool.submit_raw(&mut chainstate_copy, &sortdb, consensus_hash, &header_hash,hex_bytes(get_foo).unwrap().to_vec(), - &ExecutionCost::max_value(), - &StacksEpochId::Epoch20, - ).unwrap(); - }, - 3 => { - // On round 3, publish a "set:foo=bar" transaction (chain-id not matching). - // Will not be mined - // ./blockstack-cli --testnet contract-call 043ff5004e3d695060fa48ac94c96049b8c14ef441c50a184a6a3875d2a000f3 10 1 STGT7GSMZG7EA0TS6MVSKT5JC1DCDFGZWJJZXN8A store set-value -e \"foo\" -e \"bar\" - let set_foo_bar = "8080000000040021a3c334fc0ee50359353799e8b2605ac6be1fe40000000000000001000000000000000a010093f733efcebe2b239bb22e2e1ed25612547403af66b29282ed1f6fdfbbbf8f7f6ef107256d07947cbb72e165d723af99c447d6e25e7fbb6a92fd9a51c5ef7ee9030200000000021a21a3c334fc0ee50359353799e8b2605ac6be1fe40573746f7265097365742d76616c7565000000020d00000003666f6f0d00000003626172"; - tenure.mem_pool.submit_raw(&mut chainstate_copy, &sortdb, consensus_hash, &header_hash,hex_bytes(set_foo_bar).unwrap().to_vec(), - &ExecutionCost::max_value(), - &StacksEpochId::Epoch20, - ).unwrap(); - }, - 4 => { - // On round 4, publish a "get:foo" transaction - // ./blockstack-cli --testnet contract-call 043ff5004e3d695060fa48ac94c96049b8c14ef441c50a184a6a3875d2a000f3 10 1 STGT7GSMZG7EA0TS6MVSKT5JC1DCDFGZWJJZXN8A store get-value -e \"foo\" - let get_foo = "8080000000040021a3c334fc0ee50359353799e8b2605ac6be1fe40000000000000001000000000000000a0100b7ff8b6c20c427b4f4f09c1ad7e50027e2b076b2ddc0ab55e64ef5ea3771dd4763a79bc5a2b1a79b72ce03dd146ccf24b84942d675a815819a8b85aa8065dfaa030200000000021a21a3c334fc0ee50359353799e8b2605ac6be1fe40573746f7265096765742d76616c7565000000010d00000003666f6f"; - tenure.mem_pool.submit_raw(&mut chainstate_copy, &sortdb, consensus_hash, &header_hash,hex_bytes(get_foo).unwrap().to_vec(), - &ExecutionCost::max_value(), - &StacksEpochId::Epoch20, - ).unwrap(); - }, - _ => {} - }; - }); - - // Use block's hook for asserting expectations - run_loop.callbacks.on_new_stacks_chain_state( - |round, _burnchain_tip, chain_tip, _chain_state, _burn_dbconn| { - match round { - 0 => { - // Inspecting the chain at round 0. - // - Chain length should be 1. - assert!(chain_tip.metadata.stacks_block_height == 1); - - // Block #1 should only have 1 txs - assert!(chain_tip.block.txs.len() == 1); - - // Transaction #1 should be the coinbase from the leader - let coinbase_tx = &chain_tip.block.txs[0]; - assert!(coinbase_tx.chain_id == CHAIN_ID_TESTNET); - assert!(matches!( - coinbase_tx.payload, - TransactionPayload::Coinbase(..) - )); - } - 1 => { - // Inspecting the chain at round 1. - // - Chain length should be 2. - assert!(chain_tip.metadata.stacks_block_height == 2); - - // Block #2 should only have 2 txs - assert_eq!(chain_tip.block.txs.len(), 2); - - // Transaction #1 should be the coinbase from the leader - let coinbase_tx = &chain_tip.block.txs[0]; - assert!(coinbase_tx.chain_id == CHAIN_ID_TESTNET); - assert!(matches!( - coinbase_tx.payload, - TransactionPayload::Coinbase(..) - )); - - // Transaction #2 should be the smart contract published - let contract_tx = &chain_tip.block.txs[1]; - assert!(contract_tx.chain_id == CHAIN_ID_TESTNET); - assert!(matches!( - contract_tx.payload, - TransactionPayload::SmartContract(..) - )); - } - 2 => { - // Inspecting the chain at round 2. - // - Chain length should be 3. - assert!(chain_tip.metadata.stacks_block_height == 3); - - // Block #3 should only have 1 tx (the other being invalid) - assert!(chain_tip.block.txs.len() == 1); - - // Transaction #1 should be the coinbase from the leader - let coinbase_tx = &chain_tip.block.txs[0]; - assert!(coinbase_tx.chain_id == CHAIN_ID_TESTNET); - assert!(matches!( - coinbase_tx.payload, - TransactionPayload::Coinbase(..) - )); - } - 3 => { - // Inspecting the chain at round 3. - // - Chain length should be 4. - assert!(chain_tip.metadata.stacks_block_height == 4); - - // Block #4 should only have 1 tx (the other being invalid) - assert!(chain_tip.block.txs.len() == 1); - - // Transaction #1 should be the coinbase from the leader - let coinbase_tx = &chain_tip.block.txs[0]; - assert!(coinbase_tx.chain_id == CHAIN_ID_TESTNET); - assert!(matches!( - coinbase_tx.payload, - TransactionPayload::Coinbase(..) - )); - } - 4 => { - // Inspecting the chain at round 4. - // - Chain length should be 5. - assert!(chain_tip.metadata.stacks_block_height == 5); - - // Block #5 should only have 2 txs - assert!(chain_tip.block.txs.len() == 2); - - // Transaction #1 should be the coinbase from the leader - let coinbase_tx = &chain_tip.block.txs[0]; - assert!(coinbase_tx.chain_id == CHAIN_ID_TESTNET); - assert!(matches!( - coinbase_tx.payload, - TransactionPayload::Coinbase(..) - )); - - // Transaction #2 should be the contract-call - let contract_tx = &chain_tip.block.txs[1]; - assert!(contract_tx.chain_id == CHAIN_ID_TESTNET); - assert!(matches!( - contract_tx.payload, - TransactionPayload::ContractCall(_) - )); - } - _ => {} - } - }, - ); - run_loop.start(num_rounds).unwrap(); -} - #[test] fn test_sort_and_populate_candidates() { let empty: Vec = vec![]; diff --git a/stacks-signer/src/config.rs b/stacks-signer/src/config.rs index 9f7d6da022b..4a6531729fe 100644 --- a/stacks-signer/src/config.rs +++ b/stacks-signer/src/config.rs @@ -88,8 +88,6 @@ pub enum Network { Mainnet, /// The testnet network Testnet, - /// The mocknet network - Mocknet, } impl std::fmt::Display for Network { @@ -97,7 +95,6 @@ impl std::fmt::Display for Network { match self { Self::Mainnet => write!(f, "mainnet"), Self::Testnet => write!(f, "testnet"), - Self::Mocknet => write!(f, "mocknet"), } } } @@ -107,7 +104,7 @@ impl Network { pub const fn to_address_version(&self) -> u8 { match self { Self::Mainnet => C32_ADDRESS_VERSION_MAINNET_SINGLESIG, - Self::Testnet | Self::Mocknet => C32_ADDRESS_VERSION_TESTNET_SINGLESIG, + Self::Testnet => C32_ADDRESS_VERSION_TESTNET_SINGLESIG, } } @@ -115,7 +112,7 @@ impl Network { pub const fn to_transaction_version(&self) -> TransactionVersion { match self { Self::Mainnet => TransactionVersion::Mainnet, - Self::Testnet | Self::Mocknet => TransactionVersion::Testnet, + Self::Testnet => TransactionVersion::Testnet, } } @@ -123,7 +120,7 @@ impl Network { pub const fn is_mainnet(&self) -> bool { match self { Self::Mainnet => true, - Self::Testnet | Self::Mocknet => false, + Self::Testnet => false, } } } @@ -305,7 +302,7 @@ struct RawConfigFile { /// - 64 or 66 hex characters (with optional `01` compression suffix). /// - This key determines the signer's on-chain identity and address. pub stacks_private_key: String, - /// The network to use. One of `"mainnet"`, `"testnet"`, or `"mocknet"`. + /// The network to use. One of `"mainnet"` or `"testnet"`. /// --- /// @default: (required, no default) /// @notes: @@ -709,7 +706,7 @@ Dry run: {dry_run} pub fn to_chain_id(&self) -> u32 { self.chain_id.unwrap_or(match self.network { Network::Mainnet => CHAIN_ID_MAINNET, - Network::Testnet | Network::Mocknet => CHAIN_ID_TESTNET, + Network::Testnet => CHAIN_ID_TESTNET, }) } } diff --git a/stackslib/src/burnchains/burnchain.rs b/stackslib/src/burnchains/burnchain.rs index 6eccad9b260..922d412ea0a 100644 --- a/stackslib/src/burnchains/burnchain.rs +++ b/stackslib/src/burnchains/burnchain.rs @@ -1117,7 +1117,7 @@ impl Burnchain { // extract block-commit metadata // Do not emit sortition/burn block events to event observer in this method, because this - // method is deprecated and only used in defunct helium nodes + // method is deprecated db.evaluate_sortition( false, diff --git a/stackslib/src/chainstate/stacks/db/blocks.rs b/stackslib/src/chainstate/stacks/db/blocks.rs index f73a3a61eec..96444ca93c2 100644 --- a/stackslib/src/chainstate/stacks/db/blocks.rs +++ b/stackslib/src/chainstate/stacks/db/blocks.rs @@ -4907,7 +4907,7 @@ impl StacksChainState { ).expect("FATAL: Unrecoverable chainstate corruption: Epoch 2.1 code evaluated before first burn block height"); // Do not try to handle auto-unlocks on pox_reward_cycle 0 // This cannot even occur in the mainchain, because 2.1 starts much - // after the 1st reward cycle, however, this could come up in mocknets or regtest. + // after the 1st reward cycle, however, this could come up in regtest. if pox_reward_cycle <= 1 { return Ok(vec![]); } diff --git a/stackslib/src/config/mod.rs b/stackslib/src/config/mod.rs index 57e79020b3e..2dd1f7911cf 100644 --- a/stackslib/src/config/mod.rs +++ b/stackslib/src/config/mod.rs @@ -317,94 +317,6 @@ impl ConfigFile { ..ConfigFile::default() } } - - pub fn helium() -> ConfigFile { - // ## Settings for local testnet, relying on a local bitcoind server - // ## running with the following bitcoin.conf: - // ## - // ## chain=regtest - // ## disablewallet=0 - // ## txindex=1 - // ## server=1 - // ## rpcuser=helium - // ## rpcpassword=helium - // ## - let burnchain = BurnchainConfigFile { - mode: Some("helium".to_string()), - commit_anchor_block_within: Some(10_000), - rpc_port: Some(18443), - peer_port: Some(18444), - peer_host: Some("0.0.0.0".to_string()), - username: Some("helium".to_string()), - password: Some("helium".to_string()), - local_mining_public_key: Some("04ee0b1602eb18fef7986887a7e8769a30c9df981d33c8380d255edef003abdcd243a0eb74afdf6740e6c423e62aec631519a24cf5b1d62bf8a3e06ddc695dcb77".to_string()), - ..BurnchainConfigFile::default() - }; - - let node = NodeConfigFile { - miner: Some(false), - stacker: Some(false), - ..NodeConfigFile::default() - }; - - ConfigFile { - burnchain: Some(burnchain), - node: Some(node), - ..ConfigFile::default() - } - } - - pub fn mocknet() -> ConfigFile { - let burnchain = BurnchainConfigFile { - mode: Some("mocknet".to_string()), - commit_anchor_block_within: Some(10_000), - ..BurnchainConfigFile::default() - }; - - let node = NodeConfigFile { - miner: Some(false), - stacker: Some(false), - ..NodeConfigFile::default() - }; - - let balances = vec![ - InitialBalanceFile { - // "mnemonic": "point approve language letter cargo rough similar wrap focus edge polar task olympic tobacco cinnamon drop lawn boring sort trade senior screen tiger climb", - // "privateKey": "539e35c740079b79f931036651ad01f76d8fe1496dbd840ba9e62c7e7b355db001", - // "btcAddress": "n1htkoYKuLXzPbkn9avC2DJxt7X85qVNCK", - address: "ST3EQ88S02BXXD0T5ZVT3KW947CRMQ1C6DMQY8H19".to_string(), - amount: 10000000000000000, - }, - InitialBalanceFile { - // "mnemonic": "laugh capital express view pull vehicle cluster embark service clerk roast glance lumber glove purity project layer lyrics limb junior reduce apple method pear", - // "privateKey": "075754fb099a55e351fe87c68a73951836343865cd52c78ae4c0f6f48e234f3601", - // "btcAddress": "n2ZGZ7Zau2Ca8CLHGh11YRnLw93b4ufsDR", - address: "ST3KCNDSWZSFZCC6BE4VA9AXWXC9KEB16FBTRK36T".to_string(), - amount: 10000000000000000, - }, - InitialBalanceFile { - // "mnemonic": "level garlic bean design maximum inhale daring alert case worry gift frequent floor utility crowd twenty burger place time fashion slow produce column prepare", - // "privateKey": "374b6734eaff979818c5f1367331c685459b03b1a2053310906d1408dc928a0001", - // "btcAddress": "mhY4cbHAFoXNYvXdt82yobvVuvR6PHeghf", - address: "STB2BWB0K5XZGS3FXVTG3TKS46CQVV66NAK3YVN8".to_string(), - amount: 10000000000000000, - }, - InitialBalanceFile { - // "mnemonic": "drop guess similar uphold alarm remove fossil riot leaf badge lobster ability mesh parent lawn today student olympic model assault syrup end scorpion lab", - // "privateKey": "26f235698d02803955b7418842affbee600fc308936a7ca48bf5778d1ceef9df01", - // "btcAddress": "mkEDDqbELrKYGUmUbTAyQnmBAEz4V1MAro", - address: "STSTW15D618BSZQB85R058DS46THH86YQQY6XCB7".to_string(), - amount: 10000000000000000, - }, - ]; - - ConfigFile { - burnchain: Some(burnchain), - node: Some(node), - ustx_balance: Some(balances), - ..ConfigFile::default() - } - } } #[derive(Clone, Debug)] @@ -693,7 +605,6 @@ impl Config { #[cfg_attr(test, mutants::skip)] fn make_epochs( conf_epochs: &[StacksEpochConfigFile], - burn_mode: &str, bitcoin_network: BitcoinNetworkType, pox_2_activation: Option, ) -> Result, String> { @@ -813,12 +724,6 @@ impl Config { .map_err(|_| "End height must be a non-negative integer")?; } - if burn_mode == "mocknet" { - for epoch in out_epochs.iter_mut() { - epoch.block_limit = ExecutionCost::max_value(); - } - } - if let Some(pox_2_activation) = pox_2_activation { let last_epoch = out_epochs .iter() @@ -859,8 +764,6 @@ impl Config { }; let supported_modes = [ - "mocknet", - "helium", "neon", "argon", "krypton", @@ -876,10 +779,6 @@ impl Config { )); } - if burnchain.mode == "helium" && burnchain.local_mining_public_key.is_none() { - return Err("Config is missing the setting `burnchain.local_mining_public_key` (mandatory for helium)".into()); - } - let is_mainnet = burnchain.mode == "mainnet"; // Parse the node config @@ -1279,14 +1178,12 @@ pub struct BurnchainConfig { /// Supported values: /// - `"mainnet"`: mainnet /// - `"xenon"`: testnet - /// - `"mocknet"`: regtest - /// - `"helium"`: regtest /// - `"neon"`: regtest /// - `"argon"`: regtest /// - `"krypton"`: regtest /// - `"nakamoto-neon"`: regtest /// --- - /// @default: `"mocknet"` + /// @default: `"neon"` pub mode: String, /// The network-specific identifier used in P2P communication and database initialization. /// --- @@ -1397,7 +1294,7 @@ pub struct BurnchainConfig { /// public key. /// /// It is primarily used in modes that rely on a controlled Bitcoin regtest - /// backend (e.g., "helium", "mocknet", "neon") where the Stacks node itself + /// backend (e.g., "neon") where the Stacks node itself /// needs to instruct the Bitcoin node to generate blocks. /// /// The key is used to derive the Bitcoin address that receives the coinbase @@ -1405,7 +1302,6 @@ pub struct BurnchainConfig { /// --- /// @default: `None` /// @notes: - /// - Mandatory if [`BurnchainConfig::mode`] is "helium". /// - This is intended strictly for testing purposes. pub local_mining_public_key: Option, /// Optional bitcoin block height at which the Stacks node process should @@ -1636,7 +1532,7 @@ impl BurnchainConfig { fn default() -> BurnchainConfig { BurnchainConfig { chain: "bitcoin".to_string(), - mode: "mocknet".to_string(), + mode: "neon".to_string(), chain_id: CHAIN_ID_TESTNET, peer_version: PEER_VERSION_TESTNET, burn_fee_cap: 20000, @@ -1696,7 +1592,7 @@ impl BurnchainConfig { match self.mode.as_str() { "mainnet" => ("mainnet".to_string(), BitcoinNetworkType::Mainnet), "xenon" => ("testnet".to_string(), BitcoinNetworkType::Testnet), - "helium" | "neon" | "argon" | "krypton" | "mocknet" | "nakamoto-neon" => { + "neon" | "argon" | "krypton" | "nakamoto-neon" => { ("regtest".to_string(), BitcoinNetworkType::Regtest) } other => panic!("Invalid stacks-node mode: {other}"), @@ -1933,7 +1829,6 @@ impl BurnchainConfigFile { if let Some(ref conf_epochs) = self.epochs { config.epochs = Some(Config::make_epochs( conf_epochs, - &config.mode, config.get_bitcoin_network().1, self.pox_2_activation, )?); @@ -1948,7 +1843,7 @@ pub struct NodeConfig { /// Human-readable name for the node. Primarily used for identification in testing /// environments (e.g., deriving log file names, temporary directory names). /// --- - /// @default: `"helium-node"` + /// @default: `"stacks-node"` pub name: String, /// The node's Bitcoin wallet private key, provided as a hex string in the config file. /// Used to initialize the node's keychain for signing operations. @@ -2450,7 +2345,7 @@ impl Default for NodeConfig { let mut seed = [0u8; 32]; rng.fill_bytes(&mut seed); - let name = "helium-node"; + let name = "stacks-node"; NodeConfig { name: name.to_string(), seed: seed.to_vec(), diff --git a/stackslib/src/core/mod.rs b/stackslib/src/core/mod.rs index 45e23861352..5635db65385 100644 --- a/stackslib/src/core/mod.rs +++ b/stackslib/src/core/mod.rs @@ -232,7 +232,7 @@ pub const HELIUM_BLOCK_LIMIT_20: ExecutionCost = ExecutionCost { write_count: 50_000, read_length: 1_000_000_000, read_count: 50_000, - // allow much more runtime in helium blocks than mainnet + // allow much more runtime in regtest blocks than mainnet runtime: 100_000_000_000, };