Skip to content

Commit e6d54fb

Browse files
committed
test: add integration test verifying chain_id config
1 parent 787e3e9 commit e6d54fb

File tree

1 file changed

+332
-0
lines changed

1 file changed

+332
-0
lines changed

testnet/stacks-node/src/tests/nakamoto_integrations.rs

Lines changed: 332 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3958,6 +3958,338 @@ fn follower_bootup_across_multiple_cycles() {
39583958
follower_thread.join().unwrap();
39593959
}
39603960

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+
39614293
#[test]
39624294
#[ignore]
39634295
/// Test out various burn operations being processed in Nakamoto.

0 commit comments

Comments
 (0)