Skip to content

Commit 95a6bb7

Browse files
authored
Merge pull request #4989 from stacks-network/test/btc-reorgs
test: add bitcoin reorg test to bitcoind/stacks-node/signers integrations
2 parents 899ca58 + 1234be3 commit 95a6bb7

File tree

3 files changed

+166
-9
lines changed

3 files changed

+166
-9
lines changed

.github/workflows/bitcoin-tests.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@ jobs:
8989
- tests::signer::v0::end_of_tenure
9090
- tests::signer::v0::forked_tenure_okay
9191
- tests::signer::v0::forked_tenure_invalid
92+
- tests::signer::v0::bitcoind_forking_test
9293
- tests::nakamoto_integrations::stack_stx_burn_op_integration_test
9394
- tests::nakamoto_integrations::check_block_heights
9495
- tests::nakamoto_integrations::clarity_burn_state

testnet/stacks-node/src/nakamoto_node/relayer.rs

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,7 @@ pub struct LastCommit {
112112
/// the tenure consensus hash for the tip's tenure
113113
tenure_consensus_hash: ConsensusHash,
114114
/// the start-block hash of the tip's tenure
115+
#[allow(dead_code)]
115116
start_block_hash: BlockHeaderHash,
116117
/// What is the epoch in which this was sent?
117118
epoch_id: StacksEpochId,
@@ -838,14 +839,20 @@ impl RelayerThread {
838839
})?
839840
};
840841

