@@ -66,14 +66,16 @@ use tracing_subscriber::{fmt, EnvFilter};
66
66
use super :: SignerTest ;
67
67
use crate :: config:: { EventKeyType , EventObserverConfig } ;
68
68
use crate :: event_dispatcher:: MinedNakamotoBlockEvent ;
69
- use crate :: nakamoto_node:: miner:: { TEST_BLOCK_ANNOUNCE_STALL , TEST_BROADCAST_STALL } ;
69
+ use crate :: nakamoto_node:: miner:: {
70
+ TEST_BLOCK_ANNOUNCE_STALL , TEST_BROADCAST_STALL , TEST_MINE_STALL ,
71
+ } ;
70
72
use crate :: nakamoto_node:: sign_coordinator:: TEST_IGNORE_SIGNERS ;
71
73
use crate :: neon:: Counters ;
72
74
use crate :: run_loop:: boot_nakamoto;
73
75
use crate :: tests:: nakamoto_integrations:: {
74
76
boot_to_epoch_25, boot_to_epoch_3_reward_set, next_block_and, next_block_and_controller,
75
- setup_epoch_3_reward_set , wait_for , POX_4_DEFAULT_STACKER_BALANCE ,
76
- POX_4_DEFAULT_STACKER_STX_AMT ,
77
+ next_block_and_process_new_stacks_block , setup_epoch_3_reward_set , wait_for ,
78
+ POX_4_DEFAULT_STACKER_BALANCE , POX_4_DEFAULT_STACKER_STX_AMT ,
77
79
} ;
78
80
use crate :: tests:: neon_integrations:: {
79
81
get_account, get_chain_info, get_chain_info_opt, next_block_and_wait,
@@ -2562,6 +2564,338 @@ fn empty_sortition() {
2562
2564
signer_test. shutdown ( ) ;
2563
2565
}
2564
2566
2567
+ #[ test]
2568
+ #[ ignore]
2569
+ /// This test checks the behavior of signers when an empty sortition arrives
2570
+ /// before the first block of the previous tenure has been approved.
2571
+ /// Specifically:
2572
+ /// - The empty sortition will trigger the miner to attempt a tenure extend.
2573
+ /// - Signers will accept the tenure extend and sign subsequent blocks built
2574
+ /// off the old sortition
2575
+ fn empty_sortition_before_approval ( ) {
2576
+ if env:: var ( "BITCOIND_TEST" ) != Ok ( "1" . into ( ) ) {
2577
+ return ;
2578
+ }
2579
+
2580
+ tracing_subscriber:: registry ( )
2581
+ . with ( fmt:: layer ( ) )
2582
+ . with ( EnvFilter :: from_default_env ( ) )
2583
+ . init ( ) ;
2584
+
2585
+ info ! ( "------------------------- Test Setup -------------------------" ) ;
2586
+ let num_signers = 5 ;
2587
+ let sender_sk = Secp256k1PrivateKey :: new ( ) ;
2588
+ let sender_addr = tests:: to_addr ( & sender_sk) ;
2589
+ let send_amt = 100 ;
2590
+ let send_fee = 180 ;
2591
+ let recipient = PrincipalData :: from ( StacksAddress :: burn_address ( false ) ) ;
2592
+ let block_proposal_timeout = Duration :: from_secs ( 20 ) ;
2593
+ let mut signer_test: SignerTest < SpawnedSigner > = SignerTest :: new_with_config_modifications (
2594
+ num_signers,
2595
+ vec ! [ ( sender_addr. clone( ) , send_amt + send_fee) ] ,
2596
+ |config| {
2597
+ // make the duration long enough that the miner will be marked as malicious
2598
+ config. block_proposal_timeout = block_proposal_timeout;
2599
+ } ,
2600
+ |_| { } ,
2601
+ None ,
2602
+ None ,
2603
+ ) ;
2604
+ let http_origin = format ! ( "http://{}" , & signer_test. running_nodes. conf. node. rpc_bind) ;
2605
+
2606
+ signer_test. boot_to_epoch_3 ( ) ;
2607
+
2608
+ next_block_and_process_new_stacks_block (
2609
+ & mut signer_test. running_nodes . btc_regtest_controller ,
2610
+ 60 ,
2611
+ & signer_test. running_nodes . coord_channel ,
2612
+ )
2613
+ . unwrap ( ) ;
2614
+
2615
+ let info = get_chain_info ( & signer_test. running_nodes . conf ) ;
2616
+ let burn_height_before = info. burn_block_height ;
2617
+ let stacks_height_before = info. stacks_tip_height ;
2618
+
2619
+ info ! ( "Forcing miner to ignore signatures for next block" ) ;
2620
+ TEST_IGNORE_SIGNERS . lock ( ) . unwrap ( ) . replace ( true ) ;
2621
+
2622
+ info ! ( "Pausing block commits to trigger an empty sortition." ) ;
2623
+ signer_test
2624
+ . running_nodes
2625
+ . nakamoto_test_skip_commit_op
2626
+ . 0
2627
+ . lock ( )
2628
+ . unwrap ( )
2629
+ . replace ( true ) ;
2630
+
2631
+ info ! ( "------------------------- Test Mine Tenure A -------------------------" ) ;
2632
+ let proposed_before = signer_test
2633
+ . running_nodes
2634
+ . nakamoto_blocks_proposed
2635
+ . load ( Ordering :: SeqCst ) ;
2636
+ // Mine a regular tenure and wait for a block proposal
2637
+ next_block_and (
2638
+ & mut signer_test. running_nodes . btc_regtest_controller ,
2639
+ 60 ,
2640
+ || {
2641
+ let proposed_count = signer_test
2642
+ . running_nodes
2643
+ . nakamoto_blocks_proposed
2644
+ . load ( Ordering :: SeqCst ) ;
2645
+ Ok ( proposed_count > proposed_before)
2646
+ } ,
2647
+ )
2648
+ . expect ( "Failed to mine tenure A and propose a block" ) ;
2649
+
2650
+ info ! ( "------------------------- Test Mine Empty Tenure B -------------------------" ) ;
2651
+
2652
+ // Trigger an empty tenure
2653
+ next_block_and (
2654
+ & mut signer_test. running_nodes . btc_regtest_controller ,
2655
+ 60 ,
2656
+ || {
2657
+ let burn_height = get_chain_info ( & signer_test. running_nodes . conf ) . burn_block_height ;
2658
+ Ok ( burn_height == burn_height_before + 2 )
2659
+ } ,
2660
+ )
2661
+ . expect ( "Failed to mine empty tenure" ) ;
2662
+
2663
+ info ! ( "Unpause block commits" ) ;
2664
+ signer_test
2665
+ . running_nodes
2666
+ . nakamoto_test_skip_commit_op
2667
+ . 0
2668
+ . lock ( )
2669
+ . unwrap ( )
2670
+ . replace ( false ) ;
2671
+
2672
+ info ! ( "Stop ignoring signers and wait for the tip to advance" ) ;
2673
+ TEST_IGNORE_SIGNERS . lock ( ) . unwrap ( ) . replace ( false ) ;
2674
+
2675
+ wait_for ( 60 , || {
2676
+ let info = get_chain_info ( & signer_test. running_nodes . conf ) ;
2677
+ Ok ( info. stacks_tip_height > stacks_height_before)
2678
+ } )
2679
+ . expect ( "Failed to advance chain tip" ) ;
2680
+
2681
+ let info = get_chain_info ( & signer_test. running_nodes . conf ) ;
2682
+ info ! ( "Current state: {:?}" , info) ;
2683
+
2684
+ // Wait for a block with a tenure extend to be mined
2685
+ wait_for ( 60 , || {
2686
+ let blocks = test_observer:: get_blocks ( ) ;
2687
+ let last_block = blocks. last ( ) . unwrap ( ) ;
2688
+ info ! ( "Last block mined: {:?}" , last_block) ;
2689
+ for tx in last_block[ "transactions" ] . as_array ( ) . unwrap ( ) {
2690
+ let raw_tx = tx[ "raw_tx" ] . as_str ( ) . unwrap ( ) ;
2691
+ if raw_tx == "0x00" {
2692
+ continue ;
2693
+ }
2694
+ let tx_bytes = hex_bytes ( & raw_tx[ 2 ..] ) . unwrap ( ) ;
2695
+ let parsed = StacksTransaction :: consensus_deserialize ( & mut & tx_bytes[ ..] ) . unwrap ( ) ;
2696
+ match & parsed. payload {
2697
+ TransactionPayload :: TenureChange ( payload) => match payload. cause {
2698
+ TenureChangeCause :: Extended => {
2699
+ info ! ( "Found tenure extend block" ) ;
2700
+ return Ok ( true ) ;
2701
+ }
2702
+ TenureChangeCause :: BlockFound => { }
2703
+ } ,
2704
+ _ => { }
2705
+ } ;
2706
+ }
2707
+ Ok ( false )
2708
+ } )
2709
+ . expect ( "Timed out waiting for tenure extend" ) ;
2710
+
2711
+ let stacks_height_before = get_chain_info ( & signer_test. running_nodes . conf ) . stacks_tip_height ;
2712
+
2713
+ // submit a tx so that the miner will mine an extra block
2714
+ let sender_nonce = 0 ;
2715
+ let transfer_tx = make_stacks_transfer (
2716
+ & sender_sk,
2717
+ sender_nonce,
2718
+ send_fee,
2719
+ signer_test. running_nodes . conf . burnchain . chain_id ,
2720
+ & recipient,
2721
+ send_amt,
2722
+ ) ;
2723
+ submit_tx ( & http_origin, & transfer_tx) ;
2724
+
2725
+ wait_for ( 60 , || {
2726
+ let info = get_chain_info ( & signer_test. running_nodes . conf ) ;
2727
+ Ok ( info. stacks_tip_height > stacks_height_before)
2728
+ } )
2729
+ . expect ( "Failed to advance chain tip with STX transfer" ) ;
2730
+
2731
+ next_block_and_process_new_stacks_block (
2732
+ & mut signer_test. running_nodes . btc_regtest_controller ,
2733
+ 60 ,
2734
+ & signer_test. running_nodes . coord_channel ,
2735
+ )
2736
+ . expect ( "Failed to mine a normal tenure after the tenure extend" ) ;
2737
+
2738
+ signer_test. shutdown ( ) ;
2739
+ }
2740
+
2741
+ #[ test]
2742
+ #[ ignore]
2743
+ /// This test checks the behavior of signers when an empty sortition arrives
2744
+ /// before the first block of the previous tenure has been proposed.
2745
+ /// Specifically:
2746
+ /// - The empty sortition will trigger the miner to attempt a tenure extend.
2747
+ /// - Signers will accept the tenure extend and sign subsequent blocks built
2748
+ /// off the old sortition
2749
+ fn empty_sortition_before_proposal ( ) {
2750
+ if env:: var ( "BITCOIND_TEST" ) != Ok ( "1" . into ( ) ) {
2751
+ return ;
2752
+ }
2753
+
2754
+ tracing_subscriber:: registry ( )
2755
+ . with ( fmt:: layer ( ) )
2756
+ . with ( EnvFilter :: from_default_env ( ) )
2757
+ . init ( ) ;
2758
+
2759
+ info ! ( "------------------------- Test Setup -------------------------" ) ;
2760
+ let num_signers = 5 ;
2761
+ let sender_sk = Secp256k1PrivateKey :: new ( ) ;
2762
+ let sender_addr = tests:: to_addr ( & sender_sk) ;
2763
+ let send_amt = 100 ;
2764
+ let send_fee = 180 ;
2765
+ let recipient = PrincipalData :: from ( StacksAddress :: burn_address ( false ) ) ;
2766
+ let block_proposal_timeout = Duration :: from_secs ( 20 ) ;
2767
+ let mut signer_test: SignerTest < SpawnedSigner > = SignerTest :: new_with_config_modifications (
2768
+ num_signers,
2769
+ vec ! [ ( sender_addr. clone( ) , send_amt + send_fee) ] ,
2770
+ |config| {
2771
+ // make the duration long enough that the miner will be marked as malicious
2772
+ config. block_proposal_timeout = block_proposal_timeout;
2773
+ } ,
2774
+ |_| { } ,
2775
+ None ,
2776
+ None ,
2777
+ ) ;
2778
+ let http_origin = format ! ( "http://{}" , & signer_test. running_nodes. conf. node. rpc_bind) ;
2779
+
2780
+ signer_test. boot_to_epoch_3 ( ) ;
2781
+
2782
+ next_block_and_process_new_stacks_block (
2783
+ & mut signer_test. running_nodes . btc_regtest_controller ,
2784
+ 60 ,
2785
+ & signer_test. running_nodes . coord_channel ,
2786
+ )
2787
+ . unwrap ( ) ;
2788
+
2789
+ let info = get_chain_info ( & signer_test. running_nodes . conf ) ;
2790
+ let stacks_height_before = info. stacks_tip_height ;
2791
+
2792
+ info ! ( "Pause block commits to ensure we get an empty sortition" ) ;
2793
+ signer_test
2794
+ . running_nodes
2795
+ . nakamoto_test_skip_commit_op
2796
+ . 0
2797
+ . lock ( )
2798
+ . unwrap ( )
2799
+ . replace ( true ) ;
2800
+
2801
+ info ! ( "Pause miner so it doesn't propose a block before the next tenure arrives" ) ;
2802
+ TEST_MINE_STALL . lock ( ) . unwrap ( ) . replace ( true ) ;
2803
+
2804
+ let burn_height_before = get_chain_info ( & signer_test. running_nodes . conf ) . burn_block_height ;
2805
+
2806
+ info ! ( "------------------------- Test Mine Tenure A and B -------------------------" ) ;
2807
+ signer_test
2808
+ . running_nodes
2809
+ . btc_regtest_controller
2810
+ . build_next_block ( 2 ) ;
2811
+
2812
+ wait_for ( 60 , || {
2813
+ let info = get_chain_info ( & signer_test. running_nodes . conf ) ;
2814
+ Ok ( info. burn_block_height == burn_height_before + 2 )
2815
+ } )
2816
+ . expect ( "Failed to advance chain tip" ) ;
2817
+
2818
+ // Sleep a bit more to ensure the signers see both burn blocks
2819
+ sleep_ms ( 5_000 ) ;
2820
+
2821
+ info ! ( "Unpause miner" ) ;
2822
+ TEST_MINE_STALL . lock ( ) . unwrap ( ) . replace ( false ) ;
2823
+
2824
+ info ! ( "Unpause block commits" ) ;
2825
+ signer_test
2826
+ . running_nodes
2827
+ . nakamoto_test_skip_commit_op
2828
+ . 0
2829
+ . lock ( )
2830
+ . unwrap ( )
2831
+ . replace ( false ) ;
2832
+
2833
+ wait_for ( 60 , || {
2834
+ let info = get_chain_info ( & signer_test. running_nodes . conf ) ;
2835
+ Ok ( info. stacks_tip_height > stacks_height_before)
2836
+ } )
2837
+ . expect ( "Failed to advance chain tip" ) ;
2838
+
2839
+ let info = get_chain_info ( & signer_test. running_nodes . conf ) ;
2840
+ info ! ( "Current state: {:?}" , info) ;
2841
+
2842
+ // Wait for a block with a tenure extend to be mined
2843
+ wait_for ( 60 , || {
2844
+ let blocks = test_observer:: get_blocks ( ) ;
2845
+ let last_block = blocks. last ( ) . unwrap ( ) ;
2846
+ info ! ( "Last block mined: {:?}" , last_block) ;
2847
+ for tx in last_block[ "transactions" ] . as_array ( ) . unwrap ( ) {
2848
+ let raw_tx = tx[ "raw_tx" ] . as_str ( ) . unwrap ( ) ;
2849
+ if raw_tx == "0x00" {
2850
+ continue ;
2851
+ }
2852
+ let tx_bytes = hex_bytes ( & raw_tx[ 2 ..] ) . unwrap ( ) ;
2853
+ let parsed = StacksTransaction :: consensus_deserialize ( & mut & tx_bytes[ ..] ) . unwrap ( ) ;
2854
+ match & parsed. payload {
2855
+ TransactionPayload :: TenureChange ( payload) => match payload. cause {
2856
+ TenureChangeCause :: Extended => {
2857
+ info ! ( "Found tenure extend block" ) ;
2858
+ return Ok ( true ) ;
2859
+ }
2860
+ TenureChangeCause :: BlockFound => { }
2861
+ } ,
2862
+ _ => { }
2863
+ } ;
2864
+ }
2865
+ Ok ( false )
2866
+ } )
2867
+ . expect ( "Timed out waiting for tenure extend" ) ;
2868
+
2869
+ let stacks_height_before = get_chain_info ( & signer_test. running_nodes . conf ) . stacks_tip_height ;
2870
+
2871
+ // submit a tx so that the miner will mine an extra block
2872
+ let sender_nonce = 0 ;
2873
+ let transfer_tx = make_stacks_transfer (
2874
+ & sender_sk,
2875
+ sender_nonce,
2876
+ send_fee,
2877
+ signer_test. running_nodes . conf . burnchain . chain_id ,
2878
+ & recipient,
2879
+ send_amt,
2880
+ ) ;
2881
+ submit_tx ( & http_origin, & transfer_tx) ;
2882
+
2883
+ wait_for ( 60 , || {
2884
+ let info = get_chain_info ( & signer_test. running_nodes . conf ) ;
2885
+ Ok ( info. stacks_tip_height > stacks_height_before)
2886
+ } )
2887
+ . expect ( "Failed to advance chain tip with STX transfer" ) ;
2888
+
2889
+ next_block_and_process_new_stacks_block (
2890
+ & mut signer_test. running_nodes . btc_regtest_controller ,
2891
+ 60 ,
2892
+ & signer_test. running_nodes . coord_channel ,
2893
+ )
2894
+ . expect ( "Failed to mine a normal tenure after the tenure extend" ) ;
2895
+
2896
+ signer_test. shutdown ( ) ;
2897
+ }
2898
+
2565
2899
#[ test]
2566
2900
#[ ignore]
2567
2901
/// This test checks that Epoch 2.5 signers will issue a mock signature per burn block they receive.
0 commit comments