@@ -29,6 +29,7 @@ use http_types::headers::AUTHORIZATION;
29
29
use lazy_static:: lazy_static;
30
30
use libsigner:: v0:: messages:: SignerMessage as SignerMessageV0 ;
31
31
use libsigner:: { SignerSession , StackerDBSession } ;
32
+ use rusqlite:: OptionalExtension ;
32
33
use stacks:: burnchains:: { MagicBytes , Txid } ;
33
34
use stacks:: chainstate:: burn:: db:: sortdb:: SortitionDB ;
34
35
use stacks:: chainstate:: burn:: operations:: {
@@ -60,7 +61,7 @@ use stacks::core::{
60
61
StacksEpoch , StacksEpochId , BLOCK_LIMIT_MAINNET_10 , HELIUM_BLOCK_LIMIT_20 ,
61
62
PEER_VERSION_EPOCH_1_0 , PEER_VERSION_EPOCH_2_0 , PEER_VERSION_EPOCH_2_05 ,
62
63
PEER_VERSION_EPOCH_2_1 , PEER_VERSION_EPOCH_2_2 , PEER_VERSION_EPOCH_2_3 , PEER_VERSION_EPOCH_2_4 ,
63
- PEER_VERSION_EPOCH_2_5 , PEER_VERSION_EPOCH_3_0 , PEER_VERSION_TESTNET ,
64
+ PEER_VERSION_EPOCH_2_5 , PEER_VERSION_EPOCH_3_0 , PEER_VERSION_EPOCH_3_1 , PEER_VERSION_TESTNET ,
64
65
} ;
65
66
use stacks:: libstackerdb:: SlotMetadata ;
66
67
use stacks:: net:: api:: callreadonly:: CallReadOnlyRequestBody ;
@@ -70,7 +71,7 @@ use stacks::net::api::getstackers::GetStackersResponse;
70
71
use stacks:: net:: api:: postblock_proposal:: {
71
72
BlockValidateReject , BlockValidateResponse , NakamotoBlockProposal , ValidateRejectCode ,
72
73
} ;
73
- use stacks:: types:: chainstate:: StacksBlockId ;
74
+ use stacks:: types:: chainstate:: { ConsensusHash , StacksBlockId } ;
74
75
use stacks:: util:: hash:: hex_bytes;
75
76
use stacks:: util_lib:: boot:: boot_code_id;
76
77
use stacks:: util_lib:: signed_structured_data:: pox4:: {
@@ -84,7 +85,7 @@ use stacks_common::types::chainstate::{
84
85
BlockHeaderHash , BurnchainHeaderHash , StacksAddress , StacksPrivateKey , StacksPublicKey ,
85
86
TrieHash ,
86
87
} ;
87
- use stacks_common:: types:: StacksPublicKeyBuffer ;
88
+ use stacks_common:: types:: { set_test_coinbase_schedule , CoinbaseInterval , StacksPublicKeyBuffer } ;
88
89
use stacks_common:: util:: hash:: { to_hex, Hash160 , Sha512Trunc256Sum } ;
89
90
use stacks_common:: util:: secp256k1:: { MessageSignature , Secp256k1PrivateKey , Secp256k1PublicKey } ;
90
91
use stacks_common:: util:: { get_epoch_time_secs, sleep_ms} ;
@@ -114,7 +115,7 @@ pub static POX_4_DEFAULT_STACKER_BALANCE: u64 = 100_000_000_000_000;
114
115
pub static POX_4_DEFAULT_STACKER_STX_AMT : u128 = 99_000_000_000_000 ;
115
116
116
117
lazy_static ! {
117
- pub static ref NAKAMOTO_INTEGRATION_EPOCHS : [ StacksEpoch ; 9 ] = [
118
+ pub static ref NAKAMOTO_INTEGRATION_EPOCHS : [ StacksEpoch ; 10 ] = [
118
119
StacksEpoch {
119
120
epoch_id: StacksEpochId :: Epoch10 ,
120
121
start_height: 0 ,
@@ -174,10 +175,17 @@ lazy_static! {
174
175
StacksEpoch {
175
176
epoch_id: StacksEpochId :: Epoch30 ,
176
177
start_height: 231 ,
177
- end_height: STACKS_EPOCH_MAX ,
178
+ end_height: 241 ,
178
179
block_limit: HELIUM_BLOCK_LIMIT_20 . clone( ) ,
179
180
network_epoch: PEER_VERSION_EPOCH_3_0
180
181
} ,
182
+ StacksEpoch {
183
+ epoch_id: StacksEpochId :: Epoch31 ,
184
+ start_height: 241 ,
185
+ end_height: STACKS_EPOCH_MAX ,
186
+ block_limit: HELIUM_BLOCK_LIMIT_20 . clone( ) ,
187
+ network_epoch: PEER_VERSION_EPOCH_3_1
188
+ } ,
181
189
] ;
182
190
}
183
191
@@ -9500,3 +9508,221 @@ fn skip_mining_long_tx() {
9500
9508
9501
9509
run_loop_thread. join ( ) . unwrap ( ) ;
9502
9510
}
9511
+
9512
+ #[ test]
9513
+ #[ ignore]
9514
+ /// Integration test for SIP-029
9515
+ fn sip029_coinbase_change ( ) {
9516
+ if env:: var ( "BITCOIND_TEST" ) != Ok ( "1" . into ( ) ) {
9517
+ return ;
9518
+ }
9519
+
9520
+ let new_sched = vec ! [
9521
+ CoinbaseInterval {
9522
+ coinbase: 1_000_000_000 ,
9523
+ effective_start_height: 0 ,
9524
+ } ,
9525
+ // NOTE: epoch 3.1 goes into effect at 241
9526
+ CoinbaseInterval {
9527
+ coinbase: 500_000_000 ,
9528
+ effective_start_height: 245 ,
9529
+ } ,
9530
+ CoinbaseInterval {
9531
+ coinbase: 125_000_000 ,
9532
+ effective_start_height: 255 ,
9533
+ } ,
9534
+ CoinbaseInterval {
9535
+ coinbase: 62_500_000 ,
9536
+ effective_start_height: 265 ,
9537
+ } ,
9538
+ ] ;
9539
+
9540
+ set_test_coinbase_schedule ( Some ( new_sched. clone ( ) ) ) ;
9541
+
9542
+ let ( mut naka_conf, _miner_account) = naka_neon_integration_conf ( None ) ;
9543
+ naka_conf. miner . wait_on_interim_blocks = Duration :: from_secs ( 1 ) ;
9544
+ naka_conf. node . pox_sync_sample_secs = 180 ;
9545
+ naka_conf. burnchain . max_rbf = 10_000_000 ;
9546
+
9547
+ let sender_sk = Secp256k1PrivateKey :: new ( ) ;
9548
+ let sender_signer_sk = Secp256k1PrivateKey :: new ( ) ;
9549
+ let sender_signer_addr = tests:: to_addr ( & sender_signer_sk) ;
9550
+ let mut signers = TestSigners :: new ( vec ! [ sender_signer_sk] ) ;
9551
+ let tenure_count = 5 ;
9552
+ let inter_blocks_per_tenure = 9 ;
9553
+ // setup sender + recipient for some test stx transfers
9554
+ // these are necessary for the interim blocks to get mined at all
9555
+ let sender_addr = tests:: to_addr ( & sender_sk) ;
9556
+ let send_amt = 100 ;
9557
+ let send_fee = 180 ;
9558
+ naka_conf. add_initial_balance (
9559
+ PrincipalData :: from ( sender_addr) . to_string ( ) ,
9560
+ ( send_amt + send_fee) * tenure_count * inter_blocks_per_tenure,
9561
+ ) ;
9562
+ naka_conf. add_initial_balance ( PrincipalData :: from ( sender_signer_addr) . to_string ( ) , 100000 ) ;
9563
+ let stacker_sk = setup_stacker ( & mut naka_conf) ;
9564
+
9565
+ test_observer:: spawn ( ) ;
9566
+ test_observer:: register_any ( & mut naka_conf) ;
9567
+
9568
+ let mut btcd_controller = BitcoinCoreController :: new ( naka_conf. clone ( ) ) ;
9569
+ btcd_controller
9570
+ . start_bitcoind ( )
9571
+ . expect ( "Failed starting bitcoind" ) ;
9572
+ let mut btc_regtest_controller = BitcoinRegtestController :: new ( naka_conf. clone ( ) , None ) ;
9573
+ btc_regtest_controller. bootstrap_chain ( 201 ) ;
9574
+
9575
+ let mut run_loop = boot_nakamoto:: BootRunLoop :: new ( naka_conf. clone ( ) ) . unwrap ( ) ;
9576
+ let run_loop_stopper = run_loop. get_termination_switch ( ) ;
9577
+ let Counters {
9578
+ blocks_processed,
9579
+ naka_submitted_commits : commits_submitted,
9580
+ naka_proposed_blocks : proposals_submitted,
9581
+ ..
9582
+ } = run_loop. counters ( ) ;
9583
+
9584
+ let coord_channel = run_loop. coordinator_channels ( ) ;
9585
+
9586
+ let run_loop_thread = thread:: Builder :: new ( )
9587
+ . name ( "run_loop" . into ( ) )
9588
+ . spawn ( move || run_loop. start ( None , 0 ) )
9589
+ . unwrap ( ) ;
9590
+ wait_for_runloop ( & blocks_processed) ;
9591
+ boot_to_epoch_3 (
9592
+ & naka_conf,
9593
+ & blocks_processed,
9594
+ & [ stacker_sk] ,
9595
+ & [ sender_signer_sk] ,
9596
+ & mut Some ( & mut signers) ,
9597
+ & mut btc_regtest_controller,
9598
+ ) ;
9599
+
9600
+ info ! ( "Bootstrapped to Epoch-3.0 boundary, starting nakamoto miner" ) ;
9601
+
9602
+ let burnchain = naka_conf. get_burnchain ( ) ;
9603
+ let sortdb = burnchain. open_sortition_db ( true ) . unwrap ( ) ;
9604
+ let ( chainstate, _) = StacksChainState :: open (
9605
+ naka_conf. is_mainnet ( ) ,
9606
+ naka_conf. burnchain . chain_id ,
9607
+ & naka_conf. get_chainstate_path_str ( ) ,
9608
+ None ,
9609
+ )
9610
+ . unwrap ( ) ;
9611
+
9612
+ info ! ( "Nakamoto miner started..." ) ;
9613
+ blind_signer ( & naka_conf, & signers, proposals_submitted) ;
9614
+
9615
+ wait_for_first_naka_block_commit ( 60 , & commits_submitted) ;
9616
+
9617
+ // mine until burnchain height 270
9618
+ loop {
9619
+ let commits_before = commits_submitted. load ( Ordering :: SeqCst ) ;
9620
+ next_block_and_process_new_stacks_block ( & mut btc_regtest_controller, 60 , & coord_channel)
9621
+ . unwrap ( ) ;
9622
+ wait_for ( 20 , || {
9623
+ Ok ( commits_submitted. load ( Ordering :: SeqCst ) > commits_before)
9624
+ } )
9625
+ . unwrap ( ) ;
9626
+
9627
+ let node_info = get_chain_info_opt ( & naka_conf) . unwrap ( ) ;
9628
+ if node_info. burn_block_height >= 270 {
9629
+ break ;
9630
+ }
9631
+ }
9632
+
9633
+ info ! ( "Nakamoto miner has advanced to burn height 270" ) ;
9634
+
9635
+ // inspect `payments` table to see that coinbase was applied
9636
+ let all_snapshots = sortdb. get_all_snapshots ( ) . unwrap ( ) ;
9637
+
9638
+ // whether or not the last snapshot had a sortition
9639
+ let mut prev_sortition = false ;
9640
+
9641
+ // whether or not we witnessed the requisite coinbases
9642
+ let mut witnessed_1000 = false ;
9643
+ let mut witnessed_500 = false ;
9644
+ let mut witnessed_125 = false ;
9645
+ let mut witnessed_62_5 = false ;
9646
+
9647
+ // initial mining bonus
9648
+ let initial_mining_bonus = 20400000 ;
9649
+
9650
+ for sn in all_snapshots {
9651
+ if !sn. sortition {
9652
+ prev_sortition = false ;
9653
+ continue ;
9654
+ }
9655
+ if sn. consensus_hash == ConsensusHash ( [ 0x00 ; 20 ] ) {
9656
+ continue ;
9657
+ }
9658
+ let coinbase = {
9659
+ let sql = "SELECT coinbase FROM payments WHERE consensus_hash = ?1" ;
9660
+ let args = rusqlite:: params![ & sn. consensus_hash] ;
9661
+ let Some ( coinbase) = chainstate
9662
+ . db ( )
9663
+ . query_row ( sql, args, |r| {
9664
+ let coinbase_txt: String = r. get_unwrap ( 0 ) ;
9665
+ let coinbase: u64 = coinbase_txt. parse ( ) . unwrap ( ) ;
9666
+ Ok ( coinbase)
9667
+ } )
9668
+ . optional ( )
9669
+ . unwrap ( )
9670
+ else {
9671
+ info ! ( "No coinbase for {} {}" , sn. block_height, & sn. consensus_hash) ;
9672
+ continue ;
9673
+ } ;
9674
+
9675
+ coinbase
9676
+ } ;
9677
+
9678
+ info ! (
9679
+ "Coinbase at {} {}: {}" ,
9680
+ sn. block_height, & sn. consensus_hash, coinbase
9681
+ ) ;
9682
+ // use >= for coinbases since a missed sortition can lead to coinbase accumulation
9683
+ if sn. block_height < 245 {
9684
+ if prev_sortition {
9685
+ assert_eq ! ( coinbase, 1_000_000_000 + initial_mining_bonus) ;
9686
+ witnessed_1000 = true ;
9687
+ } else {
9688
+ assert ! ( coinbase >= 1_000_000_000 + initial_mining_bonus) ;
9689
+ }
9690
+ } else if sn. block_height < 255 {
9691
+ if prev_sortition {
9692
+ assert_eq ! ( coinbase, 500_000_000 + initial_mining_bonus) ;
9693
+ witnessed_500 = true ;
9694
+ } else {
9695
+ assert ! ( coinbase >= 500_000_000 + initial_mining_bonus) ;
9696
+ }
9697
+ } else if sn. block_height < 265 {
9698
+ if prev_sortition {
9699
+ assert_eq ! ( coinbase, 125_000_000 + initial_mining_bonus) ;
9700
+ witnessed_125 = true ;
9701
+ } else {
9702
+ assert ! ( coinbase >= 125_000_000 + initial_mining_bonus) ;
9703
+ }
9704
+ } else {
9705
+ if prev_sortition {
9706
+ assert_eq ! ( coinbase, 62_500_000 + initial_mining_bonus) ;
9707
+ witnessed_62_5 = true ;
9708
+ } else {
9709
+ assert ! ( coinbase >= 62_500_000 + initial_mining_bonus) ;
9710
+ }
9711
+ }
9712
+
9713
+ prev_sortition = true ;
9714
+ }
9715
+
9716
+ assert ! ( witnessed_1000) ;
9717
+ assert ! ( witnessed_500) ;
9718
+ assert ! ( witnessed_125) ;
9719
+ assert ! ( witnessed_62_5) ;
9720
+
9721
+ coord_channel
9722
+ . lock ( )
9723
+ . expect ( "Mutex poisoned" )
9724
+ . stop_chains_coordinator ( ) ;
9725
+ run_loop_stopper. store ( false , Ordering :: SeqCst ) ;
9726
+
9727
+ run_loop_thread. join ( ) . unwrap ( ) ;
9728
+ }
0 commit comments