@@ -1078,6 +1078,40 @@ fn wait_for_block_global_rejection(
1078
1078
} )
1079
1079
}
1080
1080
1081
+ /// Waits for >30% of num_signers block rejection to be observed in the test_observer stackerdb chunks for a block
1082
+ /// with the provided signer signature hash and the specified reject_reason
1083
+ fn wait_for_block_global_rejection_with_reject_reason (
1084
+ timeout_secs : u64 ,
1085
+ block_signer_signature_hash : Sha512Trunc256Sum ,
1086
+ num_signers : usize ,
1087
+ reject_reason : RejectReason ,
1088
+ ) -> Result < ( ) , String > {
1089
+ let mut found_rejections = HashSet :: new ( ) ;
1090
+ wait_for ( timeout_secs, || {
1091
+ let chunks = test_observer:: get_stackerdb_chunks ( ) ;
1092
+ for chunk in chunks. into_iter ( ) . flat_map ( |chunk| chunk. modified_slots ) {
1093
+ let Ok ( message) = SignerMessage :: consensus_deserialize ( & mut chunk. data . as_slice ( ) )
1094
+ else {
1095
+ continue ;
1096
+ } ;
1097
+ if let SignerMessage :: BlockResponse ( BlockResponse :: Rejected ( BlockRejection {
1098
+ signer_signature_hash,
1099
+ signature,
1100
+ response_data,
1101
+ ..
1102
+ } ) ) = message
1103
+ {
1104
+ if signer_signature_hash == block_signer_signature_hash
1105
+ && response_data. reject_reason == reject_reason
1106
+ {
1107
+ found_rejections. insert ( signature) ;
1108
+ }
1109
+ }
1110
+ }
1111
+ Ok ( found_rejections. len ( ) >= num_signers * 3 / 10 )
1112
+ } )
1113
+ }
1114
+
1081
1115
/// Waits for the provided number of block rejections to be observed in the test_observer stackerdb chunks for a block
1082
1116
/// with the provided signer signature hash
1083
1117
fn wait_for_block_rejections (
@@ -10221,13 +10255,13 @@ fn block_proposal_timeout() {
10221
10255
/// Signers accept and the stacks tip advances to N
10222
10256
/// Miner 1's block commits are paused so it cannot confirm the next tenure.
10223
10257
/// Sortition occurs. Miner 2 wins.
10224
- /// Miner 2 successfully mines blocks N+1, N+2, and N+3
10258
+ /// Miner 2 successfully mines blocks N+1
10225
10259
/// Sortition occurs quickly, within first_proposal_burn_block_timing_secs. Miner 1 wins.
10226
10260
/// Miner 1 proposes block N+1'
10227
10261
/// Signers approve N+1', saying "Miner is not building off of most recent tenure. A tenure they
10228
10262
/// reorg has already mined blocks, but the block was poorly timed, allowing the reorg."
10229
10263
/// Miner 1 proposes N+2' and it is accepted.
10230
- /// Miner 1 wins the next tenure and mines N+4 , off of miner 2 's tip.
10264
+ /// Miner 1 wins the next tenure and mines N+3 , off of miner 1 's tip. (miner 2's N+1 gets reorg)
10231
10265
#[ test]
10232
10266
#[ ignore]
10233
10267
fn allow_reorg_within_first_proposal_burn_block_timing_secs ( ) {
@@ -10327,18 +10361,6 @@ fn allow_reorg_within_first_proposal_burn_block_timing_secs() {
10327
10361
block_n_height + 1
10328
10362
) ;
10329
10363
10330
- info ! ( "------------------------- Miner 2 Mines N+2 and N+3 -------------------------" ) ;
10331
- miners
10332
- . send_and_mine_transfer_tx ( 30 )
10333
- . expect ( "Failed to send and mine transfer tx" ) ;
10334
- miners
10335
- . send_and_mine_transfer_tx ( 30 )
10336
- . expect ( "Failed to send and mine transfer tx" ) ;
10337
- assert_eq ! (
10338
- get_chain_info( & conf_1) . stacks_tip_height,
10339
- block_n_height + 3
10340
- ) ;
10341
-
10342
10364
info ! ( "------------------------- Miner 1 Wins the Next Tenure, Mines N+1' -------------------------" ) ;
10343
10365
miners
10344
10366
. mine_bitcoin_blocks_and_confirm ( & sortdb, 1 , 30 )
@@ -10359,18 +10381,174 @@ fn allow_reorg_within_first_proposal_burn_block_timing_secs() {
10359
10381
let _ = wait_for_block_pushed_by_miner_key ( 30 , block_n_height + 2 , & miner_pk_1)
10360
10382
. expect ( "Failed to get block N+2'" ) ;
10361
10383
10362
- info ! ( "------------------------- Miner 1 Mines N+4 in Next Tenure -------------------------" ) ;
10384
+ info ! ( "------------------------- Miner 1 Mines N+3 in Next Tenure -------------------------" ) ;
10363
10385
10364
10386
miners
10365
10387
. mine_bitcoin_block_and_tenure_change_tx ( & sortdb, TenureChangeCause :: BlockFound , 60 )
10366
- . expect ( "Failed to mine BTC block followed by Block N+3 " ) ;
10367
- let miner_1_block_n_4 = wait_for_block_pushed_by_miner_key ( 30 , block_n_height + 4 , & miner_pk_1)
10388
+ . expect ( "Failed to mine BTC block followed by Block N+2 " ) ;
10389
+ let miner_1_block_n_3 = wait_for_block_pushed_by_miner_key ( 30 , block_n_height + 3 , & miner_pk_1)
10368
10390
. expect ( "Failed to get block N+3" ) ;
10369
10391
10370
10392
let peer_info = miners. get_peer_info ( ) ;
10371
- assert_eq ! ( peer_info. stacks_tip_height, block_n_height + 4 ) ;
10372
- assert_eq ! ( peer_info. stacks_tip, miner_1_block_n_4. header. block_hash( ) ) ;
10393
+ assert_eq ! ( peer_info. stacks_tip_height, block_n_height + 3 ) ;
10394
+ assert_eq ! ( peer_info. stacks_tip, miner_1_block_n_3. header. block_hash( ) ) ;
10395
+
10396
+ miners. shutdown ( ) ;
10397
+ }
10398
+
10399
+ /// Test a scenario where:
10400
+ /// Two miners boot to Nakamoto.
10401
+ /// Sortition occurs. Miner 1 wins.
10402
+ /// Miner 1 proposes a block N
10403
+ /// Signers accept and the stacks tip advances to N
10404
+ /// Miner 1's block commits are paused so it cannot confirm the next tenure.
10405
+ /// Sortition occurs. Miner 2 wins.
10406
+ /// Miner 2 successfully mines blocks N+1, N+2, and N+3
10407
+ /// Sortition occurs quickly, within first_proposal_burn_block_timing_secs. Miner 1 wins.
10408
+ /// Miner 1 proposes block N+1' but gets rejected as more than one block has been mined in the current tenure (by miner2)
10409
+ #[ test]
10410
+ #[ ignore]
10411
+ fn disallow_reorg_within_first_proposal_burn_block_timing_secs_but_more_than_one_block ( ) {
10412
+ if env:: var ( "BITCOIND_TEST" ) != Ok ( "1" . into ( ) ) {
10413
+ return ;
10414
+ }
10415
+
10416
+ let num_signers = 5 ;
10417
+ let num_txs = 3 ;
10418
+
10419
+ let mut miners = MultipleMinerTest :: new_with_config_modifications (
10420
+ num_signers,
10421
+ num_txs,
10422
+ |signer_config| {
10423
+ // Lets make sure we never time out since we need to stall some things to force our scenario
10424
+ signer_config. block_proposal_validation_timeout = Duration :: from_secs ( 1800 ) ;
10425
+ signer_config. tenure_last_block_proposal_timeout = Duration :: from_secs ( 1800 ) ;
10426
+ signer_config. first_proposal_burn_block_timing = Duration :: from_secs ( 1800 ) ;
10427
+ } ,
10428
+ |_| { } ,
10429
+ |_| { } ,
10430
+ ) ;
10431
+ let rl1_skip_commit_op = miners
10432
+ . signer_test
10433
+ . running_nodes
10434
+ . counters
10435
+ . naka_skip_commit_op
10436
+ . clone ( ) ;
10437
+ let rl2_skip_commit_op = miners. rl2_counters . naka_skip_commit_op . clone ( ) ;
10438
+
10439
+ let ( conf_1, _) = miners. get_node_configs ( ) ;
10440
+ let ( miner_pkh_1, miner_pkh_2) = miners. get_miner_public_key_hashes ( ) ;
10441
+ let ( miner_pk_1, miner_pk_2) = miners. get_miner_public_keys ( ) ;
10373
10442
10443
+ info ! ( "------------------------- Pause Miner 2's Block Commits -------------------------" ) ;
10444
+
10445
+ // Make sure Miner 2 cannot win a sortition at first.
10446
+ rl2_skip_commit_op. set ( true ) ;
10447
+
10448
+ miners. boot_to_epoch_3 ( ) ;
10449
+
10450
+ let burnchain = conf_1. get_burnchain ( ) ;
10451
+ let sortdb = burnchain. open_sortition_db ( true ) . unwrap ( ) ;
10452
+
10453
+ info ! ( "------------------------- Pause Miner 1's Block Commits -------------------------" ) ;
10454
+ rl1_skip_commit_op. set ( true ) ;
10455
+
10456
+ info ! ( "------------------------- Miner 1 Mines a Nakamoto Block N -------------------------" ) ;
10457
+ let stacks_height_before = miners. get_peer_stacks_tip_height ( ) ;
10458
+ miners
10459
+ . mine_bitcoin_block_and_tenure_change_tx ( & sortdb, TenureChangeCause :: BlockFound , 60 )
10460
+ . expect ( "Failed to mine BTC block followed by Block N" ) ;
10461
+
10462
+ let miner_1_block_n =
10463
+ wait_for_block_pushed_by_miner_key ( 30 , stacks_height_before + 1 , & miner_pk_1)
10464
+ . expect ( "Failed to get block N" ) ;
10465
+
10466
+ let block_n_height = miner_1_block_n. header . chain_length ;
10467
+ info ! ( "Block N: {block_n_height}" ) ;
10468
+ let info_after = get_chain_info ( & conf_1) ;
10469
+ assert_eq ! ( info_after. stacks_tip, miner_1_block_n. header. block_hash( ) ) ;
10470
+ assert_eq ! ( info_after. stacks_tip_height, block_n_height) ;
10471
+ assert_eq ! ( block_n_height, stacks_height_before + 1 ) ;
10472
+
10473
+ // assure we have a successful sortition that miner 1 won
10474
+ verify_sortition_winner ( & sortdb, & miner_pkh_1) ;
10475
+
10476
+ info ! ( "------------------------- Miner 2 Submits a Block Commit -------------------------" ) ;
10477
+ miners. submit_commit_miner_2 ( & sortdb) ;
10478
+
10479
+ info ! ( "------------------------- Pause Miner 2's Block Mining -------------------------" ) ;
10480
+ TEST_MINE_STALL . set ( true ) ;
10481
+
10482
+ info ! ( "------------------------- Mine Tenure -------------------------" ) ;
10483
+ miners
10484
+ . mine_bitcoin_blocks_and_confirm ( & sortdb, 1 , 60 )
10485
+ . expect ( "Failed to mine BTC block" ) ;
10486
+
10487
+ info ! ( "------------------------- Miner 1 Submits a Block Commit -------------------------" ) ;
10488
+ miners. submit_commit_miner_1 ( & sortdb) ;
10489
+
10490
+ info ! ( "------------------------- Miner 2 Mines Block N+1 -------------------------" ) ;
10491
+
10492
+ TEST_MINE_STALL . set ( false ) ;
10493
+ let _ = wait_for_block_pushed_by_miner_key ( 30 , block_n_height + 1 , & miner_pk_2)
10494
+ . expect ( "Failed to get block N+1" ) ;
10495
+
10496
+ // assure we have a successful sortition that miner 2 won
10497
+ verify_sortition_winner ( & sortdb, & miner_pkh_2) ;
10498
+
10499
+ assert_eq ! (
10500
+ get_chain_info( & conf_1) . stacks_tip_height,
10501
+ block_n_height + 1
10502
+ ) ;
10503
+
10504
+ info ! ( "------------------------- Miner 2 Mines N+2 and N+3 -------------------------" ) ;
10505
+ miners
10506
+ . send_and_mine_transfer_tx ( 30 )
10507
+ . expect ( "Failed to send and mine transfer tx" ) ;
10508
+ miners
10509
+ . send_and_mine_transfer_tx ( 30 )
10510
+ . expect ( "Failed to send and mine transfer tx" ) ;
10511
+ assert_eq ! (
10512
+ get_chain_info( & conf_1) . stacks_tip_height,
10513
+ block_n_height + 3
10514
+ ) ;
10515
+
10516
+ info ! ( "------------------------- Miner 1 Wins the Next Tenure, Mines N+1', got rejected -------------------------" ) ;
10517
+ miners. btc_regtest_controller_mut ( ) . build_next_block ( 1 ) ;
10518
+
10519
+ // wait for a block N+1' proposal from miner1
10520
+ let proposed_block = wait_for_block_proposal ( 30 , block_n_height + 1 , & miner_pk_1)
10521
+ . expect ( "Timed out waiting for block proposal" ) ;
10522
+ // check it has been rejected
10523
+ wait_for_block_global_rejection_with_reject_reason (
10524
+ 30 ,
10525
+ proposed_block. header . signer_signature_hash ( ) ,
10526
+ num_signers,
10527
+ RejectReason :: ReorgNotAllowed ,
10528
+ )
10529
+ . expect ( "Timed out waiting for a block proposal to be rejected due to invalid reorg" ) ;
10530
+
10531
+ // check only 1 block from miner1 has been added after the epoch3 boot
10532
+ let miner1_blocks_after_boot_to_epoch3 = get_nakamoto_headers ( & conf_1)
10533
+ . into_iter ( )
10534
+ . filter ( |block| {
10535
+ // skip first nakamoto block
10536
+ if block. stacks_block_height == stacks_height_before {
10537
+ return false ;
10538
+ }
10539
+ let nakamoto_block_header = block. anchored_header . as_stacks_nakamoto ( ) . unwrap ( ) ;
10540
+ miner_pk_1
10541
+ . verify (
10542
+ nakamoto_block_header. miner_signature_hash ( ) . as_bytes ( ) ,
10543
+ & nakamoto_block_header. miner_signature ,
10544
+ )
10545
+ . unwrap ( )
10546
+ } )
10547
+ . count ( ) ;
10548
+
10549
+ assert_eq ! ( miner1_blocks_after_boot_to_epoch3, 1 ) ;
10550
+
10551
+ info ! ( "------------------------- Shutdown -------------------------" ) ;
10374
10552
miners. shutdown ( ) ;
10375
10553
}
10376
10554
0 commit comments