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