@@ -10,7 +10,7 @@ use clarity::vm::ast::ASTRules;
1010use clarity:: vm:: costs:: ExecutionCost ;
1111use clarity:: vm:: types:: PrincipalData ;
1212use clarity:: vm:: { ClarityName , ClarityVersion , ContractName , Value , MAX_CALL_STACK_DEPTH } ;
13- use rand:: Rng ;
13+ use rand:: { Rng , RngCore } ;
1414use rusqlite:: types:: ToSql ;
1515use serde_json:: json;
1616use stacks:: burnchains:: bitcoin:: address:: { BitcoinAddress , LegacyBitcoinAddressType } ;
@@ -42,6 +42,7 @@ use stacks::core::{
4242 BLOCK_LIMIT_MAINNET_21 , CHAIN_ID_TESTNET , HELIUM_BLOCK_LIMIT_20 , PEER_VERSION_EPOCH_1_0 ,
4343 PEER_VERSION_EPOCH_2_0 , PEER_VERSION_EPOCH_2_05 , PEER_VERSION_EPOCH_2_1 ,
4444 PEER_VERSION_EPOCH_2_2 , PEER_VERSION_EPOCH_2_3 , PEER_VERSION_EPOCH_2_4 , PEER_VERSION_EPOCH_2_5 ,
45+ PEER_VERSION_TESTNET ,
4546} ;
4647use stacks:: net:: api:: getaccount:: AccountEntryResponse ;
4748use stacks:: net:: api:: getcontractsrc:: ContractSrcResponse ;
@@ -12259,3 +12260,202 @@ fn bitcoin_reorg_flap() {
1225912260 btcd_controller. stop_bitcoind ( ) . unwrap ( ) ;
1226012261 channel. stop_chains_coordinator ( ) ;
1226112262}
12263+
12264+ fn next_block_and_wait_all (
12265+ btc_controller : & mut BitcoinRegtestController ,
12266+ miner_blocks_processed : & Arc < AtomicU64 > ,
12267+ follower_blocks_processed : & [ & Arc < AtomicU64 > ] ,
12268+ ) -> bool {
12269+ let followers_current: Vec < _ > = follower_blocks_processed
12270+ . iter ( )
12271+ . map ( |blocks_processed| blocks_processed. load ( Ordering :: SeqCst ) )
12272+ . collect ( ) ;
12273+
12274+ if !next_block_and_wait ( btc_controller, miner_blocks_processed) {
12275+ return false ;
12276+ }
12277+
12278+ // wait for followers to catch up
12279+ loop {
12280+ let finished = follower_blocks_processed
12281+ . iter ( )
12282+ . zip ( followers_current. iter ( ) )
12283+ . map ( |( blocks_processed, current) | blocks_processed. load ( Ordering :: SeqCst ) <= * current)
12284+ . fold ( true , |acc, loaded| acc && loaded) ;
12285+
12286+ if finished {
12287+ break ;
12288+ }
12289+
12290+ thread:: sleep ( Duration :: from_millis ( 100 ) ) ;
12291+ }
12292+
12293+ true
12294+ }
12295+
12296+ #[ test]
12297+ #[ ignore]
12298+ fn bitcoin_reorg_flap_with_follower ( ) {
12299+ if env:: var ( "BITCOIND_TEST" ) != Ok ( "1" . into ( ) ) {
12300+ return ;
12301+ }
12302+
12303+ let ( conf, _miner_account) = neon_integration_test_conf ( ) ;
12304+
12305+ let mut btcd_controller = BitcoinCoreController :: new ( conf. clone ( ) ) ;
12306+ btcd_controller
12307+ . start_bitcoind ( )
12308+ . expect ( "Failed starting bitcoind" ) ;
12309+
12310+ let mut btc_regtest_controller = BitcoinRegtestController :: new ( conf. clone ( ) , None ) ;
12311+
12312+ btc_regtest_controller. bootstrap_chain ( 201 ) ;
12313+
12314+ eprintln ! ( "Chain bootstrapped..." ) ;
12315+
12316+ let mut miner_run_loop = neon:: RunLoop :: new ( conf. clone ( ) ) ;
12317+ let miner_blocks_processed = miner_run_loop. get_blocks_processed_arc ( ) ;
12318+ let miner_channel = miner_run_loop. get_coordinator_channel ( ) . unwrap ( ) ;
12319+
12320+ let mut follower_conf = conf. clone ( ) ;
12321+ follower_conf. events_observers . clear ( ) ;
12322+ follower_conf. node . working_dir = format ! ( "{}-follower" , & conf. node. working_dir) ;
12323+ follower_conf. node . seed = vec ! [ 0x01 ; 32 ] ;
12324+ follower_conf. node . local_peer_seed = vec ! [ 0x02 ; 32 ] ;
12325+
12326+ let mut rng = rand:: thread_rng ( ) ;
12327+ let mut buf = [ 0u8 ; 8 ] ;
12328+ rng. fill_bytes ( & mut buf) ;
12329+
12330+ let rpc_port = u16:: from_be_bytes ( buf[ 0 ..2 ] . try_into ( ) . unwrap ( ) ) . saturating_add ( 1025 ) - 1 ; // use a non-privileged port between 1024 and 65534
12331+ let p2p_port = u16:: from_be_bytes ( buf[ 2 ..4 ] . try_into ( ) . unwrap ( ) ) . saturating_add ( 1025 ) - 1 ; // use a non-privileged port between 1024 and 65534
12332+
12333+ let localhost = "127.0.0.1" ;
12334+ follower_conf. node . rpc_bind = format ! ( "{}:{}" , & localhost, rpc_port) ;
12335+ follower_conf. node . p2p_bind = format ! ( "{}:{}" , & localhost, p2p_port) ;
12336+ follower_conf. node . data_url = format ! ( "http://{}:{}" , & localhost, rpc_port) ;
12337+ follower_conf. node . p2p_address = format ! ( "{}:{}" , & localhost, p2p_port) ;
12338+
12339+ thread:: spawn ( move || miner_run_loop. start ( None , 0 ) ) ;
12340+ wait_for_runloop ( & miner_blocks_processed) ;
12341+
12342+ // figure out the started node's port
12343+ let node_info = get_chain_info ( & conf) ;
12344+ follower_conf. node . add_bootstrap_node (
12345+ & format ! (
12346+ "{}@{}" ,
12347+ & node_info. node_public_key. unwrap( ) ,
12348+ conf. node. p2p_bind
12349+ ) ,
12350+ CHAIN_ID_TESTNET ,
12351+ PEER_VERSION_TESTNET ,
12352+ ) ;
12353+
12354+ let mut follower_run_loop = neon:: RunLoop :: new ( follower_conf. clone ( ) ) ;
12355+ let follower_blocks_processed = follower_run_loop. get_blocks_processed_arc ( ) ;
12356+ let follower_channel = follower_run_loop. get_coordinator_channel ( ) . unwrap ( ) ;
12357+
12358+ thread:: spawn ( move || follower_run_loop. start ( None , 0 ) ) ;
12359+ wait_for_runloop ( & follower_blocks_processed) ;
12360+
12361+ eprintln ! ( "Follower bootup complete!" ) ;
12362+
12363+ // first block wakes up the run loop
12364+ next_block_and_wait_all ( & mut btc_regtest_controller, & miner_blocks_processed, & [ ] ) ;
12365+
12366+ // first block will hold our VRF registration
12367+ next_block_and_wait_all (
12368+ & mut btc_regtest_controller,
12369+ & miner_blocks_processed,
12370+ & [ & follower_blocks_processed] ,
12371+ ) ;
12372+
12373+ let mut miner_sort_height = miner_channel. get_sortitions_processed ( ) ;
12374+ let mut follower_sort_height = follower_channel. get_sortitions_processed ( ) ;
12375+ eprintln ! (
12376+ "Miner sort height: {}, follower sort height: {}" ,
12377+ miner_sort_height, follower_sort_height
12378+ ) ;
12379+
12380+ while miner_sort_height < 210 && follower_sort_height < 210 {
12381+ next_block_and_wait_all (
12382+ & mut btc_regtest_controller,
12383+ & miner_blocks_processed,
12384+ & [ & follower_blocks_processed] ,
12385+ ) ;
12386+ miner_sort_height = miner_channel. get_sortitions_processed ( ) ;
12387+ follower_sort_height = miner_channel. get_sortitions_processed ( ) ;
12388+ eprintln ! (
12389+ "Miner sort height: {}, follower sort height: {}" ,
12390+ miner_sort_height, follower_sort_height
12391+ ) ;
12392+ }
12393+
12394+ // stop bitcoind and copy its DB to simulate a chain flap
12395+ btcd_controller. stop_bitcoind ( ) . unwrap ( ) ;
12396+ thread:: sleep ( Duration :: from_secs ( 5 ) ) ;
12397+
12398+ let btcd_dir = conf. get_burnchain_path_str ( ) ;
12399+ let mut new_conf = conf. clone ( ) ;
12400+ new_conf. node . working_dir = format ! ( "{}.new" , & conf. node. working_dir) ;
12401+ fs:: create_dir_all ( & new_conf. node . working_dir ) . unwrap ( ) ;
12402+
12403+ copy_dir_all ( & btcd_dir, & new_conf. get_burnchain_path_str ( ) ) . unwrap ( ) ;
12404+
12405+ // resume
12406+ let mut btcd_controller = BitcoinCoreController :: new ( conf. clone ( ) ) ;
12407+ btcd_controller
12408+ . start_bitcoind ( )
12409+ . expect ( "Failed starting bitcoind" ) ;
12410+
12411+ let btc_regtest_controller = BitcoinRegtestController :: new ( conf. clone ( ) , None ) ;
12412+ thread:: sleep ( Duration :: from_secs ( 5 ) ) ;
12413+
12414+ info ! ( "\n \n Begin fork A\n \n " ) ;
12415+
12416+ // make fork A
12417+ for _i in 0 ..3 {
12418+ btc_regtest_controller. build_next_block ( 1 ) ;
12419+ thread:: sleep ( Duration :: from_secs ( 5 ) ) ;
12420+ }
12421+
12422+ btcd_controller. stop_bitcoind ( ) . unwrap ( ) ;
12423+
12424+ info ! ( "\n \n Begin reorg flap from A to B\n \n " ) ;
12425+
12426+ // carry out the flap to fork B -- new_conf's state was the same as before the reorg
12427+ let mut btcd_controller = BitcoinCoreController :: new ( new_conf. clone ( ) ) ;
12428+ let btc_regtest_controller = BitcoinRegtestController :: new ( new_conf. clone ( ) , None ) ;
12429+
12430+ btcd_controller
12431+ . start_bitcoind ( )
12432+ . expect ( "Failed starting bitcoind" ) ;
12433+
12434+ for _i in 0 ..5 {
12435+ btc_regtest_controller. build_next_block ( 1 ) ;
12436+ thread:: sleep ( Duration :: from_secs ( 5 ) ) ;
12437+ }
12438+
12439+ btcd_controller. stop_bitcoind ( ) . unwrap ( ) ;
12440+
12441+ info ! ( "\n \n Begin reorg flap from B to A\n \n " ) ;
12442+
12443+ let mut btcd_controller = BitcoinCoreController :: new ( conf. clone ( ) ) ;
12444+ let btc_regtest_controller = BitcoinRegtestController :: new ( conf. clone ( ) , None ) ;
12445+ btcd_controller
12446+ . start_bitcoind ( )
12447+ . expect ( "Failed starting bitcoind" ) ;
12448+
12449+ // carry out the flap back to fork A
12450+ for _i in 0 ..7 {
12451+ btc_regtest_controller. build_next_block ( 1 ) ;
12452+ thread:: sleep ( Duration :: from_secs ( 5 ) ) ;
12453+ }
12454+
12455+ assert_eq ! ( miner_channel. get_sortitions_processed( ) , 225 ) ;
12456+ assert_eq ! ( follower_channel. get_sortitions_processed( ) , 225 ) ;
12457+
12458+ btcd_controller. stop_bitcoind ( ) . unwrap ( ) ;
12459+ miner_channel. stop_chains_coordinator ( ) ;
12460+ follower_channel. stop_chains_coordinator ( ) ;
12461+ }
0 commit comments