@@ -43,6 +43,7 @@ use stacks::net::api::postblock_proposal::{ValidateRejectCode, TEST_VALIDATE_STA
43
43
use stacks:: net:: relay:: fault_injection:: set_ignore_block;
44
44
use stacks:: types:: chainstate:: { StacksAddress , StacksBlockId , StacksPrivateKey , StacksPublicKey } ;
45
45
use stacks:: types:: PublicKey ;
46
+ use stacks:: util:: get_epoch_time_secs;
46
47
use stacks:: util:: hash:: { hex_bytes, Hash160 , MerkleHashFunc } ;
47
48
use stacks:: util:: secp256k1:: { Secp256k1PrivateKey , Secp256k1PublicKey } ;
48
49
use stacks:: util_lib:: boot:: boot_code_id;
@@ -811,14 +812,8 @@ fn reloads_signer_set_in() {
811
812
let sender_addr = tests:: to_addr ( & sender_sk) ;
812
813
let send_amt = 100 ;
813
814
let send_fee = 180 ;
814
- let mut signer_test: SignerTest < SpawnedSigner > = SignerTest :: new_with_config_modifications (
815
- num_signers,
816
- vec ! [ ( sender_addr, send_amt + send_fee) ] ,
817
- |_config| { } ,
818
- |_| { } ,
819
- None ,
820
- None ,
821
- ) ;
815
+ let mut signer_test: SignerTest < SpawnedSigner > =
816
+ SignerTest :: new ( num_signers, vec ! [ ( sender_addr, send_amt + send_fee) ] ) ;
822
817
823
818
setup_epoch_3_reward_set (
824
819
& signer_test. running_nodes . conf ,
@@ -8574,3 +8569,115 @@ fn tenure_extend_after_2_bad_commits() {
8574
8569
run_loop_2_thread. join ( ) . unwrap ( ) ;
8575
8570
signer_test. shutdown ( ) ;
8576
8571
}
8572
+
8573
+ #[ test]
8574
+ #[ ignore]
8575
+ /// Test the block_proposal_max_age_secs signer configuration option. It should reject blocks that are
8576
+ /// invalid but within the max age window, otherwise it should simply drop the block without further processing.
8577
+ ///
8578
+ /// Test Setup:
8579
+ /// The test spins up five stacks signers, one miner Nakamoto node, and a corresponding bitcoind.
8580
+ ///
8581
+ /// Test Execution:
8582
+ /// The stacks node is advanced to epoch 3.0 reward set calculation to ensure the signer set is determined.
8583
+ /// An invalid block proposal with a recent timestamp is forcibly written to the miner's slot to simulate the miner proposing a block.
8584
+ /// The signers process the invalid block and broadcast a block response rejection to the respective .signers-XXX-YYY contract.
8585
+ /// A second block proposal with an outdated timestamp is then submitted to the miner's slot to simulate the miner proposing a very old block.
8586
+ /// The test confirms no further block rejection response is submitted to the .signers-XXX-YYY contract.
8587
+ ///
8588
+ /// Test Assertion:
8589
+ /// - Each signer successfully rejects the recent invalid block proposal.
8590
+ /// - No signer submits a block proposal response for the outdated block proposal.
8591
+ /// - The stacks tip does not advance
8592
+ fn block_proposal_max_age_rejections ( ) {
8593
+ if env:: var ( "BITCOIND_TEST" ) != Ok ( "1" . into ( ) ) {
8594
+ return ;
8595
+ }
8596
+
8597
+ tracing_subscriber:: registry ( )
8598
+ . with ( fmt:: layer ( ) )
8599
+ . with ( EnvFilter :: from_default_env ( ) )
8600
+ . init ( ) ;
8601
+
8602
+ info ! ( "------------------------- Test Setup -------------------------" ) ;
8603
+ let num_signers = 5 ;
8604
+ let mut signer_test: SignerTest < SpawnedSigner > = SignerTest :: new_with_config_modifications (
8605
+ num_signers,
8606
+ vec ! [ ] ,
8607
+ |config| {
8608
+ config. block_proposal_max_age_secs = 30 ;
8609
+ } ,
8610
+ |_| { } ,
8611
+ None ,
8612
+ None ,
8613
+ ) ;
8614
+ signer_test. boot_to_epoch_3 ( ) ;
8615
+ let short_timeout = Duration :: from_secs ( 30 ) ;
8616
+
8617
+ // Make sure no other block approvals are in the system.
8618
+ test_observer:: clear ( ) ;
8619
+ info ! ( "------------------------- Send Block Proposal To Signers -------------------------" ) ;
8620
+ let info_before = get_chain_info ( & signer_test. running_nodes . conf ) ;
8621
+ let mut block = NakamotoBlock {
8622
+ header : NakamotoBlockHeader :: empty ( ) ,
8623
+ txs : vec ! [ ] ,
8624
+ } ;
8625
+ // First propose a stale block that is older than the block_proposal_max_age_secs
8626
+ block. header . timestamp = get_epoch_time_secs ( ) . saturating_sub (
8627
+ signer_test. signer_configs [ 0 ]
8628
+ . block_proposal_max_age_secs
8629
+ . saturating_add ( 1 ) ,
8630
+ ) ;
8631
+ let _block_signer_signature_hash_1 = block. header . signer_signature_hash ( ) ;
8632
+ signer_test. propose_block ( block. clone ( ) , short_timeout) ;
8633
+
8634
+ // Next propose a recent invalid block
8635
+ block. header . timestamp = get_epoch_time_secs ( ) ;
8636
+ let block_signer_signature_hash_2 = block. header . signer_signature_hash ( ) ;
8637
+ signer_test. propose_block ( block, short_timeout) ;
8638
+
8639
+ info ! ( "------------------------- Test Block Proposal Rejected -------------------------" ) ;
8640
+ // Verify the signers rejected only the SECOND block proposal. The first was not even processed.
8641
+ wait_for ( 30 , || {
8642
+ let rejections: Vec < _ > = test_observer:: get_stackerdb_chunks ( )
8643
+ . into_iter ( )
8644
+ . flat_map ( |chunk| chunk. modified_slots )
8645
+ . map ( |chunk| {
8646
+ let Ok ( message) = SignerMessage :: consensus_deserialize ( & mut chunk. data . as_slice ( ) )
8647
+ else {
8648
+ return None ;
8649
+ } ;
8650
+ assert ! ( matches!(
8651
+ message,
8652
+ SignerMessage :: BlockResponse ( BlockResponse :: Rejected ( _) )
8653
+ ) ) ;
8654
+ let SignerMessage :: BlockResponse ( BlockResponse :: Rejected ( BlockRejection {
8655
+ reason_code,
8656
+ signer_signature_hash,
8657
+ signature,
8658
+ ..
8659
+ } ) ) = message
8660
+ else {
8661
+ panic ! ( "Received an unexpected block approval from the signer" ) ;
8662
+ } ;
8663
+ assert_eq ! (
8664
+ signer_signature_hash, block_signer_signature_hash_2,
8665
+ "Received a rejection for an unexpected block: {signer_signature_hash}"
8666
+ ) ;
8667
+ assert ! (
8668
+ matches!( reason_code, RejectCode :: SortitionViewMismatch ) ,
8669
+ "Received a rejection for an unexpected reason: {reason_code}"
8670
+ ) ;
8671
+ Some ( signature)
8672
+ } )
8673
+ . collect ( ) ;
8674
+ Ok ( rejections. len ( ) == num_signers)
8675
+ } )
8676
+ . expect ( "Timed out waiting for block rejections" ) ;
8677
+
8678
+ info ! ( "------------------------- Test Peer Info-------------------------" ) ;
8679
+ assert_eq ! ( info_before, get_chain_info( & signer_test. running_nodes. conf) ) ;
8680
+
8681
+ info ! ( "------------------------- Test Shutdown-------------------------" ) ;
8682
+ signer_test. shutdown ( ) ;
8683
+ }
0 commit comments