@@ -3958,6 +3958,338 @@ fn follower_bootup_across_multiple_cycles() {
3958
3958
follower_thread. join ( ) . unwrap ( ) ;
3959
3959
}
3960
3960
3961
+ /// Boot up a node and a follower with a non-default chain id
3962
+ #[ test]
3963
+ #[ ignore]
3964
+ fn follower_bootup_custom_chain_id ( ) {
3965
+ if env:: var ( "BITCOIND_TEST" ) != Ok ( "1" . into ( ) ) {
3966
+ return ;
3967
+ }
3968
+
3969
+ let ( mut naka_conf, _miner_account) = naka_neon_integration_conf ( None ) ;
3970
+ naka_conf. burnchain . chain_id = 0x87654321 ;
3971
+ let http_origin = format ! ( "http://{}" , & naka_conf. node. rpc_bind) ;
3972
+ naka_conf. miner . wait_on_interim_blocks = Duration :: from_secs ( 1 ) ;
3973
+ let sender_sk = Secp256k1PrivateKey :: new ( ) ;
3974
+ let sender_signer_sk = Secp256k1PrivateKey :: new ( ) ;
3975
+ let sender_signer_addr = tests:: to_addr ( & sender_signer_sk) ;
3976
+ let mut signers = TestSigners :: new ( vec ! [ sender_signer_sk. clone( ) ] ) ;
3977
+ let tenure_count = 5 ;
3978
+ let inter_blocks_per_tenure = 9 ;
3979
+ // setup sender + recipient for some test stx transfers
3980
+ // these are necessary for the interim blocks to get mined at all
3981
+ let sender_addr = tests:: to_addr ( & sender_sk) ;
3982
+ let send_amt = 100 ;
3983
+ let send_fee = 180 ;
3984
+ naka_conf. add_initial_balance (
3985
+ PrincipalData :: from ( sender_addr. clone ( ) ) . to_string ( ) ,
3986
+ ( send_amt + send_fee) * tenure_count * inter_blocks_per_tenure,
3987
+ ) ;
3988
+ naka_conf. add_initial_balance (
3989
+ PrincipalData :: from ( sender_signer_addr. clone ( ) ) . to_string ( ) ,
3990
+ 100000 ,
3991
+ ) ;
3992
+ let recipient = PrincipalData :: from ( StacksAddress :: burn_address ( false ) ) ;
3993
+ let stacker_sk = setup_stacker ( & mut naka_conf) ;
3994
+
3995
+ test_observer:: spawn ( ) ;
3996
+ test_observer:: register_any ( & mut naka_conf) ;
3997
+
3998
+ let mut btcd_controller = BitcoinCoreController :: new ( naka_conf. clone ( ) ) ;
3999
+ btcd_controller
4000
+ . start_bitcoind ( )
4001
+ . expect ( "Failed starting bitcoind" ) ;
4002
+ let mut btc_regtest_controller = BitcoinRegtestController :: new ( naka_conf. clone ( ) , None ) ;
4003
+ btc_regtest_controller. bootstrap_chain ( 201 ) ;
4004
+
4005
+ let mut run_loop = boot_nakamoto:: BootRunLoop :: new ( naka_conf. clone ( ) ) . unwrap ( ) ;
4006
+ let run_loop_stopper = run_loop. get_termination_switch ( ) ;
4007
+ let Counters {
4008
+ blocks_processed,
4009
+ naka_submitted_commits : commits_submitted,
4010
+ naka_proposed_blocks : proposals_submitted,
4011
+ ..
4012
+ } = run_loop. counters ( ) ;
4013
+
4014
+ let coord_channel = run_loop. coordinator_channels ( ) ;
4015
+
4016
+ let run_loop_thread = thread:: Builder :: new ( )
4017
+ . name ( "run_loop" . into ( ) )
4018
+ . spawn ( move || run_loop. start ( None , 0 ) )
4019
+ . unwrap ( ) ;
4020
+ wait_for_runloop ( & blocks_processed) ;
4021
+ boot_to_epoch_3 (
4022
+ & naka_conf,
4023
+ & blocks_processed,
4024
+ & [ stacker_sk] ,
4025
+ & [ sender_signer_sk] ,
4026
+ & mut Some ( & mut signers) ,
4027
+ & mut btc_regtest_controller,
4028
+ ) ;
4029
+
4030
+ info ! ( "Bootstrapped to Epoch-3.0 boundary, starting nakamoto miner" ) ;
4031
+
4032
+ let burnchain = naka_conf. get_burnchain ( ) ;
4033
+ let sortdb = burnchain. open_sortition_db ( true ) . unwrap ( ) ;
4034
+ let ( chainstate, _) = StacksChainState :: open (
4035
+ naka_conf. is_mainnet ( ) ,
4036
+ naka_conf. burnchain . chain_id ,
4037
+ & naka_conf. get_chainstate_path_str ( ) ,
4038
+ None ,
4039
+ )
4040
+ . unwrap ( ) ;
4041
+
4042
+ let block_height_pre_3_0 =
4043
+ NakamotoChainState :: get_canonical_block_header ( chainstate. db ( ) , & sortdb)
4044
+ . unwrap ( )
4045
+ . unwrap ( )
4046
+ . stacks_block_height ;
4047
+
4048
+ info ! ( "Nakamoto miner started..." ) ;
4049
+ blind_signer ( & naka_conf, & signers, proposals_submitted) ;
4050
+
4051
+ wait_for_first_naka_block_commit ( 60 , & commits_submitted) ;
4052
+
4053
+ let mut follower_conf = naka_conf. clone ( ) ;
4054
+ follower_conf. node . miner = false ;
4055
+ follower_conf. events_observers . clear ( ) ;
4056
+ follower_conf. node . working_dir = format ! ( "{}-follower" , & naka_conf. node. working_dir) ;
4057
+ follower_conf. node . seed = vec ! [ 0x01 ; 32 ] ;
4058
+ follower_conf. node . local_peer_seed = vec ! [ 0x02 ; 32 ] ;
4059
+
4060
+ let rpc_port = gen_random_port ( ) ;
4061
+ let p2p_port = gen_random_port ( ) ;
4062
+
4063
+ let localhost = "127.0.0.1" ;
4064
+ follower_conf. node . rpc_bind = format ! ( "{localhost}:{rpc_port}" ) ;
4065
+ follower_conf. node . p2p_bind = format ! ( "{localhost}:{p2p_port}" ) ;
4066
+ follower_conf. node . data_url = format ! ( "http://{localhost}:{rpc_port}" ) ;
4067
+ follower_conf. node . p2p_address = format ! ( "{localhost}:{p2p_port}" ) ;
4068
+ follower_conf. node . pox_sync_sample_secs = 30 ;
4069
+
4070
+ let node_info = get_chain_info ( & naka_conf) ;
4071
+ follower_conf. node . add_bootstrap_node (
4072
+ & format ! (
4073
+ "{}@{}" ,
4074
+ & node_info. node_public_key. unwrap( ) ,
4075
+ naka_conf. node. p2p_bind
4076
+ ) ,
4077
+ naka_conf. burnchain . chain_id ,
4078
+ PEER_VERSION_TESTNET ,
4079
+ ) ;
4080
+
4081
+ let mut follower_run_loop = boot_nakamoto:: BootRunLoop :: new ( follower_conf. clone ( ) ) . unwrap ( ) ;
4082
+ let follower_run_loop_stopper = follower_run_loop. get_termination_switch ( ) ;
4083
+ let follower_coord_channel = follower_run_loop. coordinator_channels ( ) ;
4084
+
4085
+ debug ! (
4086
+ "Booting follower-thread ({},{})" ,
4087
+ & follower_conf. node. p2p_bind, & follower_conf. node. rpc_bind
4088
+ ) ;
4089
+ debug ! (
4090
+ "Booting follower-thread: neighbors = {:?}" ,
4091
+ & follower_conf. node. bootstrap_node
4092
+ ) ;
4093
+
4094
+ // spawn a follower thread
4095
+ let follower_thread = thread:: Builder :: new ( )
4096
+ . name ( "follower-thread" . into ( ) )
4097
+ . spawn ( move || follower_run_loop. start ( None , 0 ) )
4098
+ . unwrap ( ) ;
4099
+
4100
+ debug ! ( "Booted follower-thread" ) ;
4101
+
4102
+ // Mine `tenure_count` nakamoto tenures
4103
+ for tenure_ix in 0 ..tenure_count {
4104
+ debug ! ( "follower_bootup: Miner runs tenure {}" , tenure_ix) ;
4105
+ let commits_before = commits_submitted. load ( Ordering :: SeqCst ) ;
4106
+ next_block_and_process_new_stacks_block ( & mut btc_regtest_controller, 60 , & coord_channel)
4107
+ . unwrap ( ) ;
4108
+
4109
+ let mut last_tip = BlockHeaderHash ( [ 0x00 ; 32 ] ) ;
4110
+ let mut last_nonce = None ;
4111
+
4112
+ debug ! (
4113
+ "follower_bootup: Miner mines interum blocks for tenure {}" ,
4114
+ tenure_ix
4115
+ ) ;
4116
+
4117
+ // mine the interim blocks
4118
+ for _ in 0 ..inter_blocks_per_tenure {
4119
+ let blocks_processed_before = coord_channel
4120
+ . lock ( )
4121
+ . expect ( "Mutex poisoned" )
4122
+ . get_stacks_blocks_processed ( ) ;
4123
+
4124
+ let account = loop {
4125
+ // submit a tx so that the miner will mine an extra block
4126
+ let Ok ( account) = get_account_result ( & http_origin, & sender_addr) else {
4127
+ debug ! ( "follower_bootup: Failed to load miner account" ) ;
4128
+ thread:: sleep ( Duration :: from_millis ( 100 ) ) ;
4129
+ continue ;
4130
+ } ;
4131
+ break account;
4132
+ } ;
4133
+
4134
+ let sender_nonce = account
4135
+ . nonce
4136
+ . max ( last_nonce. as_ref ( ) . map ( |ln| * ln + 1 ) . unwrap_or ( 0 ) ) ;
4137
+ let transfer_tx = make_stacks_transfer (
4138
+ & sender_sk,
4139
+ sender_nonce,
4140
+ send_fee,
4141
+ naka_conf. burnchain . chain_id ,
4142
+ & recipient,
4143
+ send_amt,
4144
+ ) ;
4145
+ submit_tx ( & http_origin, & transfer_tx) ;
4146
+
4147
+ last_nonce = Some ( sender_nonce) ;
4148
+
4149
+ let tx = StacksTransaction :: consensus_deserialize ( & mut & transfer_tx[ ..] ) . unwrap ( ) ;
4150
+
4151
+ debug ! ( "follower_bootup: Miner account: {:?}" , & account) ;
4152
+ debug ! ( "follower_bootup: Miner sent {}: {:?}" , & tx. txid( ) , & tx) ;
4153
+
4154
+ let now = get_epoch_time_secs ( ) ;
4155
+ while get_epoch_time_secs ( ) < now + 10 {
4156
+ let Ok ( info) = get_chain_info_result ( & naka_conf) else {
4157
+ debug ! ( "follower_bootup: Could not get miner chain info" ) ;
4158
+ thread:: sleep ( Duration :: from_millis ( 100 ) ) ;
4159
+ continue ;
4160
+ } ;
4161
+
4162
+ let Ok ( follower_info) = get_chain_info_result ( & follower_conf) else {
4163
+ debug ! ( "follower_bootup: Could not get follower chain info" ) ;
4164
+ thread:: sleep ( Duration :: from_millis ( 100 ) ) ;
4165
+ continue ;
4166
+ } ;
4167
+
4168
+ if follower_info. burn_block_height < info. burn_block_height {
4169
+ debug ! ( "follower_bootup: Follower is behind miner's burnchain view" ) ;
4170
+ thread:: sleep ( Duration :: from_millis ( 100 ) ) ;
4171
+ continue ;
4172
+ }
4173
+
4174
+ if info. stacks_tip == last_tip {
4175
+ debug ! (
4176
+ "follower_bootup: Miner stacks tip hasn't changed ({})" ,
4177
+ & info. stacks_tip
4178
+ ) ;
4179
+ thread:: sleep ( Duration :: from_millis ( 100 ) ) ;
4180
+ continue ;
4181
+ }
4182
+
4183
+ let blocks_processed = coord_channel
4184
+ . lock ( )
4185
+ . expect ( "Mutex poisoned" )
4186
+ . get_stacks_blocks_processed ( ) ;
4187
+
4188
+ if blocks_processed > blocks_processed_before {
4189
+ break ;
4190
+ }
4191
+
4192
+ debug ! ( "follower_bootup: No blocks processed yet" ) ;
4193
+ thread:: sleep ( Duration :: from_millis ( 100 ) ) ;
4194
+ }
4195
+
4196
+ // compare chain tips
4197
+ loop {
4198
+ let Ok ( info) = get_chain_info_result ( & naka_conf) else {
4199
+ debug ! ( "follower_bootup: failed to load tip info" ) ;
4200
+ thread:: sleep ( Duration :: from_millis ( 100 ) ) ;
4201
+ continue ;
4202
+ } ;
4203
+
4204
+ let Ok ( follower_info) = get_chain_info_result ( & follower_conf) else {
4205
+ debug ! ( "follower_bootup: Could not get follower chain info" ) ;
4206
+ thread:: sleep ( Duration :: from_millis ( 100 ) ) ;
4207
+ continue ;
4208
+ } ;
4209
+ if info. stacks_tip == follower_info. stacks_tip {
4210
+ debug ! (
4211
+ "follower_bootup: Follower has advanced to miner's tip {}" ,
4212
+ & info. stacks_tip
4213
+ ) ;
4214
+ } else {
4215
+ debug ! (
4216
+ "follower_bootup: Follower has NOT advanced to miner's tip: {} != {}" ,
4217
+ & info. stacks_tip, follower_info. stacks_tip
4218
+ ) ;
4219
+ }
4220
+
4221
+ last_tip = info. stacks_tip ;
4222
+ break ;
4223
+ }
4224
+ }
4225
+
4226
+ debug ! ( "follower_bootup: Wait for next block-commit" ) ;
4227
+ let start_time = Instant :: now ( ) ;
4228
+ while commits_submitted. load ( Ordering :: SeqCst ) <= commits_before {
4229
+ if start_time. elapsed ( ) >= Duration :: from_secs ( 20 ) {
4230
+ panic ! ( "Timed out waiting for block-commit" ) ;
4231
+ }
4232
+ thread:: sleep ( Duration :: from_millis ( 100 ) ) ;
4233
+ }
4234
+ debug ! ( "follower_bootup: Block commit submitted" ) ;
4235
+ }
4236
+
4237
+ // load the chain tip, and assert that it is a nakamoto block and at least 30 blocks have advanced in epoch 3
4238
+ let tip = NakamotoChainState :: get_canonical_block_header ( chainstate. db ( ) , & sortdb)
4239
+ . unwrap ( )
4240
+ . unwrap ( ) ;
4241
+ info ! (
4242
+ "Latest tip" ;
4243
+ "height" => tip. stacks_block_height,
4244
+ "is_nakamoto" => tip. anchored_header. as_stacks_nakamoto( ) . is_some( ) ,
4245
+ ) ;
4246
+
4247
+ assert ! ( tip. anchored_header. as_stacks_nakamoto( ) . is_some( ) ) ;
4248
+ assert_eq ! (
4249
+ tip. stacks_block_height,
4250
+ block_height_pre_3_0 + ( ( inter_blocks_per_tenure + 1 ) * tenure_count) ,
4251
+ "Should have mined (1 + interim_blocks_per_tenure) * tenure_count nakamoto blocks"
4252
+ ) ;
4253
+
4254
+ // wait for follower to reach the chain tip
4255
+ loop {
4256
+ sleep_ms ( 1000 ) ;
4257
+ let follower_node_info = get_chain_info ( & follower_conf) ;
4258
+
4259
+ info ! (
4260
+ "Follower tip is now {}/{}" ,
4261
+ & follower_node_info. stacks_tip_consensus_hash, & follower_node_info. stacks_tip
4262
+ ) ;
4263
+ if follower_node_info. stacks_tip_consensus_hash == tip. consensus_hash
4264
+ && follower_node_info. stacks_tip == tip. anchored_header . block_hash ( )
4265
+ {
4266
+ break ;
4267
+ }
4268
+ }
4269
+
4270
+ // Verify both nodes have the correct chain id
4271
+ let miner_info = get_chain_info ( & naka_conf) ;
4272
+ assert_eq ! ( miner_info. network_id, 0x87654321 ) ;
4273
+
4274
+ let follower_info = get_chain_info ( & follower_conf) ;
4275
+ assert_eq ! ( follower_info. network_id, 0x87654321 ) ;
4276
+
4277
+ coord_channel
4278
+ . lock ( )
4279
+ . expect ( "Mutex poisoned" )
4280
+ . stop_chains_coordinator ( ) ;
4281
+ run_loop_stopper. store ( false , Ordering :: SeqCst ) ;
4282
+
4283
+ follower_coord_channel
4284
+ . lock ( )
4285
+ . expect ( "Mutex poisoned" )
4286
+ . stop_chains_coordinator ( ) ;
4287
+ follower_run_loop_stopper. store ( false , Ordering :: SeqCst ) ;
4288
+
4289
+ run_loop_thread. join ( ) . unwrap ( ) ;
4290
+ follower_thread. join ( ) . unwrap ( ) ;
4291
+ }
4292
+
3961
4293
#[ test]
3962
4294
#[ ignore]
3963
4295
/// Test out various burn operations being processed in Nakamoto.
0 commit comments