@@ -77,7 +77,9 @@ use tracing_subscriber::prelude::*;
77
77
use tracing_subscriber:: { fmt, EnvFilter } ;
78
78
79
79
use super :: SignerTest ;
80
- use crate :: event_dispatcher:: { MinedNakamotoBlockEvent , TEST_SKIP_BLOCK_ANNOUNCEMENT } ;
80
+ use crate :: event_dispatcher:: {
81
+ EventObserver , MinedNakamotoBlockEvent , TEST_SKIP_BLOCK_ANNOUNCEMENT ,
82
+ } ;
81
83
use crate :: nakamoto_node:: miner:: {
82
84
TEST_BLOCK_ANNOUNCE_STALL , TEST_BROADCAST_PROPOSAL_STALL , TEST_MINE_STALL ,
83
85
TEST_P2P_BROADCAST_STALL ,
@@ -396,6 +398,10 @@ impl SignerTest<SpawnedSigner> {
396
398
}
397
399
}
398
400
401
+ fn get_miner_key ( & self ) -> & Secp256k1PrivateKey {
402
+ self . running_nodes . conf . miner . mining_key . as_ref ( ) . unwrap ( )
403
+ }
404
+
399
405
/// Propose a block to the signers
400
406
fn propose_block ( & mut self , block : NakamotoBlock , timeout : Duration ) {
401
407
let miners_contract_id = boot_code_id ( MINERS_NAME , false ) ;
@@ -407,6 +413,9 @@ impl SignerTest<SpawnedSigner> {
407
413
. get_headers_height ( ) ;
408
414
let reward_cycle = self . get_current_reward_cycle ( ) ;
409
415
let signer_signature_hash = block. header . signer_signature_hash ( ) ;
416
+ let signed_by = block. header . recover_miner_pk ( ) . expect (
417
+ "FATAL: signer tests should only propose blocks that have been signed by the signer test miner. Otherwise, signers won't even consider them via this channel."
418
+ ) ;
410
419
let message = SignerMessage :: BlockProposal ( BlockProposal {
411
420
block,
412
421
burn_height,
@@ -419,6 +428,9 @@ impl SignerTest<SpawnedSigner> {
419
428
. miner
420
429
. mining_key
421
430
. expect ( "No mining key" ) ;
431
+ assert_eq ! ( signed_by, Secp256k1PublicKey :: from_private( & miner_sk) ,
432
+ "signer tests should only propose blocks that have been signed by the signer test miner. Otherwise, signers won't even consider them via this channel." ) ;
433
+
422
434
// Submit the block proposal to the miner's slot
423
435
let mut accepted = false ;
424
436
let mut version = 0 ;
@@ -1256,6 +1268,10 @@ fn block_proposal_rejection() {
1256
1268
1257
1269
// First propose a block to the signers that does not have the correct consensus hash or BitVec. This should be rejected BEFORE
1258
1270
// the block is submitted to the node for validation.
1271
+ block
1272
+ . header
1273
+ . sign_miner ( signer_test. get_miner_key ( ) )
1274
+ . unwrap ( ) ;
1259
1275
let block_signer_signature_hash_1 = block. header . signer_signature_hash ( ) ;
1260
1276
signer_test. propose_block ( block. clone ( ) , short_timeout) ;
1261
1277
@@ -1268,6 +1284,10 @@ fn block_proposal_rejection() {
1268
1284
block. header . consensus_hash = view. cur_sortition . consensus_hash ;
1269
1285
block. header . chain_length = 35 ; // We have mined 35 blocks so far.
1270
1286
1287
+ block
1288
+ . header
1289
+ . sign_miner ( signer_test. get_miner_key ( ) )
1290
+ . unwrap ( ) ;
1271
1291
let block_signer_signature_hash_2 = block. header . signer_signature_hash ( ) ;
1272
1292
signer_test. propose_block ( block, short_timeout) ;
1273
1293
@@ -1430,6 +1450,130 @@ fn mine_2_nakamoto_reward_cycles() {
1430
1450
signer_test. shutdown ( ) ;
1431
1451
}
1432
1452
1453
+ #[ test]
1454
+ #[ ignore]
1455
+ /// This test is a regression test for issue #5858 in which the signer runloop
1456
+ /// used the signature from the stackerdb to determine the miner public key.
1457
+ /// This does not work in cases where events get coalesced. The fix was to use
1458
+ /// the signature in the proposal's block header instead.
1459
+ ///
1460
+ /// This test covers the regression by adding a thread that interposes on the
1461
+ /// stackerdb events sent to the test signers and mutating the signatures
1462
+ /// so that the stackerdb chunks are signed by the wrong signer. After the
1463
+ /// fix to #5848, signers are resilient to this behavior because they check
1464
+ /// the signature on the block proposal (not the chunk).
1465
+ fn regr_use_block_header_pk ( ) {
1466
+ if env:: var ( "BITCOIND_TEST" ) != Ok ( "1" . into ( ) ) {
1467
+ return ;
1468
+ }
1469
+
1470
+ tracing_subscriber:: registry ( )
1471
+ . with ( fmt:: layer ( ) )
1472
+ . with ( EnvFilter :: from_default_env ( ) )
1473
+ . init ( ) ;
1474
+
1475
+ info ! ( "------------------------- Test Setup -------------------------" ) ;
1476
+ let num_signers = 5 ;
1477
+ let signer_listeners: Mutex < Vec < String > > = Mutex :: default ( ) ;
1478
+ let mut signer_test: SignerTest < SpawnedSigner > = SignerTest :: new_with_config_modifications (
1479
+ num_signers,
1480
+ vec ! [ ] ,
1481
+ |_| { } ,
1482
+ |node_config| {
1483
+ node_config. events_observers = node_config
1484
+ . events_observers
1485
+ . clone ( )
1486
+ . into_iter ( )
1487
+ . map ( |mut event_observer| {
1488
+ if event_observer
1489
+ . endpoint
1490
+ . ends_with ( & test_observer:: EVENT_OBSERVER_PORT . to_string ( ) )
1491
+ {
1492
+ event_observer
1493
+ } else if event_observer
1494
+ . events_keys
1495
+ . contains ( & EventKeyType :: StackerDBChunks )
1496
+ {
1497
+ event_observer
1498
+ . events_keys
1499
+ . retain ( |key| * key != EventKeyType :: StackerDBChunks ) ;
1500
+ let mut listeners_lock = signer_listeners. lock ( ) . unwrap ( ) ;
1501
+ listeners_lock. push ( event_observer. endpoint . clone ( ) ) ;
1502
+ event_observer
1503
+ } else {
1504
+ event_observer
1505
+ }
1506
+ } )
1507
+ . collect ( ) ;
1508
+ } ,
1509
+ None ,
1510
+ None ,
1511
+ ) ;
1512
+
1513
+ let signer_listeners: Vec < _ > = signer_listeners
1514
+ . lock ( )
1515
+ . unwrap ( )
1516
+ . drain ( ..)
1517
+ . map ( |endpoint| EventObserver {
1518
+ endpoint,
1519
+ db_path : None ,
1520
+ timeout : Duration :: from_secs ( 120 ) ,
1521
+ } )
1522
+ . collect ( ) ;
1523
+
1524
+ let bad_signer = Secp256k1PrivateKey :: from_seed ( & [ 0xde , 0xad , 0xbe , 0xef ] ) ;
1525
+ let bad_signer_pk = Secp256k1PublicKey :: from_private ( & bad_signer) ;
1526
+
1527
+ let broadcast_thread_stopper = Arc :: new ( AtomicBool :: new ( true ) ) ;
1528
+ let broadcast_thread_flag = broadcast_thread_stopper. clone ( ) ;
1529
+ let broadcast_thread = thread:: Builder :: new ( )
1530
+ . name ( "rebroadcast-thread" . into ( ) )
1531
+ . spawn ( move || {
1532
+ let mut last_sent = 0 ;
1533
+ while broadcast_thread_flag. load ( Ordering :: SeqCst ) {
1534
+ thread:: sleep ( Duration :: from_secs ( 1 ) ) ;
1535
+ let mut signerdb_chunks = test_observer:: get_stackerdb_chunks ( ) ;
1536
+ if last_sent >= signerdb_chunks. len ( ) {
1537
+ continue ;
1538
+ }
1539
+ let mut to_send = signerdb_chunks. split_off ( last_sent) ;
1540
+ last_sent = signerdb_chunks. len ( ) ;
1541
+ for event in to_send. iter_mut ( ) {
1542
+ // mutilate the signature
1543
+ event. modified_slots . iter_mut ( ) . for_each ( |chunk_data| {
1544
+ let pk = chunk_data. recover_pk ( ) . unwrap ( ) ;
1545
+ assert_ne ! ( pk, bad_signer_pk) ;
1546
+ chunk_data. sign ( & bad_signer) . unwrap ( ) ;
1547
+ } ) ;
1548
+
1549
+ let payload = serde_json:: to_value ( event) . unwrap ( ) ;
1550
+ for signer_listener in signer_listeners. iter ( ) {
1551
+ signer_listener. send_stackerdb_chunks ( & payload) ;
1552
+ }
1553
+ }
1554
+ }
1555
+ } )
1556
+ . unwrap ( ) ;
1557
+
1558
+ let timeout = Duration :: from_secs ( 200 ) ;
1559
+ signer_test. boot_to_epoch_3 ( ) ;
1560
+
1561
+ let prior_stacks_height = signer_test. get_peer_info ( ) . stacks_tip_height ;
1562
+
1563
+ let tenures_to_mine = 2 ;
1564
+ for _i in 0 ..tenures_to_mine {
1565
+ signer_test. mine_nakamoto_block ( timeout, false ) ;
1566
+ }
1567
+
1568
+ let current_stacks_height = signer_test. get_peer_info ( ) . stacks_tip_height ;
1569
+
1570
+ assert ! ( current_stacks_height >= prior_stacks_height + tenures_to_mine) ;
1571
+
1572
+ broadcast_thread_stopper. store ( false , Ordering :: SeqCst ) ;
1573
+ broadcast_thread. join ( ) . unwrap ( ) ;
1574
+ signer_test. shutdown ( ) ;
1575
+ }
1576
+
1433
1577
#[ test]
1434
1578
#[ ignore]
1435
1579
fn forked_tenure_invalid ( ) {
@@ -7067,6 +7211,10 @@ fn block_validation_response_timeout() {
7067
7211
block. header . consensus_hash = view. cur_sortition . consensus_hash ;
7068
7212
block. header . chain_length = info_before. stacks_tip_height + 1 ;
7069
7213
7214
+ block
7215
+ . header
7216
+ . sign_miner ( signer_test. get_miner_key ( ) )
7217
+ . unwrap ( ) ;
7070
7218
let block_signer_signature_hash_1 = block. header . signer_signature_hash ( ) ;
7071
7219
signer_test. propose_block ( block, timeout) ;
7072
7220
@@ -7352,6 +7500,10 @@ fn block_validation_pending_table() {
7352
7500
block. header . pox_treatment = BitVec :: ones ( 1 ) . unwrap ( ) ;
7353
7501
block. header . consensus_hash = view. cur_sortition . consensus_hash ;
7354
7502
block. header . chain_length = peer_info. stacks_tip_height + 1 ;
7503
+ block
7504
+ . header
7505
+ . sign_miner ( signer_test. get_miner_key ( ) )
7506
+ . unwrap ( ) ;
7355
7507
let block_signer_signature_hash = block. header . signer_signature_hash ( ) ;
7356
7508
signer_test. propose_block ( block. clone ( ) , short_timeout) ;
7357
7509
@@ -8009,11 +8161,19 @@ fn block_proposal_max_age_rejections() {
8009
8161
. block_proposal_max_age_secs
8010
8162
. saturating_add ( 1 ) ,
8011
8163
) ;
8164
+ block
8165
+ . header
8166
+ . sign_miner ( signer_test. get_miner_key ( ) )
8167
+ . unwrap ( ) ;
8012
8168
let block_signer_signature_hash_1 = block. header . signer_signature_hash ( ) ;
8013
8169
signer_test. propose_block ( block. clone ( ) , short_timeout) ;
8014
8170
8015
8171
// Next propose a recent invalid block
8016
8172
block. header . timestamp = get_epoch_time_secs ( ) ;
8173
+ block
8174
+ . header
8175
+ . sign_miner ( signer_test. get_miner_key ( ) )
8176
+ . unwrap ( ) ;
8017
8177
let block_signer_signature_hash_2 = block. header . signer_signature_hash ( ) ;
8018
8178
signer_test. propose_block ( block, short_timeout) ;
8019
8179
@@ -8614,6 +8774,10 @@ fn incoming_signers_ignore_block_proposals() {
8614
8774
txs : vec ! [ ] ,
8615
8775
} ;
8616
8776
block. header . timestamp = get_epoch_time_secs ( ) ;
8777
+ block
8778
+ . header
8779
+ . sign_miner ( signer_test. get_miner_key ( ) )
8780
+ . unwrap ( ) ;
8617
8781
let signer_signature_hash_1 = block. header . signer_signature_hash ( ) ;
8618
8782
8619
8783
info ! ( "------------------------- Test Attempt to Mine Invalid Block {signer_signature_hash_1} -------------------------" ) ;
@@ -8632,6 +8796,10 @@ fn incoming_signers_ignore_block_proposals() {
8632
8796
block. header . consensus_hash = view. cur_sortition . consensus_hash ;
8633
8797
block. header . chain_length =
8634
8798
get_chain_info ( & signer_test. running_nodes . conf ) . stacks_tip_height + 1 ;
8799
+ block
8800
+ . header
8801
+ . sign_miner ( signer_test. get_miner_key ( ) )
8802
+ . unwrap ( ) ;
8635
8803
let signer_signature_hash_2 = block. header . signer_signature_hash ( ) ;
8636
8804
8637
8805
info ! ( "------------------------- Test Attempt to Mine Invalid Block {signer_signature_hash_2} -------------------------" ) ;
@@ -8800,6 +8968,10 @@ fn outgoing_signers_ignore_block_proposals() {
8800
8968
block. header . consensus_hash = view. cur_sortition . consensus_hash ;
8801
8969
block. header . chain_length =
8802
8970
get_chain_info ( & signer_test. running_nodes . conf ) . stacks_tip_height + 1 ;
8971
+ block
8972
+ . header
8973
+ . sign_miner ( signer_test. get_miner_key ( ) )
8974
+ . unwrap ( ) ;
8803
8975
let signer_signature_hash = block. header . signer_signature_hash ( ) ;
8804
8976
8805
8977
info ! ( "------------------------- Test Attempt to Mine Invalid Block {signer_signature_hash} -------------------------" ) ;
0 commit comments