@@ -10815,3 +10815,377 @@ fn injected_signatures_are_ignored_across_boundaries() {
10815
10815
10816
10816
assert ! ( new_spawned_signer. stop( ) . is_none( ) ) ;
10817
10817
}
10818
+
10819
+ /// Test a scenario where:
10820
+ /// Two miners boot to Nakamoto.
10821
+ /// Sortition occurs. Miner 1 wins.
10822
+ /// Miner 1 proposes a block N
10823
+ /// Signers accept and the stacks tip advances to N
10824
+ /// Miner 1's block commits are paused so it cannot confirm the next tenure.
10825
+ /// Sortition occurs. Miner 2 wins.
10826
+ /// Miner 2 proposes block N+1
10827
+ /// Signers accept and the stacks tip advances to N+1
10828
+ /// Sortition occurs quickly, within first_proposal_burn_block_timing_secs. Miner 1 wins.
10829
+ /// Miner 1 proposes block N+1'
10830
+ /// Signers approve N+1', saying "Miner is not building off of most recent tenure. A tenure they
10831
+ /// reorg has already mined blocks, but the block was poorly timed, allowing the reorg."
10832
+ /// Miner 1 proposes N+2 and it is accepted.
10833
+ /// Asserts:
10834
+ /// - N+1 is signed and broadcasted
10835
+ /// - N+1' is signed and broadcasted
10836
+ /// - The tip advances to N+1 (Signed by Miner 1)
10837
+ /// - The tip advances to N+2 (Signed by Miner 1)
10838
+ #[ test]
10839
+ #[ ignore]
10840
+ fn allow_reorg_within_first_proposal_burn_block_timing_secs ( ) {
10841
+ if env:: var ( "BITCOIND_TEST" ) != Ok ( "1" . into ( ) ) {
10842
+ return ;
10843
+ }
10844
+
10845
+ let num_signers = 5 ;
10846
+ let recipient = PrincipalData :: from ( StacksAddress :: burn_address ( false ) ) ;
10847
+ let sender_sk = Secp256k1PrivateKey :: new ( ) ;
10848
+ let sender_addr = tests:: to_addr ( & sender_sk) ;
10849
+ let send_amt = 100 ;
10850
+ let send_fee = 180 ;
10851
+ let num_txs = 1 ;
10852
+
10853
+ let btc_miner_1_seed = vec ! [ 1 , 1 , 1 , 1 ] ;
10854
+ let btc_miner_2_seed = vec ! [ 2 , 2 , 2 , 2 ] ;
10855
+ let btc_miner_1_pk = Keychain :: default ( btc_miner_1_seed. clone ( ) ) . get_pub_key ( ) ;
10856
+ let btc_miner_2_pk = Keychain :: default ( btc_miner_2_seed. clone ( ) ) . get_pub_key ( ) ;
10857
+
10858
+ let node_1_rpc = gen_random_port ( ) ;
10859
+ let node_1_p2p = gen_random_port ( ) ;
10860
+ let node_2_rpc = gen_random_port ( ) ;
10861
+ let node_2_p2p = gen_random_port ( ) ;
10862
+
10863
+ let localhost = "127.0.0.1" ;
10864
+ let node_1_rpc_bind = format ! ( "{localhost}:{node_1_rpc}" ) ;
10865
+ let node_2_rpc_bind = format ! ( "{localhost}:{node_2_rpc}" ) ;
10866
+ let mut node_2_listeners = Vec :: new ( ) ;
10867
+
10868
+ let max_nakamoto_tenures = 30 ;
10869
+
10870
+ info ! ( "------------------------- Test Setup -------------------------" ) ;
10871
+ // partition the signer set so that ~half are listening and using node 1 for RPC and events,
10872
+ // and the rest are using node 2
10873
+
10874
+ let mut signer_test: SignerTest < SpawnedSigner > = SignerTest :: new_with_config_modifications (
10875
+ num_signers,
10876
+ vec ! [ ( sender_addr, ( send_amt + send_fee) * num_txs) ] ,
10877
+ |signer_config| {
10878
+ // Lets make sure we never time out since we need to stall some things to force our scenario
10879
+ signer_config. block_proposal_validation_timeout = Duration :: from_secs ( 1800 ) ;
10880
+ signer_config. tenure_last_block_proposal_timeout = Duration :: from_secs ( 1800 ) ;
10881
+ signer_config. first_proposal_burn_block_timing = Duration :: from_secs ( 1800 ) ;
10882
+ let node_host = if signer_config. endpoint . port ( ) % 2 == 0 {
10883
+ & node_1_rpc_bind
10884
+ } else {
10885
+ & node_2_rpc_bind
10886
+ } ;
10887
+ signer_config. node_host = node_host. to_string ( ) ;
10888
+ } ,
10889
+ |config| {
10890
+ config. node . rpc_bind = format ! ( "{localhost}:{node_1_rpc}" ) ;
10891
+ config. node . p2p_bind = format ! ( "{localhost}:{node_1_p2p}" ) ;
10892
+ config. node . data_url = format ! ( "http://{localhost}:{node_1_rpc}" ) ;
10893
+ config. node . p2p_address = format ! ( "{localhost}:{node_1_p2p}" ) ;
10894
+ config. miner . wait_on_interim_blocks = Duration :: from_secs ( 5 ) ;
10895
+ config. node . pox_sync_sample_secs = 30 ;
10896
+ config. burnchain . pox_reward_length = Some ( max_nakamoto_tenures) ;
10897
+
10898
+ config. node . seed = btc_miner_1_seed. clone ( ) ;
10899
+ config. node . local_peer_seed = btc_miner_1_seed. clone ( ) ;
10900
+ config. burnchain . local_mining_public_key = Some ( btc_miner_1_pk. to_hex ( ) ) ;
10901
+ config. miner . mining_key = Some ( Secp256k1PrivateKey :: from_seed ( & [ 1 ] ) ) ;
10902
+
10903
+ config. events_observers . retain ( |listener| {
10904
+ let Ok ( addr) = std:: net:: SocketAddr :: from_str ( & listener. endpoint ) else {
10905
+ warn ! (
10906
+ "Cannot parse {} to a socket, assuming it isn't a signer-listener binding" ,
10907
+ listener. endpoint
10908
+ ) ;
10909
+ return true ;
10910
+ } ;
10911
+ if addr. port ( ) % 2 == 0 || addr. port ( ) == test_observer:: EVENT_OBSERVER_PORT {
10912
+ return true ;
10913
+ }
10914
+ node_2_listeners. push ( listener. clone ( ) ) ;
10915
+ false
10916
+ } )
10917
+ } ,
10918
+ Some ( vec ! [ btc_miner_1_pk, btc_miner_2_pk] ) ,
10919
+ None ,
10920
+ ) ;
10921
+ let conf = signer_test. running_nodes . conf . clone ( ) ;
10922
+ let mut conf_node_2 = conf. clone ( ) ;
10923
+ conf_node_2. node . rpc_bind = format ! ( "{localhost}:{node_2_rpc}" ) ;
10924
+ conf_node_2. node . p2p_bind = format ! ( "{localhost}:{node_2_p2p}" ) ;
10925
+ conf_node_2. node . data_url = format ! ( "http://{localhost}:{node_2_rpc}" ) ;
10926
+ conf_node_2. node . p2p_address = format ! ( "{localhost}:{node_2_p2p}" ) ;
10927
+ conf_node_2. node . seed = btc_miner_2_seed. clone ( ) ;
10928
+ conf_node_2. burnchain . local_mining_public_key = Some ( btc_miner_2_pk. to_hex ( ) ) ;
10929
+ conf_node_2. node . local_peer_seed = btc_miner_2_seed. clone ( ) ;
10930
+ conf_node_2. miner . mining_key = Some ( Secp256k1PrivateKey :: from_seed ( & [ 2 ] ) ) ;
10931
+ conf_node_2. node . miner = true ;
10932
+ conf_node_2. events_observers . clear ( ) ;
10933
+ conf_node_2. events_observers . extend ( node_2_listeners) ;
10934
+ assert ! ( !conf_node_2. events_observers. is_empty( ) ) ;
10935
+
10936
+ let node_1_sk = Secp256k1PrivateKey :: from_seed ( & conf. node . local_peer_seed ) ;
10937
+ let node_1_pk = StacksPublicKey :: from_private ( & node_1_sk) ;
10938
+
10939
+ conf_node_2. node . working_dir = format ! ( "{}-1" , conf_node_2. node. working_dir) ;
10940
+
10941
+ conf_node_2. node . set_bootstrap_nodes (
10942
+ format ! ( "{}@{}" , & node_1_pk. to_hex( ) , conf. node. p2p_bind) ,
10943
+ conf. burnchain . chain_id ,
10944
+ conf. burnchain . peer_version ,
10945
+ ) ;
10946
+ let http_origin = format ! ( "http://{}" , & signer_test. running_nodes. conf. node. rpc_bind) ;
10947
+
10948
+ let mut run_loop_2 = boot_nakamoto:: BootRunLoop :: new ( conf_node_2. clone ( ) ) . unwrap ( ) ;
10949
+ let run_loop_stopper_2 = run_loop_2. get_termination_switch ( ) ;
10950
+ let rl2_coord_channels = run_loop_2. coordinator_channels ( ) ;
10951
+ let Counters {
10952
+ naka_submitted_commits : rl2_commits,
10953
+ naka_skip_commit_op : rl2_skip_commit_op,
10954
+ naka_mined_blocks : blocks_mined2,
10955
+ ..
10956
+ } = run_loop_2. counters ( ) ;
10957
+
10958
+ let blocks_mined1 = signer_test. running_nodes . nakamoto_blocks_mined . clone ( ) ;
10959
+ let rl1_commits = signer_test. running_nodes . commits_submitted . clone ( ) ;
10960
+
10961
+ info ! ( "------------------------- Pause Miner 2's Block Commits -------------------------" ) ;
10962
+
10963
+ // Make sure Miner 2 cannot win a sortition at first.
10964
+ rl2_skip_commit_op. set ( true ) ;
10965
+
10966
+ info ! ( "------------------------- Boot to Epoch 3.0 -------------------------" ) ;
10967
+
10968
+ let run_loop_2_thread = thread:: Builder :: new ( )
10969
+ . name ( "run_loop_2" . into ( ) )
10970
+ . spawn ( move || run_loop_2. start ( None , 0 ) )
10971
+ . unwrap ( ) ;
10972
+
10973
+ signer_test. boot_to_epoch_3 ( ) ;
10974
+
10975
+ wait_for ( 120 , || {
10976
+ let Some ( node_1_info) = get_chain_info_opt ( & conf) else {
10977
+ return Ok ( false ) ;
10978
+ } ;
10979
+ let Some ( node_2_info) = get_chain_info_opt ( & conf_node_2) else {
10980
+ return Ok ( false ) ;
10981
+ } ;
10982
+ Ok ( node_1_info. stacks_tip_height == node_2_info. stacks_tip_height )
10983
+ } )
10984
+ . expect ( "Timed out waiting for boostrapped node to catch up to the miner" ) ;
10985
+
10986
+ let mining_pk_1 = StacksPublicKey :: from_private ( & conf. miner . mining_key . unwrap ( ) ) ;
10987
+ let mining_pk_2 = StacksPublicKey :: from_private ( & conf_node_2. miner . mining_key . unwrap ( ) ) ;
10988
+ let mining_pkh_1 = Hash160 :: from_node_public_key ( & mining_pk_1) ;
10989
+ let mining_pkh_2 = Hash160 :: from_node_public_key ( & mining_pk_2) ;
10990
+ debug ! ( "The mining key for miner 1 is {mining_pkh_1}" ) ;
10991
+ debug ! ( "The mining key for miner 2 is {mining_pkh_2}" ) ;
10992
+
10993
+ info ! ( "------------------------- Reached Epoch 3.0 -------------------------" ) ;
10994
+
10995
+ let burnchain = signer_test. running_nodes . conf . get_burnchain ( ) ;
10996
+ let sortdb = burnchain. open_sortition_db ( true ) . unwrap ( ) ;
10997
+
10998
+ let get_burn_height = || {
10999
+ SortitionDB :: get_canonical_burn_chain_tip ( sortdb. conn ( ) )
11000
+ . unwrap ( )
11001
+ . block_height
11002
+ } ;
11003
+ let starting_burn_height = get_burn_height ( ) ;
11004
+
11005
+ info ! ( "------------------------- Pause Miner 1's Block Commits -------------------------" ) ;
11006
+ signer_test
11007
+ . running_nodes
11008
+ . nakamoto_test_skip_commit_op
11009
+ . set ( true ) ;
11010
+
11011
+ info ! ( "------------------------- Miner 1 Mines a Nakamoto Block N (Globally Accepted) -------------------------" ) ;
11012
+ let blocks_processed_before_1 = blocks_mined1. load ( Ordering :: SeqCst ) ;
11013
+ let stacks_height_before = signer_test
11014
+ . stacks_client
11015
+ . get_peer_info ( )
11016
+ . expect ( "Failed to get peer info" )
11017
+ . stacks_tip_height ;
11018
+ let info_before = get_chain_info ( & conf) ;
11019
+ let mined_before = test_observer:: get_mined_nakamoto_blocks ( ) . len ( ) ;
11020
+
11021
+ next_block_and (
11022
+ & mut signer_test. running_nodes . btc_regtest_controller ,
11023
+ 30 ,
11024
+ || {
11025
+ Ok ( get_burn_height ( ) > starting_burn_height
11026
+ && signer_test
11027
+ . stacks_client
11028
+ . get_peer_info ( )
11029
+ . expect ( "Failed to get peer info" )
11030
+ . stacks_tip_height
11031
+ > stacks_height_before
11032
+ && blocks_mined1. load ( Ordering :: SeqCst ) > blocks_processed_before_1
11033
+ && get_chain_info ( & conf) . stacks_tip_height > info_before. stacks_tip_height
11034
+ && test_observer:: get_mined_nakamoto_blocks ( ) . len ( ) > mined_before)
11035
+ } ,
11036
+ )
11037
+ . expect ( "Timed out waiting for Miner 1 to Mine Block N" ) ;
11038
+
11039
+ let blocks = test_observer:: get_mined_nakamoto_blocks ( ) ;
11040
+ let block_n = blocks. last ( ) . unwrap ( ) . clone ( ) ;
11041
+ let block_n_signature_hash = block_n. signer_signature_hash ;
11042
+
11043
+ let info_after = get_chain_info ( & conf) ;
11044
+ assert_eq ! ( info_after. stacks_tip. to_string( ) , block_n. block_hash) ;
11045
+ assert_eq ! ( block_n. signer_signature_hash, block_n_signature_hash) ;
11046
+ assert_eq ! (
11047
+ info_after. stacks_tip_height,
11048
+ info_before. stacks_tip_height + 1
11049
+ ) ;
11050
+
11051
+ // assure we have a successful sortition that miner 1 won
11052
+ let tip = SortitionDB :: get_canonical_burn_chain_tip ( sortdb. conn ( ) ) . unwrap ( ) ;
11053
+ assert ! ( tip. sortition) ;
11054
+ assert_eq ! ( tip. miner_pk_hash. unwrap( ) , mining_pkh_1) ;
11055
+
11056
+ debug ! ( "Miner 1 mined block N: {block_n_signature_hash}" ) ;
11057
+
11058
+ info ! ( "------------------------- Miner 2 Submits a Block Commit -------------------------" ) ;
11059
+ let rl2_commits_before = rl2_commits. load ( Ordering :: SeqCst ) ;
11060
+ rl2_skip_commit_op. set ( false ) ;
11061
+
11062
+ wait_for ( 30 , || {
11063
+ Ok ( rl2_commits. load ( Ordering :: SeqCst ) > rl2_commits_before)
11064
+ } )
11065
+ . expect ( "Timed out waiting for Miner 2 to submit its block commit" ) ;
11066
+
11067
+ rl2_skip_commit_op. set ( true ) ;
11068
+
11069
+ info ! ( "------------------------- Pause Miner 2's Block Mining -------------------------" ) ;
11070
+ TEST_MINE_STALL . lock ( ) . unwrap ( ) . replace ( true ) ;
11071
+
11072
+ let burn_height_before = get_chain_info ( & signer_test. running_nodes . conf ) . burn_block_height ;
11073
+
11074
+ info ! ( "------------------------- Mine Tenure -------------------------" ) ;
11075
+ signer_test
11076
+ . running_nodes
11077
+ . btc_regtest_controller
11078
+ . build_next_block ( 1 ) ;
11079
+
11080
+ wait_for ( 60 , || {
11081
+ let info = get_chain_info ( & signer_test. running_nodes . conf ) ;
11082
+ Ok ( info. burn_block_height > burn_height_before)
11083
+ } )
11084
+ . expect ( "Failed to advance chain tip" ) ;
11085
+
11086
+ info ! ( "------------------------- Miner 1 Submits a Block Commit -------------------------" ) ;
11087
+ let rl1_commits_before = rl1_commits. load ( Ordering :: SeqCst ) ;
11088
+ signer_test
11089
+ . running_nodes
11090
+ . nakamoto_test_skip_commit_op
11091
+ . set ( false ) ;
11092
+
11093
+ wait_for ( 30 , || {
11094
+ Ok ( rl1_commits. load ( Ordering :: SeqCst ) > rl1_commits_before)
11095
+ } )
11096
+ . expect ( "Timed out waiting for Miner 1 to submit its block commit" ) ;
11097
+ signer_test
11098
+ . running_nodes
11099
+ . nakamoto_test_skip_commit_op
11100
+ . set ( true ) ;
11101
+
11102
+ info ! ( "------------------------- Miner 2 Mines Block N + 1 -------------------------" ) ;
11103
+ let blocks_processed_before_2 = blocks_mined2. load ( Ordering :: SeqCst ) ;
11104
+ let stacks_height_before = signer_test
11105
+ . stacks_client
11106
+ . get_peer_info ( )
11107
+ . expect ( "Failed to get peer info" )
11108
+ . stacks_tip_height ;
11109
+ let info_before = get_chain_info ( & conf) ;
11110
+
11111
+ TEST_MINE_STALL . lock ( ) . unwrap ( ) . replace ( false ) ;
11112
+
11113
+ wait_for ( 30 , || {
11114
+ Ok ( signer_test
11115
+ . stacks_client
11116
+ . get_peer_info ( )
11117
+ . expect ( "Failed to get peer info" )
11118
+ . stacks_tip_height
11119
+ > stacks_height_before
11120
+ && blocks_mined2. load ( Ordering :: SeqCst ) > blocks_processed_before_2
11121
+ && get_chain_info ( & conf) . stacks_tip_height > info_before. stacks_tip_height )
11122
+ } )
11123
+ . expect ( "Timed out waiting for Miner 2 to Mine Block N + 1" ) ;
11124
+
11125
+ // assure we have a successful sortition that miner 2 won
11126
+ let tip = SortitionDB :: get_canonical_burn_chain_tip ( sortdb. conn ( ) ) . unwrap ( ) ;
11127
+ assert ! ( tip. sortition) ;
11128
+ assert_eq ! ( tip. miner_pk_hash. unwrap( ) , mining_pkh_2) ;
11129
+
11130
+ info ! ( "------------------------- Miner 1 Wins the Next Tenure -------------------------" ) ;
11131
+
11132
+ let blocks_processed_before_1 = blocks_mined1. load ( Ordering :: SeqCst ) ;
11133
+ let mined_before = test_observer:: get_mined_nakamoto_blocks ( ) . len ( ) ;
11134
+
11135
+ next_block_and (
11136
+ & mut signer_test. running_nodes . btc_regtest_controller ,
11137
+ 30 ,
11138
+ || {
11139
+ Ok (
11140
+ blocks_mined1. load ( Ordering :: SeqCst ) > blocks_processed_before_1
11141
+ && test_observer:: get_mined_nakamoto_blocks ( ) . len ( ) > mined_before,
11142
+ )
11143
+ } ,
11144
+ )
11145
+ . expect ( "Timed out waiting for Miner 1 to Mine Block N+1'" ) ;
11146
+
11147
+ info ! ( "------------------------- Miner 1 Mines N+2 -------------------------" ) ;
11148
+
11149
+ let blocks_processed_before_1 = blocks_mined1. load ( Ordering :: SeqCst ) ;
11150
+ let stacks_height_before = signer_test
11151
+ . stacks_client
11152
+ . get_peer_info ( )
11153
+ . expect ( "Failed to get peer info" )
11154
+ . stacks_tip_height ;
11155
+ let info_before = get_chain_info ( & conf) ;
11156
+ let mined_before = test_observer:: get_mined_nakamoto_blocks ( ) . len ( ) ;
11157
+
11158
+ // submit a tx so that the miner will ATTEMPT to mine a stacks block N
11159
+ let transfer_tx = make_stacks_transfer (
11160
+ & sender_sk,
11161
+ 0 ,
11162
+ send_fee,
11163
+ signer_test. running_nodes . conf . burnchain . chain_id ,
11164
+ & recipient,
11165
+ send_amt,
11166
+ ) ;
11167
+ let tx = submit_tx ( & http_origin, & transfer_tx) ;
11168
+ info ! ( "Submitted tx {tx} in attempt to mine block N+2" ) ;
11169
+
11170
+ wait_for ( 30 , || {
11171
+ Ok ( signer_test
11172
+ . stacks_client
11173
+ . get_peer_info ( )
11174
+ . expect ( "Failed to get peer info" )
11175
+ . stacks_tip_height
11176
+ > stacks_height_before
11177
+ && blocks_mined1. load ( Ordering :: SeqCst ) > blocks_processed_before_1
11178
+ && get_chain_info ( & conf) . stacks_tip_height > info_before. stacks_tip_height
11179
+ && test_observer:: get_mined_nakamoto_blocks ( ) . len ( ) > mined_before)
11180
+ } )
11181
+ . expect ( "Timed out waiting for Miner 1 to Mine Block N+2" ) ;
11182
+
11183
+ info ! ( "------------------------- Shutdown -------------------------" ) ;
11184
+ rl2_coord_channels
11185
+ . lock ( )
11186
+ . expect ( "Mutex poisoned" )
11187
+ . stop_chains_coordinator ( ) ;
11188
+ run_loop_stopper_2. store ( false , Ordering :: SeqCst ) ;
11189
+ run_loop_2_thread. join ( ) . unwrap ( ) ;
11190
+ signer_test. shutdown ( ) ;
11191
+ }
0 commit comments