From 8c36319fe08464c1c74d80a10254b4f0ad6d7d81 Mon Sep 17 00:00:00 2001 From: Federico De Felici Date: Wed, 2 Jul 2025 14:22:44 +0200 Subject: [PATCH 01/12] refactor: remove `attempt` argument from `build_leader_block_commit_tx` --- .../burnchains/bitcoin_regtest_controller.rs | 95 ++++++++++++------- 1 file changed, 59 insertions(+), 36 deletions(-) diff --git a/testnet/stacks-node/src/burnchains/bitcoin_regtest_controller.rs b/testnet/stacks-node/src/burnchains/bitcoin_regtest_controller.rs index 16742f4fb8..be4913e29e 100644 --- a/testnet/stacks-node/src/burnchains/bitcoin_regtest_controller.rs +++ b/testnet/stacks-node/src/burnchains/bitcoin_regtest_controller.rs @@ -1509,7 +1509,6 @@ impl BitcoinRegtestController { epoch_id: StacksEpochId, payload: LeaderBlockCommitOp, signer: &mut BurnchainOpSigner, - _attempt: u64, ) -> Result { // Are we currently tracking an operation? if self.ongoing_block_commit.is_none() || !self.allow_rbf { @@ -2069,7 +2068,7 @@ impl BitcoinRegtestController { ) -> Result { let transaction = match operation { BlockstackOperationType::LeaderBlockCommit(payload) => { - self.build_leader_block_commit_tx(epoch_id, payload, op_signer, attempt) + self.build_leader_block_commit_tx(epoch_id, payload, op_signer) } BlockstackOperationType::LeaderKeyRegister(payload) => { self.build_leader_key_register_tx(epoch_id, payload, op_signer, attempt) @@ -3517,12 +3516,7 @@ mod tests { commit_op.burn_fee = 110_000; let tx = btc_controller - .build_leader_block_commit_tx( - StacksEpochId::Epoch31, - commit_op.clone(), - &mut op_signer, - 0, - ) + .build_leader_block_commit_tx(StacksEpochId::Epoch31, commit_op.clone(), &mut op_signer) .expect("Build leader block commit should work"); assert!(op_signer.is_disposed()); @@ -3575,12 +3569,7 @@ mod tests { let commit_op = utils::create_templated_commit_op(); let _first_tx_ok = btc_controller - .build_leader_block_commit_tx( - StacksEpochId::Epoch31, - commit_op.clone(), - &mut op_signer, - 0, - ) + .build_leader_block_commit_tx(StacksEpochId::Epoch31, commit_op.clone(), &mut op_signer) .expect("At first, building leader block commit should work"); // re-submitting same commit while previous it is not confirmed by the burnchain @@ -3588,7 +3577,6 @@ mod tests { StacksEpochId::Epoch31, commit_op, &mut op_signer, - 0, ); assert!(resubmit.is_err()); @@ -3626,12 +3614,7 @@ mod tests { let commit_op = utils::create_templated_commit_op(); let first_tx_ok = btc_controller - .build_leader_block_commit_tx( - StacksEpochId::Epoch31, - commit_op.clone(), - &mut op_signer, - 0, - ) + .build_leader_block_commit_tx(StacksEpochId::Epoch31, commit_op.clone(), &mut op_signer) .expect("At first, building leader block commit should work"); utils::mine_tx(&btc_controller, first_tx_ok); // Now tx is confirmed @@ -3641,7 +3624,6 @@ mod tests { StacksEpochId::Epoch31, commit_op, &mut op_signer, - 0, ); assert!(resubmit.is_err()); @@ -3681,12 +3663,7 @@ mod tests { commit_op.burn_fee = 110_000; let first_tx_ok = btc_controller - .build_leader_block_commit_tx( - StacksEpochId::Epoch31, - commit_op.clone(), - &mut op_signer, - 0, - ) + .build_leader_block_commit_tx(StacksEpochId::Epoch31, commit_op.clone(), &mut op_signer) .expect("At first, building leader block commit should work"); let first_txid = first_tx_ok.txid(); @@ -3700,7 +3677,7 @@ mod tests { commit_op.burn_fee += 10; let rbf_tx = btc_controller - .build_leader_block_commit_tx(StacksEpochId::Epoch31, commit_op.clone(), &mut signer, 0) + .build_leader_block_commit_tx(StacksEpochId::Epoch31, commit_op.clone(), &mut signer) .expect("Commit tx should be rbf-ed"); assert!(op_signer.is_disposed()); @@ -3761,12 +3738,7 @@ mod tests { commit_op.burn_fee = 110_000; let _first_tx_ok = btc_controller - .build_leader_block_commit_tx( - StacksEpochId::Epoch31, - commit_op.clone(), - &mut op_signer, - 0, - ) + .build_leader_block_commit_tx(StacksEpochId::Epoch31, commit_op.clone(), &mut op_signer) .expect("At first, building leader block commit should work"); //re-gen signer othewise fails because it will be disposed during previous commit tx. @@ -3775,7 +3747,7 @@ mod tests { commit_op.burn_fee += 10; let rbf_tx = btc_controller - .build_leader_block_commit_tx(StacksEpochId::Epoch31, commit_op.clone(), &mut signer, 0) + .build_leader_block_commit_tx(StacksEpochId::Epoch31, commit_op.clone(), &mut signer) .expect("Commit tx should be rbf-ed"); assert!(op_signer.is_disposed()); @@ -3800,4 +3772,55 @@ mod tests { assert_eq!(op_commit_2, rbf_tx.output[2]); assert_eq!(op_change, rbf_tx.output[3]); } + + /// Tests related to `BitcoinRegtestController::make_operation_tx` + mod make_operation { + use super::*; + + #[test] + #[ignore] + fn test_make_operation_leader_block_commit_tx_ok() { + if env::var("BITCOIND_TEST") != Ok("1".into()) { + return; + } + + let keychain = utils::create_keychain(); + let miner_pubkey = keychain.get_pub_key(); + let mut op_signer = keychain.generate_op_signer(); + + let mut config = utils::create_config(); + config.burnchain.local_mining_public_key = Some(miner_pubkey.to_hex()); + + let mut btcd_controller = BitcoinCoreController::new(config.clone()); + btcd_controller + .start_bitcoind() + .expect("bitcoind should be started!"); + + let mut btc_controller = BitcoinRegtestController::new(config.clone(), None); + btc_controller + .connect_dbs() + .expect("Dbs initialization required!"); + btc_controller.bootstrap_chain(101); // now, one utxo exists + + let mut commit_op = utils::create_templated_commit_op(); + commit_op.sunset_burn = 5_500; + commit_op.burn_fee = 110_000; + + let tx = btc_controller + .make_operation_tx( + StacksEpochId::Epoch31, + BlockstackOperationType::LeaderBlockCommit(commit_op), + &mut op_signer, + 0, + ) + .expect("Build leader block commit should work"); + + assert!(op_signer.is_disposed()); + + assert_eq!( + "01000000014d9e9dc7d126446e90dd013f023937eba9cb2c88f4d12707400a3ede994a62c5000000008b483045022100e4f934cf20a42ae5709f96505b73ad4e7ab19f41931940257089bfe6935840780220503af1cafd02e42ed008ad473dd619ee591d6926333413275168cf2697ce91430141044227d7e5c0997524ce011c126f0464d43e7518872a9b1ad29436ac5142d73eab5fb48d764676900fc2fac56917412114bf7dfafe51f715cf466fe0c1a6c69d11fdffffff047c15000000000000536a4c5054335be88c3d30cb59a142f83de3b27f897a43bbb0f13316911bb98a3229973dae32afd5b9f21bc1f40f24e2c101ecd13c55b8619e5e03dad81de2c62a1cc1d8c1b375000008a300010000059800015ad8d60000000000001976a914000000000000000000000000000000000000000088acd8d60000000000001976a914000000000000000000000000000000000000000088acd4e3032a010000001976a9145e52c53cb96b55f0e3d719adbca21005bc54cb2e88ac00000000", + tx.to_hex() + ); + } + } } From 60861d611742d1a106280797936873b2d543eabd Mon Sep 17 00:00:00 2001 From: Federico De Felici Date: Wed, 2 Jul 2025 15:14:21 +0200 Subject: [PATCH 02/12] refactor: remove `attempt` argument from `build_leader_key_register_tx` --- .../burnchains/bitcoin_regtest_controller.rs | 134 +++++++++++++++++- 1 file changed, 130 insertions(+), 4 deletions(-) diff --git a/testnet/stacks-node/src/burnchains/bitcoin_regtest_controller.rs b/testnet/stacks-node/src/burnchains/bitcoin_regtest_controller.rs index be4913e29e..98d617e6e1 100644 --- a/testnet/stacks-node/src/burnchains/bitcoin_regtest_controller.rs +++ b/testnet/stacks-node/src/burnchains/bitcoin_regtest_controller.rs @@ -839,7 +839,6 @@ impl BitcoinRegtestController { epoch_id: StacksEpochId, payload: LeaderKeyRegisterOp, signer: &mut BurnchainOpSigner, - _attempt: u64, ) -> Result { let public_key = signer.get_public_key(); @@ -2064,14 +2063,14 @@ impl BitcoinRegtestController { epoch_id: StacksEpochId, operation: BlockstackOperationType, op_signer: &mut BurnchainOpSigner, - attempt: u64, + _attempt: u64, ) -> Result { let transaction = match operation { BlockstackOperationType::LeaderBlockCommit(payload) => { self.build_leader_block_commit_tx(epoch_id, payload, op_signer) } BlockstackOperationType::LeaderKeyRegister(payload) => { - self.build_leader_key_register_tx(epoch_id, payload, op_signer, attempt) + self.build_leader_key_register_tx(epoch_id, payload, op_signer) } BlockstackOperationType::PreStx(payload) => { self.build_pre_stacks_tx(epoch_id, payload, op_signer) @@ -2838,7 +2837,7 @@ mod tests { use std::io::Write; use stacks::burnchains::BurnchainSigner; - use stacks::config::DEFAULT_SATS_PER_VB; + use stacks::config::{DEFAULT_SATS_PER_VB}; use stacks_common::deps_common::bitcoin::blockdata::script::Builder; use stacks_common::types::chainstate::{BlockHeaderHash, StacksAddress, VRFSeed}; use stacks_common::util::hash::to_hex; @@ -2852,6 +2851,8 @@ mod tests { use std::net::TcpListener; use stacks::burnchains::MagicBytes; + use stacks::chainstate::burn::ConsensusHash; + use stacks::util::vrf::{VRFPrivateKey, VRFPublicKey}; use super::*; use crate::tests::bitcoin_regtest::BURNCHAIN_CONFIG_PEER_PORT_DISABLED; @@ -3052,6 +3053,38 @@ mod tests { tx.input[index].clone() } + + + pub fn create_templated_leader_key_op() -> LeaderKeyRegisterOp { + LeaderKeyRegisterOp { + consensus_hash: ConsensusHash([0u8; 20]), + public_key: VRFPublicKey::from_private(&VRFPrivateKey::new()), + memo: vec![], + txid: Txid([3u8; 32]), + vtxindex: 0, + block_height: 1, + burn_header_hash: BurnchainHeaderHash([9u8; 32]), + } + } + + pub fn txout_opreturn_v2(op: &T, magic: &MagicBytes, value: u64) -> TxOut { + let op_bytes = { + let mut buffer = vec![]; + let mut magic_bytes = magic.as_bytes().to_vec(); + buffer.append(&mut magic_bytes); + op.consensus_serialize(&mut buffer) + .expect("FATAL: invalid operation"); + buffer + }; + + TxOut { + value, + script_pubkey: Builder::new() + .push_opcode(opcodes::All::OP_RETURN) + .push_slice(&op_bytes) + .into_script(), + } + } } #[test] @@ -3773,6 +3806,55 @@ mod tests { assert_eq!(op_change, rbf_tx.output[3]); } + #[test] + #[ignore] + fn test_build_leader_key_tx_ok() { + if env::var("BITCOIND_TEST") != Ok("1".into()) { + return; + } + + let keychain = utils::create_keychain(); + let miner_pubkey = keychain.get_pub_key(); + let mut op_signer = keychain.generate_op_signer(); + + let mut config = utils::create_config(); + config.burnchain.local_mining_public_key = Some(miner_pubkey.to_hex()); + + let mut btcd_controller = BitcoinCoreController::new(config.clone()); + btcd_controller + .start_bitcoind() + .expect("bitcoind should be started!"); + + let mut btc_controller = BitcoinRegtestController::new(config.clone(), None); + btc_controller + .connect_dbs() + .expect("Dbs initialization required!"); + btc_controller.bootstrap_chain(101); // now, one utxo exists + + let leader_key_op = utils::create_templated_leader_key_op(); + + let tx = btc_controller + .build_leader_key_register_tx(StacksEpochId::Epoch31, leader_key_op.clone(), &mut op_signer) + .expect("Build leader key should work"); + + assert!(op_signer.is_disposed()); + + assert_eq!(1, tx.version); + assert_eq!(0, tx.lock_time); + assert_eq!(1, tx.input.len()); + assert_eq!(2, tx.output.len()); + + // utxos list contains the only existing utxo + let used_utxos = btc_controller.get_all_utxos(&miner_pubkey); + let input_0 = utils::txin_at_index(&tx, &op_signer, &used_utxos, 0); + assert_eq!(input_0, tx.input[0]); + + let op_return = utils::txout_opreturn_v2(&leader_key_op, &config.burnchain.magic_bytes, 0); + let op_change = utils::txout_opdup_change_legacy(&mut op_signer, 4_999_980_000); + assert_eq!(op_return, tx.output[0]); + assert_eq!(op_change, tx.output[1]); + } + /// Tests related to `BitcoinRegtestController::make_operation_tx` mod make_operation { use super::*; @@ -3822,5 +3904,49 @@ mod tests { tx.to_hex() ); } + + #[test] + #[ignore] + fn test_make_operation_leader_key_register_tx_ok() { + if env::var("BITCOIND_TEST") != Ok("1".into()) { + return; + } + + let keychain = utils::create_keychain(); + let miner_pubkey = keychain.get_pub_key(); + let mut op_signer = keychain.generate_op_signer(); + + let mut config = utils::create_config(); + config.burnchain.local_mining_public_key = Some(miner_pubkey.to_hex()); + + let mut btcd_controller = BitcoinCoreController::new(config.clone()); + btcd_controller + .start_bitcoind() + .expect("bitcoind should be started!"); + + let mut btc_controller = BitcoinRegtestController::new(config.clone(), None); + btc_controller + .connect_dbs() + .expect("Dbs initialization required!"); + btc_controller.bootstrap_chain(101); // now, one utxo exists + + let leader_key_op = utils::create_templated_leader_key_op(); + + let tx = btc_controller + .make_operation_tx( + StacksEpochId::Epoch31, + BlockstackOperationType::LeaderKeyRegister(leader_key_op), + &mut op_signer, + 0, + ) + .expect("Build leader block commit should work"); + + assert!(op_signer.is_disposed()); + + assert_eq!( + "01000000014d9e9dc7d126446e90dd013f023937eba9cb2c88f4d12707400a3ede994a62c5000000008b483045022100f25168ce653d1f40aa7bf48d5dcad97e96ecdeaa8c142f74316bf6e151c918b002201211c493f3add7302d286af9009a18c685f8733504ffda9b80963bd6900819f40141044227d7e5c0997524ce011c126f0464d43e7518872a9b1ad29436ac5142d73eab5fb48d764676900fc2fac56917412114bf7dfafe51f715cf466fe0c1a6c69d11fdffffff020000000000000000396a3754335e0000000000000000000000000000000000000000edb3ebc987bf6911e64048c5637c85687815fb777bf882bce10a4d692dc9631ee0a3052a010000001976a9145e52c53cb96b55f0e3d719adbca21005bc54cb2e88ac00000000", + tx.to_hex() + ); + } } } From 5f6adacbdcb34168ecb09880e0621c4750212289 Mon Sep 17 00:00:00 2001 From: Federico De Felici Date: Wed, 2 Jul 2025 15:17:09 +0200 Subject: [PATCH 03/12] refactor: change `make_operation_tx` to private --- .../stacks-node/src/burnchains/bitcoin_regtest_controller.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/testnet/stacks-node/src/burnchains/bitcoin_regtest_controller.rs b/testnet/stacks-node/src/burnchains/bitcoin_regtest_controller.rs index 98d617e6e1..3a201d9d4b 100644 --- a/testnet/stacks-node/src/burnchains/bitcoin_regtest_controller.rs +++ b/testnet/stacks-node/src/burnchains/bitcoin_regtest_controller.rs @@ -2058,7 +2058,7 @@ impl BitcoinRegtestController { // TODO: add tests from mutation testing results #4866 #[cfg_attr(test, mutants::skip)] - pub fn make_operation_tx( + fn make_operation_tx( &mut self, epoch_id: StacksEpochId, operation: BlockstackOperationType, From b66abde67e68e5552f413da7f9ee4ee1c07105ce Mon Sep 17 00:00:00 2001 From: Federico De Felici Date: Wed, 2 Jul 2025 15:31:11 +0200 Subject: [PATCH 04/12] refactor: remove `attempt` from `make_operation_tx` --- .../src/burnchains/bitcoin_regtest_controller.rs | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/testnet/stacks-node/src/burnchains/bitcoin_regtest_controller.rs b/testnet/stacks-node/src/burnchains/bitcoin_regtest_controller.rs index 3a201d9d4b..dae2d877f5 100644 --- a/testnet/stacks-node/src/burnchains/bitcoin_regtest_controller.rs +++ b/testnet/stacks-node/src/burnchains/bitcoin_regtest_controller.rs @@ -2063,7 +2063,6 @@ impl BitcoinRegtestController { epoch_id: StacksEpochId, operation: BlockstackOperationType, op_signer: &mut BurnchainOpSigner, - _attempt: u64, ) -> Result { let transaction = match operation { BlockstackOperationType::LeaderBlockCommit(payload) => { @@ -2254,9 +2253,9 @@ impl BurnchainController for BitcoinRegtestController { epoch_id: StacksEpochId, operation: BlockstackOperationType, op_signer: &mut BurnchainOpSigner, - attempt: u64, + _attempt: u64, ) -> Result { - let transaction = self.make_operation_tx(epoch_id, operation, op_signer, attempt)?; + let transaction = self.make_operation_tx(epoch_id, operation, op_signer)?; self.send_transaction(transaction) } @@ -3058,7 +3057,7 @@ mod tests { pub fn create_templated_leader_key_op() -> LeaderKeyRegisterOp { LeaderKeyRegisterOp { consensus_hash: ConsensusHash([0u8; 20]), - public_key: VRFPublicKey::from_private(&VRFPrivateKey::new()), + public_key: VRFPublicKey::from_private(&VRFPrivateKey::from_bytes(&[0u8; 32]).unwrap()), memo: vec![], txid: Txid([3u8; 32]), vtxindex: 0, @@ -3893,7 +3892,6 @@ mod tests { StacksEpochId::Epoch31, BlockstackOperationType::LeaderBlockCommit(commit_op), &mut op_signer, - 0, ) .expect("Build leader block commit should work"); @@ -3937,14 +3935,13 @@ mod tests { StacksEpochId::Epoch31, BlockstackOperationType::LeaderKeyRegister(leader_key_op), &mut op_signer, - 0, ) .expect("Build leader block commit should work"); assert!(op_signer.is_disposed()); assert_eq!( - "01000000014d9e9dc7d126446e90dd013f023937eba9cb2c88f4d12707400a3ede994a62c5000000008b483045022100f25168ce653d1f40aa7bf48d5dcad97e96ecdeaa8c142f74316bf6e151c918b002201211c493f3add7302d286af9009a18c685f8733504ffda9b80963bd6900819f40141044227d7e5c0997524ce011c126f0464d43e7518872a9b1ad29436ac5142d73eab5fb48d764676900fc2fac56917412114bf7dfafe51f715cf466fe0c1a6c69d11fdffffff020000000000000000396a3754335e0000000000000000000000000000000000000000edb3ebc987bf6911e64048c5637c85687815fb777bf882bce10a4d692dc9631ee0a3052a010000001976a9145e52c53cb96b55f0e3d719adbca21005bc54cb2e88ac00000000", + "01000000014d9e9dc7d126446e90dd013f023937eba9cb2c88f4d12707400a3ede994a62c5000000008b483045022100c8694688b4269585ef63bfeb96d017bafae02621ebd0b5012e7564d3efcb71f70220070528674f75ca3503246030f064a85d2010256336372b246100f29ba21bf28b0141044227d7e5c0997524ce011c126f0464d43e7518872a9b1ad29436ac5142d73eab5fb48d764676900fc2fac56917412114bf7dfafe51f715cf466fe0c1a6c69d11fdffffff020000000000000000396a3754335e00000000000000000000000000000000000000003b6a27bcceb6a42d62a3a8d02a6f0d73653215771de243a63ac048a18b59da29e0a3052a010000001976a9145e52c53cb96b55f0e3d719adbca21005bc54cb2e88ac00000000", tx.to_hex() ); } From c53ad5aff0e86e23014fb9436c9f6f0631e2668d Mon Sep 17 00:00:00 2001 From: Federico De Felici Date: Wed, 2 Jul 2025 15:52:33 +0200 Subject: [PATCH 05/12] test: add test for `submit_operation` --- .../burnchains/bitcoin_regtest_controller.rs | 103 +++++++++++++++++- 1 file changed, 99 insertions(+), 4 deletions(-) diff --git a/testnet/stacks-node/src/burnchains/bitcoin_regtest_controller.rs b/testnet/stacks-node/src/burnchains/bitcoin_regtest_controller.rs index dae2d877f5..5635d575ae 100644 --- a/testnet/stacks-node/src/burnchains/bitcoin_regtest_controller.rs +++ b/testnet/stacks-node/src/burnchains/bitcoin_regtest_controller.rs @@ -3887,7 +3887,7 @@ mod tests { commit_op.sunset_burn = 5_500; commit_op.burn_fee = 110_000; - let tx = btc_controller + let ser_tx = btc_controller .make_operation_tx( StacksEpochId::Epoch31, BlockstackOperationType::LeaderBlockCommit(commit_op), @@ -3899,7 +3899,7 @@ mod tests { assert_eq!( "01000000014d9e9dc7d126446e90dd013f023937eba9cb2c88f4d12707400a3ede994a62c5000000008b483045022100e4f934cf20a42ae5709f96505b73ad4e7ab19f41931940257089bfe6935840780220503af1cafd02e42ed008ad473dd619ee591d6926333413275168cf2697ce91430141044227d7e5c0997524ce011c126f0464d43e7518872a9b1ad29436ac5142d73eab5fb48d764676900fc2fac56917412114bf7dfafe51f715cf466fe0c1a6c69d11fdffffff047c15000000000000536a4c5054335be88c3d30cb59a142f83de3b27f897a43bbb0f13316911bb98a3229973dae32afd5b9f21bc1f40f24e2c101ecd13c55b8619e5e03dad81de2c62a1cc1d8c1b375000008a300010000059800015ad8d60000000000001976a914000000000000000000000000000000000000000088acd8d60000000000001976a914000000000000000000000000000000000000000088acd4e3032a010000001976a9145e52c53cb96b55f0e3d719adbca21005bc54cb2e88ac00000000", - tx.to_hex() + ser_tx.to_hex() ); } @@ -3930,7 +3930,7 @@ mod tests { let leader_key_op = utils::create_templated_leader_key_op(); - let tx = btc_controller + let ser_tx = btc_controller .make_operation_tx( StacksEpochId::Epoch31, BlockstackOperationType::LeaderKeyRegister(leader_key_op), @@ -3942,7 +3942,102 @@ mod tests { assert_eq!( "01000000014d9e9dc7d126446e90dd013f023937eba9cb2c88f4d12707400a3ede994a62c5000000008b483045022100c8694688b4269585ef63bfeb96d017bafae02621ebd0b5012e7564d3efcb71f70220070528674f75ca3503246030f064a85d2010256336372b246100f29ba21bf28b0141044227d7e5c0997524ce011c126f0464d43e7518872a9b1ad29436ac5142d73eab5fb48d764676900fc2fac56917412114bf7dfafe51f715cf466fe0c1a6c69d11fdffffff020000000000000000396a3754335e00000000000000000000000000000000000000003b6a27bcceb6a42d62a3a8d02a6f0d73653215771de243a63ac048a18b59da29e0a3052a010000001976a9145e52c53cb96b55f0e3d719adbca21005bc54cb2e88ac00000000", - tx.to_hex() + ser_tx.to_hex() + ); + } + } + + /// Tests related to `BitcoinRegtestController::submit_operation` + mod submit_operation { + use super::*; + + #[test] + #[ignore] + fn test_submit_leader_block_commit_tx_ok() { + if env::var("BITCOIND_TEST") != Ok("1".into()) { + return; + } + + let keychain = utils::create_keychain(); + let miner_pubkey = keychain.get_pub_key(); + let mut op_signer = keychain.generate_op_signer(); + + let mut config = utils::create_config(); + config.burnchain.local_mining_public_key = Some(miner_pubkey.to_hex()); + + let mut btcd_controller = BitcoinCoreController::new(config.clone()); + btcd_controller + .start_bitcoind() + .expect("bitcoind should be started!"); + + let mut btc_controller = BitcoinRegtestController::new(config.clone(), None); + btc_controller + .connect_dbs() + .expect("Dbs initialization required!"); + btc_controller.bootstrap_chain(101); // now, one utxo exists + + let mut commit_op = utils::create_templated_commit_op(); + commit_op.sunset_burn = 5_500; + commit_op.burn_fee = 110_000; + + let tx_id = btc_controller + .submit_operation( + StacksEpochId::Epoch31, + BlockstackOperationType::LeaderBlockCommit(commit_op), + &mut op_signer, + 0, + ) + .expect("Build leader block commit should work"); + + assert!(op_signer.is_disposed()); + + assert_eq!( + "1a74106bd760117892fbd90fca11646b4de46f99fd2b065c9e0706cfdcea0336", + tx_id.to_hex() + ); + } + + #[test] + #[ignore] + fn test_submit_operation_leader_key_register_tx_ok() { + if env::var("BITCOIND_TEST") != Ok("1".into()) { + return; + } + + let keychain = utils::create_keychain(); + let miner_pubkey = keychain.get_pub_key(); + let mut op_signer = keychain.generate_op_signer(); + + let mut config = utils::create_config(); + config.burnchain.local_mining_public_key = Some(miner_pubkey.to_hex()); + + let mut btcd_controller = BitcoinCoreController::new(config.clone()); + btcd_controller + .start_bitcoind() + .expect("bitcoind should be started!"); + + let mut btc_controller = BitcoinRegtestController::new(config.clone(), None); + btc_controller + .connect_dbs() + .expect("Dbs initialization required!"); + btc_controller.bootstrap_chain(101); // now, one utxo exists + + let leader_key_op = utils::create_templated_leader_key_op(); + + let tx_id = btc_controller + .submit_operation( + StacksEpochId::Epoch31, + BlockstackOperationType::LeaderKeyRegister(leader_key_op), + &mut op_signer, + 0 + ) + .expect("Build leader block commit should work"); + + assert!(op_signer.is_disposed()); + + assert_eq!( + "4ecd7ba71bebd1aaed49dd63747ee424473f1c571bb9a576361607a669191024", + tx_id.to_hex() ); } } From 7d7857ef4663b797d3cd81f2bbaabe7b55676076 Mon Sep 17 00:00:00 2001 From: Federico De Felici Date: Wed, 2 Jul 2025 16:02:08 +0200 Subject: [PATCH 06/12] refactor: remove `attempt` arg from `submit_operation` --- .../burnchains/bitcoin_regtest_controller.rs | 34 +++++++++++-------- .../src/burnchains/mocknet_controller.rs | 1 - testnet/stacks-node/src/burnchains/mod.rs | 1 - .../stacks-node/src/nakamoto_node/relayer.rs | 3 +- testnet/stacks-node/src/neon_node.rs | 4 +-- testnet/stacks-node/src/node.rs | 4 +-- testnet/stacks-node/src/tests/epoch_205.rs | 8 ++--- testnet/stacks-node/src/tests/epoch_21.rs | 6 +--- .../src/tests/nakamoto_integrations.rs | 10 ------ .../src/tests/neon_integrations.rs | 10 ------ testnet/stacks-node/src/tests/signer/v0.rs | 4 +-- 11 files changed, 29 insertions(+), 56 deletions(-) diff --git a/testnet/stacks-node/src/burnchains/bitcoin_regtest_controller.rs b/testnet/stacks-node/src/burnchains/bitcoin_regtest_controller.rs index 5635d575ae..6078ae0b7f 100644 --- a/testnet/stacks-node/src/burnchains/bitcoin_regtest_controller.rs +++ b/testnet/stacks-node/src/burnchains/bitcoin_regtest_controller.rs @@ -2253,7 +2253,6 @@ impl BurnchainController for BitcoinRegtestController { epoch_id: StacksEpochId, operation: BlockstackOperationType, op_signer: &mut BurnchainOpSigner, - _attempt: u64, ) -> Result { let transaction = self.make_operation_tx(epoch_id, operation, op_signer)?; self.send_transaction(transaction) @@ -2836,7 +2835,7 @@ mod tests { use std::io::Write; use stacks::burnchains::BurnchainSigner; - use stacks::config::{DEFAULT_SATS_PER_VB}; + use stacks::config::DEFAULT_SATS_PER_VB; use stacks_common::deps_common::bitcoin::blockdata::script::Builder; use stacks_common::types::chainstate::{BlockHeaderHash, StacksAddress, VRFSeed}; use stacks_common::util::hash::to_hex; @@ -3053,11 +3052,12 @@ mod tests { tx.input[index].clone() } - pub fn create_templated_leader_key_op() -> LeaderKeyRegisterOp { LeaderKeyRegisterOp { consensus_hash: ConsensusHash([0u8; 20]), - public_key: VRFPublicKey::from_private(&VRFPrivateKey::from_bytes(&[0u8; 32]).unwrap()), + public_key: VRFPublicKey::from_private( + &VRFPrivateKey::from_bytes(&[0u8; 32]).unwrap(), + ), memo: vec![], txid: Txid([3u8; 32]), vtxindex: 0, @@ -3066,7 +3066,11 @@ mod tests { } } - pub fn txout_opreturn_v2(op: &T, magic: &MagicBytes, value: u64) -> TxOut { + pub fn txout_opreturn_v2( + op: &T, + magic: &MagicBytes, + value: u64, + ) -> TxOut { let op_bytes = { let mut buffer = vec![]; let mut magic_bytes = magic.as_bytes().to_vec(); @@ -3083,7 +3087,7 @@ mod tests { .push_slice(&op_bytes) .into_script(), } - } + } } #[test] @@ -3831,9 +3835,13 @@ mod tests { btc_controller.bootstrap_chain(101); // now, one utxo exists let leader_key_op = utils::create_templated_leader_key_op(); - + let tx = btc_controller - .build_leader_key_register_tx(StacksEpochId::Epoch31, leader_key_op.clone(), &mut op_signer) + .build_leader_key_register_tx( + StacksEpochId::Epoch31, + leader_key_op.clone(), + &mut op_signer, + ) .expect("Build leader key should work"); assert!(op_signer.is_disposed()); @@ -3854,7 +3862,7 @@ mod tests { assert_eq!(op_change, tx.output[1]); } - /// Tests related to `BitcoinRegtestController::make_operation_tx` + /// Tests related to `BitcoinRegtestController::make_operation_tx` mod make_operation { use super::*; @@ -3929,7 +3937,7 @@ mod tests { btc_controller.bootstrap_chain(101); // now, one utxo exists let leader_key_op = utils::create_templated_leader_key_op(); - + let ser_tx = btc_controller .make_operation_tx( StacksEpochId::Epoch31, @@ -3947,7 +3955,7 @@ mod tests { } } - /// Tests related to `BitcoinRegtestController::submit_operation` + /// Tests related to `BitcoinRegtestController::submit_operation` mod submit_operation { use super::*; @@ -3985,7 +3993,6 @@ mod tests { StacksEpochId::Epoch31, BlockstackOperationType::LeaderBlockCommit(commit_op), &mut op_signer, - 0, ) .expect("Build leader block commit should work"); @@ -4023,13 +4030,12 @@ mod tests { btc_controller.bootstrap_chain(101); // now, one utxo exists let leader_key_op = utils::create_templated_leader_key_op(); - + let tx_id = btc_controller .submit_operation( StacksEpochId::Epoch31, BlockstackOperationType::LeaderKeyRegister(leader_key_op), &mut op_signer, - 0 ) .expect("Build leader block commit should work"); diff --git a/testnet/stacks-node/src/burnchains/mocknet_controller.rs b/testnet/stacks-node/src/burnchains/mocknet_controller.rs index cdcda3a85a..488bed2316 100644 --- a/testnet/stacks-node/src/burnchains/mocknet_controller.rs +++ b/testnet/stacks-node/src/burnchains/mocknet_controller.rs @@ -167,7 +167,6 @@ impl BurnchainController for MocknetController { _epoch_id: StacksEpochId, operation: BlockstackOperationType, _op_signer: &mut BurnchainOpSigner, - _attempt: u64, ) -> Result { let txid = operation.txid(); self.queued_operations.push_back(operation); diff --git a/testnet/stacks-node/src/burnchains/mod.rs b/testnet/stacks-node/src/burnchains/mod.rs index 11b946b0cc..70a335348d 100644 --- a/testnet/stacks-node/src/burnchains/mod.rs +++ b/testnet/stacks-node/src/burnchains/mod.rs @@ -60,7 +60,6 @@ pub trait BurnchainController { epoch_id: StacksEpochId, operation: BlockstackOperationType, op_signer: &mut BurnchainOpSigner, - attempt: u64, ) -> Result; fn sync(&mut self, target_block_height_opt: Option) -> Result<(BurnchainTip, u64), Error>; fn sortdb_ref(&self) -> &SortitionDB; diff --git a/testnet/stacks-node/src/nakamoto_node/relayer.rs b/testnet/stacks-node/src/nakamoto_node/relayer.rs index e1a19dea30..8ffc7a6bc4 100644 --- a/testnet/stacks-node/src/nakamoto_node/relayer.rs +++ b/testnet/stacks-node/src/nakamoto_node/relayer.rs @@ -961,7 +961,7 @@ impl RelayerThread { let mut op_signer = self.keychain.generate_op_signer(); if let Ok(txid) = self .bitcoin_controller - .submit_operation(cur_epoch, op, &mut op_signer, 1) + .submit_operation(cur_epoch, op, &mut op_signer) { // advance key registration state self.last_vrf_key_burn_height = Some(burn_block.block_height); @@ -1664,7 +1664,6 @@ impl RelayerThread { *last_committed.get_epoch_id(), BlockstackOperationType::LeaderBlockCommit(last_committed.get_block_commit().clone()), &mut op_signer, - 1, ); let txid = match res { Ok(txid) => txid, diff --git a/testnet/stacks-node/src/neon_node.rs b/testnet/stacks-node/src/neon_node.rs index 1594b885f4..9fddda7d7e 100644 --- a/testnet/stacks-node/src/neon_node.rs +++ b/testnet/stacks-node/src/neon_node.rs @@ -2745,7 +2745,7 @@ impl BlockMinerThread { .. } = self.config.get_node_config(false); - let res = bitcoin_controller.submit_operation(target_epoch_id, op, &mut op_signer, attempt); + let res = bitcoin_controller.submit_operation(target_epoch_id, op, &mut op_signer); match res { Ok(_) => { self.failed_to_submit_last_attempt = false; @@ -3613,7 +3613,7 @@ impl RelayerThread { let mut one_off_signer = self.keychain.generate_op_signer(); if let Ok(txid) = self.bitcoin_controller - .submit_operation(cur_epoch, op, &mut one_off_signer, 1) + .submit_operation(cur_epoch, op, &mut one_off_signer) { // advance key registration state self.last_vrf_key_burn_height = burn_block.block_height; diff --git a/testnet/stacks-node/src/node.rs b/testnet/stacks-node/src/node.rs index d16e21bce5..3ef15d45ab 100644 --- a/testnet/stacks-node/src/node.rs +++ b/testnet/stacks-node/src/node.rs @@ -560,7 +560,7 @@ impl Node { let key_reg_op = self.generate_leader_key_register_op(vrf_pk, &consensus_hash); let mut op_signer = self.keychain.generate_op_signer(); let key_txid = burnchain_controller - .submit_operation(cur_epoch.epoch_id, key_reg_op, &mut op_signer, 1) + .submit_operation(cur_epoch.epoch_id, key_reg_op, &mut op_signer) .expect("FATAL: failed to submit leader key register operation"); self.leader_key_registers.insert(key_txid); @@ -768,7 +768,7 @@ impl Node { let mut op_signer = self.keychain.generate_op_signer(); let txid = burnchain_controller - .submit_operation(cur_epoch.epoch_id, op, &mut op_signer, 1) + .submit_operation(cur_epoch.epoch_id, op, &mut op_signer) .expect("FATAL: failed to submit block-commit"); self.block_commits.insert(txid); diff --git a/testnet/stacks-node/src/tests/epoch_205.rs b/testnet/stacks-node/src/tests/epoch_205.rs index a6b47ccc51..882a8dcd4d 100644 --- a/testnet/stacks-node/src/tests/epoch_205.rs +++ b/testnet/stacks-node/src/tests/epoch_205.rs @@ -627,12 +627,8 @@ fn transition_empty_blocks() { commit_outs, }); let mut op_signer = keychain.generate_op_signer(); - let res = bitcoin_controller.submit_operation( - StacksEpochId::Epoch2_05, - op, - &mut op_signer, - 1, - ); + let res = + bitcoin_controller.submit_operation(StacksEpochId::Epoch2_05, op, &mut op_signer); assert!(res.is_ok(), "Failed to submit block-commit"); } diff --git a/testnet/stacks-node/src/tests/epoch_21.rs b/testnet/stacks-node/src/tests/epoch_21.rs index 877b4fa4e0..0d1cbde027 100644 --- a/testnet/stacks-node/src/tests/epoch_21.rs +++ b/testnet/stacks-node/src/tests/epoch_21.rs @@ -670,7 +670,6 @@ fn transition_fixes_bitcoin_rigidity() { StacksEpochId::Epoch2_05, BlockstackOperationType::PreStx(pre_stx_op), &mut miner_signer, - 1 ) .is_ok(), "Pre-stx operation should submit successfully" @@ -705,7 +704,6 @@ fn transition_fixes_bitcoin_rigidity() { StacksEpochId::Epoch2_05, BlockstackOperationType::TransferStx(transfer_stx_op), &mut spender_signer, - 1 ) .is_ok(), "Transfer operation should submit successfully" @@ -826,7 +824,6 @@ fn transition_fixes_bitcoin_rigidity() { StacksEpochId::Epoch21, BlockstackOperationType::PreStx(pre_stx_op), &mut miner_signer, - 1 ) .is_ok(), "Pre-stx operation should submit successfully" @@ -857,7 +854,6 @@ fn transition_fixes_bitcoin_rigidity() { StacksEpochId::Epoch2_05, BlockstackOperationType::TransferStx(transfer_stx_op), &mut spender_signer, - 1 ) .is_ok(), "Transfer operation should submit successfully" @@ -1899,7 +1895,7 @@ fn transition_empty_blocks() { }); let mut op_signer = keychain.generate_op_signer(); let res = - bitcoin_controller.submit_operation(StacksEpochId::Epoch21, op, &mut op_signer, 1); + bitcoin_controller.submit_operation(StacksEpochId::Epoch21, op, &mut op_signer); assert!(res.is_ok(), "Failed to submit block-commit"); } diff --git a/testnet/stacks-node/src/tests/nakamoto_integrations.rs b/testnet/stacks-node/src/tests/nakamoto_integrations.rs index 6adc8255e3..6b9fb9faac 100644 --- a/testnet/stacks-node/src/tests/nakamoto_integrations.rs +++ b/testnet/stacks-node/src/tests/nakamoto_integrations.rs @@ -3489,7 +3489,6 @@ fn vote_for_aggregate_key_burn_op() { StacksEpochId::Epoch30, BlockstackOperationType::PreStx(pre_stx_op), &mut miner_signer, - 1 ) .is_ok(), "Pre-stx operation should submit successfully" @@ -3559,7 +3558,6 @@ fn vote_for_aggregate_key_burn_op() { StacksEpochId::Epoch30, vote_for_aggregate_key_op, &mut signer_burnop_signer, - 1 ) .is_ok(), "Vote for aggregate key operation should submit successfully" @@ -4593,7 +4591,6 @@ fn burn_ops_integration_test() { StacksEpochId::Epoch30, BlockstackOperationType::PreStx(pre_stx_op), &mut miner_signer_1, - 1 ) .is_ok(), "Pre-stx operation should submit successfully" @@ -4617,7 +4614,6 @@ fn burn_ops_integration_test() { StacksEpochId::Epoch30, BlockstackOperationType::PreStx(pre_stx_op_2), &mut miner_signer_2, - 1 ) .is_ok(), "Pre-stx operation should submit successfully" @@ -4638,7 +4634,6 @@ fn burn_ops_integration_test() { StacksEpochId::Epoch30, BlockstackOperationType::PreStx(pre_stx_op_3), &mut miner_signer_3, - 1 ) .is_ok(), "Pre-stx operation should submit successfully" @@ -4659,7 +4654,6 @@ fn burn_ops_integration_test() { StacksEpochId::Epoch30, BlockstackOperationType::PreStx(pre_stx_op_4), &mut miner_signer_4, - 1 ) .is_ok(), "Pre-stx operation should submit successfully" @@ -4790,7 +4784,6 @@ fn burn_ops_integration_test() { StacksEpochId::Epoch30, BlockstackOperationType::TransferStx(transfer_stx_op), &mut stacker_burnop_signer_1, - 1 ) .is_ok(), "Transfer STX operation should submit successfully" @@ -4816,7 +4809,6 @@ fn burn_ops_integration_test() { StacksEpochId::Epoch30, BlockstackOperationType::DelegateStx(del_stx_op), &mut stacker_burnop_signer_2, - 1 ) .is_ok(), "Delegate STX operation should submit successfully" @@ -4846,7 +4838,6 @@ fn burn_ops_integration_test() { StacksEpochId::Epoch30, BlockstackOperationType::StackStx(stack_stx_op_with_some_signer_key), &mut signer_burnop_signer_1, - 1 ) .is_ok(), "Stack STX operation should submit successfully" @@ -4873,7 +4864,6 @@ fn burn_ops_integration_test() { StacksEpochId::Epoch30, BlockstackOperationType::StackStx(stack_stx_op_with_no_signer_key), &mut signer_burnop_signer_2, - 1 ) .is_ok(), "Stack STX operation should submit successfully" diff --git a/testnet/stacks-node/src/tests/neon_integrations.rs b/testnet/stacks-node/src/tests/neon_integrations.rs index ef52c03a15..15933bfa15 100644 --- a/testnet/stacks-node/src/tests/neon_integrations.rs +++ b/testnet/stacks-node/src/tests/neon_integrations.rs @@ -1997,7 +1997,6 @@ fn stx_transfer_btc_integration_test() { StacksEpochId::Epoch2_05, BlockstackOperationType::PreStx(pre_stx_op), &mut miner_signer, - 1 ) .is_ok(), "Pre-stx operation should submit successfully" @@ -2027,7 +2026,6 @@ fn stx_transfer_btc_integration_test() { StacksEpochId::Epoch2_05, BlockstackOperationType::TransferStx(transfer_stx_op), &mut spender_signer, - 1 ) .is_ok(), "Transfer operation should submit successfully" @@ -2266,7 +2264,6 @@ fn stx_delegate_btc_integration_test() { StacksEpochId::Epoch21, BlockstackOperationType::PreStx(pre_stx_op), &mut miner_signer, - 1 ) .is_ok(), "Pre-stx operation should submit successfully" @@ -2295,7 +2292,6 @@ fn stx_delegate_btc_integration_test() { StacksEpochId::Epoch21, BlockstackOperationType::DelegateStx(del_stx_op), &mut spender_signer, - 1 ) .is_ok(), "Delegate operation should submit successfully" @@ -2555,7 +2551,6 @@ fn stack_stx_burn_op_test() { StacksEpochId::Epoch25, BlockstackOperationType::PreStx(pre_stx_op_1), &mut miner_signer_1, - 1 ) .is_ok(), "Pre-stx operation should submit successfully" @@ -2576,7 +2571,6 @@ fn stack_stx_burn_op_test() { StacksEpochId::Epoch25, BlockstackOperationType::PreStx(pre_stx_op_2), &mut miner_signer_2, - 1 ) .is_ok(), "Pre-stx operation should submit successfully" @@ -2663,7 +2657,6 @@ fn stack_stx_burn_op_test() { StacksEpochId::Epoch25, stack_stx_op_with_some_signer_key, &mut spender_signer_1, - 1 ) .is_ok(), "Stack STX operation with some signer key should submit successfully" @@ -2691,7 +2684,6 @@ fn stack_stx_burn_op_test() { StacksEpochId::Epoch25, stack_stx_op_with_no_signer_key, &mut spender_signer_2, - 1 ) .is_ok(), "Stack STX operation with no signer key should submit successfully" @@ -2991,7 +2983,6 @@ fn vote_for_aggregate_key_burn_op_test() { StacksEpochId::Epoch25, BlockstackOperationType::PreStx(pre_stx_op), &mut miner_signer, - 1 ) .is_ok(), "Pre-stx operation should submit successfully" @@ -3048,7 +3039,6 @@ fn vote_for_aggregate_key_burn_op_test() { StacksEpochId::Epoch25, vote_for_aggregate_key_op, &mut spender_signer, - 1 ) .is_ok(), "Vote for aggregate key operation should submit successfully" diff --git a/testnet/stacks-node/src/tests/signer/v0.rs b/testnet/stacks-node/src/tests/signer/v0.rs index b04a4144e0..b0e8a6a867 100644 --- a/testnet/stacks-node/src/tests/signer/v0.rs +++ b/testnet/stacks-node/src/tests/signer/v0.rs @@ -3669,7 +3669,6 @@ fn tx_replay_btc_on_stx_invalidation() { StacksEpochId::Epoch30, BlockstackOperationType::PreStx(pre_stx_op), &mut miner_keychain, - 1 ) .is_ok(), "Pre-stx operation should submit successfully" @@ -3698,8 +3697,7 @@ fn tx_replay_btc_on_stx_invalidation() { .submit_operation( StacksEpochId::Epoch30, BlockstackOperationType::TransferStx(transfer_stx_op), - &mut sender_burnop_signer, - 1 + &mut sender_burnop_signer ) .is_ok(), "Transfer STX operation should submit successfully" From 1497673621b1346b9a257a894cd95fcb23b46e44 Mon Sep 17 00:00:00 2001 From: Federico De Felici Date: Thu, 3 Jul 2025 13:17:09 +0200 Subject: [PATCH 07/12] test: add leader key op tests --- .../burnchains/bitcoin_regtest_controller.rs | 186 ++++++++++-------- 1 file changed, 107 insertions(+), 79 deletions(-) diff --git a/testnet/stacks-node/src/burnchains/bitcoin_regtest_controller.rs b/testnet/stacks-node/src/burnchains/bitcoin_regtest_controller.rs index 6078ae0b7f..8b549ee1d7 100644 --- a/testnet/stacks-node/src/burnchains/bitcoin_regtest_controller.rs +++ b/testnet/stacks-node/src/burnchains/bitcoin_regtest_controller.rs @@ -3809,61 +3809,8 @@ mod tests { assert_eq!(op_change, rbf_tx.output[3]); } - #[test] - #[ignore] - fn test_build_leader_key_tx_ok() { - if env::var("BITCOIND_TEST") != Ok("1".into()) { - return; - } - - let keychain = utils::create_keychain(); - let miner_pubkey = keychain.get_pub_key(); - let mut op_signer = keychain.generate_op_signer(); - - let mut config = utils::create_config(); - config.burnchain.local_mining_public_key = Some(miner_pubkey.to_hex()); - - let mut btcd_controller = BitcoinCoreController::new(config.clone()); - btcd_controller - .start_bitcoind() - .expect("bitcoind should be started!"); - - let mut btc_controller = BitcoinRegtestController::new(config.clone(), None); - btc_controller - .connect_dbs() - .expect("Dbs initialization required!"); - btc_controller.bootstrap_chain(101); // now, one utxo exists - - let leader_key_op = utils::create_templated_leader_key_op(); - - let tx = btc_controller - .build_leader_key_register_tx( - StacksEpochId::Epoch31, - leader_key_op.clone(), - &mut op_signer, - ) - .expect("Build leader key should work"); - - assert!(op_signer.is_disposed()); - - assert_eq!(1, tx.version); - assert_eq!(0, tx.lock_time); - assert_eq!(1, tx.input.len()); - assert_eq!(2, tx.output.len()); - - // utxos list contains the only existing utxo - let used_utxos = btc_controller.get_all_utxos(&miner_pubkey); - let input_0 = utils::txin_at_index(&tx, &op_signer, &used_utxos, 0); - assert_eq!(input_0, tx.input[0]); - - let op_return = utils::txout_opreturn_v2(&leader_key_op, &config.burnchain.magic_bytes, 0); - let op_change = utils::txout_opdup_change_legacy(&mut op_signer, 4_999_980_000); - assert_eq!(op_return, tx.output[0]); - assert_eq!(op_change, tx.output[1]); - } - - /// Tests related to `BitcoinRegtestController::make_operation_tx` - mod make_operation { + /// Tests related to Leader Block Commit operation + mod leader_commit_op { use super::*; #[test] @@ -3913,7 +3860,7 @@ mod tests { #[test] #[ignore] - fn test_make_operation_leader_key_register_tx_ok() { + fn test_submit_leader_block_commit_tx_ok() { if env::var("BITCOIND_TEST") != Ok("1".into()) { return; } @@ -3936,12 +3883,14 @@ mod tests { .expect("Dbs initialization required!"); btc_controller.bootstrap_chain(101); // now, one utxo exists - let leader_key_op = utils::create_templated_leader_key_op(); + let mut commit_op = utils::create_templated_commit_op(); + commit_op.sunset_burn = 5_500; + commit_op.burn_fee = 110_000; - let ser_tx = btc_controller - .make_operation_tx( + let tx_id = btc_controller + .submit_operation( StacksEpochId::Epoch31, - BlockstackOperationType::LeaderKeyRegister(leader_key_op), + BlockstackOperationType::LeaderBlockCommit(commit_op), &mut op_signer, ) .expect("Build leader block commit should work"); @@ -3949,19 +3898,19 @@ mod tests { assert!(op_signer.is_disposed()); assert_eq!( - "01000000014d9e9dc7d126446e90dd013f023937eba9cb2c88f4d12707400a3ede994a62c5000000008b483045022100c8694688b4269585ef63bfeb96d017bafae02621ebd0b5012e7564d3efcb71f70220070528674f75ca3503246030f064a85d2010256336372b246100f29ba21bf28b0141044227d7e5c0997524ce011c126f0464d43e7518872a9b1ad29436ac5142d73eab5fb48d764676900fc2fac56917412114bf7dfafe51f715cf466fe0c1a6c69d11fdffffff020000000000000000396a3754335e00000000000000000000000000000000000000003b6a27bcceb6a42d62a3a8d02a6f0d73653215771de243a63ac048a18b59da29e0a3052a010000001976a9145e52c53cb96b55f0e3d719adbca21005bc54cb2e88ac00000000", - ser_tx.to_hex() + "1a74106bd760117892fbd90fca11646b4de46f99fd2b065c9e0706cfdcea0336", + tx_id.to_hex() ); } } - /// Tests related to `BitcoinRegtestController::submit_operation` - mod submit_operation { + /// Tests related to Leader Key Register operation + mod leader_key_op { use super::*; #[test] #[ignore] - fn test_submit_leader_block_commit_tx_ok() { + fn test_build_leader_key_tx_ok() { if env::var("BITCOIND_TEST") != Ok("1".into()) { return; } @@ -3979,19 +3928,101 @@ mod tests { .expect("bitcoind should be started!"); let mut btc_controller = BitcoinRegtestController::new(config.clone(), None); - btc_controller - .connect_dbs() - .expect("Dbs initialization required!"); btc_controller.bootstrap_chain(101); // now, one utxo exists - let mut commit_op = utils::create_templated_commit_op(); - commit_op.sunset_burn = 5_500; - commit_op.burn_fee = 110_000; + let leader_key_op = utils::create_templated_leader_key_op(); - let tx_id = btc_controller - .submit_operation( + let tx = btc_controller + .build_leader_key_register_tx( StacksEpochId::Epoch31, - BlockstackOperationType::LeaderBlockCommit(commit_op), + leader_key_op.clone(), + &mut op_signer, + ) + .expect("Build leader key should work"); + + assert!(op_signer.is_disposed()); + + assert_eq!(1, tx.version); + assert_eq!(0, tx.lock_time); + assert_eq!(1, tx.input.len()); + assert_eq!(2, tx.output.len()); + + // utxos list contains the only existing utxo + let used_utxos = btc_controller.get_all_utxos(&miner_pubkey); + let input_0 = utils::txin_at_index(&tx, &op_signer, &used_utxos, 0); + assert_eq!(input_0, tx.input[0]); + + let op_return = + utils::txout_opreturn_v2(&leader_key_op, &config.burnchain.magic_bytes, 0); + let op_change = utils::txout_opdup_change_legacy(&mut op_signer, 4_999_980_000); + assert_eq!(op_return, tx.output[0]); + assert_eq!(op_change, tx.output[1]); + } + + #[test] + #[ignore] + fn test_build_leader_key_tx_fails_due_to_no_utxos() { + if env::var("BITCOIND_TEST") != Ok("1".into()) { + return; + } + + let keychain = utils::create_keychain(); + let miner_pubkey = keychain.get_pub_key(); + let mut op_signer = keychain.generate_op_signer(); + + let mut config = utils::create_config(); + config.burnchain.local_mining_public_key = Some(miner_pubkey.to_hex()); + + let mut btcd_controller = BitcoinCoreController::new(config.clone()); + btcd_controller + .start_bitcoind() + .expect("bitcoind should be started!"); + + let mut btc_controller = BitcoinRegtestController::new(config.clone(), None); + btc_controller.bootstrap_chain(100); // no utxos exist + + let leader_key_op = utils::create_templated_leader_key_op(); + + let error = btc_controller + .build_leader_key_register_tx( + StacksEpochId::Epoch31, + leader_key_op.clone(), + &mut op_signer, + ) + .expect_err("Leader key build should fail!"); + + assert!(!op_signer.is_disposed()); + assert_eq!(BurnchainControllerError::NoUTXOs, error); + } + + #[test] + #[ignore] + fn test_make_operation_leader_key_register_tx_ok() { + if env::var("BITCOIND_TEST") != Ok("1".into()) { + return; + } + + let keychain = utils::create_keychain(); + let miner_pubkey = keychain.get_pub_key(); + let mut op_signer = keychain.generate_op_signer(); + + let mut config = utils::create_config(); + config.burnchain.local_mining_public_key = Some(miner_pubkey.to_hex()); + + let mut btcd_controller = BitcoinCoreController::new(config.clone()); + btcd_controller + .start_bitcoind() + .expect("bitcoind should be started!"); + + let mut btc_controller = BitcoinRegtestController::new(config.clone(), None); + btc_controller.bootstrap_chain(101); // now, one utxo exists + + let leader_key_op = utils::create_templated_leader_key_op(); + + let ser_tx = btc_controller + .make_operation_tx( + StacksEpochId::Epoch31, + BlockstackOperationType::LeaderKeyRegister(leader_key_op), &mut op_signer, ) .expect("Build leader block commit should work"); @@ -3999,8 +4030,8 @@ mod tests { assert!(op_signer.is_disposed()); assert_eq!( - "1a74106bd760117892fbd90fca11646b4de46f99fd2b065c9e0706cfdcea0336", - tx_id.to_hex() + "01000000014d9e9dc7d126446e90dd013f023937eba9cb2c88f4d12707400a3ede994a62c5000000008b483045022100c8694688b4269585ef63bfeb96d017bafae02621ebd0b5012e7564d3efcb71f70220070528674f75ca3503246030f064a85d2010256336372b246100f29ba21bf28b0141044227d7e5c0997524ce011c126f0464d43e7518872a9b1ad29436ac5142d73eab5fb48d764676900fc2fac56917412114bf7dfafe51f715cf466fe0c1a6c69d11fdffffff020000000000000000396a3754335e00000000000000000000000000000000000000003b6a27bcceb6a42d62a3a8d02a6f0d73653215771de243a63ac048a18b59da29e0a3052a010000001976a9145e52c53cb96b55f0e3d719adbca21005bc54cb2e88ac00000000", + ser_tx.to_hex() ); } @@ -4024,9 +4055,6 @@ mod tests { .expect("bitcoind should be started!"); let mut btc_controller = BitcoinRegtestController::new(config.clone(), None); - btc_controller - .connect_dbs() - .expect("Dbs initialization required!"); btc_controller.bootstrap_chain(101); // now, one utxo exists let leader_key_op = utils::create_templated_leader_key_op(); From eb95ccffe9541bfe1009eb903c937e00e1a91b81 Mon Sep 17 00:00:00 2001 From: Federico De Felici Date: Thu, 3 Jul 2025 15:05:44 +0200 Subject: [PATCH 08/12] test: add test for pre_stx_op --- .../burnchains/bitcoin_regtest_controller.rs | 181 +++++++++++++++++- 1 file changed, 179 insertions(+), 2 deletions(-) diff --git a/testnet/stacks-node/src/burnchains/bitcoin_regtest_controller.rs b/testnet/stacks-node/src/burnchains/bitcoin_regtest_controller.rs index 8b549ee1d7..78fd3542fe 100644 --- a/testnet/stacks-node/src/burnchains/bitcoin_regtest_controller.rs +++ b/testnet/stacks-node/src/burnchains/bitcoin_regtest_controller.rs @@ -3088,6 +3088,16 @@ mod tests { .into_script(), } } + + pub fn create_templated_pre_stx_op() -> PreStxOp { + PreStxOp { + output: StacksAddress::p2pkh_from_hash(false, Hash160::from_data(&[2u8; 20])), + txid: Txid([0u8; 32]), + vtxindex: 0, + block_height: 0, + burn_header_hash: BurnchainHeaderHash([0u8; 32]), + } + } } #[test] @@ -3848,7 +3858,7 @@ mod tests { BlockstackOperationType::LeaderBlockCommit(commit_op), &mut op_signer, ) - .expect("Build leader block commit should work"); + .expect("Make op should work"); assert!(op_signer.is_disposed()); @@ -4025,7 +4035,7 @@ mod tests { BlockstackOperationType::LeaderKeyRegister(leader_key_op), &mut op_signer, ) - .expect("Build leader block commit should work"); + .expect("Make op should work"); assert!(op_signer.is_disposed()); @@ -4075,4 +4085,171 @@ mod tests { ); } } + + /// Tests related to Pre Stacks operation + mod pre_stx_op { + use super::*; + + #[test] + #[ignore] + fn test_build_pre_stx_tx_ok() { + if env::var("BITCOIND_TEST") != Ok("1".into()) { + return; + } + + let keychain = utils::create_keychain(); + let miner_pubkey = keychain.get_pub_key(); + let mut op_signer = keychain.generate_op_signer(); + + let mut config = utils::create_config(); + config.burnchain.local_mining_public_key = Some(miner_pubkey.to_hex()); + + let mut btcd_controller = BitcoinCoreController::new(config.clone()); + btcd_controller + .start_bitcoind() + .expect("bitcoind should be started!"); + + let mut btc_controller = BitcoinRegtestController::new(config.clone(), None); + btc_controller.bootstrap_chain(101); // now, one utxo exists + + let mut pre_stx_op = utils::create_templated_pre_stx_op(); + pre_stx_op.output = keychain.get_address(false); + + let tx = btc_controller + .build_pre_stacks_tx(StacksEpochId::Epoch31, pre_stx_op.clone(), &mut op_signer) + .expect("Build leader key should work"); + + assert!(op_signer.is_disposed()); + + assert_eq!(1, tx.version); + assert_eq!(0, tx.lock_time); + assert_eq!(1, tx.input.len()); + assert_eq!(3, tx.output.len()); + + // utxos list contains the only existing utxo + let used_utxos = btc_controller.get_all_utxos(&miner_pubkey); + let input_0 = utils::txin_at_index(&tx, &op_signer, &used_utxos, 0); + assert_eq!(input_0, tx.input[0]); + + let op_return = utils::txout_opreturn_v2(&pre_stx_op, &config.burnchain.magic_bytes, 0); + let op_change = utils::txout_opdup_change_legacy(&mut op_signer, 24_500); + assert_eq!(op_return, tx.output[0]); + assert_eq!(op_change, tx.output[1]); + } + + #[test] + #[ignore] + fn test_build_pre_stx_tx_fails_due_to_no_utxos() { + if env::var("BITCOIND_TEST") != Ok("1".into()) { + return; + } + + let keychain = utils::create_keychain(); + let miner_pubkey = keychain.get_pub_key(); + let mut op_signer = keychain.generate_op_signer(); + + let mut config = utils::create_config(); + config.burnchain.local_mining_public_key = Some(miner_pubkey.to_hex()); + + let mut btcd_controller = BitcoinCoreController::new(config.clone()); + btcd_controller + .start_bitcoind() + .expect("bitcoind should be started!"); + + let mut btc_controller = BitcoinRegtestController::new(config.clone(), None); + btc_controller.bootstrap_chain(100); // no utxo exists + + let mut pre_stx_op = utils::create_templated_pre_stx_op(); + pre_stx_op.output = keychain.get_address(false); + + let error = btc_controller + .build_pre_stacks_tx(StacksEpochId::Epoch31, pre_stx_op.clone(), &mut op_signer) + .expect_err("Leader key build should fail!"); + + assert!(!op_signer.is_disposed()); + assert_eq!(BurnchainControllerError::NoUTXOs, error); + } + + #[test] + #[ignore] + fn test_make_operation_pre_stx_tx_ok() { + if env::var("BITCOIND_TEST") != Ok("1".into()) { + return; + } + + let keychain = utils::create_keychain(); + let miner_pubkey = keychain.get_pub_key(); + let mut op_signer = keychain.generate_op_signer(); + + let mut config = utils::create_config(); + config.burnchain.local_mining_public_key = Some(miner_pubkey.to_hex()); + + let mut btcd_controller = BitcoinCoreController::new(config.clone()); + btcd_controller + .start_bitcoind() + .expect("bitcoind should be started!"); + + let mut btc_controller = BitcoinRegtestController::new(config.clone(), None); + btc_controller.bootstrap_chain(101); // now, one utxo exists + + let mut pre_stx_op = utils::create_templated_pre_stx_op(); + pre_stx_op.output = keychain.get_address(false); + + let ser_tx = btc_controller + .make_operation_tx( + StacksEpochId::Epoch31, + BlockstackOperationType::PreStx(pre_stx_op), + &mut op_signer, + ) + .expect("Make op should work"); + + assert!(op_signer.is_disposed()); + + assert_eq!( + "01000000014d9e9dc7d126446e90dd013f023937eba9cb2c88f4d12707400a3ede994a62c5000000008a47304402203351a9351e887f4b66023893f55e308c3c345aec6f50dd3bd11fc90b7049703102200fbbf08747e4961ec8e0a5f5e991fa5f709cac9083d22772cd48e9325a48bba30141044227d7e5c0997524ce011c126f0464d43e7518872a9b1ad29436ac5142d73eab5fb48d764676900fc2fac56917412114bf7dfafe51f715cf466fe0c1a6c69d11fdffffff030000000000000000056a03543370b45f0000000000001976a9145e52c53cb96b55f0e3d719adbca21005bc54cb2e88ac9c5b052a010000001976a9145e52c53cb96b55f0e3d719adbca21005bc54cb2e88ac00000000", + ser_tx.to_hex() + ); + } + + #[test] + #[ignore] + fn test_submit_operation_pre_stx_tx_ok() { + if env::var("BITCOIND_TEST") != Ok("1".into()) { + return; + } + + let keychain = utils::create_keychain(); + let miner_pubkey = keychain.get_pub_key(); + let mut op_signer = keychain.generate_op_signer(); + + let mut config = utils::create_config(); + config.burnchain.local_mining_public_key = Some(miner_pubkey.to_hex()); + + let mut btcd_controller = BitcoinCoreController::new(config.clone()); + btcd_controller + .start_bitcoind() + .expect("bitcoind should be started!"); + + let mut btc_controller = BitcoinRegtestController::new(config.clone(), None); + btc_controller.bootstrap_chain(101); // now, one utxo exists + + let mut pre_stx_op = utils::create_templated_pre_stx_op(); + pre_stx_op.output = keychain.get_address(false); + + let tx_id = btc_controller + .submit_operation( + StacksEpochId::Epoch31, + BlockstackOperationType::PreStx(pre_stx_op), + &mut op_signer, + ) + .expect("submit op should work"); + + assert!(op_signer.is_disposed()); + + assert_eq!( + "2d061c42c6f13a62fd9d80dc9fdcd19bdb4f9e4a07f786e42530c64c52ed9d1d", + tx_id.to_hex() + ); + } + } } From 94b118cda9747c40f6206b0f88279bee8cf24f6c Mon Sep 17 00:00:00 2001 From: Federico De Felici Date: Thu, 3 Jul 2025 15:20:45 +0200 Subject: [PATCH 09/12] refactor: remove llow_rbf field from BitcoinRegtestController --- .../src/burnchains/bitcoin_regtest_controller.rs | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/testnet/stacks-node/src/burnchains/bitcoin_regtest_controller.rs b/testnet/stacks-node/src/burnchains/bitcoin_regtest_controller.rs index 78fd3542fe..5d77190ac9 100644 --- a/testnet/stacks-node/src/burnchains/bitcoin_regtest_controller.rs +++ b/testnet/stacks-node/src/burnchains/bitcoin_regtest_controller.rs @@ -101,7 +101,6 @@ pub struct BitcoinRegtestController { burnchain_config: Option, ongoing_block_commit: Option, should_keep_running: Option>, - allow_rbf: bool, } #[derive(Clone)] @@ -357,7 +356,6 @@ impl BitcoinRegtestController { burnchain_config: burnchain, ongoing_block_commit: None, should_keep_running, - allow_rbf: true, } } @@ -403,7 +401,6 @@ impl BitcoinRegtestController { burnchain_config: None, ongoing_block_commit: None, should_keep_running: None, - allow_rbf: true, } } @@ -748,19 +745,14 @@ impl BitcoinRegtestController { // Configure UTXO filter let address = self.get_miner_address(epoch_id, &pubk); - test_debug!( - "Get UTXOs for {} ({}) rbf={}", - pubk.to_hex(), - addr2str(&address), - self.allow_rbf - ); + test_debug!("Get UTXOs for {} ({})", pubk.to_hex(), addr2str(&address),); let filter_addresses = vec![addr2str(&address)]; let mut utxos = loop { let result = BitcoinRPCRequest::list_unspent( &self.config, filter_addresses.clone(), - !self.allow_rbf, // if RBF is disabled, then we can use 0-conf txs + false, total_required, &utxos_to_exclude, block_height, @@ -794,7 +786,7 @@ impl BitcoinRegtestController { let result = BitcoinRPCRequest::list_unspent( &self.config, filter_addresses.clone(), - !self.allow_rbf, // if RBF is disabled, then we can use 0-conf txs + false, total_required, &utxos_to_exclude, block_height, @@ -1510,7 +1502,7 @@ impl BitcoinRegtestController { signer: &mut BurnchainOpSigner, ) -> Result { // Are we currently tracking an operation? - if self.ongoing_block_commit.is_none() || !self.allow_rbf { + if self.ongoing_block_commit.is_none() { // Good to go, let's build the transaction and send it. let res = self.send_block_commit_operation(epoch_id, payload, signer, None, None, None, &[]); From 69bf6660aef78358bdcc599c4f9b6a1f278fdc8a Mon Sep 17 00:00:00 2001 From: Federico De Felici Date: Thu, 3 Jul 2025 15:23:56 +0200 Subject: [PATCH 10/12] chore: improve test messages --- .../stacks-node/src/burnchains/bitcoin_regtest_controller.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/testnet/stacks-node/src/burnchains/bitcoin_regtest_controller.rs b/testnet/stacks-node/src/burnchains/bitcoin_regtest_controller.rs index 5d77190ac9..0bc541cc43 100644 --- a/testnet/stacks-node/src/burnchains/bitcoin_regtest_controller.rs +++ b/testnet/stacks-node/src/burnchains/bitcoin_regtest_controller.rs @@ -3895,7 +3895,7 @@ mod tests { BlockstackOperationType::LeaderBlockCommit(commit_op), &mut op_signer, ) - .expect("Build leader block commit should work"); + .expect("Submit op should work"); assert!(op_signer.is_disposed()); @@ -4067,7 +4067,7 @@ mod tests { BlockstackOperationType::LeaderKeyRegister(leader_key_op), &mut op_signer, ) - .expect("Build leader block commit should work"); + .expect("Submit op should work"); assert!(op_signer.is_disposed()); From 374aa420c4c653581b152f1b6cd56ec2113b2664 Mon Sep 17 00:00:00 2001 From: Federico De Felici Date: Tue, 8 Jul 2025 11:45:37 +0200 Subject: [PATCH 11/12] refactor: improve utils::txout_opreturn, #6253 --- .../burnchains/bitcoin_regtest_controller.rs | 41 +++++-------------- 1 file changed, 11 insertions(+), 30 deletions(-) diff --git a/testnet/stacks-node/src/burnchains/bitcoin_regtest_controller.rs b/testnet/stacks-node/src/burnchains/bitcoin_regtest_controller.rs index 0bc541cc43..ad85410915 100644 --- a/testnet/stacks-node/src/burnchains/bitcoin_regtest_controller.rs +++ b/testnet/stacks-node/src/burnchains/bitcoin_regtest_controller.rs @@ -2936,7 +2936,11 @@ mod tests { } } - pub fn txout_opreturn(op: &LeaderBlockCommitOp, magic: &MagicBytes) -> TxOut { + pub fn txout_opreturn( + op: &T, + magic: &MagicBytes, + value: u64, + ) -> TxOut { let op_bytes = { let mut buffer = vec![]; let mut magic_bytes = magic.as_bytes().to_vec(); @@ -2947,7 +2951,7 @@ mod tests { }; TxOut { - value: op.sunset_burn, + value, script_pubkey: Builder::new() .push_opcode(opcodes::All::OP_RETURN) .push_slice(&op_bytes) @@ -3058,29 +3062,6 @@ mod tests { } } - pub fn txout_opreturn_v2( - op: &T, - magic: &MagicBytes, - value: u64, - ) -> TxOut { - let op_bytes = { - let mut buffer = vec![]; - let mut magic_bytes = magic.as_bytes().to_vec(); - buffer.append(&mut magic_bytes); - op.consensus_serialize(&mut buffer) - .expect("FATAL: invalid operation"); - buffer - }; - - TxOut { - value, - script_pubkey: Builder::new() - .push_opcode(opcodes::All::OP_RETURN) - .push_slice(&op_bytes) - .into_script(), - } - } - pub fn create_templated_pre_stx_op() -> PreStxOp { PreStxOp { output: StacksAddress::p2pkh_from_hash(false, Hash160::from_data(&[2u8; 20])), @@ -3569,7 +3550,7 @@ mod tests { let input_0 = utils::txin_at_index(&tx, &op_signer, &used_utxos, 0); assert_eq!(input_0, tx.input[0]); - let op_return = utils::txout_opreturn(&commit_op, &config.burnchain.magic_bytes); + let op_return = utils::txout_opreturn(&commit_op, &config.burnchain.magic_bytes, 5_500); let op_commit_1 = utils::txout_opdup_commit_to(&commit_op.commit_outs[0], 55_000); let op_commit_2 = utils::txout_opdup_commit_to(&commit_op.commit_outs[1], 55_000); let op_change = utils::txout_opdup_change_legacy(&mut op_signer, 4_999_865_300); @@ -3736,7 +3717,7 @@ mod tests { let input_0 = utils::txin_at_index(&rbf_tx, &op_signer, &used_utxos, 0); assert_eq!(input_0, rbf_tx.input[0]); - let op_return = utils::txout_opreturn(&commit_op, &config.burnchain.magic_bytes); + let op_return = utils::txout_opreturn(&commit_op, &config.burnchain.magic_bytes, 5_500); let op_commit_1 = utils::txout_opdup_commit_to(&commit_op.commit_outs[0], 55_005); let op_commit_2 = utils::txout_opdup_commit_to(&commit_op.commit_outs[1], 55_005); let op_change = utils::txout_opdup_change_legacy(&mut signer, 4_999_730_590); @@ -3801,7 +3782,7 @@ mod tests { let input_0 = utils::txin_at_index(&rbf_tx, &op_signer, &used_utxos, 0); assert_eq!(input_0, rbf_tx.input[0]); - let op_return = utils::txout_opreturn(&commit_op, &config.burnchain.magic_bytes); + let op_return = utils::txout_opreturn(&commit_op, &config.burnchain.magic_bytes, 5_500); let op_commit_1 = utils::txout_opdup_commit_to(&commit_op.commit_outs[0], 55_005); let op_commit_2 = utils::txout_opdup_commit_to(&commit_op.commit_outs[1], 55_005); let op_change = utils::txout_opdup_change_legacy(&mut signer, 4_999_862_985); @@ -3955,7 +3936,7 @@ mod tests { assert_eq!(input_0, tx.input[0]); let op_return = - utils::txout_opreturn_v2(&leader_key_op, &config.burnchain.magic_bytes, 0); + utils::txout_opreturn(&leader_key_op, &config.burnchain.magic_bytes, 0); let op_change = utils::txout_opdup_change_legacy(&mut op_signer, 4_999_980_000); assert_eq!(op_return, tx.output[0]); assert_eq!(op_change, tx.output[1]); @@ -4123,7 +4104,7 @@ mod tests { let input_0 = utils::txin_at_index(&tx, &op_signer, &used_utxos, 0); assert_eq!(input_0, tx.input[0]); - let op_return = utils::txout_opreturn_v2(&pre_stx_op, &config.burnchain.magic_bytes, 0); + let op_return = utils::txout_opreturn(&pre_stx_op, &config.burnchain.magic_bytes, 0); let op_change = utils::txout_opdup_change_legacy(&mut op_signer, 24_500); assert_eq!(op_return, tx.output[0]); assert_eq!(op_change, tx.output[1]); From 0fcd9758c9c9e4cb55978986e3b7954b2fd523c5 Mon Sep 17 00:00:00 2001 From: Federico De Felici Date: Tue, 8 Jul 2025 11:53:59 +0200 Subject: [PATCH 12/12] chore: moved commit-op tests under related test module, #6253 --- .../burnchains/bitcoin_regtest_controller.rs | 535 +++++++++--------- 1 file changed, 281 insertions(+), 254 deletions(-) diff --git a/testnet/stacks-node/src/burnchains/bitcoin_regtest_controller.rs b/testnet/stacks-node/src/burnchains/bitcoin_regtest_controller.rs index ad85410915..483caa1f7d 100644 --- a/testnet/stacks-node/src/burnchains/bitcoin_regtest_controller.rs +++ b/testnet/stacks-node/src/burnchains/bitcoin_regtest_controller.rs @@ -3505,296 +3505,324 @@ mod tests { ); } - #[test] - #[ignore] - fn test_build_leader_block_commit_tx_ok_with_new_commit_op() { - if env::var("BITCOIND_TEST") != Ok("1".into()) { - return; - } + /// Tests related to Leader Block Commit operation + mod leader_commit_op { + use super::*; - let keychain = utils::create_keychain(); - let miner_pubkey = keychain.get_pub_key(); - let mut op_signer = keychain.generate_op_signer(); + #[test] + #[ignore] + fn test_build_leader_block_commit_tx_ok_with_new_commit_op() { + if env::var("BITCOIND_TEST") != Ok("1".into()) { + return; + } - let mut config = utils::create_config(); - config.burnchain.local_mining_public_key = Some(miner_pubkey.to_hex()); + let keychain = utils::create_keychain(); + let miner_pubkey = keychain.get_pub_key(); + let mut op_signer = keychain.generate_op_signer(); - let mut btcd_controller = BitcoinCoreController::new(config.clone()); - btcd_controller - .start_bitcoind() - .expect("bitcoind should be started!"); + let mut config = utils::create_config(); + config.burnchain.local_mining_public_key = Some(miner_pubkey.to_hex()); - let mut btc_controller = BitcoinRegtestController::new(config.clone(), None); - btc_controller - .connect_dbs() - .expect("Dbs initialization required!"); - btc_controller.bootstrap_chain(101); // now, one utxo exists - - let mut commit_op = utils::create_templated_commit_op(); - commit_op.sunset_burn = 5_500; - commit_op.burn_fee = 110_000; - - let tx = btc_controller - .build_leader_block_commit_tx(StacksEpochId::Epoch31, commit_op.clone(), &mut op_signer) - .expect("Build leader block commit should work"); - - assert!(op_signer.is_disposed()); - - assert_eq!(1, tx.version); - assert_eq!(0, tx.lock_time); - assert_eq!(1, tx.input.len()); - assert_eq!(4, tx.output.len()); - - // utxos list contains the only existing utxo - let used_utxos = btc_controller.get_all_utxos(&miner_pubkey); - let input_0 = utils::txin_at_index(&tx, &op_signer, &used_utxos, 0); - assert_eq!(input_0, tx.input[0]); - - let op_return = utils::txout_opreturn(&commit_op, &config.burnchain.magic_bytes, 5_500); - let op_commit_1 = utils::txout_opdup_commit_to(&commit_op.commit_outs[0], 55_000); - let op_commit_2 = utils::txout_opdup_commit_to(&commit_op.commit_outs[1], 55_000); - let op_change = utils::txout_opdup_change_legacy(&mut op_signer, 4_999_865_300); - assert_eq!(op_return, tx.output[0]); - assert_eq!(op_commit_1, tx.output[1]); - assert_eq!(op_commit_2, tx.output[2]); - assert_eq!(op_change, tx.output[3]); - } + let mut btcd_controller = BitcoinCoreController::new(config.clone()); + btcd_controller + .start_bitcoind() + .expect("bitcoind should be started!"); - #[test] - #[ignore] - fn test_build_leader_block_commit_tx_fails_resub_same_commit_op_while_prev_not_confirmed() { - if env::var("BITCOIND_TEST") != Ok("1".into()) { - return; + let mut btc_controller = BitcoinRegtestController::new(config.clone(), None); + btc_controller + .connect_dbs() + .expect("Dbs initialization required!"); + btc_controller.bootstrap_chain(101); // now, one utxo exists + + let mut commit_op = utils::create_templated_commit_op(); + commit_op.sunset_burn = 5_500; + commit_op.burn_fee = 110_000; + + let tx = btc_controller + .build_leader_block_commit_tx( + StacksEpochId::Epoch31, + commit_op.clone(), + &mut op_signer, + ) + .expect("Build leader block commit should work"); + + assert!(op_signer.is_disposed()); + + assert_eq!(1, tx.version); + assert_eq!(0, tx.lock_time); + assert_eq!(1, tx.input.len()); + assert_eq!(4, tx.output.len()); + + // utxos list contains the only existing utxo + let used_utxos = btc_controller.get_all_utxos(&miner_pubkey); + let input_0 = utils::txin_at_index(&tx, &op_signer, &used_utxos, 0); + assert_eq!(input_0, tx.input[0]); + + let op_return = utils::txout_opreturn(&commit_op, &config.burnchain.magic_bytes, 5_500); + let op_commit_1 = utils::txout_opdup_commit_to(&commit_op.commit_outs[0], 55_000); + let op_commit_2 = utils::txout_opdup_commit_to(&commit_op.commit_outs[1], 55_000); + let op_change = utils::txout_opdup_change_legacy(&mut op_signer, 4_999_865_300); + assert_eq!(op_return, tx.output[0]); + assert_eq!(op_commit_1, tx.output[1]); + assert_eq!(op_commit_2, tx.output[2]); + assert_eq!(op_change, tx.output[3]); } - let keychain = utils::create_keychain(); - let miner_pubkey = keychain.get_pub_key(); - let mut op_signer = keychain.generate_op_signer(); + #[test] + #[ignore] + fn test_build_leader_block_commit_tx_fails_resub_same_commit_op_while_prev_not_confirmed() { + if env::var("BITCOIND_TEST") != Ok("1".into()) { + return; + } - let mut config = utils::create_config(); - config.burnchain.local_mining_public_key = Some(miner_pubkey.to_hex()); + let keychain = utils::create_keychain(); + let miner_pubkey = keychain.get_pub_key(); + let mut op_signer = keychain.generate_op_signer(); - let mut btcd_controller = BitcoinCoreController::new(config.clone()); - btcd_controller - .start_bitcoind() - .expect("bitcoind should be started!"); + let mut config = utils::create_config(); + config.burnchain.local_mining_public_key = Some(miner_pubkey.to_hex()); - let mut btc_controller = BitcoinRegtestController::new(config, None); - btc_controller - .connect_dbs() - .expect("Dbs initialization required!"); - btc_controller.bootstrap_chain(101); // now, one utxo exists + let mut btcd_controller = BitcoinCoreController::new(config.clone()); + btcd_controller + .start_bitcoind() + .expect("bitcoind should be started!"); - let commit_op = utils::create_templated_commit_op(); + let mut btc_controller = BitcoinRegtestController::new(config, None); + btc_controller + .connect_dbs() + .expect("Dbs initialization required!"); + btc_controller.bootstrap_chain(101); // now, one utxo exists - let _first_tx_ok = btc_controller - .build_leader_block_commit_tx(StacksEpochId::Epoch31, commit_op.clone(), &mut op_signer) - .expect("At first, building leader block commit should work"); + let commit_op = utils::create_templated_commit_op(); - // re-submitting same commit while previous it is not confirmed by the burnchain - let resubmit = btc_controller.build_leader_block_commit_tx( - StacksEpochId::Epoch31, - commit_op, - &mut op_signer, - ); + let _first_tx_ok = btc_controller + .build_leader_block_commit_tx( + StacksEpochId::Epoch31, + commit_op.clone(), + &mut op_signer, + ) + .expect("At first, building leader block commit should work"); - assert!(resubmit.is_err()); - assert_eq!( - BurnchainControllerError::IdenticalOperation, - resubmit.unwrap_err() - ); - } + // re-submitting same commit while previous it is not confirmed by the burnchain + let resubmit = btc_controller.build_leader_block_commit_tx( + StacksEpochId::Epoch31, + commit_op, + &mut op_signer, + ); - #[test] - #[ignore] - fn test_build_leader_block_commit_tx_fails_resub_same_commit_op_while_prev_is_confirmed() { - if env::var("BITCOIND_TEST") != Ok("1".into()) { - return; + assert!(resubmit.is_err()); + assert_eq!( + BurnchainControllerError::IdenticalOperation, + resubmit.unwrap_err() + ); } - let keychain = utils::create_keychain(); - let miner_pubkey = keychain.get_pub_key(); - let mut op_signer = keychain.generate_op_signer(); + #[test] + #[ignore] + fn test_build_leader_block_commit_tx_fails_resub_same_commit_op_while_prev_is_confirmed() { + if env::var("BITCOIND_TEST") != Ok("1".into()) { + return; + } - let mut config = utils::create_config(); - config.burnchain.local_mining_public_key = Some(miner_pubkey.to_hex()); + let keychain = utils::create_keychain(); + let miner_pubkey = keychain.get_pub_key(); + let mut op_signer = keychain.generate_op_signer(); - let mut btcd_controller = BitcoinCoreController::new(config.clone()); - btcd_controller - .start_bitcoind() - .expect("bitcoind should be started!"); + let mut config = utils::create_config(); + config.burnchain.local_mining_public_key = Some(miner_pubkey.to_hex()); - let mut btc_controller = BitcoinRegtestController::new(config, None); - btc_controller - .connect_dbs() - .expect("Dbs initialization required!"); - btc_controller.bootstrap_chain(101); // now, one utxo exists + let mut btcd_controller = BitcoinCoreController::new(config.clone()); + btcd_controller + .start_bitcoind() + .expect("bitcoind should be started!"); - let commit_op = utils::create_templated_commit_op(); + let mut btc_controller = BitcoinRegtestController::new(config, None); + btc_controller + .connect_dbs() + .expect("Dbs initialization required!"); + btc_controller.bootstrap_chain(101); // now, one utxo exists - let first_tx_ok = btc_controller - .build_leader_block_commit_tx(StacksEpochId::Epoch31, commit_op.clone(), &mut op_signer) - .expect("At first, building leader block commit should work"); + let commit_op = utils::create_templated_commit_op(); - utils::mine_tx(&btc_controller, first_tx_ok); // Now tx is confirmed + let first_tx_ok = btc_controller + .build_leader_block_commit_tx( + StacksEpochId::Epoch31, + commit_op.clone(), + &mut op_signer, + ) + .expect("At first, building leader block commit should work"); - // re-submitting same commit while previous it is confirmed by the burnchain - let resubmit = btc_controller.build_leader_block_commit_tx( - StacksEpochId::Epoch31, - commit_op, - &mut op_signer, - ); + utils::mine_tx(&btc_controller, first_tx_ok); // Now tx is confirmed - assert!(resubmit.is_err()); - assert_eq!( - BurnchainControllerError::IdenticalOperation, - resubmit.unwrap_err() - ); - } + // re-submitting same commit while previous it is confirmed by the burnchain + let resubmit = btc_controller.build_leader_block_commit_tx( + StacksEpochId::Epoch31, + commit_op, + &mut op_signer, + ); - #[test] - #[ignore] - fn test_build_leader_block_commit_tx_ok_rbf_while_prev_is_confirmed() { - if env::var("BITCOIND_TEST") != Ok("1".into()) { - return; + assert!(resubmit.is_err()); + assert_eq!( + BurnchainControllerError::IdenticalOperation, + resubmit.unwrap_err() + ); } - let keychain = utils::create_keychain(); - let miner_pubkey = keychain.get_pub_key(); - let mut op_signer = keychain.generate_op_signer(); + #[test] + #[ignore] + fn test_build_leader_block_commit_tx_ok_rbf_while_prev_is_confirmed() { + if env::var("BITCOIND_TEST") != Ok("1".into()) { + return; + } - let mut config = utils::create_config(); - config.burnchain.local_mining_public_key = Some(miner_pubkey.to_hex()); + let keychain = utils::create_keychain(); + let miner_pubkey = keychain.get_pub_key(); + let mut op_signer = keychain.generate_op_signer(); - let mut btcd_controller = BitcoinCoreController::new(config.clone()); - btcd_controller - .start_bitcoind() - .expect("bitcoind should be started!"); + let mut config = utils::create_config(); + config.burnchain.local_mining_public_key = Some(miner_pubkey.to_hex()); - let mut btc_controller = BitcoinRegtestController::new(config.clone(), None); - btc_controller - .connect_dbs() - .expect("Dbs initialization required!"); - btc_controller.bootstrap_chain(101); // now, one utxo exists - - let mut commit_op = utils::create_templated_commit_op(); - commit_op.sunset_burn = 5_500; - commit_op.burn_fee = 110_000; - - let first_tx_ok = btc_controller - .build_leader_block_commit_tx(StacksEpochId::Epoch31, commit_op.clone(), &mut op_signer) - .expect("At first, building leader block commit should work"); - - let first_txid = first_tx_ok.txid(); - - // Now tx is confirmed: prev utxo is updated and one more utxo is generated - utils::mine_tx(&btc_controller, first_tx_ok); - - //re-gen signer othewise fails because it will be disposed during previous commit tx. - let mut signer = keychain.generate_op_signer(); - //small change to the commit op payload - commit_op.burn_fee += 10; - - let rbf_tx = btc_controller - .build_leader_block_commit_tx(StacksEpochId::Epoch31, commit_op.clone(), &mut signer) - .expect("Commit tx should be rbf-ed"); - - assert!(op_signer.is_disposed()); - - assert_eq!(1, rbf_tx.version); - assert_eq!(0, rbf_tx.lock_time); - assert_eq!(1, rbf_tx.input.len()); - assert_eq!(4, rbf_tx.output.len()); - - // utxos list contains the sole utxo used by prev commit operation - // because has enough amount to cover the rfb commit - let used_utxos: Vec = btc_controller - .get_all_utxos(&miner_pubkey) - .into_iter() - .filter(|utxo| utxo.txid == first_txid) - .collect(); - - let input_0 = utils::txin_at_index(&rbf_tx, &op_signer, &used_utxos, 0); - assert_eq!(input_0, rbf_tx.input[0]); - - let op_return = utils::txout_opreturn(&commit_op, &config.burnchain.magic_bytes, 5_500); - let op_commit_1 = utils::txout_opdup_commit_to(&commit_op.commit_outs[0], 55_005); - let op_commit_2 = utils::txout_opdup_commit_to(&commit_op.commit_outs[1], 55_005); - let op_change = utils::txout_opdup_change_legacy(&mut signer, 4_999_730_590); - assert_eq!(op_return, rbf_tx.output[0]); - assert_eq!(op_commit_1, rbf_tx.output[1]); - assert_eq!(op_commit_2, rbf_tx.output[2]); - assert_eq!(op_change, rbf_tx.output[3]); - } + let mut btcd_controller = BitcoinCoreController::new(config.clone()); + btcd_controller + .start_bitcoind() + .expect("bitcoind should be started!"); - #[test] - #[ignore] - fn test_build_leader_block_commit_tx_ok_rbf_while_prev_not_confirmed() { - if env::var("BITCOIND_TEST") != Ok("1".into()) { - return; + let mut btc_controller = BitcoinRegtestController::new(config.clone(), None); + btc_controller + .connect_dbs() + .expect("Dbs initialization required!"); + btc_controller.bootstrap_chain(101); // now, one utxo exists + + let mut commit_op = utils::create_templated_commit_op(); + commit_op.sunset_burn = 5_500; + commit_op.burn_fee = 110_000; + + let first_tx_ok = btc_controller + .build_leader_block_commit_tx( + StacksEpochId::Epoch31, + commit_op.clone(), + &mut op_signer, + ) + .expect("At first, building leader block commit should work"); + + let first_txid = first_tx_ok.txid(); + + // Now tx is confirmed: prev utxo is updated and one more utxo is generated + utils::mine_tx(&btc_controller, first_tx_ok); + + //re-gen signer othewise fails because it will be disposed during previous commit tx. + let mut signer = keychain.generate_op_signer(); + //small change to the commit op payload + commit_op.burn_fee += 10; + + let rbf_tx = btc_controller + .build_leader_block_commit_tx( + StacksEpochId::Epoch31, + commit_op.clone(), + &mut signer, + ) + .expect("Commit tx should be rbf-ed"); + + assert!(op_signer.is_disposed()); + + assert_eq!(1, rbf_tx.version); + assert_eq!(0, rbf_tx.lock_time); + assert_eq!(1, rbf_tx.input.len()); + assert_eq!(4, rbf_tx.output.len()); + + // utxos list contains the sole utxo used by prev commit operation + // because has enough amount to cover the rfb commit + let used_utxos: Vec = btc_controller + .get_all_utxos(&miner_pubkey) + .into_iter() + .filter(|utxo| utxo.txid == first_txid) + .collect(); + + let input_0 = utils::txin_at_index(&rbf_tx, &op_signer, &used_utxos, 0); + assert_eq!(input_0, rbf_tx.input[0]); + + let op_return = utils::txout_opreturn(&commit_op, &config.burnchain.magic_bytes, 5_500); + let op_commit_1 = utils::txout_opdup_commit_to(&commit_op.commit_outs[0], 55_005); + let op_commit_2 = utils::txout_opdup_commit_to(&commit_op.commit_outs[1], 55_005); + let op_change = utils::txout_opdup_change_legacy(&mut signer, 4_999_730_590); + assert_eq!(op_return, rbf_tx.output[0]); + assert_eq!(op_commit_1, rbf_tx.output[1]); + assert_eq!(op_commit_2, rbf_tx.output[2]); + assert_eq!(op_change, rbf_tx.output[3]); } - let keychain = utils::create_keychain(); - let miner_pubkey = keychain.get_pub_key(); - let mut op_signer = keychain.generate_op_signer(); + #[test] + #[ignore] + fn test_build_leader_block_commit_tx_ok_rbf_while_prev_not_confirmed() { + if env::var("BITCOIND_TEST") != Ok("1".into()) { + return; + } - let mut config = utils::create_config(); - config.burnchain.local_mining_public_key = Some(miner_pubkey.to_hex()); + let keychain = utils::create_keychain(); + let miner_pubkey = keychain.get_pub_key(); + let mut op_signer = keychain.generate_op_signer(); - let mut btcd_controller = BitcoinCoreController::new(config.clone()); - btcd_controller - .start_bitcoind() - .expect("bitcoind should be started!"); + let mut config = utils::create_config(); + config.burnchain.local_mining_public_key = Some(miner_pubkey.to_hex()); - let mut btc_controller = BitcoinRegtestController::new(config.clone(), None); - btc_controller - .connect_dbs() - .expect("Dbs initialization required!"); - btc_controller.bootstrap_chain(101); // Now, one utxo exists - - let mut commit_op = utils::create_templated_commit_op(); - commit_op.sunset_burn = 5_500; - commit_op.burn_fee = 110_000; - - let _first_tx_ok = btc_controller - .build_leader_block_commit_tx(StacksEpochId::Epoch31, commit_op.clone(), &mut op_signer) - .expect("At first, building leader block commit should work"); - - //re-gen signer othewise fails because it will be disposed during previous commit tx. - let mut signer = keychain.generate_op_signer(); - //small change to the commit op payload - commit_op.burn_fee += 10; - - let rbf_tx = btc_controller - .build_leader_block_commit_tx(StacksEpochId::Epoch31, commit_op.clone(), &mut signer) - .expect("Commit tx should be rbf-ed"); - - assert!(op_signer.is_disposed()); - - assert_eq!(1, rbf_tx.version); - assert_eq!(0, rbf_tx.lock_time); - assert_eq!(1, rbf_tx.input.len()); - assert_eq!(4, rbf_tx.output.len()); - - // utxos list contains the only existing utxo - let used_utxos = btc_controller.get_all_utxos(&miner_pubkey); - - let input_0 = utils::txin_at_index(&rbf_tx, &op_signer, &used_utxos, 0); - assert_eq!(input_0, rbf_tx.input[0]); - - let op_return = utils::txout_opreturn(&commit_op, &config.burnchain.magic_bytes, 5_500); - let op_commit_1 = utils::txout_opdup_commit_to(&commit_op.commit_outs[0], 55_005); - let op_commit_2 = utils::txout_opdup_commit_to(&commit_op.commit_outs[1], 55_005); - let op_change = utils::txout_opdup_change_legacy(&mut signer, 4_999_862_985); - assert_eq!(op_return, rbf_tx.output[0]); - assert_eq!(op_commit_1, rbf_tx.output[1]); - assert_eq!(op_commit_2, rbf_tx.output[2]); - assert_eq!(op_change, rbf_tx.output[3]); - } + let mut btcd_controller = BitcoinCoreController::new(config.clone()); + btcd_controller + .start_bitcoind() + .expect("bitcoind should be started!"); - /// Tests related to Leader Block Commit operation - mod leader_commit_op { - use super::*; + let mut btc_controller = BitcoinRegtestController::new(config.clone(), None); + btc_controller + .connect_dbs() + .expect("Dbs initialization required!"); + btc_controller.bootstrap_chain(101); // Now, one utxo exists + + let mut commit_op = utils::create_templated_commit_op(); + commit_op.sunset_burn = 5_500; + commit_op.burn_fee = 110_000; + + let _first_tx_ok = btc_controller + .build_leader_block_commit_tx( + StacksEpochId::Epoch31, + commit_op.clone(), + &mut op_signer, + ) + .expect("At first, building leader block commit should work"); + + //re-gen signer othewise fails because it will be disposed during previous commit tx. + let mut signer = keychain.generate_op_signer(); + //small change to the commit op payload + commit_op.burn_fee += 10; + + let rbf_tx = btc_controller + .build_leader_block_commit_tx( + StacksEpochId::Epoch31, + commit_op.clone(), + &mut signer, + ) + .expect("Commit tx should be rbf-ed"); + + assert!(op_signer.is_disposed()); + + assert_eq!(1, rbf_tx.version); + assert_eq!(0, rbf_tx.lock_time); + assert_eq!(1, rbf_tx.input.len()); + assert_eq!(4, rbf_tx.output.len()); + + // utxos list contains the only existing utxo + let used_utxos = btc_controller.get_all_utxos(&miner_pubkey); + + let input_0 = utils::txin_at_index(&rbf_tx, &op_signer, &used_utxos, 0); + assert_eq!(input_0, rbf_tx.input[0]); + + let op_return = utils::txout_opreturn(&commit_op, &config.burnchain.magic_bytes, 5_500); + let op_commit_1 = utils::txout_opdup_commit_to(&commit_op.commit_outs[0], 55_005); + let op_commit_2 = utils::txout_opdup_commit_to(&commit_op.commit_outs[1], 55_005); + let op_change = utils::txout_opdup_change_legacy(&mut signer, 4_999_862_985); + assert_eq!(op_return, rbf_tx.output[0]); + assert_eq!(op_commit_1, rbf_tx.output[1]); + assert_eq!(op_commit_2, rbf_tx.output[2]); + assert_eq!(op_change, rbf_tx.output[3]); + } #[test] #[ignore] @@ -3935,8 +3963,7 @@ mod tests { let input_0 = utils::txin_at_index(&tx, &op_signer, &used_utxos, 0); assert_eq!(input_0, tx.input[0]); - let op_return = - utils::txout_opreturn(&leader_key_op, &config.burnchain.magic_bytes, 0); + let op_return = utils::txout_opreturn(&leader_key_op, &config.burnchain.magic_bytes, 0); let op_change = utils::txout_opdup_change_legacy(&mut op_signer, 4_999_980_000); assert_eq!(op_return, tx.output[0]); assert_eq!(op_change, tx.output[1]);