841-
if last_winner_snapshot.miner_pk_hash != Some(mining_pkh) {
842-
debug!("Relayer: the miner did not win the last sortition. No tenure to continue.";
843-
"current_mining_pkh" => %mining_pkh,
844-
"last_winner_snapshot.miner_pk_hash" => ?last_winner_snapshot.miner_pk_hash,
845-
);
842+
let won_last_sortition = last_winner_snapshot.miner_pk_hash == Some(mining_pkh);
843+
debug!(
844+
"Relayer: Current burn block had no sortition. Checking for tenure continuation.";
845+
"won_last_sortition" => won_last_sortition,
846+
"current_mining_pkh" => %mining_pkh,
847+
"last_winner_snapshot.miner_pk_hash" => ?last_winner_snapshot.miner_pk_hash,
848+
"canonical_stacks_tip_id" => %canonical_stacks_tip,
849+
"canonical_stacks_tip_ch" => %canonical_stacks_tip_ch,
850+
"block_election_ch" => %block_election_snapshot.consensus_hash,
851+
"burn_view_ch" => %new_burn_view,
852+
);
853+
854+
if !won_last_sortition {
846855
return Ok(());
847-
} else {
848-
debug!("Relayer: the miner won the last sortition. Continuing tenure.");
849856
}
850857

851858
match self.start_new_tenure(

testnet/stacks-node/src/tests/signer/v0.rs

Lines changed: 151 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,10 +46,10 @@ use crate::nakamoto_node::miner::TEST_BROADCAST_STALL;
4646
use crate::nakamoto_node::relayer::TEST_SKIP_COMMIT_OP;
4747
use crate::tests::nakamoto_integrations::{boot_to_epoch_3_reward_set, next_block_and};
4848
use crate::tests::neon_integrations::{
49-
get_chain_info, next_block_and_wait, submit_tx, test_observer,
49+
get_account, get_chain_info, next_block_and_wait, submit_tx, test_observer,
5050
};
5151
use crate::tests::{self, make_stacks_transfer};
52-
use crate::{nakamoto_node, BurnchainController};
52+
use crate::{nakamoto_node, BurnchainController, Keychain};
5353

5454
impl SignerTest<SpawnedSigner> {
5555
/// Run the test until the epoch 3 boundary
@@ -787,6 +787,155 @@ fn forked_tenure_testing(
787787
}
788788
}
789789

790+
#[test]
791+
#[ignore]
792+
fn bitcoind_forking_test() {
793+
if env::var("BITCOIND_TEST") != Ok("1".into()) {
794+
return;
795+
}
796+
797+
let num_signers = 5;
798+
let sender_sk = Secp256k1PrivateKey::new();
799+
let sender_addr = tests::to_addr(&sender_sk);
800+
let send_amt = 100;
801+
let send_fee = 180;
802+
let mut signer_test: SignerTest<SpawnedSigner> = SignerTest::new_with_config_modifications(
803+
num_signers,
804+
vec![(sender_addr.clone(), send_amt + send_fee)],
805+
Some(Duration::from_secs(15)),
806+
|_config| {},
807+
);
808+
let conf = signer_test.running_nodes.conf.clone();
809+
let http_origin = format!("http://{}", &conf.node.rpc_bind);
810+
let miner_address = Keychain::default(conf.node.seed.clone())
811+
.origin_address(conf.is_mainnet())
812+
.unwrap();
813+
814+
signer_test.boot_to_epoch_3();
815+
info!("------------------------- Reached Epoch 3.0 -------------------------");
816+
let pre_epoch_3_nonce = get_account(&http_origin, &miner_address).nonce;
817+
let pre_fork_tenures = 10;
818+
819+
for _i in 0..pre_fork_tenures {
820+
let _mined_block = signer_test.mine_nakamoto_block(Duration::from_secs(30));
821+
}
822+
823+
let pre_fork_1_nonce = get_account(&http_origin, &miner_address).nonce;
824+
825+
assert_eq!(pre_fork_1_nonce, pre_epoch_3_nonce + 2 * pre_fork_tenures);
826+
827+
info!("------------------------- Triggering Bitcoin Fork -------------------------");
828+
829+
let burn_block_height = get_chain_info(&signer_test.running_nodes.conf).burn_block_height;
830+
let burn_header_hash_to_fork = signer_test
831+
.running_nodes
832+
.btc_regtest_controller
833+
.get_block_hash(burn_block_height);
834+
signer_test
835+
.running_nodes
836+
.btc_regtest_controller
837+
.invalidate_block(&burn_header_hash_to_fork);
838+
signer_test
839+
.running_nodes
840+
.btc_regtest_controller
841+
.build_next_block(1);
842+
843+
info!("Wait for block off of shallow fork");
844+
thread::sleep(Duration::from_secs(15));
845+
846+
// we need to mine some blocks to get back to being considered a frequent miner
847+
for _i in 0..3 {
848+
let commits_count = signer_test
849+
.running_nodes
850+
.commits_submitted
851+
.load(Ordering::SeqCst);
852+
next_block_and(
853+
&mut signer_test.running_nodes.btc_regtest_controller,
854+
60,
855+
|| {
856+
Ok(signer_test
857+
.running_nodes
858+
.commits_submitted
859+
.load(Ordering::SeqCst)
860+
> commits_count)
861+
},
862+
)
863+
.unwrap();
864+
}
865+
866+
let post_fork_1_nonce = get_account(&http_origin, &miner_address).nonce;
867+
868+
assert_eq!(post_fork_1_nonce, pre_fork_1_nonce - 1 * 2);
869+
870+
for _i in 0..5 {
871+
signer_test.mine_nakamoto_block(Duration::from_secs(30));
872+
}
873+
874+
let pre_fork_2_nonce = get_account(&http_origin, &miner_address).nonce;
875+
assert_eq!(pre_fork_2_nonce, post_fork_1_nonce + 2 * 5);
876+
877+
info!(
878+
"New chain info: {:?}",
879+
get_chain_info(&signer_test.running_nodes.conf)
880+
);
881+
882+
info!("------------------------- Triggering Deeper Bitcoin Fork -------------------------");
883+
884+
let burn_block_height = get_chain_info(&signer_test.running_nodes.conf).burn_block_height;
885+
let burn_header_hash_to_fork = signer_test
886+
.running_nodes
887+
.btc_regtest_controller
888+
.get_block_hash(burn_block_height - 3);
889+
signer_test
890+
.running_nodes
891+
.btc_regtest_controller
892+
.invalidate_block(&burn_header_hash_to_fork);
893+
signer_test
894+
.running_nodes
895+
.btc_regtest_controller
896+
.build_next_block(4);
897+
898+
info!("Wait for block off of shallow fork");
899+
thread::sleep(Duration::from_secs(15));
900+
901+
// we need to mine some blocks to get back to being considered a frequent miner
902+
for _i in 0..3 {
903+
let commits_count = signer_test
904+
.running_nodes
905+
.commits_submitted
906+
.load(Ordering::SeqCst);
907+
next_block_and(
908+
&mut signer_test.running_nodes.btc_regtest_controller,
909+
60,
910+
|| {
911+
Ok(signer_test
912+
.running_nodes
913+
.commits_submitted
914+
.load(Ordering::SeqCst)
915+
> commits_count)
916+
},
917+
)
918+
.unwrap();
919+
}
920+
921+
let post_fork_2_nonce = get_account(&http_origin, &miner_address).nonce;
922+
923+
assert_eq!(post_fork_2_nonce, pre_fork_2_nonce - 4 * 2);
924+
925+
for _i in 0..5 {
926+
signer_test.mine_nakamoto_block(Duration::from_secs(30));
927+
}
928+
929+
let test_end_nonce = get_account(&http_origin, &miner_address).nonce;
930+
assert_eq!(test_end_nonce, post_fork_2_nonce + 2 * 5);
931+
932+
info!(
933+
"New chain info: {:?}",
934+
get_chain_info(&signer_test.running_nodes.conf)
935+
);
936+
signer_test.shutdown();
937+
}
938+
790939
#[test]
791940
#[ignore]
792941
/// This test checks the behavior at the end of a tenure. Specifically:

0 commit comments

Comments
 (0)