Skip to content

Commit 12e6f4c

Browse files
committed
feat: new test scenario, fix ordering of txs
1 parent 2b134d8 commit 12e6f4c

File tree

3 files changed

+213
-32
lines changed

3 files changed

+213
-32
lines changed

stacks-signer/src/v0/signer_state.rs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1002,15 +1002,19 @@ impl LocalStateMachine {
10021002
}
10031003
let fork_info =
10041004
client.get_tenure_forking_info(&first_forked_tenure, &last_forked_tenure)?;
1005-
let forked_txs = fork_info
1005+
let mut forked_blocks = fork_info
10061006
.iter()
10071007
.flat_map(|fork_info| {
10081008
fork_info
10091009
.nakamoto_blocks
10101010
.iter()
10111011
.flat_map(|blocks| blocks.iter())
1012-
.flat_map(|block| block.txs.iter())
10131012
})
1013+
.collect::<Vec<_>>();
1014+
forked_blocks.sort_by_key(|block| block.header.chain_length);
1015+
let forked_txs = forked_blocks
1016+
.iter()
1017+
.flat_map(|block| block.txs.iter())
10141018
.filter(|tx| match tx.payload {
10151019
// Don't include Coinbase, TenureChange, or PoisonMicroblock transactions
10161020
TransactionPayload::TenureChange(..)

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

Lines changed: 59 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ use std::time::{Duration, Instant};
2323

2424
use clarity::boot_util::boot_code_id;
2525
use clarity::vm::types::PrincipalData;
26+
use clarity::vm::Value;
2627
use libsigner::v0::messages::{
2728
BlockAccepted, BlockResponse, MessageSlotID, PeerInfo, SignerMessage,
2829
};
@@ -451,43 +452,36 @@ impl<S: Signer<T> + Send + 'static, T: SignerEventTrait + 'static> SignerTest<Sp
451452
submit_tx_fallible(&http_origin, &transfer_tx).map(|resp| (resp, sender_nonce))
452453
}
453454

454-
/// Submit a burn block dependent contract for publishing
455-
/// and wait until it is included in a block
456-
pub fn submit_burn_block_contract_and_wait(
455+
/// Submit a contract deploy and return (txid, sender_nonce)
456+
pub fn submit_contract_deploy(
457457
&mut self,
458458
sender_sk: &StacksPrivateKey,
459-
) -> Result<String, String> {
459+
contract_code: &str,
460+
contract_name: &str,
461+
) -> Result<(String, u64), String> {
460462
let http_origin = format!("http://{}", &self.running_nodes.conf.node.rpc_bind);
461463
let sender_addr = to_addr(&sender_sk);
462464
let sender_nonce = get_account(&http_origin, &sender_addr).nonce;
463-
let burn_height_contract = "
464-
(define-data-var local-burn-block-ht uint u0)
465-
(define-public (run-update)
466-
(ok (var-set local-burn-block-ht burn-block-height)))
467-
";
465+
468466
let contract_tx = make_contract_publish(
469467
&sender_sk,
470-
0,
468+
sender_nonce,
471469
1000,
472470
self.running_nodes.conf.burnchain.chain_id,
473-
"burn-height-local",
474-
burn_height_contract,
471+
contract_name,
472+
contract_code,
475473
);
476-
let txid = submit_tx_fallible(&http_origin, &contract_tx)?;
477-
478-
wait_for(120, || {
479-
let next_nonce = get_account(&http_origin, &sender_addr).nonce;
480-
Ok(next_nonce > sender_nonce)
481-
})
482-
.map(|()| txid)
474+
submit_tx_fallible(&http_origin, &contract_tx).map(|resp| (resp, sender_nonce))
483475
}
484476

485-
/// Submit a burn block dependent contract-call
486-
/// and wait until it is included in a block
487-
pub fn submit_burn_block_call_and_wait(
477+
/// Submit a contract call and return (txid, sender_nonce)
478+
pub fn submit_contract_call(
488479
&mut self,
489480
sender_sk: &StacksPrivateKey,
490-
) -> Result<String, String> {
481+
contract_name: &str,
482+
contract_func: &str,
483+
contract_args: &[Value],
484+
) -> Result<(String, u64), String> {
491485
let http_origin = format!("http://{}", &self.running_nodes.conf.node.rpc_bind);
492486
let sender_addr = to_addr(&sender_sk);
493487
let sender_nonce = get_account(&http_origin, &sender_addr).nonce;
@@ -497,17 +491,54 @@ impl<S: Signer<T> + Send + 'static, T: SignerEventTrait + 'static> SignerTest<Sp
497491
1000,
498492
self.running_nodes.conf.burnchain.chain_id,
499493
&sender_addr,
500-
"burn-height-local",
501-
"run-update",
502-
&[],
494+
contract_name,
495+
contract_func,
496+
contract_args,
503497
);
504-
let txid = submit_tx_fallible(&http_origin, &contract_call_tx)?;
498+
submit_tx_fallible(&http_origin, &contract_call_tx).map(|resp| (resp, sender_nonce))
499+
}
505500

501+
pub fn wait_for_nonce_increase(
502+
&mut self,
503+
sender_addr: &StacksAddress,
504+
sender_nonce: u64,
505+
) -> Result<(), String> {
506+
let http_origin = format!("http://{}", &self.running_nodes.conf.node.rpc_bind);
506507
wait_for(120, || {
507508
let next_nonce = get_account(&http_origin, &sender_addr).nonce;
508509
Ok(next_nonce > sender_nonce)
509510
})
510-
.map(|()| txid)
511+
}
512+
513+
/// Submit a burn block dependent contract for publishing
514+
/// and wait until it is included in a block
515+
pub fn submit_burn_block_contract_and_wait(
516+
&mut self,
517+
sender_sk: &StacksPrivateKey,
518+
) -> Result<String, String> {
519+
let burn_height_contract = "
520+
(define-data-var local-burn-block-ht uint u0)
521+
(define-public (run-update)
522+
(ok (var-set local-burn-block-ht burn-block-height)))
523+
";
524+
let (txid, sender_nonce) =
525+
self.submit_contract_deploy(sender_sk, burn_height_contract, "burn-height-local")?;
526+
527+
self.wait_for_nonce_increase(&to_addr(&sender_sk), sender_nonce)?;
528+
Ok(txid)
529+
}
530+
531+
/// Submit a burn block dependent contract-call
532+
/// and wait until it is included in a block
533+
pub fn submit_burn_block_call_and_wait(
534+
&mut self,
535+
sender_sk: &StacksPrivateKey,
536+
) -> Result<String, String> {
537+
let (txid, sender_nonce) =
538+
self.submit_contract_call(sender_sk, "burn-height-local", "run-update", &[])?;
539+
540+
self.wait_for_nonce_increase(&to_addr(&sender_sk), sender_nonce)?;
541+
Ok(txid)
511542
}
512543

513544
/// Get the local state machines and most recent peer info from the stacks-node,

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

Lines changed: 148 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3049,6 +3049,9 @@ fn bitcoind_forking_test() {
30493049
/// - Verify that the signer moves into tx replay state
30503050
/// - Verify that the signer correctly includes the stx transfer
30513051
/// in the tx replay set
3052+
///
3053+
/// Then, a second fork scenario is tested, which
3054+
/// includes multiple txs across multiple tenures.
30523055
fn tx_replay_forking_test() {
30533056
if env::var("BITCOIND_TEST") != Ok("1".into()) {
30543057
return;
@@ -3061,7 +3064,7 @@ fn tx_replay_forking_test() {
30613064
let send_fee = 180;
30623065
let mut signer_test: SignerTest<SpawnedSigner> = SignerTest::new_with_config_modifications(
30633066
num_signers,
3064-
vec![(sender_addr, send_amt + send_fee)],
3067+
vec![(sender_addr, (send_amt + send_fee) * 10)],
30653068
|_| {},
30663069
|node_config| {
30673070
node_config.miner.block_commit_delay = Duration::from_secs(1);
@@ -3079,9 +3082,10 @@ fn tx_replay_forking_test() {
30793082
for i in 0..pre_fork_tenures {
30803083
info!("Mining pre-fork tenure {} of {pre_fork_tenures}", i + 1);
30813084
signer_test.mine_nakamoto_block(Duration::from_secs(30), true);
3082-
signer_test.check_signer_states_normal();
30833085
}
30843086

3087+
signer_test.check_signer_states_normal();
3088+
30853089
let burn_blocks = test_observer::get_burn_blocks();
30863090
let forked_blocks = burn_blocks.iter().rev().take(2).collect::<Vec<_>>();
30873091
let last_forked_tenure: ConsensusHash = hex_bytes(
@@ -3220,6 +3224,8 @@ fn tx_replay_forking_test() {
32203224

32213225
TEST_MINE_STALL.set(false);
32223226

3227+
info!("---- Mining post-fork block to clear tx replay set ----");
3228+
32233229
// Now, make a new stacks block, which should clear the tx replay set
32243230
signer_test.mine_nakamoto_block(Duration::from_secs(30), true);
32253231
let (signer_states, _) = signer_test.get_burn_updated_states();
@@ -3230,6 +3236,146 @@ fn tx_replay_forking_test() {
32303236
);
32313237
}
32323238

3239+
// Now, we'll trigger another fork, with more txs, across tenures
3240+
3241+
// The forked blocks are:
3242+
// Tenure 1:
3243+
// - Block with stx transfer
3244+
// Tenure 2:
3245+
// - Block with contract deploy
3246+
// - Block with contract call
3247+
3248+
signer_test.mine_nakamoto_block(Duration::from_secs(30), true);
3249+
3250+
let pre_fork_2_tip = get_chain_info(&signer_test.running_nodes.conf);
3251+
3252+
let contract_code = "
3253+
(define-public (call-fn)
3254+
(ok true)
3255+
)
3256+
";
3257+
let contract_name = "test-contract";
3258+
3259+
let (transfer_txid, transfer_nonce) = signer_test
3260+
.submit_transfer_tx(&sender_sk, send_fee, send_amt)
3261+
.expect("Failed to submit transfer tx");
3262+
signer_test
3263+
.wait_for_nonce_increase(&sender_addr, transfer_nonce)
3264+
.expect("Failed to wait for nonce increase");
3265+
signer_test.mine_nakamoto_block(Duration::from_secs(30), true);
3266+
3267+
let (contract_deploy_txid, deploy_nonce) = signer_test
3268+
.submit_contract_deploy(&sender_sk, contract_code, contract_name)
3269+
.expect("Failed to submit contract deploy");
3270+
signer_test
3271+
.wait_for_nonce_increase(&sender_addr, deploy_nonce)
3272+
.expect("Failed to wait for nonce increase");
3273+
3274+
let (contract_call_txid, contract_call_nonce) = signer_test
3275+
.submit_contract_call(&sender_sk, contract_name, "call-fn", &[])
3276+
.expect("Failed to submit contract call");
3277+
signer_test
3278+
.wait_for_nonce_increase(&sender_addr, contract_call_nonce)
3279+
.expect("Failed to wait for nonce increase");
3280+
signer_test.mine_nakamoto_block(Duration::from_secs(30), true);
3281+
3282+
TEST_MINE_STALL.set(true);
3283+
3284+
let burn_header_hash_to_fork = signer_test
3285+
.running_nodes
3286+
.btc_regtest_controller
3287+
.get_block_hash(pre_fork_2_tip.burn_block_height);
3288+
signer_test
3289+
.running_nodes
3290+
.btc_regtest_controller
3291+
.invalidate_block(&burn_header_hash_to_fork);
3292+
signer_test
3293+
.running_nodes
3294+
.btc_regtest_controller
3295+
.build_next_block(3);
3296+
3297+
let burn_blocks = test_observer::get_burn_blocks();
3298+
let forked_blocks = burn_blocks.iter().rev().take(2).collect::<Vec<_>>();
3299+
let last_forked_tenure: ConsensusHash = hex_bytes(
3300+
&forked_blocks[0]
3301+
.get("consensus_hash")
3302+
.unwrap()
3303+
.as_str()
3304+
.unwrap()[2..],
3305+
)
3306+
.unwrap()
3307+
.as_slice()
3308+
.into();
3309+
let first_forked_tenure: ConsensusHash = hex_bytes(
3310+
&forked_blocks[1]
3311+
.get("consensus_hash")
3312+
.unwrap()
3313+
.as_str()
3314+
.unwrap()[2..],
3315+
)
3316+
.unwrap()
3317+
.as_slice()
3318+
.into();
3319+
3320+
let fork_info = signer_test
3321+
.stacks_client
3322+
.get_tenure_forking_info(&first_forked_tenure, &last_forked_tenure)
3323+
.unwrap();
3324+
3325+
info!("---- Fork info: {fork_info:?} ----");
3326+
3327+
for fork in fork_info {
3328+
info!("---- Fork: {} ----", fork.consensus_hash);
3329+
fork.nakamoto_blocks.inspect(|blocks| {
3330+
for block in blocks {
3331+
info!("---- Block: {} ----", block.header.chain_length);
3332+
}
3333+
});
3334+
}
3335+
3336+
for i in 0..3 {
3337+
let current_burn_height = get_chain_info(&signer_test.running_nodes.conf).burn_block_height;
3338+
info!(
3339+
"Mining block #{i} to be considered a frequent miner";
3340+
"current_burn_height" => current_burn_height,
3341+
);
3342+
let commits_count = submitted_commits.load(Ordering::SeqCst);
3343+
next_block_and_controller(
3344+
&mut signer_test.running_nodes.btc_regtest_controller,
3345+
60,
3346+
|_btc_controller| {
3347+
let commits_submitted = submitted_commits.load(Ordering::SeqCst);
3348+
Ok(commits_submitted > commits_count)
3349+
},
3350+
)
3351+
.unwrap();
3352+
}
3353+
3354+
let expected_tx_replay_txids = vec![transfer_txid, contract_deploy_txid, contract_call_txid];
3355+
3356+
let (signer_states, _) = signer_test.get_burn_updated_states();
3357+
for state in signer_states {
3358+
match state {
3359+
LocalStateMachine::Initialized(signer_state_machine) => {
3360+
let Some(tx_replay_set) = signer_state_machine.tx_replay_set else {
3361+
panic!(
3362+
"Signer state machine is in tx replay state, but tx replay set is not set"
3363+
);
3364+
};
3365+
info!("---- Tx replay set: {:?} ----", tx_replay_set);
3366+
assert_eq!(tx_replay_set.len(), expected_tx_replay_txids.len());
3367+
let state_replay_txids = tx_replay_set
3368+
.iter()
3369+
.map(|tx| tx.txid().to_hex())
3370+
.collect::<Vec<_>>();
3371+
assert_eq!(state_replay_txids, expected_tx_replay_txids);
3372+
}
3373+
_ => {
3374+
panic!("Signer state is not in the initialized state");
3375+
}
3376+
}
3377+
}
3378+
32333379
signer_test.shutdown();
32343380
}
32353381

0 commit comments

Comments
 (0)