From ca3c28896c5f3b1c88b325694513c2bdd1665cc1 Mon Sep 17 00:00:00 2001 From: ASuciuX Date: Wed, 25 Sep 2024 19:14:50 +0300 Subject: [PATCH 1/9] add initial version of test --- .../src/tests/nakamoto_integrations.rs | 342 +++++++++++++++++- 1 file changed, 340 insertions(+), 2 deletions(-) diff --git a/testnet/stacks-node/src/tests/nakamoto_integrations.rs b/testnet/stacks-node/src/tests/nakamoto_integrations.rs index 17b829557f..2e53bb217c 100644 --- a/testnet/stacks-node/src/tests/nakamoto_integrations.rs +++ b/testnet/stacks-node/src/tests/nakamoto_integrations.rs @@ -31,8 +31,10 @@ use libsigner::v0::messages::SignerMessage as SignerMessageV0; use libsigner::v1::messages::SignerMessage as SignerMessageV1; use libsigner::{BlockProposal, SignerSession, StackerDBSession}; use rand::RngCore; +use stacks::burnchains::bitcoin::address::{BitcoinAddress, LegacyBitcoinAddressType}; +use stacks::burnchains::bitcoin::BitcoinNetworkType; use stacks::burnchains::{MagicBytes, Txid}; -use stacks::chainstate::burn::db::sortdb::SortitionDB; +use stacks::chainstate::burn::db::sortdb::{get_block_commit_by_txid, SortitionDB}; use stacks::chainstate::burn::operations::{ BlockstackOperationType, DelegateStxOp, PreStxOp, StackStxOp, TransferStxOp, VoteForAggregateKeyOp, @@ -72,6 +74,7 @@ use stacks::net::api::getstackers::GetStackersResponse; use stacks::net::api::postblock_proposal::{ BlockValidateReject, BlockValidateResponse, NakamotoBlockProposal, ValidateRejectCode, }; +use stacks::types::PublicKey; use stacks::util::hash::hex_bytes; use stacks::util_lib::boot::boot_code_id; use stacks::util_lib::signed_structured_data::pox4::{ @@ -2137,7 +2140,6 @@ fn mine_multiple_per_tenure_integration() { } #[test] -#[ignore] /// This test spins up two nakamoto nodes, both configured to mine. /// It starts in Epoch 2.0, mines with `neon_node` to Epoch 3.0, and then switches /// to Nakamoto operation (activating pox-4 by submitting a stack-stx tx). The BootLoop @@ -2394,6 +2396,342 @@ fn multiple_miners() { run_loop_thread.join().unwrap(); } +#[test] +/// This test spins up two nakamoto nodes, both configured to mine. +/// It starts in Epoch 2.0, mines with `neon_node` to Epoch 3.0, and then switches +/// to Nakamoto operation (activating pox-4 by submitting a stack-stx tx). The BootLoop +/// struct handles the epoch-2/3 tear-down and spin-up. +/// This test makes these assertions: +/// * When a new tenure starts, the flash blocks for the old one stop +/// * The tenure height is increased by 1 +/// * The block_consensus_hash is different from the previous one +/// * Mine 2 tenures +/// +/// * Each tenure has 6 blocks (the coinbase block and 5 interim blocks) +/// * TODO: does this happen here? Both nodes see the same chainstate at the end of the test +fn new_tenure_stop_fast_blocks() { + if env::var("BITCOIND_TEST") != Ok("1".into()) { + return; + } + let (mut naka_conf, _miner_account) = naka_neon_integration_conf(None); + naka_conf.node.local_peer_seed = vec![1, 1, 1, 1]; + naka_conf.miner.mining_key = Some(Secp256k1PrivateKey::from_seed(&[1])); + + let node_2_rpc = 51026; + let node_2_p2p = 51025; + let http_origin = format!("http://{}", &naka_conf.node.rpc_bind); + naka_conf.miner.wait_on_interim_blocks = Duration::from_secs(1); + let sender_sk = Secp256k1PrivateKey::new(); + let sender_signer_sk = Secp256k1PrivateKey::new(); + let sender_signer_addr = tests::to_addr(&sender_signer_sk); + let tenure_count = 2; + let inter_blocks_per_tenure = 6; + // setup sender + recipient for some test stx transfers + // these are necessary for the interim blocks to get mined at all + let sender_addr = tests::to_addr(&sender_sk); + let send_amt = 100; + let send_fee = 180; + naka_conf.add_initial_balance( + PrincipalData::from(sender_addr.clone()).to_string(), + (send_amt + send_fee) * tenure_count * inter_blocks_per_tenure, + ); + naka_conf.add_initial_balance( + PrincipalData::from(sender_signer_addr.clone()).to_string(), + 100000, + ); + let recipient = PrincipalData::from(StacksAddress::burn_address(false)); + let stacker_sk = setup_stacker(&mut naka_conf); + + let mut conf_node_2 = naka_conf.clone(); + let localhost = "127.0.0.1"; + conf_node_2.node.rpc_bind = format!("{}:{}", localhost, node_2_rpc); + conf_node_2.node.p2p_bind = format!("{}:{}", localhost, node_2_p2p); + conf_node_2.node.data_url = format!("http://{}:{}", localhost, node_2_rpc); + conf_node_2.node.p2p_address = format!("{}:{}", localhost, node_2_p2p); + conf_node_2.node.seed = vec![2, 2, 2, 2]; + conf_node_2.burnchain.local_mining_public_key = Some( + Keychain::default(conf_node_2.node.seed.clone()) + .get_pub_key() + .to_hex(), + ); + conf_node_2.node.local_peer_seed = vec![2, 2, 2, 2]; + conf_node_2.node.miner = true; + conf_node_2.miner.mining_key = Some(Secp256k1PrivateKey::from_seed(&[2])); + conf_node_2.events_observers.clear(); + + let node_1_sk = Secp256k1PrivateKey::from_seed(&naka_conf.node.local_peer_seed); + let node_1_pk = StacksPublicKey::from_private(&node_1_sk); + + conf_node_2.node.working_dir = format!("{}-{}", conf_node_2.node.working_dir, "1"); + + conf_node_2.node.set_bootstrap_nodes( + format!("{}@{}", &node_1_pk.to_hex(), naka_conf.node.p2p_bind), + naka_conf.burnchain.chain_id, + naka_conf.burnchain.peer_version, + ); + + let miner_a_pubkey = Secp256k1PublicKey::from_private(&naka_conf.miner.mining_key.unwrap()); + let miner_b_pubkey = Secp256k1PublicKey::from_private(&conf_node_2.miner.mining_key.unwrap()); + + // Create Bitcoin addresses from public keys + let miner_a_address = BitcoinAddress::from_bytes_legacy( + BitcoinNetworkType::Testnet, + LegacyBitcoinAddressType::PublicKeyHash, + &Hash160::from_data(&miner_a_pubkey.to_bytes()).0, + ) + .expect("Failed to create Bitcoin address for miner A"); + let miner_b_address = BitcoinAddress::from_bytes_legacy( + BitcoinNetworkType::Testnet, + LegacyBitcoinAddressType::PublicKeyHash, + &Hash160::from_data(&miner_b_pubkey.to_bytes()).0, + ) + .expect("Failed to create Bitcoin address for miner B"); + + test_observer::spawn(); + let observer_port = test_observer::EVENT_OBSERVER_PORT; + naka_conf.events_observers.insert(EventObserverConfig { + endpoint: format!("localhost:{observer_port}"), + events_keys: vec![EventKeyType::AnyEvent], + }); + + let mut btcd_controller = BitcoinCoreController::new(naka_conf.clone()); + btcd_controller + .start_bitcoind() + .expect("Failed starting bitcoind"); + let mut btc_regtest_controller = BitcoinRegtestController::new(naka_conf.clone(), None); + btc_regtest_controller.bootstrap_chain_to_pks( + 201, + &[ + Secp256k1PublicKey::from_hex( + naka_conf + .burnchain + .local_mining_public_key + .as_ref() + .unwrap(), + ) + .unwrap(), + Secp256k1PublicKey::from_hex( + conf_node_2 + .burnchain + .local_mining_public_key + .as_ref() + .unwrap(), + ) + .unwrap(), + ], + ); + + let mut run_loop = boot_nakamoto::BootRunLoop::new(naka_conf.clone()).unwrap(); + let mut run_loop_2 = boot_nakamoto::BootRunLoop::new(conf_node_2.clone()).unwrap(); + let run_loop_stopper = run_loop.get_termination_switch(); + let Counters { + blocks_processed, + naka_submitted_commits: commits_submitted, + naka_proposed_blocks: proposals_submitted, + .. + } = run_loop.counters(); + + let run_loop_2_stopper = run_loop.get_termination_switch(); + let Counters { + naka_proposed_blocks: proposals_submitted_2, + .. + } = run_loop_2.counters(); + + let coord_channel = run_loop.coordinator_channels(); + let coord_channel_2 = run_loop_2.coordinator_channels(); + + let _run_loop_2_thread = thread::Builder::new() + .name("run_loop_2".into()) + .spawn(move || run_loop_2.start(None, 0)) + .unwrap(); + + let run_loop_thread = thread::Builder::new() + .name("run_loop".into()) + .spawn(move || run_loop.start(None, 0)) + .unwrap(); + wait_for_runloop(&blocks_processed); + + let mut signers = TestSigners::new(vec![sender_signer_sk.clone()]); + boot_to_epoch_3( + &naka_conf, + &blocks_processed, + &[stacker_sk], + &[sender_signer_sk], + &mut Some(&mut signers), + &mut btc_regtest_controller, + ); + + info!("Bootstrapped to Epoch-3.0 boundary, starting nakamoto miner"); + + let burnchain = naka_conf.get_burnchain(); + let sortdb = burnchain.open_sortition_db(true).unwrap(); + let (chainstate, _) = StacksChainState::open( + naka_conf.is_mainnet(), + naka_conf.burnchain.chain_id, + &naka_conf.get_chainstate_path_str(), + None, + ) + .unwrap(); + + let block_height_pre_3_0 = + NakamotoChainState::get_canonical_block_header(chainstate.db(), &sortdb) + .unwrap() + .unwrap() + .stacks_block_height; + + info!("Nakamoto miner started..."); + blind_signer_multinode( + &signers, + &[&naka_conf, &conf_node_2], + vec![proposals_submitted, proposals_submitted_2], + ); + + info!("Neighbors 1"; "neighbors" => ?get_neighbors(&naka_conf)); + info!("Neighbors 2"; "neighbors" => ?get_neighbors(&conf_node_2)); + + // Wait one block to confirm the VRF register, wait until a block commit is submitted + wait_for_first_naka_block_commit(60, &commits_submitted); + + // TODO: + // in 1st tenure, miner_A mines the blocks normally and signers are following miner_A + // in 2nd tenure, + // miner_B mines the blocks, + // the signers are following miner_A + // miner_B tries to mine 1 more block, the signers will not sign it -> it will not be added + // miner_A should continue to mine the blocks as he has the right view of the chain + // in 3rd tenure, miner_ + + // Mine `tenure_count` nakamoto tenures + for tenure_ix in 0..tenure_count { + info!("Mining tenure {}", tenure_ix); + let commits_before = commits_submitted.load(Ordering::SeqCst); + next_block_and_process_new_stacks_block(&mut btc_regtest_controller, 60, &coord_channel) + .unwrap(); + + // Get the latest block from chainstate + let burnchain = naka_conf.get_burnchain(); + let burnchain_header_hash = burnchain + .get_highest_burnchain_block() + .unwrap() + .unwrap() + .block_hash; + let sortdb = burnchain.open_sortition_db(true).unwrap(); + let (chainstate, _) = StacksChainState::open( + naka_conf.is_mainnet(), + naka_conf.burnchain.chain_id, + &naka_conf.get_chainstate_path_str(), + None, + ) + .unwrap(); + + info!("Miner A address: {}", miner_a_address); + info!("Miner B address: {}", miner_b_address); + + let block_snapshot: stacks::chainstate::burn::BlockSnapshot = + SortitionDB::get_all_snapshots_for_burn_block(sortdb.conn(), &burnchain_header_hash) + .unwrap()[0] + .clone(); + // let miner_hash = block_snapshot.miner_pk_hash.unwrap(); + let winning_block_txid: Txid = block_snapshot.winning_block_txid; + info!("Winner Miner txid: {}", winning_block_txid); + + let blocks_commits = get_block_commit_by_txid( + sortdb.conn(), + &block_snapshot.sortition_id, + &winning_block_txid, + ) + .unwrap() + .unwrap(); + info!("Miner Address: {}", blocks_commits.apparent_sender); + + // sortition db + // block_commits -> block_height to be what i am looking for + // 2 apparent_sender -> bitcoind address of miner + txid + // table snapshots: + // block_height -> what we want + // winning_block_txid -> check with the previous who won the block + + // if tenure_ix == 1 { // Second tenure (0-indexed) + // assert_eq!( + // anchor_miner, miner_b_address, + // "Expected miner B to mine the second tenure's anchor block" + // ); + // } + + let mut last_tip = BlockHeaderHash([0x00; 32]); + let mut last_tip_height = 0; + + // mine the interim blocks + for interim_block_ix in 0..inter_blocks_per_tenure { + let blocks_processed_before = coord_channel + .lock() + .expect("Mutex poisoned") + .get_stacks_blocks_processed(); + // submit a tx so that the miner will mine an extra block + let sender_nonce = tenure_ix * inter_blocks_per_tenure + interim_block_ix; + let transfer_tx = + make_stacks_transfer(&sender_sk, sender_nonce, send_fee, &recipient, send_amt); + submit_tx(&http_origin, &transfer_tx); + + wait_for(20, || { + let blocks_processed = coord_channel + .lock() + .expect("Mutex poisoned") + .get_stacks_blocks_processed(); + Ok(blocks_processed > blocks_processed_before) + }) + .unwrap(); + + let info = get_chain_info_result(&naka_conf).unwrap(); + assert_ne!(info.stacks_tip, last_tip); + assert_ne!(info.stacks_tip_height, last_tip_height); + + last_tip = info.stacks_tip; + last_tip_height = info.stacks_tip_height; + } + + wait_for(20, || { + Ok(commits_submitted.load(Ordering::SeqCst) > commits_before) + }) + .unwrap(); + } + + // load the chain tip, and assert that it is a nakamoto block and at least 30 blocks have advanced in epoch 3 + let tip = NakamotoChainState::get_canonical_block_header(chainstate.db(), &sortdb) + .unwrap() + .unwrap(); + info!( + "Latest tip"; + "height" => tip.stacks_block_height, + "is_nakamoto" => tip.anchored_header.as_stacks_nakamoto().is_some(), + ); + + let peer_1_height = get_chain_info(&naka_conf).stacks_tip_height; + let peer_2_height = get_chain_info(&conf_node_2).stacks_tip_height; + info!("Peer height information"; "peer_1" => peer_1_height, "peer_2" => peer_2_height); + assert_eq!(peer_1_height, peer_2_height); + + assert!(tip.anchored_header.as_stacks_nakamoto().is_some()); + assert_eq!( + tip.stacks_block_height, + block_height_pre_3_0 + ((inter_blocks_per_tenure + 1) * tenure_count), + "Should have mined (1 + interim_blocks_per_tenure) * tenure_count nakamoto blocks" + ); + + coord_channel + .lock() + .expect("Mutex poisoned") + .stop_chains_coordinator(); + coord_channel_2 + .lock() + .expect("Mutex poisoned") + .stop_chains_coordinator(); + run_loop_stopper.store(false, Ordering::SeqCst); + run_loop_2_stopper.store(false, Ordering::SeqCst); + + run_loop_thread.join().unwrap(); +} + #[test] #[ignore] fn correct_burn_outs() { From 86308a0244ab295a5ddd0342f6aadc865e601252 Mon Sep 17 00:00:00 2001 From: ASuciuX Date: Wed, 25 Sep 2024 20:59:25 +0300 Subject: [PATCH 2/9] also debug utxos for miners --- .../src/tests/nakamoto_integrations.rs | 24 +++++++++++++++---- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/testnet/stacks-node/src/tests/nakamoto_integrations.rs b/testnet/stacks-node/src/tests/nakamoto_integrations.rs index 2e53bb217c..6f7ea598d9 100644 --- a/testnet/stacks-node/src/tests/nakamoto_integrations.rs +++ b/testnet/stacks-node/src/tests/nakamoto_integrations.rs @@ -31,7 +31,10 @@ use libsigner::v0::messages::SignerMessage as SignerMessageV0; use libsigner::v1::messages::SignerMessage as SignerMessageV1; use libsigner::{BlockProposal, SignerSession, StackerDBSession}; use rand::RngCore; -use stacks::burnchains::bitcoin::address::{BitcoinAddress, LegacyBitcoinAddressType}; +use stacks::burnchains::bitcoin::address::{ + BitcoinAddress, LegacyBitcoinAddress, LegacyBitcoinAddressType, +}; +use stacks::burnchains::bitcoin::keys::BitcoinPublicKey; use stacks::burnchains::bitcoin::BitcoinNetworkType; use stacks::burnchains::{MagicBytes, Txid}; use stacks::chainstate::burn::db::sortdb::{get_block_commit_by_txid, SortitionDB}; @@ -74,7 +77,7 @@ use stacks::net::api::getstackers::GetStackersResponse; use stacks::net::api::postblock_proposal::{ BlockValidateReject, BlockValidateResponse, NakamotoBlockProposal, ValidateRejectCode, }; -use stacks::types::PublicKey; +use stacks::types::{Address, PublicKey}; use stacks::util::hash::hex_bytes; use stacks::util_lib::boot::boot_code_id; use stacks::util_lib::signed_structured_data::pox4::{ @@ -2470,8 +2473,8 @@ fn new_tenure_stop_fast_blocks() { naka_conf.burnchain.peer_version, ); - let miner_a_pubkey = Secp256k1PublicKey::from_private(&naka_conf.miner.mining_key.unwrap()); - let miner_b_pubkey = Secp256k1PublicKey::from_private(&conf_node_2.miner.mining_key.unwrap()); + let miner_a_pubkey = BitcoinPublicKey::from_private(&naka_conf.miner.mining_key.unwrap()); + let miner_b_pubkey = BitcoinPublicKey::from_private(&conf_node_2.miner.mining_key.unwrap()); // Create Bitcoin addresses from public keys let miner_a_address = BitcoinAddress::from_bytes_legacy( @@ -2642,7 +2645,18 @@ fn new_tenure_stop_fast_blocks() { ) .unwrap() .unwrap(); - info!("Miner Address: {}", blocks_commits.apparent_sender); + let winner_address = blocks_commits.apparent_sender; + let winner_bitcoin_address = BitcoinAddress::Legacy( + LegacyBitcoinAddress::from_string(winner_address.to_string().as_str()).unwrap(), + ); + + info!("Winner Miner Address: {}", winner_bitcoin_address); + + let a_utxos = btc_regtest_controller.get_all_utxos(&miner_a_pubkey); + let b_utxos = btc_regtest_controller.get_all_utxos(&miner_b_pubkey); + + info!("Miner A UTXOs: {}", a_utxos.len()); + info!("Miner B UTXOs: {}", b_utxos.len()); // sortition db // block_commits -> block_height to be what i am looking for From 31f428e5cd6fd92094928e0e70c57bb2501dd0aa Mon Sep 17 00:00:00 2001 From: ASuciuX Date: Wed, 25 Sep 2024 22:49:43 +0300 Subject: [PATCH 3/9] update addresses to use node_pk instead of mining_key - still not works --- .../src/tests/nakamoto_integrations.rs | 45 ++++++++++--------- 1 file changed, 24 insertions(+), 21 deletions(-) diff --git a/testnet/stacks-node/src/tests/nakamoto_integrations.rs b/testnet/stacks-node/src/tests/nakamoto_integrations.rs index 6f7ea598d9..b8cfe60f79 100644 --- a/testnet/stacks-node/src/tests/nakamoto_integrations.rs +++ b/testnet/stacks-node/src/tests/nakamoto_integrations.rs @@ -2473,23 +2473,6 @@ fn new_tenure_stop_fast_blocks() { naka_conf.burnchain.peer_version, ); - let miner_a_pubkey = BitcoinPublicKey::from_private(&naka_conf.miner.mining_key.unwrap()); - let miner_b_pubkey = BitcoinPublicKey::from_private(&conf_node_2.miner.mining_key.unwrap()); - - // Create Bitcoin addresses from public keys - let miner_a_address = BitcoinAddress::from_bytes_legacy( - BitcoinNetworkType::Testnet, - LegacyBitcoinAddressType::PublicKeyHash, - &Hash160::from_data(&miner_a_pubkey.to_bytes()).0, - ) - .expect("Failed to create Bitcoin address for miner A"); - let miner_b_address = BitcoinAddress::from_bytes_legacy( - BitcoinNetworkType::Testnet, - LegacyBitcoinAddressType::PublicKeyHash, - &Hash160::from_data(&miner_b_pubkey.to_bytes()).0, - ) - .expect("Failed to create Bitcoin address for miner B"); - test_observer::spawn(); let observer_port = test_observer::EVENT_OBSERVER_PORT; naka_conf.events_observers.insert(EventObserverConfig { @@ -2627,6 +2610,26 @@ fn new_tenure_stop_fast_blocks() { ) .unwrap(); + let node_2_sk = Secp256k1PrivateKey::from_seed(&conf_node_2.node.local_peer_seed); + let node_2_pk = StacksPublicKey::from_private(&node_2_sk); + + let miner_a_pubkey = BitcoinPublicKey::from_private(&naka_conf.miner.mining_key.unwrap()); + let miner_b_pubkey = BitcoinPublicKey::from_private(&conf_node_2.miner.mining_key.unwrap()); + + // Create Bitcoin addresses from public keys + let miner_a_address = BitcoinAddress::from_bytes_legacy( + BitcoinNetworkType::Regtest, + LegacyBitcoinAddressType::PublicKeyHash, + &Hash160::from_data(&node_1_pk.to_bytes()).0, + ) + .expect("Failed to create Bitcoin address for miner A"); + let miner_b_address = BitcoinAddress::from_bytes_legacy( + BitcoinNetworkType::Regtest, + LegacyBitcoinAddressType::PublicKeyHash, + &Hash160::from_data(&node_2_pk.to_bytes()).0, + ) + .expect("Failed to create Bitcoin address for miner B"); + info!("Miner A address: {}", miner_a_address); info!("Miner B address: {}", miner_b_address); @@ -2652,11 +2655,11 @@ fn new_tenure_stop_fast_blocks() { info!("Winner Miner Address: {}", winner_bitcoin_address); - let a_utxos = btc_regtest_controller.get_all_utxos(&miner_a_pubkey); - let b_utxos = btc_regtest_controller.get_all_utxos(&miner_b_pubkey); + // let a_utxos = btc_regtest_controller.get_all_utxos(&node_1_pk); + // let b_utxos = btc_regtest_controller.get_all_utxos(&); - info!("Miner A UTXOs: {}", a_utxos.len()); - info!("Miner B UTXOs: {}", b_utxos.len()); + // info!("Miner A UTXOs: {}", a_utxos.len()); + // info!("Miner B UTXOs: {}", b_utxos.len()); // sortition db // block_commits -> block_height to be what i am looking for From e15d1ba7f1ff9ec447b903069f81f98009d4c3d2 Mon Sep 17 00:00:00 2001 From: ASuciuX Date: Mon, 30 Sep 2024 17:52:26 +0300 Subject: [PATCH 4/9] fix test miner a and b address conversion --- .../src/tests/nakamoto_integrations.rs | 25 +++++++++++++------ 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/testnet/stacks-node/src/tests/nakamoto_integrations.rs b/testnet/stacks-node/src/tests/nakamoto_integrations.rs index b8cfe60f79..d85f84d951 100644 --- a/testnet/stacks-node/src/tests/nakamoto_integrations.rs +++ b/testnet/stacks-node/src/tests/nakamoto_integrations.rs @@ -2610,23 +2610,32 @@ fn new_tenure_stop_fast_blocks() { ) .unwrap(); - let node_2_sk = Secp256k1PrivateKey::from_seed(&conf_node_2.node.local_peer_seed); - let node_2_pk = StacksPublicKey::from_private(&node_2_sk); - - let miner_a_pubkey = BitcoinPublicKey::from_private(&naka_conf.miner.mining_key.unwrap()); - let miner_b_pubkey = BitcoinPublicKey::from_private(&conf_node_2.miner.mining_key.unwrap()); - + let miner_a_pubkey = naka_conf + .burnchain + .local_mining_public_key + .as_ref() + .map(|pubkey_hex| Secp256k1PublicKey::from_hex(pubkey_hex)) + .expect("Failed to parse miner A's public key") + .expect("Invalid A's public key format"); + let miner_b_pubkey = conf_node_2 + .burnchain + .local_mining_public_key + .as_ref() + .map(|pubkey_hex| Secp256k1PublicKey::from_hex(pubkey_hex)) + .expect("Failed to parse miner B's public key") + .expect("Invalid B's public key format"); + // Create Bitcoin addresses from public keys let miner_a_address = BitcoinAddress::from_bytes_legacy( BitcoinNetworkType::Regtest, LegacyBitcoinAddressType::PublicKeyHash, - &Hash160::from_data(&node_1_pk.to_bytes()).0, + &Hash160::from_data(&miner_a_pubkey.to_bytes()).0, ) .expect("Failed to create Bitcoin address for miner A"); let miner_b_address = BitcoinAddress::from_bytes_legacy( BitcoinNetworkType::Regtest, LegacyBitcoinAddressType::PublicKeyHash, - &Hash160::from_data(&node_2_pk.to_bytes()).0, + &Hash160::from_data(&miner_b_pubkey.to_bytes()).0, ) .expect("Failed to create Bitcoin address for miner B"); From 775eac7b4d19da920b17e958b389a45a2da55a41 Mon Sep 17 00:00:00 2001 From: ASuciuX Date: Thu, 3 Oct 2024 16:56:38 +0300 Subject: [PATCH 5/9] stopped miner a, failing to propose blocks miner b - added the logs for reference --- .../src/tests/nakamoto_integrations.rs | 54 +++++++++++------ .../stacks-node/src/tests/relevant-output.txt | 59 +++++++++++++++++++ 2 files changed, 95 insertions(+), 18 deletions(-) create mode 100644 testnet/stacks-node/src/tests/relevant-output.txt diff --git a/testnet/stacks-node/src/tests/nakamoto_integrations.rs b/testnet/stacks-node/src/tests/nakamoto_integrations.rs index d85f84d951..a4d3cc6b46 100644 --- a/testnet/stacks-node/src/tests/nakamoto_integrations.rs +++ b/testnet/stacks-node/src/tests/nakamoto_integrations.rs @@ -2427,7 +2427,7 @@ fn new_tenure_stop_fast_blocks() { let sender_sk = Secp256k1PrivateKey::new(); let sender_signer_sk = Secp256k1PrivateKey::new(); let sender_signer_addr = tests::to_addr(&sender_signer_sk); - let tenure_count = 2; + let tenure_count = 5; let inter_blocks_per_tenure = 6; // setup sender + recipient for some test stx transfers // these are necessary for the interim blocks to get mined at all @@ -2472,6 +2472,7 @@ fn new_tenure_stop_fast_blocks() { naka_conf.burnchain.chain_id, naka_conf.burnchain.peer_version, ); + let http_tx_origin = format!("http://{}", &conf_node_2.node.rpc_bind); test_observer::spawn(); let observer_port = test_observer::EVENT_OBSERVER_PORT; @@ -2588,6 +2589,8 @@ fn new_tenure_stop_fast_blocks() { // in 3rd tenure, miner_ // Mine `tenure_count` nakamoto tenures + let mut stopped_miner_a = false; + for tenure_ix in 0..tenure_count { info!("Mining tenure {}", tenure_ix); let commits_before = commits_submitted.load(Ordering::SeqCst); @@ -2595,7 +2598,7 @@ fn new_tenure_stop_fast_blocks() { .unwrap(); // Get the latest block from chainstate - let burnchain = naka_conf.get_burnchain(); + let burnchain = conf_node_2.get_burnchain(); let burnchain_header_hash = burnchain .get_highest_burnchain_block() .unwrap() @@ -2603,9 +2606,9 @@ fn new_tenure_stop_fast_blocks() { .block_hash; let sortdb = burnchain.open_sortition_db(true).unwrap(); let (chainstate, _) = StacksChainState::open( - naka_conf.is_mainnet(), - naka_conf.burnchain.chain_id, - &naka_conf.get_chainstate_path_str(), + conf_node_2.is_mainnet(), + conf_node_2.burnchain.chain_id, + &conf_node_2.get_chainstate_path_str(), None, ) .unwrap(); @@ -2684,33 +2687,49 @@ fn new_tenure_stop_fast_blocks() { // ); // } + if !stopped_miner_a && winner_bitcoin_address.to_string() == miner_b_address.to_string() { + info!("Miner B is the winner for tenure {}. Stopping miner A", tenure_ix); + coord_channel + .lock() + .expect("Mutex poisoned") + .stop_chains_coordinator(); + run_loop_stopper.store(false, Ordering::SeqCst); + stopped_miner_a = true; + }; + let mut last_tip = BlockHeaderHash([0x00; 32]); let mut last_tip_height = 0; // mine the interim blocks for interim_block_ix in 0..inter_blocks_per_tenure { - let blocks_processed_before = coord_channel + info!("a"); + let blocks_processed_before = coord_channel_2 .lock() .expect("Mutex poisoned") .get_stacks_blocks_processed(); + info!("b"); // submit a tx so that the miner will mine an extra block let sender_nonce = tenure_ix * inter_blocks_per_tenure + interim_block_ix; let transfer_tx = make_stacks_transfer(&sender_sk, sender_nonce, send_fee, &recipient, send_amt); - submit_tx(&http_origin, &transfer_tx); + info!("c"); + submit_tx(&http_tx_origin, &transfer_tx); + info!("d"); wait_for(20, || { - let blocks_processed = coord_channel + let blocks_processed = coord_channel_2 .lock() .expect("Mutex poisoned") .get_stacks_blocks_processed(); Ok(blocks_processed > blocks_processed_before) }) .unwrap(); + info!("e"); - let info = get_chain_info_result(&naka_conf).unwrap(); + let info = get_chain_info_result(&conf_node_2).unwrap(); assert_ne!(info.stacks_tip, last_tip); assert_ne!(info.stacks_tip_height, last_tip_height); + info!("f"); last_tip = info.stacks_tip; last_tip_height = info.stacks_tip_height; @@ -2732,10 +2751,14 @@ fn new_tenure_stop_fast_blocks() { "is_nakamoto" => tip.anchored_header.as_stacks_nakamoto().is_some(), ); - let peer_1_height = get_chain_info(&naka_conf).stacks_tip_height; - let peer_2_height = get_chain_info(&conf_node_2).stacks_tip_height; - info!("Peer height information"; "peer_1" => peer_1_height, "peer_2" => peer_2_height); - assert_eq!(peer_1_height, peer_2_height); + if stopped_miner_a { + info!("Miner A stopped, skipping peer height comparison"); + } else { + let peer_1_height = get_chain_info(&naka_conf).stacks_tip_height; + let peer_2_height = get_chain_info(&conf_node_2).stacks_tip_height; + info!("Peer height information"; "peer_1" => peer_1_height, "peer_2" => peer_2_height); + assert_eq!(peer_1_height, peer_2_height); + } assert!(tip.anchored_header.as_stacks_nakamoto().is_some()); assert_eq!( @@ -2744,15 +2767,10 @@ fn new_tenure_stop_fast_blocks() { "Should have mined (1 + interim_blocks_per_tenure) * tenure_count nakamoto blocks" ); - coord_channel - .lock() - .expect("Mutex poisoned") - .stop_chains_coordinator(); coord_channel_2 .lock() .expect("Mutex poisoned") .stop_chains_coordinator(); - run_loop_stopper.store(false, Ordering::SeqCst); run_loop_2_stopper.store(false, Ordering::SeqCst); run_loop_thread.join().unwrap(); diff --git a/testnet/stacks-node/src/tests/relevant-output.txt b/testnet/stacks-node/src/tests/relevant-output.txt new file mode 100644 index 0000000000..2eabb99c0e --- /dev/null +++ b/testnet/stacks-node/src/tests/relevant-output.txt @@ -0,0 +1,59 @@ +INFO [1727961693.679825] [testnet/stacks-node/src/tests/nakamoto_integrations.rs:2645] [tests::nakamoto_integrations::new_tenure_stop_fast_blocks] Miner A address: mtFzK54XtpktHj7fKonFExEPEGkUMsiXdy +INFO [1727961693.679831] [testnet/stacks-node/src/tests/nakamoto_integrations.rs:2646] [tests::nakamoto_integrations::new_tenure_stop_fast_blocks] Miner B address: mgPGTnrKZrLHHqCsQVtA5oy7RXeCey1s4w +INFO [1727961693.680003] [testnet/stacks-node/src/tests/nakamoto_integrations.rs:2654] [tests::nakamoto_integrations::new_tenure_stop_fast_blocks] Winner Miner txid: e8f59d578a0f291f72a2a5cbbf8632472f9e8ae94f537718ad8381fd4993761c +INFO [1727961693.680126] [testnet/stacks-node/src/tests/nakamoto_integrations.rs:2668] [tests::nakamoto_integrations::new_tenure_stop_fast_blocks] Winner Miner Address: mgPGTnrKZrLHHqCsQVtA5oy7RXeCey1s4w +INFO [1727961693.680135] [testnet/stacks-node/src/tests/nakamoto_integrations.rs:2691] [tests::nakamoto_integrations::new_tenure_stop_fast_blocks] Miner B is the winner for tenure 2. Stopping miner A +INFO [1727961693.680140] [testnet/stacks-node/src/tests/nakamoto_integrations.rs:2705] [tests::nakamoto_integrations::new_tenure_stop_fast_blocks] a +INFO [1727961693.680141] [testnet/stacks-node/src/tests/nakamoto_integrations.rs:2710] [tests::nakamoto_integrations::new_tenure_stop_fast_blocks] b +INFO [1727961693.680203] [testnet/stacks-node/src/tests/nakamoto_integrations.rs:2715] [tests::nakamoto_integrations::new_tenure_stop_fast_blocks] c +INFO [1727961693.683340] [stackslib/src/chainstate/burn/db/sortdb.rs:3982] [p2p-(127.0.0.1:10355,127.0.0.1:19350)] Fetching preprocessed reward set, tip_sortition_id: 46a5ad77c0b1635f5d9236f83ad9fad7430a37754e88b7eee0ad6cd239187897, reward_cycle_id: 11, prepare_phase_start_sortition_id: 46efc863a361be2670f0baffe06dae88b355a128846acaec5bb661c58d690a14 +INFO [1727961693.684798] [stackslib/src/chainstate/burn/db/sortdb.rs:3982] [p2p-(127.0.0.1:51025,127.0.0.1:51026)] Fetching preprocessed reward set, tip_sortition_id: 46a5ad77c0b1635f5d9236f83ad9fad7430a37754e88b7eee0ad6cd239187897, reward_cycle_id: 11, prepare_phase_start_sortition_id: 46efc863a361be2670f0baffe06dae88b355a128846acaec5bb661c58d690a14 +INFO [1727961693.686132] [stackslib/src/chainstate/burn/db/sortdb.rs:3982] [p2p-(127.0.0.1:10355,127.0.0.1:19350)] Fetching preprocessed reward set, tip_sortition_id: 333a4ca317a440b50c8525e8831ff1e693b0958e007df114bf3e068c680379e0, reward_cycle_id: 10, prepare_phase_start_sortition_id: 63a9fecc440bf3a57a25336b3db987af07b8c9d03c81483aaa147dde5cefca49 +INFO [1727961693.687858] [stackslib/src/chainstate/burn/db/sortdb.rs:3982] [p2p-(127.0.0.1:51025,127.0.0.1:51026)] Fetching preprocessed reward set, tip_sortition_id: 333a4ca317a440b50c8525e8831ff1e693b0958e007df114bf3e068c680379e0, reward_cycle_id: 10, prepare_phase_start_sortition_id: 63a9fecc440bf3a57a25336b3db987af07b8c9d03c81483aaa147dde5cefca49 +INFO [1727961693.688962] [stackslib/src/chainstate/burn/db/sortdb.rs:3982] [p2p-(127.0.0.1:10355,127.0.0.1:19350)] Fetching preprocessed reward set, tip_sortition_id: a6fd0c7207447a628c1fd79ea36969df7483622883a14bcf48b6b3fd1a2cbdfb, reward_cycle_id: 9, prepare_phase_start_sortition_id: e345956a63ef22c3dcc5957acead4c7d2f094728491735a8c886f3c61aafeeb5 +INFO [1727961693.690649] [stackslib/src/chainstate/burn/db/sortdb.rs:3982] [p2p-(127.0.0.1:51025,127.0.0.1:51026)] Fetching preprocessed reward set, tip_sortition_id: a6fd0c7207447a628c1fd79ea36969df7483622883a14bcf48b6b3fd1a2cbdfb, reward_cycle_id: 9, prepare_phase_start_sortition_id: e345956a63ef22c3dcc5957acead4c7d2f094728491735a8c886f3c61aafeeb5 +INFO [1727961693.729049] [testnet/stacks-node/src/nakamoto_node/peer.rs:140] [p2p-(127.0.0.1:10355,127.0.0.1:19350)] P2P thread exit! +INFO [1727961693.736785] [stackslib/src/net/rpc.rs:556] [p2p-(127.0.0.1:51025,127.0.0.1:51026)] Handled StacksHTTPRequest, verb: POST, path: /v2/transactions, processing_time_ms: 1, latency_ms: 0, conn_id: 65, peer_addr: 127.0.0.1:58343, p2p_msg: Some(Transaction(StacksTransaction { version: Testnet, chain_id: 2147483648, auth: Standard(Singlesig(SinglesigSpendingCondition { hash_mode: P2PKH, signer: d0d6ea35c353fa4cc1ed715c788860cbfe175603, nonce: 12, tx_fee: 180, key_encoding: Compressed, signature: 00240bb6f9960bd3e5e016ea164110aa36f26349539a2bce6fd6580e3ea6c629b5555e19ab3ba712976edb6f9de0b196090737f5f68a58727aabba4af6df0b0673 })), anchor_mode: OnChainOnly, post_condition_mode: Allow, post_conditions: [], payload: TokenTransfer(Standard(StandardPrincipalData(ST000000000000000000002AMW42H)), 100, 00000000000000000000000000000000000000000000000000000000000000000000) })) +INFO [1727961693.737025] [testnet/stacks-node/src/tests/nakamoto_integrations.rs:2717] [tests::nakamoto_integrations::new_tenure_stop_fast_blocks] d +INFO [1727961693.741390] [stackslib/src/chainstate/burn/db/sortdb.rs:1664] [relayer-http://127.0.0.1:51026] PoX recipient chosen, recipient: 1a-ee02bf1889dab2264a8ff035bb3100b39faa1c2d, block_height: 234, stacks_block_hash: 58e4ef89c3c93679884cebd7052049dde199c27346fb7575ba6abe3be75a26b9 +INFO [1727961693.741431] [stackslib/src/chainstate/burn/db/sortdb.rs:1664] [relayer-http://127.0.0.1:51026] PoX recipient chosen, recipient: 1a-ee02bf1889dab2264a8ff035bb3100b39faa1c2d, block_height: 234, stacks_block_hash: 58e4ef89c3c93679884cebd7052049dde199c27346fb7575ba6abe3be75a26b9 +INFO [1727961693.743017] [testnet/stacks-node/src/burnchains/bitcoin_regtest_controller.rs:1666] [relayer-http://127.0.0.1:51026] Attempt to replace by fee an outdated leader block commit, ongoing_txids: [112c8263a6fc6fe20af2b545720a0341b63ee48a718c8ab677fc74a0a611551d] +INFO [1727961693.743589] [testnet/stacks-node/src/burnchains/bitcoin_regtest_controller.rs:1514] [relayer-http://127.0.0.1:51026] Miner node: submitting leader_block_commit (txid: 88ec34967cf7de7f68d4b57a2d22f62f1e77791e7dbab9e25804e6438e53307d, rbf: true, total spent: 41505, size: 384, fee_rate: 55) +INFO [1727961693.755863] [testnet/stacks-node/src/nakamoto_node/relayer.rs:1084] [relayer-http://127.0.0.1:51026] Relayer: Submitted block-commit, tip_consensus_hash: c1b66ce402f74926f4495fe91bb97a7068d42512, tip_block_hash: 109165b6974b5b77ce3bd1b7c3b1afbaa97264a67d1b1608556f63113311cf30, tip_height: 39, tip_block_id: 55a6bf40061874cf1f48b7ac81a2331c6e7d99bf537eed13fc4f08600749b282, txid: 88ec34967cf7de7f68d4b57a2d22f62f1e77791e7dbab9e25804e6438e53307d +INFO [1727961694.504681] [testnet/stacks-node/src/run_loop/nakamoto.rs:540] [run_loop] Terminating p2p process +INFO [1727961694.504939] [testnet/stacks-node/src/run_loop/nakamoto.rs:541] [run_loop] Terminating relayer +INFO [1727961694.504946] [testnet/stacks-node/src/run_loop/nakamoto.rs:542] [run_loop] Terminating chains-coordinator +INFO [1727961694.505198] [testnet/stacks-node/src/run_loop/nakamoto.rs:548] [run_loop] Exiting stacks-node +INFO [1727961694.662242] [stackslib/src/chainstate/nakamoto/miner.rs:702] [miner.4e0550e802a1f63ffa07530bca902f5dfbc550d3e77d727ff41e361edc84b039] Include tx, tx: 0d6b677d33a7d92cf8e26378daba6478cb54df35b4fde26ac4276a5c406d95c8, payload: TokenTransfer, origin: ST38DDTHNRD9ZMK61XNRNRY48C35ZW5TP0F7HFBK4 +INFO [1727961694.662267] [stackslib/src/chainstate/stacks/miner.rs:397] [miner.4e0550e802a1f63ffa07530bca902f5dfbc550d3e77d727ff41e361edc84b039] Tx successfully processed., event_name: transaction_result, tx_id: 0d6b677d33a7d92cf8e26378daba6478cb54df35b4fde26ac4276a5c406d95c8, event_type: success +INFO [1727961694.663782] [stackslib/src/chainstate/nakamoto/miner.rs:575] [miner.4e0550e802a1f63ffa07530bca902f5dfbc550d3e77d727ff41e361edc84b039] Miner: mined Nakamoto block, stacks_block_hash: dcb1342357566626da28f44bba8d53f929a06b6f6c8f816930ac596062999f77, stacks_block_id: d204c421298df0e55cd1a92982625b3ab5e7f083b6d35bc706412f7b7d1bbbb0, height: 40, tx_count: 1, parent_block_id: 55a6bf40061874cf1f48b7ac81a2331c6e7d99bf537eed13fc4f08600749b282, block_size: 180, execution_consumed: {"runtime": 0, "write_len": 0, "write_cnt": 0, "read_len": 0, "read_cnt": 0}, %-full: 0, assembly_time_ms: 12, consensus_hash: c1b66ce402f74926f4495fe91bb97a7068d42512 +INFO [1727961694.664053] [testnet/stacks-node/src/nakamoto_node/miner.rs:1221] [miner.4e0550e802a1f63ffa07530bca902f5dfbc550d3e77d727ff41e361edc84b039] Miner: Assembled block #40 for signer set proposal: eb99579f338af2f5c21a1d7e2a9b368b7b4b98ece296c328cc75a74984d167fa, with 1 txs, signer_sighash: eb99579f338af2f5c21a1d7e2a9b368b7b4b98ece296c328cc75a74984d167fa, consensus_hash: c1b66ce402f74926f4495fe91bb97a7068d42512, parent_block_id: 55a6bf40061874cf1f48b7ac81a2331c6e7d99bf537eed13fc4f08600749b282, timestamp: 1727961694 +INFO [1727961694.683078] [stackslib/src/net/rpc.rs:556] [p2p-(127.0.0.1:51025,127.0.0.1:51026)] Handled StacksHTTPRequest, verb: POST, path: /v2/stackerdb/ST000000000000000000002AMW42H/miners/chunks, processing_time_ms: 0, latency_ms: 0, conn_id: 66, peer_addr: 127.0.0.1:58348, p2p_msg: Some(StackerDBPushChunk(StackerDBPushChunkData { contract_id: QualifiedContractIdentifier { issuer: StandardPrincipalData(ST000000000000000000002AMW42H), name: ContractName("miners") }, rc_consensus_hash: 549333be8bf4ab28a67b6821d368b4de48878eab, chunk_data: StackerDBChunkData(0,2,01ad2dd511af39f618fcc2aab6577590488921cc958faebc534f9a3e9f4ff8f14e704b00e7dc945aa32d56339c53820339d9390b328e8b41f285f9e5847f044833,000000000000000000280000000000116520c1b66ce402f74926f4495fe91bb97a7068d4251255a6bf40061874cf1f48b7ac81a2331c6e7d99bf537eed13fc4f08600749b2822224580a5e313e05203f3acde2da54075f2a41be40c78e96b597a9f11f421cc732bd2f8aad2096ff1fb551b4b435624cbfff0a4f864d03c5b1c0...(421)) })) +INFO [1727961694.683684] [testnet/stacks-node/src/nakamoto_node/sign_coordinator.rs:728] [miner.4e0550e802a1f63ffa07530bca902f5dfbc550d3e77d727ff41e361edc84b039] SignCoordinator: sent block proposal to .miners, waiting for test signing channel +INFO [1727961695.694734] [stackslib/src/net/stackerdb/mod.rs:493] [p2p-(127.0.0.1:51025,127.0.0.1:51026)] Failed to run StackerDB state machine for ST000000000000000000002AMW42H.signers-1-6: PeerNotConnected +INFO [1727961696.732321] [testnet/stacks-node/src/tests/nakamoto_integrations.rs:346] [blind-signer] Checking for a block proposal to sign... +INFO [1727961697.723343] [stackslib/src/net/stackerdb/mod.rs:493] [p2p-(127.0.0.1:51025,127.0.0.1:51026)] Failed to run StackerDB state machine for ST000000000000000000002AMW42H.signers-1-6: PeerNotConnected +INFO [1727961697.723631] [stackslib/src/net/stackerdb/mod.rs:493] [p2p-(127.0.0.1:51025,127.0.0.1:51026)] Failed to run StackerDB state machine for ST000000000000000000002AMW42H.signers-0-8: PeerNotConnected +INFO [1727961699.745941] [stackslib/src/net/stackerdb/mod.rs:493] [p2p-(127.0.0.1:51025,127.0.0.1:51026)] Failed to run StackerDB state machine for ST000000000000000000002AMW42H.signers-1-6: PeerNotConnected +INFO [1727961701.774656] [stackslib/src/net/stackerdb/mod.rs:493] [p2p-(127.0.0.1:51025,127.0.0.1:51026)] Failed to run StackerDB state machine for ST000000000000000000002AMW42H.signers-1-6: PeerNotConnected +INFO [1727961701.775029] [stackslib/src/net/stackerdb/mod.rs:493] [p2p-(127.0.0.1:51025,127.0.0.1:51026)] Failed to run StackerDB state machine for ST000000000000000000002AMW42H.signers-0-8: PeerNotConnected +INFO [1727961701.775290] [stackslib/src/net/stackerdb/mod.rs:493] [p2p-(127.0.0.1:51025,127.0.0.1:51026)] Failed to run StackerDB state machine for ST000000000000000000002AMW42H.signers-0-3: PeerNotConnected +INFO [1727961703.813978] [stackslib/src/net/stackerdb/mod.rs:493] [p2p-(127.0.0.1:51025,127.0.0.1:51026)] Failed to run StackerDB state machine for ST000000000000000000002AMW42H.signers-1-6: PeerNotConnected +INFO [1727961705.853196] [stackslib/src/net/stackerdb/mod.rs:493] [p2p-(127.0.0.1:51025,127.0.0.1:51026)] Failed to run StackerDB state machine for ST000000000000000000002AMW42H.signers-1-6: PeerNotConnected +INFO [1727961705.853615] [stackslib/src/net/stackerdb/mod.rs:493] [p2p-(127.0.0.1:51025,127.0.0.1:51026)] Failed to run StackerDB state machine for ST000000000000000000002AMW42H.signers-0-8: PeerNotConnected +INFO [1727961707.892266] [stackslib/src/net/stackerdb/mod.rs:493] [p2p-(127.0.0.1:51025,127.0.0.1:51026)] Failed to run StackerDB state machine for ST000000000000000000002AMW42H.signers-1-6: PeerNotConnected +INFO [1727961709.929316] [stackslib/src/net/stackerdb/mod.rs:493] [p2p-(127.0.0.1:51025,127.0.0.1:51026)] Failed to run StackerDB state machine for ST000000000000000000002AMW42H.signers-1-6: PeerNotConnected +INFO [1727961709.929819] [stackslib/src/net/stackerdb/mod.rs:493] [p2p-(127.0.0.1:51025,127.0.0.1:51026)] Failed to run StackerDB state machine for ST000000000000000000002AMW42H.signers-0-8: PeerNotConnected +INFO [1727961709.930107] [stackslib/src/net/stackerdb/mod.rs:493] [p2p-(127.0.0.1:51025,127.0.0.1:51026)] Failed to run StackerDB state machine for ST000000000000000000002AMW42H.signers-0-3: PeerNotConnected +INFO [1727961709.930356] [stackslib/src/net/stackerdb/mod.rs:493] [p2p-(127.0.0.1:51025,127.0.0.1:51026)] Failed to run StackerDB state machine for ST000000000000000000002AMW42H.signers-0-0: PeerNotConnected +INFO [1727961711.966333] [stackslib/src/net/stackerdb/mod.rs:493] [p2p-(127.0.0.1:51025,127.0.0.1:51026)] Failed to run StackerDB state machine for ST000000000000000000002AMW42H.signers-1-6: PeerNotConnected +ERRO [1727961713.780909] [testnet/stacks-node/src/tests/nakamoto_integrations.rs:645] [tests::nakamoto_integrations::new_tenure_stop_fast_blocks] Timed out waiting for check to process +test tests::nakamoto_integrations::new_tenure_stop_fast_blocks ... FAILED + +failures: + +failures: + tests::nakamoto_integrations::new_tenure_stop_fast_blocks + +test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 192 filtered out; finished in 118.55s + From 8b3b1f0e89aeb295b219f1b7c792c2055de028ff Mon Sep 17 00:00:00 2001 From: ASuciuX Date: Fri, 4 Oct 2024 01:38:26 +0300 Subject: [PATCH 6/9] move new_tenure_stop_fast_blocks test to use v0 test infra --- .../src/tests/nakamoto_integrations.rs | 377 ------------------ testnet/stacks-node/src/tests/signer/v0.rs | 321 ++++++++++++++- 2 files changed, 319 insertions(+), 379 deletions(-) diff --git a/testnet/stacks-node/src/tests/nakamoto_integrations.rs b/testnet/stacks-node/src/tests/nakamoto_integrations.rs index 7e61b721bd..c84eb0beea 100644 --- a/testnet/stacks-node/src/tests/nakamoto_integrations.rs +++ b/testnet/stacks-node/src/tests/nakamoto_integrations.rs @@ -2294,383 +2294,6 @@ fn multiple_miners() { run_loop_thread.join().unwrap(); } -#[test] -/// This test spins up two nakamoto nodes, both configured to mine. -/// It starts in Epoch 2.0, mines with `neon_node` to Epoch 3.0, and then switches -/// to Nakamoto operation (activating pox-4 by submitting a stack-stx tx). The BootLoop -/// struct handles the epoch-2/3 tear-down and spin-up. -/// This test makes these assertions: -/// * When a new tenure starts, the flash blocks for the old one stop -/// * The tenure height is increased by 1 -/// * The block_consensus_hash is different from the previous one -/// * Mine 2 tenures -/// -/// * Each tenure has 6 blocks (the coinbase block and 5 interim blocks) -/// * TODO: does this happen here? Both nodes see the same chainstate at the end of the test -fn new_tenure_stop_fast_blocks() { - if env::var("BITCOIND_TEST") != Ok("1".into()) { - return; - } - let (mut naka_conf, _miner_account) = naka_neon_integration_conf(None); - naka_conf.node.local_peer_seed = vec![1, 1, 1, 1]; - naka_conf.miner.mining_key = Some(Secp256k1PrivateKey::from_seed(&[1])); - - let node_2_rpc = 51026; - let node_2_p2p = 51025; - let http_origin = format!("http://{}", &naka_conf.node.rpc_bind); - naka_conf.miner.wait_on_interim_blocks = Duration::from_secs(1); - let sender_sk = Secp256k1PrivateKey::new(); - let sender_signer_sk = Secp256k1PrivateKey::new(); - let sender_signer_addr = tests::to_addr(&sender_signer_sk); - let tenure_count = 5; - let inter_blocks_per_tenure = 6; - // setup sender + recipient for some test stx transfers - // these are necessary for the interim blocks to get mined at all - let sender_addr = tests::to_addr(&sender_sk); - let send_amt = 100; - let send_fee = 180; - naka_conf.add_initial_balance( - PrincipalData::from(sender_addr.clone()).to_string(), - (send_amt + send_fee) * tenure_count * inter_blocks_per_tenure, - ); - naka_conf.add_initial_balance( - PrincipalData::from(sender_signer_addr.clone()).to_string(), - 100000, - ); - let recipient = PrincipalData::from(StacksAddress::burn_address(false)); - let stacker_sk = setup_stacker(&mut naka_conf); - - let mut conf_node_2 = naka_conf.clone(); - let localhost = "127.0.0.1"; - conf_node_2.node.rpc_bind = format!("{}:{}", localhost, node_2_rpc); - conf_node_2.node.p2p_bind = format!("{}:{}", localhost, node_2_p2p); - conf_node_2.node.data_url = format!("http://{}:{}", localhost, node_2_rpc); - conf_node_2.node.p2p_address = format!("{}:{}", localhost, node_2_p2p); - conf_node_2.node.seed = vec![2, 2, 2, 2]; - conf_node_2.burnchain.local_mining_public_key = Some( - Keychain::default(conf_node_2.node.seed.clone()) - .get_pub_key() - .to_hex(), - ); - conf_node_2.node.local_peer_seed = vec![2, 2, 2, 2]; - conf_node_2.node.miner = true; - conf_node_2.miner.mining_key = Some(Secp256k1PrivateKey::from_seed(&[2])); - conf_node_2.events_observers.clear(); - - let node_1_sk = Secp256k1PrivateKey::from_seed(&naka_conf.node.local_peer_seed); - let node_1_pk = StacksPublicKey::from_private(&node_1_sk); - - conf_node_2.node.working_dir = format!("{}-{}", conf_node_2.node.working_dir, "1"); - - conf_node_2.node.set_bootstrap_nodes( - format!("{}@{}", &node_1_pk.to_hex(), naka_conf.node.p2p_bind), - naka_conf.burnchain.chain_id, - naka_conf.burnchain.peer_version, - ); - let http_tx_origin = format!("http://{}", &conf_node_2.node.rpc_bind); - - test_observer::spawn(); - let observer_port = test_observer::EVENT_OBSERVER_PORT; - naka_conf.events_observers.insert(EventObserverConfig { - endpoint: format!("localhost:{observer_port}"), - events_keys: vec![EventKeyType::AnyEvent], - }); - - let mut btcd_controller = BitcoinCoreController::new(naka_conf.clone()); - btcd_controller - .start_bitcoind() - .expect("Failed starting bitcoind"); - let mut btc_regtest_controller = BitcoinRegtestController::new(naka_conf.clone(), None); - btc_regtest_controller.bootstrap_chain_to_pks( - 201, - &[ - Secp256k1PublicKey::from_hex( - naka_conf - .burnchain - .local_mining_public_key - .as_ref() - .unwrap(), - ) - .unwrap(), - Secp256k1PublicKey::from_hex( - conf_node_2 - .burnchain - .local_mining_public_key - .as_ref() - .unwrap(), - ) - .unwrap(), - ], - ); - - let mut run_loop = boot_nakamoto::BootRunLoop::new(naka_conf.clone()).unwrap(); - let mut run_loop_2 = boot_nakamoto::BootRunLoop::new(conf_node_2.clone()).unwrap(); - let run_loop_stopper = run_loop.get_termination_switch(); - let Counters { - blocks_processed, - naka_submitted_commits: commits_submitted, - naka_proposed_blocks: proposals_submitted, - .. - } = run_loop.counters(); - - let run_loop_2_stopper = run_loop.get_termination_switch(); - let Counters { - naka_proposed_blocks: proposals_submitted_2, - .. - } = run_loop_2.counters(); - - let coord_channel = run_loop.coordinator_channels(); - let coord_channel_2 = run_loop_2.coordinator_channels(); - - let _run_loop_2_thread = thread::Builder::new() - .name("run_loop_2".into()) - .spawn(move || run_loop_2.start(None, 0)) - .unwrap(); - - let run_loop_thread = thread::Builder::new() - .name("run_loop".into()) - .spawn(move || run_loop.start(None, 0)) - .unwrap(); - wait_for_runloop(&blocks_processed); - - let mut signers = TestSigners::new(vec![sender_signer_sk.clone()]); - boot_to_epoch_3( - &naka_conf, - &blocks_processed, - &[stacker_sk], - &[sender_signer_sk], - &mut Some(&mut signers), - &mut btc_regtest_controller, - ); - - info!("Bootstrapped to Epoch-3.0 boundary, starting nakamoto miner"); - - let burnchain = naka_conf.get_burnchain(); - let sortdb = burnchain.open_sortition_db(true).unwrap(); - let (chainstate, _) = StacksChainState::open( - naka_conf.is_mainnet(), - naka_conf.burnchain.chain_id, - &naka_conf.get_chainstate_path_str(), - None, - ) - .unwrap(); - - let block_height_pre_3_0 = - NakamotoChainState::get_canonical_block_header(chainstate.db(), &sortdb) - .unwrap() - .unwrap() - .stacks_block_height; - - info!("Nakamoto miner started..."); - blind_signer_multinode( - &signers, - &[&naka_conf, &conf_node_2], - vec![proposals_submitted, proposals_submitted_2], - ); - - info!("Neighbors 1"; "neighbors" => ?get_neighbors(&naka_conf)); - info!("Neighbors 2"; "neighbors" => ?get_neighbors(&conf_node_2)); - - // Wait one block to confirm the VRF register, wait until a block commit is submitted - wait_for_first_naka_block_commit(60, &commits_submitted); - - // TODO: - // in 1st tenure, miner_A mines the blocks normally and signers are following miner_A - // in 2nd tenure, - // miner_B mines the blocks, - // the signers are following miner_A - // miner_B tries to mine 1 more block, the signers will not sign it -> it will not be added - // miner_A should continue to mine the blocks as he has the right view of the chain - // in 3rd tenure, miner_ - - // Mine `tenure_count` nakamoto tenures - let mut stopped_miner_a = false; - - for tenure_ix in 0..tenure_count { - info!("Mining tenure {}", tenure_ix); - let commits_before = commits_submitted.load(Ordering::SeqCst); - next_block_and_process_new_stacks_block(&mut btc_regtest_controller, 60, &coord_channel) - .unwrap(); - - // Get the latest block from chainstate - let burnchain = conf_node_2.get_burnchain(); - let burnchain_header_hash = burnchain - .get_highest_burnchain_block() - .unwrap() - .unwrap() - .block_hash; - let sortdb = burnchain.open_sortition_db(true).unwrap(); - let (chainstate, _) = StacksChainState::open( - conf_node_2.is_mainnet(), - conf_node_2.burnchain.chain_id, - &conf_node_2.get_chainstate_path_str(), - None, - ) - .unwrap(); - - let miner_a_pubkey = naka_conf - .burnchain - .local_mining_public_key - .as_ref() - .map(|pubkey_hex| Secp256k1PublicKey::from_hex(pubkey_hex)) - .expect("Failed to parse miner A's public key") - .expect("Invalid A's public key format"); - let miner_b_pubkey = conf_node_2 - .burnchain - .local_mining_public_key - .as_ref() - .map(|pubkey_hex| Secp256k1PublicKey::from_hex(pubkey_hex)) - .expect("Failed to parse miner B's public key") - .expect("Invalid B's public key format"); - - // Create Bitcoin addresses from public keys - let miner_a_address = BitcoinAddress::from_bytes_legacy( - BitcoinNetworkType::Regtest, - LegacyBitcoinAddressType::PublicKeyHash, - &Hash160::from_data(&miner_a_pubkey.to_bytes()).0, - ) - .expect("Failed to create Bitcoin address for miner A"); - let miner_b_address = BitcoinAddress::from_bytes_legacy( - BitcoinNetworkType::Regtest, - LegacyBitcoinAddressType::PublicKeyHash, - &Hash160::from_data(&miner_b_pubkey.to_bytes()).0, - ) - .expect("Failed to create Bitcoin address for miner B"); - - info!("Miner A address: {}", miner_a_address); - info!("Miner B address: {}", miner_b_address); - - let block_snapshot: stacks::chainstate::burn::BlockSnapshot = - SortitionDB::get_all_snapshots_for_burn_block(sortdb.conn(), &burnchain_header_hash) - .unwrap()[0] - .clone(); - // let miner_hash = block_snapshot.miner_pk_hash.unwrap(); - let winning_block_txid: Txid = block_snapshot.winning_block_txid; - info!("Winner Miner txid: {}", winning_block_txid); - - let blocks_commits = get_block_commit_by_txid( - sortdb.conn(), - &block_snapshot.sortition_id, - &winning_block_txid, - ) - .unwrap() - .unwrap(); - let winner_address = blocks_commits.apparent_sender; - let winner_bitcoin_address = BitcoinAddress::Legacy( - LegacyBitcoinAddress::from_string(winner_address.to_string().as_str()).unwrap(), - ); - - info!("Winner Miner Address: {}", winner_bitcoin_address); - - // let a_utxos = btc_regtest_controller.get_all_utxos(&node_1_pk); - // let b_utxos = btc_regtest_controller.get_all_utxos(&); - - // info!("Miner A UTXOs: {}", a_utxos.len()); - // info!("Miner B UTXOs: {}", b_utxos.len()); - - // sortition db - // block_commits -> block_height to be what i am looking for - // 2 apparent_sender -> bitcoind address of miner + txid - // table snapshots: - // block_height -> what we want - // winning_block_txid -> check with the previous who won the block - - // if tenure_ix == 1 { // Second tenure (0-indexed) - // assert_eq!( - // anchor_miner, miner_b_address, - // "Expected miner B to mine the second tenure's anchor block" - // ); - // } - - if !stopped_miner_a && winner_bitcoin_address.to_string() == miner_b_address.to_string() { - info!("Miner B is the winner for tenure {}. Stopping miner A", tenure_ix); - coord_channel - .lock() - .expect("Mutex poisoned") - .stop_chains_coordinator(); - run_loop_stopper.store(false, Ordering::SeqCst); - stopped_miner_a = true; - }; - - let mut last_tip = BlockHeaderHash([0x00; 32]); - let mut last_tip_height = 0; - - // mine the interim blocks - for interim_block_ix in 0..inter_blocks_per_tenure { - info!("a"); - let blocks_processed_before = coord_channel_2 - .lock() - .expect("Mutex poisoned") - .get_stacks_blocks_processed(); - info!("b"); - // submit a tx so that the miner will mine an extra block - let sender_nonce = tenure_ix * inter_blocks_per_tenure + interim_block_ix; - let transfer_tx = - make_stacks_transfer(&sender_sk, sender_nonce, send_fee, &recipient, send_amt); - info!("c"); - submit_tx(&http_tx_origin, &transfer_tx); - info!("d"); - - wait_for(20, || { - let blocks_processed = coord_channel_2 - .lock() - .expect("Mutex poisoned") - .get_stacks_blocks_processed(); - Ok(blocks_processed > blocks_processed_before) - }) - .unwrap(); - info!("e"); - - let info = get_chain_info_result(&conf_node_2).unwrap(); - assert_ne!(info.stacks_tip, last_tip); - assert_ne!(info.stacks_tip_height, last_tip_height); - info!("f"); - - last_tip = info.stacks_tip; - last_tip_height = info.stacks_tip_height; - } - - wait_for(20, || { - Ok(commits_submitted.load(Ordering::SeqCst) > commits_before) - }) - .unwrap(); - } - - // load the chain tip, and assert that it is a nakamoto block and at least 30 blocks have advanced in epoch 3 - let tip = NakamotoChainState::get_canonical_block_header(chainstate.db(), &sortdb) - .unwrap() - .unwrap(); - info!( - "Latest tip"; - "height" => tip.stacks_block_height, - "is_nakamoto" => tip.anchored_header.as_stacks_nakamoto().is_some(), - ); - - if stopped_miner_a { - info!("Miner A stopped, skipping peer height comparison"); - } else { - let peer_1_height = get_chain_info(&naka_conf).stacks_tip_height; - let peer_2_height = get_chain_info(&conf_node_2).stacks_tip_height; - info!("Peer height information"; "peer_1" => peer_1_height, "peer_2" => peer_2_height); - assert_eq!(peer_1_height, peer_2_height); - } - - assert!(tip.anchored_header.as_stacks_nakamoto().is_some()); - assert_eq!( - tip.stacks_block_height, - block_height_pre_3_0 + ((inter_blocks_per_tenure + 1) * tenure_count), - "Should have mined (1 + interim_blocks_per_tenure) * tenure_count nakamoto blocks" - ); - - coord_channel_2 - .lock() - .expect("Mutex poisoned") - .stop_chains_coordinator(); - run_loop_2_stopper.store(false, Ordering::SeqCst); - - run_loop_thread.join().unwrap(); -} - #[test] #[ignore] fn correct_burn_outs() { diff --git a/testnet/stacks-node/src/tests/signer/v0.rs b/testnet/stacks-node/src/tests/signer/v0.rs index 667d91730a..8c136008cd 100644 --- a/testnet/stacks-node/src/tests/signer/v0.rs +++ b/testnet/stacks-node/src/tests/signer/v0.rs @@ -27,8 +27,10 @@ use libsigner::v0::messages::{ }; use libsigner::{BlockProposal, SignerSession, StackerDBSession}; use stacks::address::AddressHashMode; +use stacks::burnchains::bitcoin::address::{BitcoinAddress, LegacyBitcoinAddress, LegacyBitcoinAddressType}; +use stacks::burnchains::bitcoin::BitcoinNetworkType; use stacks::burnchains::Txid; -use stacks::chainstate::burn::db::sortdb::SortitionDB; +use stacks::chainstate::burn::db::sortdb::{self, get_block_commit_by_txid, SortitionDB}; use stacks::chainstate::burn::operations::LeaderBlockCommitOp; use stacks::chainstate::nakamoto::{NakamotoBlock, NakamotoBlockHeader, NakamotoChainState}; use stacks::chainstate::stacks::address::PoxAddress; @@ -42,7 +44,7 @@ use stacks::net::api::postblock_proposal::{ValidateRejectCode, TEST_VALIDATE_STA use stacks::net::relay::fault_injection::set_ignore_block; use stacks::types::chainstate::{StacksAddress, StacksBlockId, StacksPrivateKey, StacksPublicKey}; use stacks::types::PublicKey; -use stacks::util::hash::MerkleHashFunc; +use stacks::util::hash::{Hash160, MerkleHashFunc}; use stacks::util::secp256k1::{Secp256k1PrivateKey, Secp256k1PublicKey}; use stacks::util_lib::boot::boot_code_id; use stacks::util_lib::signed_structured_data::pox4::{ @@ -1649,6 +1651,321 @@ fn multiple_miners() { signer_test.shutdown(); } +#[test] +#[ignore] +fn new_tenure_stop_fast_blocks() { + if env::var("BITCOIND_TEST") != Ok("1".into()) { + return; + } + + let num_signers = 5; + let sender_sk = Secp256k1PrivateKey::new(); + let sender_addr = tests::to_addr(&sender_sk); + let send_amt = 100; + let send_fee = 180; + + let btc_miner_1_seed = vec![1, 1, 1, 1]; + let btc_miner_2_seed = vec![2, 2, 2, 2]; + let btc_miner_1_pk = Keychain::default(btc_miner_1_seed.clone()).get_pub_key(); + let btc_miner_2_pk = Keychain::default(btc_miner_2_seed.clone()).get_pub_key(); + + let node_1_rpc = 51024; + let node_1_p2p = 51023; + let node_2_rpc = 51026; + let node_2_p2p = 51025; + + let localhost = "127.0.0.1"; + let node_1_rpc_bind = format!("{localhost}:{node_1_rpc}"); + let node_2_rpc_bind = format!("{localhost}:{node_2_rpc}"); + let mut node_2_listeners = Vec::new(); + + // partition the signer set so that ~half are listening and using node 1 for RPC and events, + // and the rest are using node 2 + + let mut signer_test: SignerTest = SignerTest::new_with_config_modifications( + num_signers, + vec![(sender_addr.clone(), send_amt + send_fee)], + |signer_config| { + let node_host = if signer_config.endpoint.port() % 5 == 0 { + &node_1_rpc_bind + } else { + &node_2_rpc_bind + }; + signer_config.node_host = node_host.to_string(); + }, + |config| { + config.node.rpc_bind = format!("{localhost}:{node_1_rpc}"); + config.node.p2p_bind = format!("{localhost}:{node_1_p2p}"); + config.node.data_url = format!("http://{localhost}:{node_1_rpc}"); + config.node.p2p_address = format!("{localhost}:{node_1_p2p}"); + config.miner.wait_on_interim_blocks = Duration::from_secs(5); + config.node.pox_sync_sample_secs = 5; + + config.node.seed = btc_miner_1_seed.clone(); + config.node.local_peer_seed = btc_miner_1_seed.clone(); + config.burnchain.local_mining_public_key = Some(btc_miner_1_pk.to_hex()); + config.miner.mining_key = Some(Secp256k1PrivateKey::from_seed(&[1])); + + config.events_observers.retain(|listener| { + let Ok(addr) = std::net::SocketAddr::from_str(&listener.endpoint) else { + warn!( + "Cannot parse {} to a socket, assuming it isn't a signer-listener binding", + listener.endpoint + ); + return true; + }; + if addr.port() % 2 == 0 || addr.port() == test_observer::EVENT_OBSERVER_PORT { + return true; + } + node_2_listeners.push(listener.clone()); + false + }) + }, + Some(vec![btc_miner_1_pk.clone(), btc_miner_2_pk.clone()]), + None, + ); + let conf = signer_test.running_nodes.conf.clone(); + let mut conf_node_2 = conf.clone(); + conf_node_2.node.rpc_bind = format!("{localhost}:{node_2_rpc}"); + conf_node_2.node.p2p_bind = format!("{localhost}:{node_2_p2p}"); + conf_node_2.node.data_url = format!("http://{localhost}:{node_2_rpc}"); + conf_node_2.node.p2p_address = format!("{localhost}:{node_2_p2p}"); + conf_node_2.node.seed = btc_miner_2_seed.clone(); + conf_node_2.burnchain.local_mining_public_key = Some(btc_miner_2_pk.to_hex()); + conf_node_2.node.local_peer_seed = btc_miner_2_seed.clone(); + conf_node_2.miner.mining_key = Some(Secp256k1PrivateKey::from_seed(&[2])); + conf_node_2.node.miner = true; + conf_node_2.events_observers.clear(); + conf_node_2.events_observers.extend(node_2_listeners); + assert!(!conf_node_2.events_observers.is_empty()); + + let node_1_sk = Secp256k1PrivateKey::from_seed(&conf.node.local_peer_seed); + let node_1_pk = StacksPublicKey::from_private(&node_1_sk); + + conf_node_2.node.working_dir = format!("{}-{}", conf_node_2.node.working_dir, "1"); + + conf_node_2.node.set_bootstrap_nodes( + format!("{}@{}", &node_1_pk.to_hex(), conf.node.p2p_bind), + conf.burnchain.chain_id, + conf.burnchain.peer_version, + ); + + let mut run_loop_2 = boot_nakamoto::BootRunLoop::new(conf_node_2.clone()).unwrap(); + let run_loop_stopper_2 = run_loop_2.get_termination_switch(); + let rl2_coord_channels = run_loop_2.coordinator_channels(); + let Counters { + naka_submitted_commits: rl2_commits, + .. + } = run_loop_2.counters(); + let run_loop_2_thread = thread::Builder::new() + .name("run_loop_2".into()) + .spawn(move || run_loop_2.start(None, 0)) + .unwrap(); + + signer_test.boot_to_epoch_3(); + + wait_for(120, || { + let Some(node_1_info) = get_chain_info_opt(&conf) else { + return Ok(false); + }; + let Some(node_2_info) = get_chain_info_opt(&conf_node_2) else { + return Ok(false); + }; + Ok(node_1_info.stacks_tip_height == node_2_info.stacks_tip_height) + }) + .expect("Timed out waiting for boostrapped node to catch up to the miner"); + + let pre_nakamoto_peer_1_height = get_chain_info(&conf).stacks_tip_height; + + info!("------------------------- Reached Epoch 3.0 -------------------------"); + + let max_nakamoto_tenures = 5; + + // due to the random nature of mining sortitions, the way this test is structured + // is that we keep track of how many tenures each miner produced, and once enough sortitions + // have been produced such that each miner has produced 3 tenures, we stop and check the + // results at the end + + // mine tenures until we have miner_b winner of one tenure + // then stop the miner_a + // then try to mine with miner_b and not produce any blocks + + + let rl1_coord_channels = signer_test.running_nodes.coord_channel.clone(); + let rl1_commits = signer_test.running_nodes.commits_submitted.clone(); + + let miner_1_pk = StacksPublicKey::from_private(conf.miner.mining_key.as_ref().unwrap()); + let miner_2_pk = StacksPublicKey::from_private(conf_node_2.miner.mining_key.as_ref().unwrap()); + let mut btc_blocks_mined = 1; + let mut miner_1_tenures = 0; + let mut miner_2_tenures = 0; + let mut stopped_miner_b = false; + let mut miner_a_tries = 0; + + let miner_1_pubkey = conf + .burnchain + .local_mining_public_key + .as_ref() + .map(|pubkey_hex| Secp256k1PublicKey::from_hex(pubkey_hex)) + .expect("Failed to parse miner A's public key") + .expect("Invalid A's public key format"); + let miner_2_pubkey = conf_node_2 + .burnchain + .local_mining_public_key + .as_ref() + .map(|pubkey_hex| Secp256k1PublicKey::from_hex(pubkey_hex)) + .expect("Failed to parse miner B's public key") + .expect("Invalid B's public key format"); + + // Create Bitcoin addresses from public keys + let miner_a_address = BitcoinAddress::from_bytes_legacy( + BitcoinNetworkType::Regtest, + LegacyBitcoinAddressType::PublicKeyHash, + &Hash160::from_data(&miner_1_pubkey.to_bytes()).0, + ) + .expect("Failed to create Bitcoin address for miner A"); + + let miner_b_address = BitcoinAddress::from_bytes_legacy( + BitcoinNetworkType::Regtest, + LegacyBitcoinAddressType::PublicKeyHash, + &Hash160::from_data(&miner_2_pubkey.to_bytes()).0, + ) + .expect("Failed to create Bitcoin address for miner B"); + + info!("Miner A address: {}", miner_a_address); + info!("Miner B address: {}", miner_b_address); + + while !stopped_miner_b || miner_a_tries < 3 { + assert!( + max_nakamoto_tenures >= btc_blocks_mined, + "Produced {btc_blocks_mined} sortitions, but didn't cover the test scenarios, aborting" + ); + + let info_1 = get_chain_info(&conf); + let info_2 = get_chain_info(&conf_node_2); + + info!( + "Issue next block-build request\ninfo 1: {:?}\ninfo 2: {:?}\n", + &info_1, &info_2 + ); + + signer_test.mine_block_wait_on_processing( + &[&rl1_coord_channels, &rl2_coord_channels], + &[&rl1_commits, &rl2_commits], + Duration::from_secs(30), + ); + + btc_blocks_mined += 1; + if stopped_miner_b { + miner_a_tries += 1; + } + + // Get the latest block from chainstate + let burnchain = conf_node_2.get_burnchain(); + let burnchain_header_hash = burnchain + .get_highest_burnchain_block() + .unwrap() + .unwrap() + .block_hash; + let sortdb = burnchain.open_sortition_db(true).unwrap(); + let (chainstate, _) = StacksChainState::open( + conf_node_2.is_mainnet(), + conf_node_2.burnchain.chain_id, + &conf_node_2.get_chainstate_path_str(), + None, + ) + .unwrap(); + + let block_snapshot: stacks::chainstate::burn::BlockSnapshot = SortitionDB::get_all_snapshots_for_burn_block(sortdb.conn(), &burnchain_header_hash) + .unwrap()[0] + .clone(); + let winning_block_txid: Txid = block_snapshot.winning_block_txid; + let blocks_commits = get_block_commit_by_txid( + sortdb.conn(), + &block_snapshot.sortition_id, + &winning_block_txid, + ) + .unwrap() + .unwrap(); + + let winner_address = blocks_commits.apparent_sender; + let winner_bitcoin_address = BitcoinAddress::Legacy( + LegacyBitcoinAddress::from_b58(winner_address.to_string().as_str()).unwrap(), + ); + + info!("Winner Miner Address: {}", winner_bitcoin_address); + + if !stopped_miner_b && winner_bitcoin_address.to_string() == miner_a_address.to_string() { + info!("Miner A is the winner for tenure {}. Stopping miner B", btc_blocks_mined); + rl2_coord_channels + .lock() + .expect("Mutex poisoned") + .stop_chains_coordinator(); + run_loop_stopper_2.store(false, Ordering::SeqCst); + stopped_miner_b = true; + }; + info!("Miner A tries: {}", miner_a_tries); + let blocks = get_nakamoto_headers(&conf); + // for this test, there should be one block per tenure + let consensus_hash_set: HashSet<_> = blocks + .iter() + .map(|header| header.consensus_hash.clone()) + .collect(); + assert_eq!( + consensus_hash_set.len(), + blocks.len(), + "In this test, there should only be one block per tenure" + ); + miner_1_tenures = blocks + .iter() + .filter(|header| { + let header = header.anchored_header.as_stacks_nakamoto().unwrap(); + miner_1_pk + .verify( + header.miner_signature_hash().as_bytes(), + &header.miner_signature, + ) + .unwrap() + }) + .count(); + miner_2_tenures = blocks + .iter() + .filter(|header| { + let header = header.anchored_header.as_stacks_nakamoto().unwrap(); + miner_2_pk + .verify( + header.miner_signature_hash().as_bytes(), + &header.miner_signature, + ) + .unwrap() + }) + .count(); + } + + info!( + "New chain info: {:?}", + get_chain_info(&signer_test.running_nodes.conf) + ); + + info!("New chain info: {:?}", get_chain_info(&conf_node_2)); + + let peer_1_height = get_chain_info(&conf).stacks_tip_height; + let peer_2_height = get_chain_info(&conf_node_2).stacks_tip_height; + info!("Peer height information"; "peer_1" => peer_1_height, "peer_2" => peer_2_height, "pre_naka_height" => pre_nakamoto_peer_1_height); + assert_eq!(peer_1_height, peer_2_height); + assert_eq!( + peer_1_height, + pre_nakamoto_peer_1_height + btc_blocks_mined - 1 + ); + assert_eq!( + btc_blocks_mined, + u64::try_from(miner_1_tenures + miner_2_tenures).unwrap() + ); + + run_loop_2_thread.join().unwrap(); + signer_test.shutdown(); +} + /// Read processed nakamoto block IDs from the test observer, and use `config` to open /// a chainstate DB and returns their corresponding StacksHeaderInfos fn get_nakamoto_headers(config: &Config) -> Vec { From 14086e57e59b429b7868e83e15873a98639b0f35 Mon Sep 17 00:00:00 2001 From: ASuciuX Date: Fri, 4 Oct 2024 01:54:52 +0300 Subject: [PATCH 7/9] signers old view of network --- testnet/stacks-node/src/tests/signer/v0.rs | 60 +++++++++++++--------- 1 file changed, 36 insertions(+), 24 deletions(-) diff --git a/testnet/stacks-node/src/tests/signer/v0.rs b/testnet/stacks-node/src/tests/signer/v0.rs index 8c136008cd..379b928494 100644 --- a/testnet/stacks-node/src/tests/signer/v0.rs +++ b/testnet/stacks-node/src/tests/signer/v0.rs @@ -1842,18 +1842,28 @@ fn new_tenure_stop_fast_blocks() { ); let info_1 = get_chain_info(&conf); - let info_2 = get_chain_info(&conf_node_2); + if !stopped_miner_b { + let info_2 = get_chain_info(&conf_node_2); + info!( + "Issue next block-build request\ninfo 1: {:?}\ninfo 2: {:?}\n", + &info_1, &info_2 + ); + }; info!( - "Issue next block-build request\ninfo 1: {:?}\ninfo 2: {:?}\n", - &info_1, &info_2 + "Issue next block-build request\ninfo 1: {:?}\n", + &info_1 ); - signer_test.mine_block_wait_on_processing( - &[&rl1_coord_channels, &rl2_coord_channels], - &[&rl1_commits, &rl2_commits], - Duration::from_secs(30), - ); + if !stopped_miner_b { + signer_test.mine_block_wait_on_processing( + &[&rl1_coord_channels, &rl2_coord_channels], + &[&rl1_commits, &rl2_commits], + Duration::from_secs(30), + ); + } else { + signer_test.mine_nakamoto_block(Duration::from_secs(30)); + } btc_blocks_mined += 1; if stopped_miner_b { @@ -1861,7 +1871,7 @@ fn new_tenure_stop_fast_blocks() { } // Get the latest block from chainstate - let burnchain = conf_node_2.get_burnchain(); + let burnchain = conf.get_burnchain(); let burnchain_header_hash = burnchain .get_highest_burnchain_block() .unwrap() @@ -1869,9 +1879,9 @@ fn new_tenure_stop_fast_blocks() { .block_hash; let sortdb = burnchain.open_sortition_db(true).unwrap(); let (chainstate, _) = StacksChainState::open( - conf_node_2.is_mainnet(), - conf_node_2.burnchain.chain_id, - &conf_node_2.get_chainstate_path_str(), + conf.is_mainnet(), + conf.burnchain.chain_id, + &conf.get_chainstate_path_str(), None, ) .unwrap(); @@ -1928,18 +1938,20 @@ fn new_tenure_stop_fast_blocks() { .unwrap() }) .count(); - miner_2_tenures = blocks - .iter() - .filter(|header| { - let header = header.anchored_header.as_stacks_nakamoto().unwrap(); - miner_2_pk - .verify( - header.miner_signature_hash().as_bytes(), - &header.miner_signature, - ) - .unwrap() - }) - .count(); + if !stopped_miner_b { + miner_2_tenures = blocks + .iter() + .filter(|header| { + let header = header.anchored_header.as_stacks_nakamoto().unwrap(); + miner_2_pk + .verify( + header.miner_signature_hash().as_bytes(), + &header.miner_signature, + ) + .unwrap() + }) + .count(); + } } info!( From 7828599cf96cc89b1d5fca10ef9ba142fbaeddcc Mon Sep 17 00:00:00 2001 From: ASuciuX Date: Fri, 4 Oct 2024 17:26:09 +0300 Subject: [PATCH 8/9] add tenure and block consensus hash validations --- .../src/tests/nakamoto_integrations.rs | 31 +++++++++++++------ testnet/stacks-node/src/tests/signer/v0.rs | 22 +++++++------ 2 files changed, 34 insertions(+), 19 deletions(-) diff --git a/testnet/stacks-node/src/tests/nakamoto_integrations.rs b/testnet/stacks-node/src/tests/nakamoto_integrations.rs index c84eb0beea..4da9dda5c3 100644 --- a/testnet/stacks-node/src/tests/nakamoto_integrations.rs +++ b/testnet/stacks-node/src/tests/nakamoto_integrations.rs @@ -2217,14 +2217,13 @@ fn multiple_miners() { for tenure_ix in 0..tenure_count { info!("Mining tenure {}", tenure_ix); let commits_before = commits_submitted.load(Ordering::SeqCst); + let info_old_tenure = get_chain_info_result(&naka_conf).unwrap(); next_block_and_process_new_stacks_block(&mut btc_regtest_controller, 60, &coord_channel) .unwrap(); - let mut last_tip = BlockHeaderHash([0x00; 32]); - let mut last_tip_height = 0; - // mine the interim blocks for interim_block_ix in 0..inter_blocks_per_tenure { + let info_old_stacks = get_chain_info_result(&naka_conf).unwrap(); let blocks_processed_before = coord_channel .lock() .expect("Mutex poisoned") @@ -2244,12 +2243,26 @@ fn multiple_miners() { }) .unwrap(); - let info = get_chain_info_result(&naka_conf).unwrap(); - assert_ne!(info.stacks_tip, last_tip); - assert_ne!(info.stacks_tip_height, last_tip_height); - - last_tip = info.stacks_tip; - last_tip_height = info.stacks_tip_height; + let info_new_stacks = get_chain_info_result(&naka_conf).unwrap(); + assert_ne!(info_new_stacks.stacks_tip, info_old_stacks.stacks_tip); + assert_eq!( + info_new_stacks.stacks_tip_consensus_hash, + info_old_stacks.stacks_tip_consensus_hash + ); + assert_eq!( + info_new_stacks.stacks_tip_height, + info_old_stacks.stacks_tip_height + 1, + "Stacks block height should increment by 1" + ); + assert_ne!( + info_old_tenure.stacks_tip_consensus_hash, + info_old_stacks.stacks_tip_consensus_hash + ); + assert_eq!( + info_new_stacks.burn_block_height, + info_old_tenure.burn_block_height + 1, + "Tenure block height should increment by 1" + ); } wait_for(20, || { diff --git a/testnet/stacks-node/src/tests/signer/v0.rs b/testnet/stacks-node/src/tests/signer/v0.rs index 379b928494..d19fb2d7cd 100644 --- a/testnet/stacks-node/src/tests/signer/v0.rs +++ b/testnet/stacks-node/src/tests/signer/v0.rs @@ -27,7 +27,9 @@ use libsigner::v0::messages::{ }; use libsigner::{BlockProposal, SignerSession, StackerDBSession}; use stacks::address::AddressHashMode; -use stacks::burnchains::bitcoin::address::{BitcoinAddress, LegacyBitcoinAddress, LegacyBitcoinAddressType}; +use stacks::burnchains::bitcoin::address::{ + BitcoinAddress, LegacyBitcoinAddress, LegacyBitcoinAddressType, +}; use stacks::burnchains::bitcoin::BitcoinNetworkType; use stacks::burnchains::Txid; use stacks::chainstate::burn::db::sortdb::{self, get_block_commit_by_txid, SortitionDB}; @@ -1790,7 +1792,6 @@ fn new_tenure_stop_fast_blocks() { // then stop the miner_a // then try to mine with miner_b and not produce any blocks - let rl1_coord_channels = signer_test.running_nodes.coord_channel.clone(); let rl1_commits = signer_test.running_nodes.commits_submitted.clone(); @@ -1850,10 +1851,7 @@ fn new_tenure_stop_fast_blocks() { ); }; - info!( - "Issue next block-build request\ninfo 1: {:?}\n", - &info_1 - ); + info!("Issue next block-build request\ninfo 1: {:?}\n", &info_1); if !stopped_miner_b { signer_test.mine_block_wait_on_processing( @@ -1886,9 +1884,10 @@ fn new_tenure_stop_fast_blocks() { ) .unwrap(); - let block_snapshot: stacks::chainstate::burn::BlockSnapshot = SortitionDB::get_all_snapshots_for_burn_block(sortdb.conn(), &burnchain_header_hash) - .unwrap()[0] - .clone(); + let block_snapshot: stacks::chainstate::burn::BlockSnapshot = + SortitionDB::get_all_snapshots_for_burn_block(sortdb.conn(), &burnchain_header_hash) + .unwrap()[0] + .clone(); let winning_block_txid: Txid = block_snapshot.winning_block_txid; let blocks_commits = get_block_commit_by_txid( sortdb.conn(), @@ -1906,7 +1905,10 @@ fn new_tenure_stop_fast_blocks() { info!("Winner Miner Address: {}", winner_bitcoin_address); if !stopped_miner_b && winner_bitcoin_address.to_string() == miner_a_address.to_string() { - info!("Miner A is the winner for tenure {}. Stopping miner B", btc_blocks_mined); + info!( + "Miner A is the winner for tenure {}. Stopping miner B", + btc_blocks_mined + ); rl2_coord_channels .lock() .expect("Mutex poisoned") From c4676c803ad8d851c48d5695cd781bf8ab6d86f8 Mon Sep 17 00:00:00 2001 From: ASuciuX Date: Sun, 6 Oct 2024 16:20:29 +0300 Subject: [PATCH 9/9] add initial test case 2 and consensus hash comparison on blocks --- .../src/tests/nakamoto_integrations.rs | 70 ++- .../stacks-node/src/tests/relevant-output.txt | 59 --- testnet/stacks-node/src/tests/signer/v0.rs | 450 ++++++++++++++---- 3 files changed, 436 insertions(+), 143 deletions(-) delete mode 100644 testnet/stacks-node/src/tests/relevant-output.txt diff --git a/testnet/stacks-node/src/tests/nakamoto_integrations.rs b/testnet/stacks-node/src/tests/nakamoto_integrations.rs index 4da9dda5c3..5888864ac8 100644 --- a/testnet/stacks-node/src/tests/nakamoto_integrations.rs +++ b/testnet/stacks-node/src/tests/nakamoto_integrations.rs @@ -23,7 +23,7 @@ use std::{env, thread}; use clarity::vm::ast::ASTRules; use clarity::vm::costs::ExecutionCost; -use clarity::vm::types::{PrincipalData, QualifiedContractIdentifier}; +use clarity::vm::types::{PrincipalData, QualifiedContractIdentifier, StandardPrincipalData}; use clarity::vm::{ClarityName, ClarityVersion, Value}; use http_types::headers::AUTHORIZATION; use lazy_static::lazy_static; @@ -2037,6 +2037,7 @@ fn mine_multiple_per_tenure_integration() { } #[test] +#[ignore] /// This test spins up two nakamoto nodes, both configured to mine. /// It starts in Epoch 2.0, mines with `neon_node` to Epoch 3.0, and then switches /// to Nakamoto operation (activating pox-4 by submitting a stack-stx tx). The BootLoop @@ -2271,6 +2272,61 @@ fn multiple_miners() { .unwrap(); } + // check most recently process blocks + // case to check: + // old stacks_tip_consensus_hash != new stacks_tip_consensus_hash and second tx is in the new burn block + let info_node_1_old_block_consensus_hash = get_chain_info(&naka_conf).stacks_tip_consensus_hash; + let info_node_2_old_block_consensus_hash = + get_chain_info(&conf_node_2).stacks_tip_consensus_hash; + + // TODO: submit burn block without waiting for it to be mined + // btc_regtest_controller.build_next_block(1); - flashblock + next_block_and_process_new_stacks_block(&mut btc_regtest_controller, 60, &coord_channel) + .unwrap(); + + // submit tx 1 + let miner_1_stacks_address = StandardPrincipalData::from(&node_1_sk); + let http_origin = &naka_conf.node.data_url; + let recipient = PrincipalData::from(StacksAddress::burn_address(false)); + let miner_1_account = get_account(http_origin, &miner_1_stacks_address); + let miner_1_nonce = miner_1_account.nonce; + let transfer_tx = + make_stacks_transfer(&sender_sk, miner_1_nonce, send_fee, &recipient, send_amt); + submit_tx(&http_origin, &transfer_tx); + thread::sleep(Duration::from_secs(15)); + + // submit tx 2 + let transfer_tx = make_stacks_transfer( + &sender_sk, + miner_1_nonce + 1, + send_fee, + &recipient, + send_amt, + ); + submit_tx(&http_origin, &transfer_tx); + + // TODO: wait so the burn block is mined + thread::sleep(Duration::from_secs(15)); + + let info_node_1_second_tx_consensus_hash = get_chain_info(&naka_conf).stacks_tip_consensus_hash; + let info_node_2_second_tx_consensus_hash = + get_chain_info(&conf_node_2).stacks_tip_consensus_hash; + + let info_node_1_new_block_consensus_hash = get_chain_info(&naka_conf).stacks_tip_consensus_hash; + let info_node_2_new_block_consensus_hash = + get_chain_info(&conf_node_2).stacks_tip_consensus_hash; + + assert_eq!( + info_node_1_second_tx_consensus_hash, + info_node_1_new_block_consensus_hash + ); + assert!(info_node_1_old_block_consensus_hash != info_node_1_new_block_consensus_hash); + assert_eq!( + info_node_2_second_tx_consensus_hash, + info_node_2_new_block_consensus_hash + ); + assert!(info_node_2_old_block_consensus_hash != info_node_2_new_block_consensus_hash); + // load the chain tip, and assert that it is a nakamoto block and at least 30 blocks have advanced in epoch 3 let tip = NakamotoChainState::get_canonical_block_header(chainstate.db(), &sortdb) .unwrap() @@ -2285,12 +2341,18 @@ fn multiple_miners() { let peer_2_height = get_chain_info(&conf_node_2).stacks_tip_height; info!("Peer height information"; "peer_1" => peer_1_height, "peer_2" => peer_2_height); assert_eq!(peer_1_height, peer_2_height); - + info!( + "Stacks block height {} vs previous formula {}", + tip.stacks_block_height, + block_height_pre_3_0 + ((inter_blocks_per_tenure + 1) * tenure_count) + ); assert!(tip.anchored_header.as_stacks_nakamoto().is_some()); + + // TODO: shouldn't this be + 2 instead of +1 as 2 stx transactions are submitted with time difference, making 2 different stacks blocks for them assert_eq!( tip.stacks_block_height, - block_height_pre_3_0 + ((inter_blocks_per_tenure + 1) * tenure_count), - "Should have mined (1 + interim_blocks_per_tenure) * tenure_count nakamoto blocks" + block_height_pre_3_0 + ((inter_blocks_per_tenure + 1) * tenure_count) + 1, + "Should have mined (1 + interim_blocks_per_tenure) * tenure_count + 1 nakamoto blocks" ); coord_channel diff --git a/testnet/stacks-node/src/tests/relevant-output.txt b/testnet/stacks-node/src/tests/relevant-output.txt deleted file mode 100644 index 2eabb99c0e..0000000000 --- a/testnet/stacks-node/src/tests/relevant-output.txt +++ /dev/null @@ -1,59 +0,0 @@ -INFO [1727961693.679825] [testnet/stacks-node/src/tests/nakamoto_integrations.rs:2645] [tests::nakamoto_integrations::new_tenure_stop_fast_blocks] Miner A address: mtFzK54XtpktHj7fKonFExEPEGkUMsiXdy -INFO [1727961693.679831] [testnet/stacks-node/src/tests/nakamoto_integrations.rs:2646] [tests::nakamoto_integrations::new_tenure_stop_fast_blocks] Miner B address: mgPGTnrKZrLHHqCsQVtA5oy7RXeCey1s4w -INFO [1727961693.680003] [testnet/stacks-node/src/tests/nakamoto_integrations.rs:2654] [tests::nakamoto_integrations::new_tenure_stop_fast_blocks] Winner Miner txid: e8f59d578a0f291f72a2a5cbbf8632472f9e8ae94f537718ad8381fd4993761c -INFO [1727961693.680126] [testnet/stacks-node/src/tests/nakamoto_integrations.rs:2668] [tests::nakamoto_integrations::new_tenure_stop_fast_blocks] Winner Miner Address: mgPGTnrKZrLHHqCsQVtA5oy7RXeCey1s4w -INFO [1727961693.680135] [testnet/stacks-node/src/tests/nakamoto_integrations.rs:2691] [tests::nakamoto_integrations::new_tenure_stop_fast_blocks] Miner B is the winner for tenure 2. Stopping miner A -INFO [1727961693.680140] [testnet/stacks-node/src/tests/nakamoto_integrations.rs:2705] [tests::nakamoto_integrations::new_tenure_stop_fast_blocks] a -INFO [1727961693.680141] [testnet/stacks-node/src/tests/nakamoto_integrations.rs:2710] [tests::nakamoto_integrations::new_tenure_stop_fast_blocks] b -INFO [1727961693.680203] [testnet/stacks-node/src/tests/nakamoto_integrations.rs:2715] [tests::nakamoto_integrations::new_tenure_stop_fast_blocks] c -INFO [1727961693.683340] [stackslib/src/chainstate/burn/db/sortdb.rs:3982] [p2p-(127.0.0.1:10355,127.0.0.1:19350)] Fetching preprocessed reward set, tip_sortition_id: 46a5ad77c0b1635f5d9236f83ad9fad7430a37754e88b7eee0ad6cd239187897, reward_cycle_id: 11, prepare_phase_start_sortition_id: 46efc863a361be2670f0baffe06dae88b355a128846acaec5bb661c58d690a14 -INFO [1727961693.684798] [stackslib/src/chainstate/burn/db/sortdb.rs:3982] [p2p-(127.0.0.1:51025,127.0.0.1:51026)] Fetching preprocessed reward set, tip_sortition_id: 46a5ad77c0b1635f5d9236f83ad9fad7430a37754e88b7eee0ad6cd239187897, reward_cycle_id: 11, prepare_phase_start_sortition_id: 46efc863a361be2670f0baffe06dae88b355a128846acaec5bb661c58d690a14 -INFO [1727961693.686132] [stackslib/src/chainstate/burn/db/sortdb.rs:3982] [p2p-(127.0.0.1:10355,127.0.0.1:19350)] Fetching preprocessed reward set, tip_sortition_id: 333a4ca317a440b50c8525e8831ff1e693b0958e007df114bf3e068c680379e0, reward_cycle_id: 10, prepare_phase_start_sortition_id: 63a9fecc440bf3a57a25336b3db987af07b8c9d03c81483aaa147dde5cefca49 -INFO [1727961693.687858] [stackslib/src/chainstate/burn/db/sortdb.rs:3982] [p2p-(127.0.0.1:51025,127.0.0.1:51026)] Fetching preprocessed reward set, tip_sortition_id: 333a4ca317a440b50c8525e8831ff1e693b0958e007df114bf3e068c680379e0, reward_cycle_id: 10, prepare_phase_start_sortition_id: 63a9fecc440bf3a57a25336b3db987af07b8c9d03c81483aaa147dde5cefca49 -INFO [1727961693.688962] [stackslib/src/chainstate/burn/db/sortdb.rs:3982] [p2p-(127.0.0.1:10355,127.0.0.1:19350)] Fetching preprocessed reward set, tip_sortition_id: a6fd0c7207447a628c1fd79ea36969df7483622883a14bcf48b6b3fd1a2cbdfb, reward_cycle_id: 9, prepare_phase_start_sortition_id: e345956a63ef22c3dcc5957acead4c7d2f094728491735a8c886f3c61aafeeb5 -INFO [1727961693.690649] [stackslib/src/chainstate/burn/db/sortdb.rs:3982] [p2p-(127.0.0.1:51025,127.0.0.1:51026)] Fetching preprocessed reward set, tip_sortition_id: a6fd0c7207447a628c1fd79ea36969df7483622883a14bcf48b6b3fd1a2cbdfb, reward_cycle_id: 9, prepare_phase_start_sortition_id: e345956a63ef22c3dcc5957acead4c7d2f094728491735a8c886f3c61aafeeb5 -INFO [1727961693.729049] [testnet/stacks-node/src/nakamoto_node/peer.rs:140] [p2p-(127.0.0.1:10355,127.0.0.1:19350)] P2P thread exit! -INFO [1727961693.736785] [stackslib/src/net/rpc.rs:556] [p2p-(127.0.0.1:51025,127.0.0.1:51026)] Handled StacksHTTPRequest, verb: POST, path: /v2/transactions, processing_time_ms: 1, latency_ms: 0, conn_id: 65, peer_addr: 127.0.0.1:58343, p2p_msg: Some(Transaction(StacksTransaction { version: Testnet, chain_id: 2147483648, auth: Standard(Singlesig(SinglesigSpendingCondition { hash_mode: P2PKH, signer: d0d6ea35c353fa4cc1ed715c788860cbfe175603, nonce: 12, tx_fee: 180, key_encoding: Compressed, signature: 00240bb6f9960bd3e5e016ea164110aa36f26349539a2bce6fd6580e3ea6c629b5555e19ab3ba712976edb6f9de0b196090737f5f68a58727aabba4af6df0b0673 })), anchor_mode: OnChainOnly, post_condition_mode: Allow, post_conditions: [], payload: TokenTransfer(Standard(StandardPrincipalData(ST000000000000000000002AMW42H)), 100, 00000000000000000000000000000000000000000000000000000000000000000000) })) -INFO [1727961693.737025] [testnet/stacks-node/src/tests/nakamoto_integrations.rs:2717] [tests::nakamoto_integrations::new_tenure_stop_fast_blocks] d -INFO [1727961693.741390] [stackslib/src/chainstate/burn/db/sortdb.rs:1664] [relayer-http://127.0.0.1:51026] PoX recipient chosen, recipient: 1a-ee02bf1889dab2264a8ff035bb3100b39faa1c2d, block_height: 234, stacks_block_hash: 58e4ef89c3c93679884cebd7052049dde199c27346fb7575ba6abe3be75a26b9 -INFO [1727961693.741431] [stackslib/src/chainstate/burn/db/sortdb.rs:1664] [relayer-http://127.0.0.1:51026] PoX recipient chosen, recipient: 1a-ee02bf1889dab2264a8ff035bb3100b39faa1c2d, block_height: 234, stacks_block_hash: 58e4ef89c3c93679884cebd7052049dde199c27346fb7575ba6abe3be75a26b9 -INFO [1727961693.743017] [testnet/stacks-node/src/burnchains/bitcoin_regtest_controller.rs:1666] [relayer-http://127.0.0.1:51026] Attempt to replace by fee an outdated leader block commit, ongoing_txids: [112c8263a6fc6fe20af2b545720a0341b63ee48a718c8ab677fc74a0a611551d] -INFO [1727961693.743589] [testnet/stacks-node/src/burnchains/bitcoin_regtest_controller.rs:1514] [relayer-http://127.0.0.1:51026] Miner node: submitting leader_block_commit (txid: 88ec34967cf7de7f68d4b57a2d22f62f1e77791e7dbab9e25804e6438e53307d, rbf: true, total spent: 41505, size: 384, fee_rate: 55) -INFO [1727961693.755863] [testnet/stacks-node/src/nakamoto_node/relayer.rs:1084] [relayer-http://127.0.0.1:51026] Relayer: Submitted block-commit, tip_consensus_hash: c1b66ce402f74926f4495fe91bb97a7068d42512, tip_block_hash: 109165b6974b5b77ce3bd1b7c3b1afbaa97264a67d1b1608556f63113311cf30, tip_height: 39, tip_block_id: 55a6bf40061874cf1f48b7ac81a2331c6e7d99bf537eed13fc4f08600749b282, txid: 88ec34967cf7de7f68d4b57a2d22f62f1e77791e7dbab9e25804e6438e53307d -INFO [1727961694.504681] [testnet/stacks-node/src/run_loop/nakamoto.rs:540] [run_loop] Terminating p2p process -INFO [1727961694.504939] [testnet/stacks-node/src/run_loop/nakamoto.rs:541] [run_loop] Terminating relayer -INFO [1727961694.504946] [testnet/stacks-node/src/run_loop/nakamoto.rs:542] [run_loop] Terminating chains-coordinator -INFO [1727961694.505198] [testnet/stacks-node/src/run_loop/nakamoto.rs:548] [run_loop] Exiting stacks-node -INFO [1727961694.662242] [stackslib/src/chainstate/nakamoto/miner.rs:702] [miner.4e0550e802a1f63ffa07530bca902f5dfbc550d3e77d727ff41e361edc84b039] Include tx, tx: 0d6b677d33a7d92cf8e26378daba6478cb54df35b4fde26ac4276a5c406d95c8, payload: TokenTransfer, origin: ST38DDTHNRD9ZMK61XNRNRY48C35ZW5TP0F7HFBK4 -INFO [1727961694.662267] [stackslib/src/chainstate/stacks/miner.rs:397] [miner.4e0550e802a1f63ffa07530bca902f5dfbc550d3e77d727ff41e361edc84b039] Tx successfully processed., event_name: transaction_result, tx_id: 0d6b677d33a7d92cf8e26378daba6478cb54df35b4fde26ac4276a5c406d95c8, event_type: success -INFO [1727961694.663782] [stackslib/src/chainstate/nakamoto/miner.rs:575] [miner.4e0550e802a1f63ffa07530bca902f5dfbc550d3e77d727ff41e361edc84b039] Miner: mined Nakamoto block, stacks_block_hash: dcb1342357566626da28f44bba8d53f929a06b6f6c8f816930ac596062999f77, stacks_block_id: d204c421298df0e55cd1a92982625b3ab5e7f083b6d35bc706412f7b7d1bbbb0, height: 40, tx_count: 1, parent_block_id: 55a6bf40061874cf1f48b7ac81a2331c6e7d99bf537eed13fc4f08600749b282, block_size: 180, execution_consumed: {"runtime": 0, "write_len": 0, "write_cnt": 0, "read_len": 0, "read_cnt": 0}, %-full: 0, assembly_time_ms: 12, consensus_hash: c1b66ce402f74926f4495fe91bb97a7068d42512 -INFO [1727961694.664053] [testnet/stacks-node/src/nakamoto_node/miner.rs:1221] [miner.4e0550e802a1f63ffa07530bca902f5dfbc550d3e77d727ff41e361edc84b039] Miner: Assembled block #40 for signer set proposal: eb99579f338af2f5c21a1d7e2a9b368b7b4b98ece296c328cc75a74984d167fa, with 1 txs, signer_sighash: eb99579f338af2f5c21a1d7e2a9b368b7b4b98ece296c328cc75a74984d167fa, consensus_hash: c1b66ce402f74926f4495fe91bb97a7068d42512, parent_block_id: 55a6bf40061874cf1f48b7ac81a2331c6e7d99bf537eed13fc4f08600749b282, timestamp: 1727961694 -INFO [1727961694.683078] [stackslib/src/net/rpc.rs:556] [p2p-(127.0.0.1:51025,127.0.0.1:51026)] Handled StacksHTTPRequest, verb: POST, path: /v2/stackerdb/ST000000000000000000002AMW42H/miners/chunks, processing_time_ms: 0, latency_ms: 0, conn_id: 66, peer_addr: 127.0.0.1:58348, p2p_msg: Some(StackerDBPushChunk(StackerDBPushChunkData { contract_id: QualifiedContractIdentifier { issuer: StandardPrincipalData(ST000000000000000000002AMW42H), name: ContractName("miners") }, rc_consensus_hash: 549333be8bf4ab28a67b6821d368b4de48878eab, chunk_data: StackerDBChunkData(0,2,01ad2dd511af39f618fcc2aab6577590488921cc958faebc534f9a3e9f4ff8f14e704b00e7dc945aa32d56339c53820339d9390b328e8b41f285f9e5847f044833,000000000000000000280000000000116520c1b66ce402f74926f4495fe91bb97a7068d4251255a6bf40061874cf1f48b7ac81a2331c6e7d99bf537eed13fc4f08600749b2822224580a5e313e05203f3acde2da54075f2a41be40c78e96b597a9f11f421cc732bd2f8aad2096ff1fb551b4b435624cbfff0a4f864d03c5b1c0...(421)) })) -INFO [1727961694.683684] [testnet/stacks-node/src/nakamoto_node/sign_coordinator.rs:728] [miner.4e0550e802a1f63ffa07530bca902f5dfbc550d3e77d727ff41e361edc84b039] SignCoordinator: sent block proposal to .miners, waiting for test signing channel -INFO [1727961695.694734] [stackslib/src/net/stackerdb/mod.rs:493] [p2p-(127.0.0.1:51025,127.0.0.1:51026)] Failed to run StackerDB state machine for ST000000000000000000002AMW42H.signers-1-6: PeerNotConnected -INFO [1727961696.732321] [testnet/stacks-node/src/tests/nakamoto_integrations.rs:346] [blind-signer] Checking for a block proposal to sign... -INFO [1727961697.723343] [stackslib/src/net/stackerdb/mod.rs:493] [p2p-(127.0.0.1:51025,127.0.0.1:51026)] Failed to run StackerDB state machine for ST000000000000000000002AMW42H.signers-1-6: PeerNotConnected -INFO [1727961697.723631] [stackslib/src/net/stackerdb/mod.rs:493] [p2p-(127.0.0.1:51025,127.0.0.1:51026)] Failed to run StackerDB state machine for ST000000000000000000002AMW42H.signers-0-8: PeerNotConnected -INFO [1727961699.745941] [stackslib/src/net/stackerdb/mod.rs:493] [p2p-(127.0.0.1:51025,127.0.0.1:51026)] Failed to run StackerDB state machine for ST000000000000000000002AMW42H.signers-1-6: PeerNotConnected -INFO [1727961701.774656] [stackslib/src/net/stackerdb/mod.rs:493] [p2p-(127.0.0.1:51025,127.0.0.1:51026)] Failed to run StackerDB state machine for ST000000000000000000002AMW42H.signers-1-6: PeerNotConnected -INFO [1727961701.775029] [stackslib/src/net/stackerdb/mod.rs:493] [p2p-(127.0.0.1:51025,127.0.0.1:51026)] Failed to run StackerDB state machine for ST000000000000000000002AMW42H.signers-0-8: PeerNotConnected -INFO [1727961701.775290] [stackslib/src/net/stackerdb/mod.rs:493] [p2p-(127.0.0.1:51025,127.0.0.1:51026)] Failed to run StackerDB state machine for ST000000000000000000002AMW42H.signers-0-3: PeerNotConnected -INFO [1727961703.813978] [stackslib/src/net/stackerdb/mod.rs:493] [p2p-(127.0.0.1:51025,127.0.0.1:51026)] Failed to run StackerDB state machine for ST000000000000000000002AMW42H.signers-1-6: PeerNotConnected -INFO [1727961705.853196] [stackslib/src/net/stackerdb/mod.rs:493] [p2p-(127.0.0.1:51025,127.0.0.1:51026)] Failed to run StackerDB state machine for ST000000000000000000002AMW42H.signers-1-6: PeerNotConnected -INFO [1727961705.853615] [stackslib/src/net/stackerdb/mod.rs:493] [p2p-(127.0.0.1:51025,127.0.0.1:51026)] Failed to run StackerDB state machine for ST000000000000000000002AMW42H.signers-0-8: PeerNotConnected -INFO [1727961707.892266] [stackslib/src/net/stackerdb/mod.rs:493] [p2p-(127.0.0.1:51025,127.0.0.1:51026)] Failed to run StackerDB state machine for ST000000000000000000002AMW42H.signers-1-6: PeerNotConnected -INFO [1727961709.929316] [stackslib/src/net/stackerdb/mod.rs:493] [p2p-(127.0.0.1:51025,127.0.0.1:51026)] Failed to run StackerDB state machine for ST000000000000000000002AMW42H.signers-1-6: PeerNotConnected -INFO [1727961709.929819] [stackslib/src/net/stackerdb/mod.rs:493] [p2p-(127.0.0.1:51025,127.0.0.1:51026)] Failed to run StackerDB state machine for ST000000000000000000002AMW42H.signers-0-8: PeerNotConnected -INFO [1727961709.930107] [stackslib/src/net/stackerdb/mod.rs:493] [p2p-(127.0.0.1:51025,127.0.0.1:51026)] Failed to run StackerDB state machine for ST000000000000000000002AMW42H.signers-0-3: PeerNotConnected -INFO [1727961709.930356] [stackslib/src/net/stackerdb/mod.rs:493] [p2p-(127.0.0.1:51025,127.0.0.1:51026)] Failed to run StackerDB state machine for ST000000000000000000002AMW42H.signers-0-0: PeerNotConnected -INFO [1727961711.966333] [stackslib/src/net/stackerdb/mod.rs:493] [p2p-(127.0.0.1:51025,127.0.0.1:51026)] Failed to run StackerDB state machine for ST000000000000000000002AMW42H.signers-1-6: PeerNotConnected -ERRO [1727961713.780909] [testnet/stacks-node/src/tests/nakamoto_integrations.rs:645] [tests::nakamoto_integrations::new_tenure_stop_fast_blocks] Timed out waiting for check to process -test tests::nakamoto_integrations::new_tenure_stop_fast_blocks ... FAILED - -failures: - -failures: - tests::nakamoto_integrations::new_tenure_stop_fast_blocks - -test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 192 filtered out; finished in 118.55s - diff --git a/testnet/stacks-node/src/tests/signer/v0.rs b/testnet/stacks-node/src/tests/signer/v0.rs index d19fb2d7cd..cf24f0f4a4 100644 --- a/testnet/stacks-node/src/tests/signer/v0.rs +++ b/testnet/stacks-node/src/tests/signer/v0.rs @@ -20,7 +20,7 @@ use std::sync::atomic::Ordering; use std::time::{Duration, Instant}; use std::{env, thread}; -use clarity::vm::types::PrincipalData; +use clarity::vm::types::{PrincipalData, StandardPrincipalData}; use clarity::vm::StacksEpoch; use libsigner::v0::messages::{ BlockRejection, BlockResponse, MessageSlotID, MinerSlotID, RejectCode, SignerMessage, @@ -1777,17 +1777,10 @@ fn new_tenure_stop_fast_blocks() { }) .expect("Timed out waiting for boostrapped node to catch up to the miner"); - let pre_nakamoto_peer_1_height = get_chain_info(&conf).stacks_tip_height; - info!("------------------------- Reached Epoch 3.0 -------------------------"); let max_nakamoto_tenures = 5; - // due to the random nature of mining sortitions, the way this test is structured - // is that we keep track of how many tenures each miner produced, and once enough sortitions - // have been produced such that each miner has produced 3 tenures, we stop and check the - // results at the end - // mine tenures until we have miner_b winner of one tenure // then stop the miner_a // then try to mine with miner_b and not produce any blocks @@ -1795,55 +1788,52 @@ fn new_tenure_stop_fast_blocks() { let rl1_coord_channels = signer_test.running_nodes.coord_channel.clone(); let rl1_commits = signer_test.running_nodes.commits_submitted.clone(); - let miner_1_pk = StacksPublicKey::from_private(conf.miner.mining_key.as_ref().unwrap()); - let miner_2_pk = StacksPublicKey::from_private(conf_node_2.miner.mining_key.as_ref().unwrap()); + let miner_1_stacks_address = StandardPrincipalData::from(&node_1_sk); let mut btc_blocks_mined = 1; - let mut miner_1_tenures = 0; - let mut miner_2_tenures = 0; - let mut stopped_miner_b = false; - let mut miner_a_tries = 0; + let mut stopped_miner_2 = false; + let mut miner_1_tries = 0; let miner_1_pubkey = conf .burnchain .local_mining_public_key .as_ref() .map(|pubkey_hex| Secp256k1PublicKey::from_hex(pubkey_hex)) - .expect("Failed to parse miner A's public key") + .expect("Failed to parse 1's public key") .expect("Invalid A's public key format"); let miner_2_pubkey = conf_node_2 .burnchain .local_mining_public_key .as_ref() .map(|pubkey_hex| Secp256k1PublicKey::from_hex(pubkey_hex)) - .expect("Failed to parse miner B's public key") + .expect("Failed to parse miner 2's public key") .expect("Invalid B's public key format"); // Create Bitcoin addresses from public keys - let miner_a_address = BitcoinAddress::from_bytes_legacy( + let miner_1_bitcoin_address = BitcoinAddress::from_bytes_legacy( BitcoinNetworkType::Regtest, LegacyBitcoinAddressType::PublicKeyHash, &Hash160::from_data(&miner_1_pubkey.to_bytes()).0, ) - .expect("Failed to create Bitcoin address for miner A"); + .expect("Failed to create Bitcoin address for miner 1"); - let miner_b_address = BitcoinAddress::from_bytes_legacy( + let miner_2_bitcoin_address = BitcoinAddress::from_bytes_legacy( BitcoinNetworkType::Regtest, LegacyBitcoinAddressType::PublicKeyHash, &Hash160::from_data(&miner_2_pubkey.to_bytes()).0, ) - .expect("Failed to create Bitcoin address for miner B"); + .expect("Failed to create Bitcoin address for miner 2"); - info!("Miner A address: {}", miner_a_address); - info!("Miner B address: {}", miner_b_address); + info!("Miner 1 address: {}", miner_1_bitcoin_address); + info!("Miner 2 address: {}", miner_2_bitcoin_address); - while !stopped_miner_b || miner_a_tries < 3 { + while !stopped_miner_2 || miner_1_tries < 3 { assert!( max_nakamoto_tenures >= btc_blocks_mined, "Produced {btc_blocks_mined} sortitions, but didn't cover the test scenarios, aborting" ); let info_1 = get_chain_info(&conf); - if !stopped_miner_b { + if !stopped_miner_2 { let info_2 = get_chain_info(&conf_node_2); info!( "Issue next block-build request\ninfo 1: {:?}\ninfo 2: {:?}\n", @@ -1852,20 +1842,35 @@ fn new_tenure_stop_fast_blocks() { }; info!("Issue next block-build request\ninfo 1: {:?}\n", &info_1); + let info_old_stacks = get_chain_info(&conf); - if !stopped_miner_b { + if !stopped_miner_2 { signer_test.mine_block_wait_on_processing( &[&rl1_coord_channels, &rl2_coord_channels], &[&rl1_commits, &rl2_commits], Duration::from_secs(30), ); } else { - signer_test.mine_nakamoto_block(Duration::from_secs(30)); + // create interim block and check that it is not signed and mined + let http_origin = &conf.node.data_url; + let recipient = PrincipalData::from(StacksAddress::burn_address(false)); + let miner_a_account = get_account(http_origin, &miner_1_stacks_address); + let miner_a_nonce = miner_a_account.nonce; + let transfer_tx = + make_stacks_transfer(&sender_sk, miner_a_nonce, send_fee, &recipient, send_amt); + submit_tx(http_origin, &transfer_tx); + info!("Creating interim block7"); + wait_for(15, || { + let info_new_stacks = get_chain_info(&conf); + info!("Creating interim block8 {}, {}", info_new_stacks.stacks_tip_height, info_old_stacks.stacks_tip_height); + Ok(info_new_stacks.stacks_tip_height <= info_old_stacks.stacks_tip_height) + }).expect("The stacks tip height should not have increased as the signers should not have been able to receive the proposal to sign"); } - btc_blocks_mined += 1; - if stopped_miner_b { - miner_a_tries += 1; + if stopped_miner_2 { + miner_1_tries += 1; + } else { + btc_blocks_mined += 1; } // Get the latest block from chainstate @@ -1876,14 +1881,6 @@ fn new_tenure_stop_fast_blocks() { .unwrap() .block_hash; let sortdb = burnchain.open_sortition_db(true).unwrap(); - let (chainstate, _) = StacksChainState::open( - conf.is_mainnet(), - conf.burnchain.chain_id, - &conf.get_chainstate_path_str(), - None, - ) - .unwrap(); - let block_snapshot: stacks::chainstate::burn::BlockSnapshot = SortitionDB::get_all_snapshots_for_burn_block(sortdb.conn(), &burnchain_header_hash) .unwrap()[0] @@ -1904,9 +1901,11 @@ fn new_tenure_stop_fast_blocks() { info!("Winner Miner Address: {}", winner_bitcoin_address); - if !stopped_miner_b && winner_bitcoin_address.to_string() == miner_a_address.to_string() { + if !stopped_miner_2 + && winner_bitcoin_address.to_string() == miner_1_bitcoin_address.to_string() + { info!( - "Miner A is the winner for tenure {}. Stopping miner B", + "Miner 1 is the winner for tenure {}. Stopping miner 2", btc_blocks_mined ); rl2_coord_channels @@ -1914,9 +1913,9 @@ fn new_tenure_stop_fast_blocks() { .expect("Mutex poisoned") .stop_chains_coordinator(); run_loop_stopper_2.store(false, Ordering::SeqCst); - stopped_miner_b = true; + stopped_miner_2 = true; }; - info!("Miner A tries: {}", miner_a_tries); + info!("Miner 1 tries: {}", miner_1_tries); let blocks = get_nakamoto_headers(&conf); // for this test, there should be one block per tenure let consensus_hash_set: HashSet<_> = blocks @@ -1928,53 +1927,344 @@ fn new_tenure_stop_fast_blocks() { blocks.len(), "In this test, there should only be one block per tenure" ); - miner_1_tenures = blocks - .iter() - .filter(|header| { - let header = header.anchored_header.as_stacks_nakamoto().unwrap(); - miner_1_pk - .verify( - header.miner_signature_hash().as_bytes(), - &header.miner_signature, - ) - .unwrap() - }) - .count(); - if !stopped_miner_b { - miner_2_tenures = blocks - .iter() - .filter(|header| { - let header = header.anchored_header.as_stacks_nakamoto().unwrap(); - miner_2_pk - .verify( - header.miner_signature_hash().as_bytes(), - &header.miner_signature, - ) - .unwrap() - }) - .count(); - } } - info!( - "New chain info: {:?}", - get_chain_info(&signer_test.running_nodes.conf) + run_loop_2_thread.join().unwrap(); + signer_test.shutdown(); +} + +// case 2 - stop miner b and continue to mine with miner a. it should continue increasing the number of tenures +// try starting miner b again. it should be able to resync with miner a after some time. make a long wait there and some block mine +#[test] +#[ignore] +fn new_tenure_stop_fast_blocks_case_2() { + if env::var("BITCOIND_TEST") != Ok("1".into()) { + return; + } + + let num_signers = 5; + let sender_sk = Secp256k1PrivateKey::new(); + let sender_addr = tests::to_addr(&sender_sk); + let send_amt = 100; + let send_fee = 180; + + let btc_miner_1_seed = vec![1, 1, 1, 1]; + let btc_miner_2_seed = vec![2, 2, 2, 2]; + let btc_miner_1_pk = Keychain::default(btc_miner_1_seed.clone()).get_pub_key(); + let btc_miner_2_pk = Keychain::default(btc_miner_2_seed.clone()).get_pub_key(); + + let node_1_rpc = 51024; + let node_1_p2p = 51023; + let node_2_rpc = 51026; + let node_2_p2p = 51025; + + let localhost = "127.0.0.1"; + let node_1_rpc_bind = format!("{localhost}:{node_1_rpc}"); + let node_2_rpc_bind = format!("{localhost}:{node_2_rpc}"); + let mut node_2_listeners = Vec::new(); + + // partition the signer set so that ~half are listening and using node 1 for RPC and events, + // and the rest are using node 2 + + let mut signer_test: SignerTest = SignerTest::new_with_config_modifications( + num_signers, + vec![(sender_addr.clone(), send_amt + send_fee)], + |signer_config| { + let node_host = if signer_config.endpoint.port() % 5 != 0 { + &node_1_rpc_bind + } else { + &node_2_rpc_bind + }; + signer_config.node_host = node_host.to_string(); + }, + |config| { + config.node.rpc_bind = format!("{localhost}:{node_1_rpc}"); + config.node.p2p_bind = format!("{localhost}:{node_1_p2p}"); + config.node.data_url = format!("http://{localhost}:{node_1_rpc}"); + config.node.p2p_address = format!("{localhost}:{node_1_p2p}"); + config.miner.wait_on_interim_blocks = Duration::from_secs(5); + config.node.pox_sync_sample_secs = 5; + + config.node.seed = btc_miner_1_seed.clone(); + config.node.local_peer_seed = btc_miner_1_seed.clone(); + config.burnchain.local_mining_public_key = Some(btc_miner_1_pk.to_hex()); + config.miner.mining_key = Some(Secp256k1PrivateKey::from_seed(&[1])); + + config.events_observers.retain(|listener| { + let Ok(addr) = std::net::SocketAddr::from_str(&listener.endpoint) else { + warn!( + "Cannot parse {} to a socket, assuming it isn't a signer-listener binding", + listener.endpoint + ); + return true; + }; + if addr.port() % 2 == 0 || addr.port() == test_observer::EVENT_OBSERVER_PORT { + return true; + } + node_2_listeners.push(listener.clone()); + false + }) + }, + Some(vec![btc_miner_1_pk.clone(), btc_miner_2_pk.clone()]), + None, ); + let conf = signer_test.running_nodes.conf.clone(); + let mut conf_node_2 = conf.clone(); + conf_node_2.node.rpc_bind = format!("{localhost}:{node_2_rpc}"); + conf_node_2.node.p2p_bind = format!("{localhost}:{node_2_p2p}"); + conf_node_2.node.data_url = format!("http://{localhost}:{node_2_rpc}"); + conf_node_2.node.p2p_address = format!("{localhost}:{node_2_p2p}"); + conf_node_2.node.seed = btc_miner_2_seed.clone(); + conf_node_2.burnchain.local_mining_public_key = Some(btc_miner_2_pk.to_hex()); + conf_node_2.node.local_peer_seed = btc_miner_2_seed.clone(); + conf_node_2.miner.mining_key = Some(Secp256k1PrivateKey::from_seed(&[2])); + conf_node_2.node.miner = true; + conf_node_2.events_observers.clear(); + conf_node_2.events_observers.extend(node_2_listeners); + assert!(!conf_node_2.events_observers.is_empty()); - info!("New chain info: {:?}", get_chain_info(&conf_node_2)); + let node_1_sk = Secp256k1PrivateKey::from_seed(&conf.node.local_peer_seed); + let node_1_pk = StacksPublicKey::from_private(&node_1_sk); + + conf_node_2.node.working_dir = format!("{}-{}", conf_node_2.node.working_dir, "1"); + + conf_node_2.node.set_bootstrap_nodes( + format!("{}@{}", &node_1_pk.to_hex(), conf.node.p2p_bind), + conf.burnchain.chain_id, + conf.burnchain.peer_version, + ); + + let mut run_loop_2 = boot_nakamoto::BootRunLoop::new(conf_node_2.clone()).unwrap(); + let run_loop_stopper_2 = run_loop_2.get_termination_switch(); + let rl2_coord_channels = run_loop_2.coordinator_channels(); + let Counters { + naka_submitted_commits: rl2_commits, + .. + } = run_loop_2.counters(); + let run_loop_2_thread = thread::Builder::new() + .name("run_loop_2".into()) + .spawn(move || run_loop_2.start(None, 0)) + .unwrap(); + + signer_test.boot_to_epoch_3(); + + wait_for(120, || { + let Some(node_1_info) = get_chain_info_opt(&conf) else { + return Ok(false); + }; + let Some(node_2_info) = get_chain_info_opt(&conf_node_2) else { + return Ok(false); + }; + Ok(node_1_info.stacks_tip_height == node_2_info.stacks_tip_height) + }) + .expect("Timed out waiting for boostrapped node to catch up to the miner"); + + info!("------------------------- Reached Epoch 3.0 -------------------------"); + + let max_nakamoto_tenures = 5; + // mine tenures until we have miner_b winner of one tenure + // then stop the miner_a + // then try to mine with miner_b and not produce any blocks + + let rl1_coord_channels = signer_test.running_nodes.coord_channel.clone(); + let rl1_commits = signer_test.running_nodes.commits_submitted.clone(); + + let mut btc_blocks_mined = 1; + let mut stopped_miner_2 = false; + + let miner_1_pubkey = conf + .burnchain + .local_mining_public_key + .as_ref() + .map(|pubkey_hex| Secp256k1PublicKey::from_hex(pubkey_hex)) + .expect("Failed to parse miner 1's public key") + .expect("Invalid A's public key format"); + let miner_2_pubkey = conf_node_2 + .burnchain + .local_mining_public_key + .as_ref() + .map(|pubkey_hex| Secp256k1PublicKey::from_hex(pubkey_hex)) + .expect("Failed to parse miner 2's public key") + .expect("Invalid B's public key format"); + + // Create Bitcoin addresses from public keys + let miner_1_bitcoin_address = BitcoinAddress::from_bytes_legacy( + BitcoinNetworkType::Regtest, + LegacyBitcoinAddressType::PublicKeyHash, + &Hash160::from_data(&miner_1_pubkey.to_bytes()).0, + ) + .expect("Failed to create Bitcoin address for miner 1"); + + let miner_2_bitcoin_address = BitcoinAddress::from_bytes_legacy( + BitcoinNetworkType::Regtest, + LegacyBitcoinAddressType::PublicKeyHash, + &Hash160::from_data(&miner_2_pubkey.to_bytes()).0, + ) + .expect("Failed to create Bitcoin address for miner 2"); + + info!("Miner 1 address: {}", miner_1_bitcoin_address); + info!("Miner 2 address: {}", miner_2_bitcoin_address); + + let mut mined_blocks_1_alone = 0; + // TODO: 5 more mined blocks + while !stopped_miner_2 || mined_blocks_1_alone < 5 { + assert!( + max_nakamoto_tenures >= btc_blocks_mined, + "Produced {btc_blocks_mined} sortitions, but didn't cover the test scenarios, aborting" + ); + + let info_1 = get_chain_info(&conf); + if !stopped_miner_2 { + let info_2 = get_chain_info(&conf_node_2); + info!( + "Issue next block-build request\ninfo 1: {:?}\ninfo 2: {:?}\n", + &info_1, &info_2 + ); + }; + + info!("Issue next block-build request\ninfo 1: {:?}\n", &info_1); + let info_old = get_chain_info(&conf); + + // verify advancement of burn chain tip height + if !stopped_miner_2 { + signer_test.mine_block_wait_on_processing( + &[&rl1_coord_channels, &rl2_coord_channels], + &[&rl1_commits, &rl2_commits], + Duration::from_secs(30), + ); + } else { + mined_blocks_1_alone += 1; + + info!("Miner 2 is stopped, mine a block for node 1"); + signer_test.mine_block_wait_on_processing( + &[&rl1_coord_channels, &rl2_coord_channels], + &[&rl1_commits, &rl2_commits], + Duration::from_secs(30), + ); + info!("Miner 2 is stopped, mine a block for node 1 passed"); + // signer_test.mine_nakamoto_block(Duration::from_secs(30)); + } + let info_new = get_chain_info(&conf); + assert_eq!( + info_new.burn_block_height, + info_old.burn_block_height + 1, + "Burn chain tip height should advance" + ); + + if stopped_miner_2 {} + btc_blocks_mined += 1; + + // Get the latest block from chainstate + let burnchain = conf.get_burnchain(); + let burnchain_header_hash = burnchain + .get_highest_burnchain_block() + .unwrap() + .unwrap() + .block_hash; + let sortdb = burnchain.open_sortition_db(true).unwrap(); + let block_snapshot: stacks::chainstate::burn::BlockSnapshot = + SortitionDB::get_all_snapshots_for_burn_block(sortdb.conn(), &burnchain_header_hash) + .unwrap()[0] + .clone(); + let winning_block_txid: Txid = block_snapshot.winning_block_txid; + let blocks_commits = get_block_commit_by_txid( + sortdb.conn(), + &block_snapshot.sortition_id, + &winning_block_txid, + ) + .unwrap() + .unwrap(); + + let winner_address = blocks_commits.apparent_sender; + let winner_bitcoin_address = BitcoinAddress::Legacy( + LegacyBitcoinAddress::from_b58(winner_address.to_string().as_str()).unwrap(), + ); + + info!("Winner Miner Address: {}", winner_bitcoin_address); + + if !stopped_miner_2 + && winner_bitcoin_address.to_string() == miner_1_bitcoin_address.to_string() + { + info!( + "Miner 1 is the winner for tenure {}. Stopping miner 2", + btc_blocks_mined + ); + rl2_coord_channels + .lock() + .expect("Mutex poisoned") + .stop_chains_coordinator(); + run_loop_stopper_2.store(false, Ordering::SeqCst); + stopped_miner_2 = true; + }; + let blocks = get_nakamoto_headers(&conf); + // for this test, there should be one block per tenure + let consensus_hash_set: HashSet<_> = blocks + .iter() + .map(|header| header.consensus_hash.clone()) + .collect(); + assert_eq!( + consensus_hash_set.len(), + blocks.len(), + "In this test, there should only be one block per tenure" + ); + } + // TODO: reopen run_loop_2 + // let run_loop_2 = boot_nakamoto::BootRunLoop::new(conf_node_2.clone()).unwrap(); + // let run_loop_2_thread = thread::Builder::new() + // .name("run_loop_2".into()) + // .spawn(move || run_loop_2.start(None, 0)) + // .unwrap(); + // TODO: wait for node 2 to catch up to node 1 through mining a new block + + // check they are at the same height let peer_1_height = get_chain_info(&conf).stacks_tip_height; let peer_2_height = get_chain_info(&conf_node_2).stacks_tip_height; - info!("Peer height information"; "peer_1" => peer_1_height, "peer_2" => peer_2_height, "pre_naka_height" => pre_nakamoto_peer_1_height); + assert_eq!(peer_1_height, peer_2_height); - assert_eq!( - peer_1_height, - pre_nakamoto_peer_1_height + btc_blocks_mined - 1 - ); - assert_eq!( - btc_blocks_mined, - u64::try_from(miner_1_tenures + miner_2_tenures).unwrap() - ); + + // check afterwards that node 2 has also got to succesfully mine at least 1 block + + let info_new = get_chain_info(&conf_node_2); + let mut tries = 0; + + loop { + // Get the latest block from chainstate + let burnchain = conf.get_burnchain(); + let burnchain_header_hash = burnchain + .get_highest_burnchain_block() + .unwrap() + .unwrap() + .block_hash; + let sortdb = burnchain.open_sortition_db(true).unwrap(); + let block_snapshot: stacks::chainstate::burn::BlockSnapshot = + SortitionDB::get_all_snapshots_for_burn_block(sortdb.conn(), &burnchain_header_hash) + .unwrap()[0] + .clone(); + let winning_block_txid: Txid = block_snapshot.winning_block_txid; + let blocks_commits = get_block_commit_by_txid( + sortdb.conn(), + &block_snapshot.sortition_id, + &winning_block_txid, + ) + .unwrap() + .unwrap(); + + let winner_address = blocks_commits.apparent_sender; + let winner_bitcoin_address = BitcoinAddress::Legacy( + LegacyBitcoinAddress::from_b58(winner_address.to_string().as_str()).unwrap(), + ); + if winner_bitcoin_address.to_string() == miner_2_bitcoin_address.to_string() { + // TODO: add logic to mine 1 block for node 2 + break; + } else { + // TODO: remove this + tries += 1; + if tries > 7 { + break; + } + } + } run_loop_2_thread.join().unwrap(); signer_test.shutdown();