@@ -12046,10 +12046,10 @@ fn mark_miner_as_invalid_if_reorg_is_rejected() {
12046
12046
.expect("Failed to get block proposal N+1'");
12047
12047
// Stall the miner from proposing again until we're ready
12048
12048
TEST_BROADCAST_PROPOSAL_STALL.set(vec![miner_pk_1]);
12049
-
12049
+ // Due to reorging signers capitulating to the majority rejection of the reorg...all signers will update their state to reject
12050
12050
miners
12051
12051
.signer_test
12052
- .check_signer_states_reorg(&approving_signers , &rejecting_signers );
12052
+ .check_signer_states_reorg(&[] , &all_signers );
12053
12053
12054
12054
info!("------------------------- Wait for 3 acceptances and 2 rejections -------------------------");
12055
12055
let signer_signature_hash = block_n_1_prime.header.signer_signature_hash();
@@ -13391,81 +13391,194 @@ fn signers_send_state_message_updates() {
13391
13391
13392
13392
#[test]
13393
13393
#[ignore]
13394
- fn reorging_capitulate_to_nonreorging_signers_during_tenure_fork () {
13394
+ fn reorging_signers_capitulate_to_nonreorging_signers_during_tenure_fork () {
13395
13395
if env::var("BITCOIND_TEST") != Ok("1".into()) {
13396
13396
return;
13397
13397
}
13398
13398
13399
- let result = forked_tenure_testing(
13400
- Duration::from_secs(360),
13401
- Some(Duration::from_secs(5)),
13402
- Duration::from_secs(7),
13403
- false,
13404
- );
13399
+ let num_signers = 5;
13400
+ let num_txs = 5;
13405
13401
13406
- assert_ne!(
13407
- result.tip_b.index_block_hash(),
13408
- result.tip_a.index_block_hash()
13409
- );
13410
- assert_eq!(
13411
- result.tip_b.index_block_hash(),
13412
- result.tip_c.index_block_hash()
13402
+ let disallow_reorg_proposal_timeout = Duration::from_secs(10);
13403
+ let allow_reorg_proposal_timeout = Duration::from_secs(360);
13404
+ let post_btc_block_pause =
13405
+ disallow_reorg_proposal_timeout.saturating_add(Duration::from_secs(1));
13406
+ let mut miners = MultipleMinerTest::new_with_config_modifications(
13407
+ num_signers,
13408
+ num_txs,
13409
+ |config| {
13410
+ config.first_proposal_burn_block_timing = if config.endpoint.port() % 2 == 1 {
13411
+ // 2/5 or 40% of signers will allow the reorg
13412
+ allow_reorg_proposal_timeout
13413
+ } else {
13414
+ // 3/5 or 60% of signers will reject the reorg
13415
+ disallow_reorg_proposal_timeout
13416
+ };
13417
+ // don't allow signers to post signed blocks (limits the amount of fault injection we
13418
+ // need)
13419
+ TEST_SKIP_BLOCK_BROADCAST.set(true);
13420
+ },
13421
+ |config| {
13422
+ config.burnchain.pox_reward_length = Some(30);
13423
+ config.miner.tenure_cost_limit_per_block_percentage = None;
13424
+ // this test relies on the miner submitting these timed out commits.
13425
+ // the test still passes without this override, but the default timeout
13426
+ // makes the test take longer than strictly necessary
13427
+ config.miner.block_commit_delay = Duration::from_secs(10);
13428
+ },
13429
+ |_| {},
13413
13430
);
13414
- assert_ne!(result.tip_c, result.tip_a);
13431
+ let rl1_skip_commit_op = miners
13432
+ .signer_test
13433
+ .running_nodes
13434
+ .counters
13435
+ .naka_skip_commit_op
13436
+ .clone();
13437
+
13438
+ let rl2_skip_commit_op = miners.rl2_counters.naka_skip_commit_op.clone();
13439
+
13440
+ let (conf_1, _) = miners.get_node_configs();
13441
+ let (miner_pkh_1, miner_pkh_2) = miners.get_miner_public_key_hashes();
13442
+ let (miner_pk_1, miner_pk_2) = miners.get_miner_public_keys();
13443
+
13444
+ info!("------------------------- Pause Miner 2's Block Commits -------------------------");
13445
+
13446
+ // Make sure Miner 2 cannot win a sortition at first.
13447
+ rl2_skip_commit_op.set(true);
13448
+
13449
+ miners.boot_to_epoch_3();
13450
+
13451
+ let burnchain = conf_1.get_burnchain();
13452
+ let sortdb = burnchain.open_sortition_db(true).unwrap();
13453
+ let (chainstate, _) = StacksChainState::open(
13454
+ conf_1.is_mainnet(),
13455
+ conf_1.burnchain.chain_id,
13456
+ &conf_1.get_chainstate_path_str(),
13457
+ None,
13458
+ )
13459
+ .unwrap();
13460
+ info!("------------------------- Pause Miner 1's Block Commit -------------------------");
13461
+
13462
+ // Make sure miner 1 doesn't submit any further block commits for the next tenure BEFORE mining the bitcoin block
13463
+ rl1_skip_commit_op.set(true);
13464
+
13465
+ info!("------------------------- Miner 1 Wins Normal Tenure A -------------------------");
13466
+ miners
13467
+ .mine_bitcoin_block_and_tenure_change_tx(&sortdb, TenureChangeCause::BlockFound, 30)
13468
+ .expect("Failed to mine BTC block followed by tenure change tx");
13469
+ verify_sortition_winner(&sortdb, &miner_pkh_1);
13470
+
13471
+ info!("------------------------- Miner 1 Mines Another Block -------------------------");
13472
+ miners
13473
+ .send_and_mine_transfer_tx(30)
13474
+ .expect("Failed to mine tx");
13475
+
13476
+ let tip_a = NakamotoChainState::get_canonical_block_header(chainstate.db(), &sortdb)
13477
+ .unwrap()
13478
+ .unwrap();
13479
+
13480
+ info!("------------------------- Pause Block Proposals -------------------------");
13481
+ // For the next tenure, submit the commit op but do not allow any stacks blocks to be broadcasted
13482
+ TEST_BROADCAST_PROPOSAL_STALL.set(vec![miner_pk_1]);
13483
+ TEST_BLOCK_ANNOUNCE_STALL.set(true);
13484
+
13485
+ miners.submit_commit_miner_1(&sortdb);
13486
+
13487
+ info!("------------------------- Miner 1 Wins Tenure B -------------------------");
13488
+ miners
13489
+ .mine_bitcoin_blocks_and_confirm(&sortdb, 1, 30)
13490
+ .expect("Failed to mine BTC block");
13491
+ // assure we have a successful sortition that miner 1 won
13492
+ verify_sortition_winner(&sortdb, &miner_pkh_1);
13493
+
13494
+ info!("----------------- Miner 2 Submits Block Commit for Tenure C Before Any Tenure B Blocks Produced ------------------");
13495
+ miners.submit_commit_miner_2(&sortdb);
13496
+
13497
+ info!("----------------------------- Resume Block Production for Tenure B -----------------------------");
13498
+
13499
+ let stacks_height_before = miners.get_peer_stacks_tip_height();
13500
+ TEST_BROADCAST_PROPOSAL_STALL.set(vec![]);
13501
+
13502
+ let tenure_b_block_proposal =
13503
+ wait_for_block_proposal(30, stacks_height_before + 1, &miner_pk_1)
13504
+ .expect("Timed out waiting for Tenure B block to be proposed");
13505
+ info!("Tenure B broadcasted a block. Wait {post_btc_block_pause:?}, issue the next bitcoin block, and un-stall block commits.");
13506
+ thread::sleep(post_btc_block_pause);
13507
+
13508
+ // the block will be stored, not processed, so load it out of staging
13509
+ let tip_sn = SortitionDB::get_canonical_burn_chain_tip(sortdb.conn())
13510
+ .expect("Failed to get sortition tip");
13511
+
13512
+ let tenure_b_block = chainstate
13513
+ .nakamoto_blocks_db()
13514
+ .get_nakamoto_tenure_start_blocks(&tip_sn.consensus_hash)
13515
+ .unwrap()
13516
+ .first()
13517
+ .cloned()
13518
+ .unwrap();
13519
+
13520
+ // synthesize a StacksHeaderInfo from this unprocessed block
13521
+ let tip_b = StacksHeaderInfo {
13522
+ anchored_header: StacksBlockHeaderTypes::Nakamoto(tenure_b_block.header.clone()),
13523
+ microblock_tail: None,
13524
+ stacks_block_height: tenure_b_block.header.chain_length,
13525
+ index_root: TrieHash([0x00; 32]), // we can't know this yet since the block hasn't been processed
13526
+ consensus_hash: tenure_b_block.header.consensus_hash,
13527
+ burn_header_hash: tip_sn.burn_header_hash,
13528
+ burn_header_height: tip_sn.block_height as u32,
13529
+ burn_header_timestamp: tip_sn.burn_header_timestamp,
13530
+ anchored_block_size: tenure_b_block.serialize_to_vec().len() as u64,
13531
+ burn_view: Some(tenure_b_block.header.consensus_hash),
13532
+ };
13415
13533
13416
13534
// Block B was built atop block A
13535
+ assert_ne!(tip_b.index_block_hash(), tip_a.index_block_hash());
13536
+ assert_eq!(tip_b.stacks_block_height, tip_a.stacks_block_height + 1);
13417
13537
assert_eq!(
13418
- result.tip_b.stacks_block_height,
13419
- result.tip_a.stacks_block_height + 1
13420
- );
13421
- assert_eq!(
13422
- result.mined_b.parent_block_id,
13423
- result.tip_a.index_block_hash().to_string()
13538
+ tenure_b_block.header.parent_block_id,
13539
+ tip_a.index_block_hash()
13424
13540
);
13541
+ assert_ne!(tip_b, tip_a);
13425
13542
13426
- // Block C was built AFTER Block B was built, but BEFORE it was broadcasted, so it should be built off of Block A
13427
- assert_eq!(
13428
- result.mined_c.parent_block_id,
13429
- result.tip_a.index_block_hash().to_string()
13430
- );
13431
- assert_ne!(
13432
- result
13433
- .tip_c
13434
- .anchored_header
13435
- .as_stacks_nakamoto()
13436
- .unwrap()
13437
- .signer_signature_hash(),
13438
- result.mined_c.signer_signature_hash,
13439
- "Mined block during tenure C should not have become the chain tip"
13440
- );
13543
+ let chain_tip = SortitionDB::get_canonical_burn_chain_tip(sortdb.conn()).unwrap();
13544
+ let burn_height_before = chain_tip.block_height;
13441
13545
13442
- assert!(result.tip_c_2.is_none());
13443
- assert!(result.mined_c_2.is_none());
13546
+ // allow B to process, so it'll be distinct from C
13547
+ TEST_BLOCK_ANNOUNCE_STALL.set(false);
13548
+ sleep_ms(1000);
13444
13549
13445
- // Tenure D should continue progress
13446
- assert_ne!(result.tip_c, result.tip_d);
13447
- assert_ne!(
13448
- result.tip_b.index_block_hash(),
13449
- result.tip_d.index_block_hash()
13450
- );
13451
- assert_ne!(result.tip_a, result.tip_d);
13550
+ info!("--------------- Miner 2 Wins Tenure C With Old Block Commit ----------------");
13551
+ info!("Prevent Miner 1 from extending at first");
13552
+ TEST_BROADCAST_PROPOSAL_STALL.set(vec![miner_pk_1]);
13553
+
13554
+ test_observer::clear();
13555
+
13556
+ miners
13557
+ .mine_bitcoin_blocks_and_confirm(&sortdb, 1, 60)
13558
+ .expect("Failed to mine bitcoin block");
13559
+ // assure we have a successful sortition that miner 2
13560
+ verify_sortition_winner(&sortdb, &miner_pkh_2);
13561
+
13562
+ // Note tenure C block will attempt to reorg the prior miner so its expected height should be the same as prior to block B processing.
13563
+ let tenure_c_block_proposal =
13564
+ wait_for_block_proposal(30, tip_b.stacks_block_height, &miner_pk_2)
13565
+ .expect("Timed out waiting for miner 2's Tenure C block");
13566
+
13567
+ assert_ne!(tenure_c_block_proposal, tenure_b_block_proposal);
13568
+
13569
+ let tip_c = SortitionDB::get_canonical_burn_chain_tip(sortdb.conn()).unwrap();
13452
13570
13453
- // Tenure D builds off of Tenure B
13454
- assert_eq!(
13455
- result.tip_d.stacks_block_height,
13456
- result.tip_b.stacks_block_height + 1,
13457
- );
13458
13571
assert_eq!(
13459
- result.mined_d.parent_block_id ,
13460
- result.tip_b.index_block_hash().to_string ()
13572
+ tip_b.index_block_hash() ,
13573
+ tip_c.get_canonical_stacks_block_id ()
13461
13574
);
13575
+ assert_ne!(tip_c.consensus_hash, tip_a.consensus_hash);
13576
+ assert_ne!(tip_c.burn_header_hash, tip_a.burn_header_hash);
13577
+ assert_eq!(tip_c.block_height, burn_height_before + 1);
13462
13578
13463
- // We should see all signers rejecting the forked block
13464
- wait_for_block_global_rejection(60, result.mined_c.signer_signature_hash, 5).unwrap();
13465
-
13466
- wait_for(60, || {
13579
+ wait_for(30, || {
13580
+ let mut nmb_matches = 0;
13467
13581
let stackerdb_events = test_observer::get_stackerdb_chunks();
13468
- let mut collected = HashSet::new();
13469
13582
for chunk in stackerdb_events
13470
13583
.into_iter()
13471
13584
.flat_map(|chunk| chunk.modified_slots)
@@ -13480,26 +13593,56 @@ fn reorging_capitulate_to_nonreorging_signers_during_tenure_fork() {
13480
13593
burn_block_height,
13481
13594
current_miner:
13482
13595
StateMachineUpdateMinerState::ActiveMiner {
13483
- tenure_id,
13484
- parent_tenure_id,
13485
- ..
13596
+ current_miner_pkh, ..
13486
13597
},
13487
- } = &update.content
13598
+ ..
13599
+ } = update.content
13488
13600
else {
13489
13601
continue;
13490
13602
};
13491
- if *burn_block_height != result.tip_c.burn_header_height as u64
13492
- || *burn_block != result.tip_c.consensus_hash
13493
- || *tenure_id != result.tip_d.consensus_hash
13494
- || *parent_tenure_id != result.tip_a.consensus_hash
13603
+ if burn_block == tenure_c_block_proposal.header.consensus_hash
13604
+ && burn_block_height == burn_height_before + 1
13605
+ && current_miner_pkh == miner_pkh_1
13495
13606
{
13496
- continue ;
13607
+ nmb_matches += 1 ;
13497
13608
}
13498
- collected.insert(chunk.sig);
13499
13609
}
13500
- Ok(collected.len() == 5)
13610
+ Ok(nmb_matches == 5)
13501
13611
})
13502
- .expect(
13503
- "Timed out waiting for the expected updates from signers to arrive due to capitulation",
13612
+ .unwrap();
13613
+
13614
+ info!("--------------- Miner 1 Extends Tenure B over Tenure C ---------------");
13615
+ TEST_BROADCAST_PROPOSAL_STALL.set(vec![]);
13616
+ let tenure_extend_block =
13617
+ wait_for_block_proposal(30, tip_b.stacks_block_height + 1, &miner_pk_1)
13618
+ .expect("Timed out waiting for miner 1's tenure extend block");
13619
+ wait_for_block_acceptance_from_signers(
13620
+ 3,
13621
+ &tenure_extend_block.header.signer_signature_hash(),
13622
+ &miners.signer_test.signer_test_pks(),
13623
+ )
13624
+ .expect("Expected all signers to accept the extend");
13625
+
13626
+ info!("------------------------- Miner 1 Mines Another Block -------------------------");
13627
+ miners
13628
+ .send_and_mine_transfer_tx(30)
13629
+ .expect("Failed to mine tx");
13630
+
13631
+ info!("------------------------- Miner 2 Mines the Next Tenure -------------------------");
13632
+ miners.submit_commit_miner_2(&sortdb);
13633
+
13634
+ miners
13635
+ .mine_bitcoin_block_and_tenure_change_tx(&sortdb, TenureChangeCause::BlockFound, 30)
13636
+ .expect("Failed to mine BTC block followed by tenure change tx");
13637
+
13638
+ // assure we have a successful sortition that miner 2 won and it had a block found tenure change
13639
+ verify_sortition_winner(&sortdb, &miner_pkh_2);
13640
+
13641
+ miners.shutdown();
13642
+
13643
+ // Block C was built AFTER Block B was built, but BEFORE it was broadcasted, so it should be built off of Block A
13644
+ assert_eq!(
13645
+ tenure_c_block_proposal.header.parent_block_id,
13646
+ tip_a.index_block_hash()
13504
13647
);
13505
13648
}
0 commit comments