@@ -84,8 +84,7 @@ use crate::tests::nakamoto_integrations::{
84
84
POX_4_DEFAULT_STACKER_BALANCE , POX_4_DEFAULT_STACKER_STX_AMT ,
85
85
} ;
86
86
use crate :: tests:: neon_integrations:: {
87
- get_account, get_chain_info, get_chain_info_opt, next_block_and_wait,
88
- run_until_burnchain_height, submit_tx, submit_tx_fallible, test_observer,
87
+ get_account, get_chain_info, get_chain_info_opt, get_sortition_info, get_sortition_info_ch, next_block_and_wait, run_until_burnchain_height, submit_tx, submit_tx_fallible, test_observer
89
88
} ;
90
89
use crate :: tests:: {
91
90
self , gen_random_port, make_contract_call, make_contract_publish, make_stacks_transfer,
@@ -11251,3 +11250,283 @@ fn fast_sortition() {
11251
11250
info ! ( "------------------------- Shutdown -------------------------" ) ;
11252
11251
signer_test. shutdown ( ) ;
11253
11252
}
11253
+
11254
+ #[ test]
11255
+ #[ ignore]
11256
+ /// This test spins up two nakamoto nodes, both configured to mine.
11257
+ /// After Nakamoto blocks are mined, it waits for a normal tenure, then issues
11258
+ /// two bitcoin blocks in quick succession -- the first will contain block commits,
11259
+ /// and the second "flash block" will contain no block commits.
11260
+ /// The test checks if the winner of the first block is different than the previous tenure.
11261
+ /// If so, it performs the actual test: asserting that the miner wakes up and produces valid blocks.
11262
+ /// This test uses the burn-block-height to ensure consistent calculation of the burn view between
11263
+ /// the miner thread and the block processor
11264
+
11265
+ fn multiple_miners_empty_sortition ( ) {
11266
+ if env:: var ( "BITCOIND_TEST" ) != Ok ( "1" . into ( ) ) {
11267
+ return ;
11268
+ }
11269
+ let num_signers = 5 ;
11270
+ let sender_sk = Secp256k1PrivateKey :: new ( ) ;
11271
+ let sender_addr = tests:: to_addr ( & sender_sk) ;
11272
+ let send_fee = 180 ;
11273
+
11274
+ let btc_miner_1_seed = vec ! [ 1 , 1 , 1 , 1 ] ;
11275
+ let btc_miner_2_seed = vec ! [ 2 , 2 , 2 , 2 ] ;
11276
+ let btc_miner_1_pk = Keychain :: default ( btc_miner_1_seed. clone ( ) ) . get_pub_key ( ) ;
11277
+ let btc_miner_2_pk = Keychain :: default ( btc_miner_2_seed. clone ( ) ) . get_pub_key ( ) ;
11278
+
11279
+ let node_1_rpc = gen_random_port ( ) ;
11280
+ let node_1_p2p = gen_random_port ( ) ;
11281
+ let node_2_rpc = gen_random_port ( ) ;
11282
+ let node_2_p2p = gen_random_port ( ) ;
11283
+
11284
+ let localhost = "127.0.0.1" ;
11285
+ let node_1_rpc_bind = format ! ( "{localhost}:{node_1_rpc}" ) ;
11286
+ let node_2_rpc_bind = format ! ( "{localhost}:{node_2_rpc}" ) ;
11287
+ let mut node_2_listeners = Vec :: new ( ) ;
11288
+
11289
+ let max_nakamoto_tenures = 30 ;
11290
+ // partition the signer set so that ~half are listening and using node 1 for RPC and events,
11291
+ // and the rest are using node 2
11292
+
11293
+ let mut signer_test: SignerTest < SpawnedSigner > = SignerTest :: new_with_config_modifications (
11294
+ num_signers,
11295
+ vec ! [ ( sender_addr, send_fee * 2 * 60 + 1000 ) ] ,
11296
+ |signer_config| {
11297
+ let node_host = if signer_config. endpoint . port ( ) % 2 == 0 {
11298
+ & node_1_rpc_bind
11299
+ } else {
11300
+ & node_2_rpc_bind
11301
+ } ;
11302
+ signer_config. node_host = node_host. to_string ( ) ;
11303
+ } ,
11304
+ |config| {
11305
+ config. node . rpc_bind = format ! ( "{localhost}:{node_1_rpc}" ) ;
11306
+ config. node . p2p_bind = format ! ( "{localhost}:{node_1_p2p}" ) ;
11307
+ config. node . data_url = format ! ( "http://{localhost}:{node_1_rpc}" ) ;
11308
+ config. node . p2p_address = format ! ( "{localhost}:{node_1_p2p}" ) ;
11309
+ config. miner . wait_on_interim_blocks = Duration :: from_secs ( 5 ) ;
11310
+ config. node . pox_sync_sample_secs = 30 ;
11311
+ config. burnchain . pox_reward_length = Some ( max_nakamoto_tenures) ;
11312
+
11313
+ config. node . seed = btc_miner_1_seed. clone ( ) ;
11314
+ config. node . local_peer_seed = btc_miner_1_seed. clone ( ) ;
11315
+ config. burnchain . local_mining_public_key = Some ( btc_miner_1_pk. to_hex ( ) ) ;
11316
+ config. miner . mining_key = Some ( Secp256k1PrivateKey :: from_seed ( & [ 1 ] ) ) ;
11317
+
11318
+ config. events_observers . retain ( |listener| {
11319
+ let Ok ( addr) = std:: net:: SocketAddr :: from_str ( & listener. endpoint ) else {
11320
+ warn ! (
11321
+ "Cannot parse {} to a socket, assuming it isn't a signer-listener binding" ,
11322
+ listener. endpoint
11323
+ ) ;
11324
+ return true ;
11325
+ } ;
11326
+ if addr. port ( ) % 2 == 0 || addr. port ( ) == test_observer:: EVENT_OBSERVER_PORT {
11327
+ return true ;
11328
+ }
11329
+ node_2_listeners. push ( listener. clone ( ) ) ;
11330
+ false
11331
+ } )
11332
+ } ,
11333
+ Some ( vec ! [ btc_miner_1_pk, btc_miner_2_pk] ) ,
11334
+ None ,
11335
+ ) ;
11336
+ let conf = signer_test. running_nodes . conf . clone ( ) ;
11337
+ let mut conf_node_2 = conf. clone ( ) ;
11338
+ conf_node_2. node . rpc_bind = format ! ( "{localhost}:{node_2_rpc}" ) ;
11339
+ conf_node_2. node . p2p_bind = format ! ( "{localhost}:{node_2_p2p}" ) ;
11340
+ conf_node_2. node . data_url = format ! ( "http://{localhost}:{node_2_rpc}" ) ;
11341
+ conf_node_2. node . p2p_address = format ! ( "{localhost}:{node_2_p2p}" ) ;
11342
+ conf_node_2. node . seed = btc_miner_2_seed. clone ( ) ;
11343
+ conf_node_2. burnchain . local_mining_public_key = Some ( btc_miner_2_pk. to_hex ( ) ) ;
11344
+ conf_node_2. node . local_peer_seed = btc_miner_2_seed. clone ( ) ;
11345
+ conf_node_2. miner . mining_key = Some ( Secp256k1PrivateKey :: from_seed ( & [ 2 ] ) ) ;
11346
+ conf_node_2. node . miner = true ;
11347
+ conf_node_2. events_observers . clear ( ) ;
11348
+ conf_node_2. events_observers . extend ( node_2_listeners) ;
11349
+ assert ! ( !conf_node_2. events_observers. is_empty( ) ) ;
11350
+
11351
+ let node_1_sk = Secp256k1PrivateKey :: from_seed ( & conf. node . local_peer_seed ) ;
11352
+ let node_1_pk = StacksPublicKey :: from_private ( & node_1_sk) ;
11353
+
11354
+ conf_node_2. node . working_dir = format ! ( "{}-1" , conf_node_2. node. working_dir) ;
11355
+
11356
+ conf_node_2. node . set_bootstrap_nodes (
11357
+ format ! ( "{}@{}" , & node_1_pk. to_hex( ) , conf. node. p2p_bind) ,
11358
+ conf. burnchain . chain_id ,
11359
+ conf. burnchain . peer_version ,
11360
+ ) ;
11361
+
11362
+ let mut run_loop_2 = boot_nakamoto:: BootRunLoop :: new ( conf_node_2. clone ( ) ) . unwrap ( ) ;
11363
+ let run_loop_stopper_2 = run_loop_2. get_termination_switch ( ) ;
11364
+ let rl2_coord_channels = run_loop_2. coordinator_channels ( ) ;
11365
+ let Counters {
11366
+ naka_submitted_commits : rl2_commits,
11367
+ ..
11368
+ } = run_loop_2. counters ( ) ;
11369
+ let run_loop_2_thread = thread:: Builder :: new ( )
11370
+ . name ( "run_loop_2" . into ( ) )
11371
+ . spawn ( move || run_loop_2. start ( None , 0 ) )
11372
+ . unwrap ( ) ;
11373
+
11374
+ signer_test. boot_to_epoch_3 ( ) ;
11375
+
11376
+ wait_for ( 120 , || {
11377
+ let Some ( node_1_info) = get_chain_info_opt ( & conf) else {
11378
+ return Ok ( false ) ;
11379
+ } ;
11380
+ let Some ( node_2_info) = get_chain_info_opt ( & conf_node_2) else {
11381
+ return Ok ( false ) ;
11382
+ } ;
11383
+ Ok ( node_1_info. stacks_tip_height == node_2_info. stacks_tip_height )
11384
+ } )
11385
+ . expect ( "Timed out waiting for boostrapped node to catch up to the miner" ) ;
11386
+
11387
+ let pre_nakamoto_peer_1_height = get_chain_info ( & conf) . stacks_tip_height ;
11388
+
11389
+ info ! ( "------------------------- Reached Epoch 3.0 -------------------------" ) ;
11390
+
11391
+ let burn_height_contract = "
11392
+ (define-data-var local-burn-block-ht uint u0)
11393
+ (define-public (run-update)
11394
+ (ok (var-set local-burn-block-ht burn-block-height)))
11395
+ " ;
11396
+
11397
+ let contract_tx = make_contract_publish (
11398
+ & sender_sk,
11399
+ 0 ,
11400
+ 1000 ,
11401
+ conf. burnchain . chain_id ,
11402
+ "burn-height-local" ,
11403
+ burn_height_contract,
11404
+ ) ;
11405
+ submit_tx ( & conf. node . data_url , & contract_tx) ;
11406
+
11407
+ let rl1_coord_channels = signer_test. running_nodes . coord_channel . clone ( ) ;
11408
+ let rl1_commits = signer_test. running_nodes . commits_submitted . clone ( ) ;
11409
+
11410
+ let last_sender_nonce = loop {
11411
+ // Mine 1 nakamoto tenures
11412
+ info ! ( "Mining tenure..." ) ;
11413
+ let rl2_commits_before = rl2_commits. load ( Ordering :: SeqCst ) ;
11414
+ let rl1_commits_before = rl1_commits. load ( Ordering :: SeqCst ) ;
11415
+
11416
+ signer_test. mine_block_wait_on_processing (
11417
+ & [ & rl1_coord_channels, & rl2_coord_channels] ,
11418
+ & [ & rl1_commits, & rl2_commits] ,
11419
+ Duration :: from_secs ( 30 ) ,
11420
+ ) ;
11421
+
11422
+ // mine the interim blocks
11423
+ for _ in 0 ..2 {
11424
+ let sender_nonce = get_account ( & conf. node . data_url , & sender_addr) . nonce ;
11425
+ // check if the burn contract is already produced, if not wait for it to be included in
11426
+ // an interim block
11427
+ if sender_nonce >= 1 {
11428
+ let contract_call_tx = make_contract_call (
11429
+ & sender_sk,
11430
+ sender_nonce,
11431
+ send_fee,
11432
+ conf. burnchain . chain_id ,
11433
+ & sender_addr,
11434
+ "burn-height-local" ,
11435
+ "run-update" ,
11436
+ & [ ] ,
11437
+ ) ;
11438
+ submit_tx ( & conf. node . data_url , & contract_call_tx) ;
11439
+ }
11440
+
11441
+ // make sure the sender's tx gets included (whether it was the contract publish or call)
11442
+ wait_for ( 60 , || {
11443
+ let next_sender_nonce = get_account ( & conf. node . data_url , & sender_addr) . nonce ;
11444
+ Ok ( next_sender_nonce > sender_nonce)
11445
+ } )
11446
+ . unwrap ( ) ;
11447
+ }
11448
+
11449
+
11450
+ let last_active_sortition = get_sortition_info ( & conf) ;
11451
+ assert ! ( last_active_sortition. was_sortition) ;
11452
+
11453
+ // lets mine a btc flash block
11454
+ let rl2_commits_before = rl2_commits. load ( Ordering :: SeqCst ) ;
11455
+ let rl1_commits_before = rl1_commits. load ( Ordering :: SeqCst ) ;
11456
+ signer_test. running_nodes . btc_regtest_controller . build_next_block ( 2 ) ;
11457
+
11458
+ wait_for ( 60 , || {
11459
+ Ok ( rl2_commits. load ( Ordering :: SeqCst ) > rl2_commits_before &&
11460
+ rl1_commits. load ( Ordering :: SeqCst ) > rl1_commits_before)
11461
+ } )
11462
+ . unwrap ( ) ;
11463
+
11464
+ let cur_empty_sortition = get_sortition_info ( & conf) ;
11465
+ assert ! ( !cur_empty_sortition. was_sortition) ;
11466
+ let inactive_sortition = get_sortition_info_ch (
11467
+ & conf,
11468
+ cur_empty_sortition. last_sortition_ch . as_ref ( ) . unwrap ( ) ,
11469
+ ) ;
11470
+ assert ! ( inactive_sortition. was_sortition) ;
11471
+ assert_eq ! (
11472
+ inactive_sortition. burn_block_height,
11473
+ last_active_sortition. burn_block_height + 1
11474
+ ) ;
11475
+
11476
+ info ! ( "==================== Mined a flash block ====================" ) ;
11477
+ info ! ( "Flash block sortition info" ;
11478
+ "last_active_winner" => ?last_active_sortition. miner_pk_hash160,
11479
+ "last_winner" => ?inactive_sortition. miner_pk_hash160,
11480
+ "last_active_ch" => %last_active_sortition. consensus_hash,
11481
+ "last_winner_ch" => %inactive_sortition. consensus_hash,
11482
+ "cur_empty_sortition" => %cur_empty_sortition. consensus_hash,
11483
+ ) ;
11484
+
11485
+ if last_active_sortition. miner_pk_hash160 != inactive_sortition. miner_pk_hash160 {
11486
+ info ! (
11487
+ "==================== Mined a flash block with changed miners ===================="
11488
+ ) ;
11489
+ break get_account ( & conf. node . data_url , & sender_addr) . nonce ;
11490
+ }
11491
+ } ;
11492
+
11493
+ // after the flash block, make sure we get block processing without a new bitcoin block
11494
+ // being mined.
11495
+
11496
+ for _ in 0 ..2 {
11497
+ let sender_nonce = get_account ( & conf. node . data_url , & sender_addr) . nonce ;
11498
+ let contract_call_tx = make_contract_call (
11499
+ & sender_sk,
11500
+ sender_nonce,
11501
+ send_fee,
11502
+ conf. burnchain . chain_id ,
11503
+ & sender_addr,
11504
+ "burn-height-local" ,
11505
+ "run-update" ,
11506
+ & [ ] ,
11507
+ ) ;
11508
+ submit_tx ( & conf. node . data_url , & contract_call_tx) ;
11509
+
11510
+ wait_for ( 60 , || {
11511
+ let next_sender_nonce = get_account ( & conf. node . data_url , & sender_addr) . nonce ;
11512
+ Ok ( next_sender_nonce > sender_nonce)
11513
+ } )
11514
+ . unwrap ( ) ;
11515
+ }
11516
+
11517
+ assert_eq ! (
11518
+ get_account( & conf. node. data_url, & sender_addr) . nonce,
11519
+ last_sender_nonce + 2 ,
11520
+ "The last two transactions after the flash block must be included in a block"
11521
+ ) ;
11522
+
11523
+
11524
+ rl2_coord_channels
11525
+ . lock ( )
11526
+ . expect ( "Mutex poisoned" )
11527
+ . stop_chains_coordinator ( ) ;
11528
+ run_loop_stopper_2. store ( false , Ordering :: SeqCst ) ;
11529
+ run_loop_2_thread. join ( ) . unwrap ( ) ;
11530
+ signer_test. shutdown ( ) ;
11531
+
11532
+ }
0 commit comments