Skip to content

Commit e9be296

Browse files
authored
Merge pull request #6007 from moodmosaic/test/scenario
Add command-based test harness for integration tests
2 parents e505550 + be8256c commit e9be296

File tree

15 files changed

+1090
-4
lines changed

15 files changed

+1090
-4
lines changed

Cargo.lock

Lines changed: 30 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

testnet/stacks-node/Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,8 @@ http-types = "2.12"
5151
tempfile = "3.3"
5252
mockito = "1.5"
5353
serial_test = "3.2.0"
54+
madhouse = { git = "https://github.com/stacks-network/madhouse-rs.git", rev = "fc651ddcbaf85e888b06d4a87aa788c4b7ba9309" }
55+
proptest = { git = "https://github.com/proptest-rs/proptest.git", rev = "c9bdf18c232665b2b740c667c81866b598d06dc7" }
5456

5557
[[bin]]
5658
name = "stacks-node"
Lines changed: 244 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,244 @@
1+
use std::sync::atomic::Ordering;
2+
use std::sync::{Arc, Mutex};
3+
4+
use madhouse::{Command, CommandWrapper};
5+
use proptest::prelude::{Just, Strategy};
6+
use stacks::chainstate::stacks::TenureChangeCause;
7+
use tracing::info;
8+
9+
use super::context::{SignerTestContext, SignerTestState};
10+
use crate::tests::neon_integrations::get_chain_info;
11+
use crate::tests::signer::v0::{wait_for_block_pushed_by_miner_key, MultipleMinerTest};
12+
13+
pub struct MineBitcoinBlockTenureChangeMiner1 {
14+
miners: Arc<Mutex<MultipleMinerTest>>,
15+
}
16+
17+
impl MineBitcoinBlockTenureChangeMiner1 {
18+
pub fn new(miners: Arc<Mutex<MultipleMinerTest>>) -> Self {
19+
Self { miners }
20+
}
21+
}
22+
23+
impl Command<SignerTestState, SignerTestContext> for MineBitcoinBlockTenureChangeMiner1 {
24+
fn check(&self, state: &SignerTestState) -> bool {
25+
let (conf_1, _) = self.miners.lock().unwrap().get_node_configs();
26+
let burn_height = get_chain_info(&conf_1).burn_block_height;
27+
let miner_1_submitted_commit_last_burn_height = self
28+
.miners
29+
.lock()
30+
.unwrap()
31+
.get_primary_submitted_commit_last_burn_height()
32+
.0
33+
.load(Ordering::SeqCst);
34+
let miner_2_submitted_commit_last_burn_height = self
35+
.miners
36+
.lock()
37+
.unwrap()
38+
.get_secondary_submitted_commit_last_burn_height()
39+
.0
40+
.load(Ordering::SeqCst);
41+
42+
info!(
43+
"Checking: Miner 1 mining Bitcoin block and tenure change tx. Result: {:?} && {:?} && {:?}",
44+
state.is_booted_to_nakamoto, burn_height == miner_1_submitted_commit_last_burn_height, burn_height > miner_2_submitted_commit_last_burn_height
45+
);
46+
state.is_booted_to_nakamoto
47+
&& burn_height == miner_1_submitted_commit_last_burn_height
48+
&& burn_height > miner_2_submitted_commit_last_burn_height
49+
}
50+
51+
fn apply(&self, _state: &mut SignerTestState) {
52+
info!("Applying: Miner 1 mining Bitcoin block and tenure change tx");
53+
54+
let (stacks_height_before, conf_1, miner_pk_1) = {
55+
let mut miners = self.miners.lock().unwrap();
56+
let stacks_height_before = miners.get_peer_stacks_tip_height();
57+
let (conf_1, _) = miners.get_node_configs();
58+
let burnchain = conf_1.get_burnchain();
59+
let sortdb = burnchain.open_sortition_db(true).unwrap();
60+
61+
miners
62+
.mine_bitcoin_block_and_tenure_change_tx(&sortdb, TenureChangeCause::BlockFound, 60)
63+
.expect("Failed to mine BTC block");
64+
65+
let (miner_pk_1, _) = miners.get_miner_public_keys();
66+
67+
(stacks_height_before, conf_1, miner_pk_1)
68+
};
69+
70+
info!(
71+
"Waiting for Nakamoto block {} pushed by miner 1",
72+
stacks_height_before + 1
73+
);
74+
75+
let miner_1_block =
76+
wait_for_block_pushed_by_miner_key(30, stacks_height_before + 1, &miner_pk_1)
77+
.expect("Failed to get block");
78+
79+
let mined_block_height = miner_1_block.header.chain_length;
80+
info!(
81+
"Miner 1 mined Nakamoto block height: {}",
82+
mined_block_height
83+
);
84+
85+
let info_after = get_chain_info(&conf_1);
86+
assert_eq!(info_after.stacks_tip, miner_1_block.header.block_hash());
87+
assert_eq!(info_after.stacks_tip_height, mined_block_height);
88+
assert_eq!(mined_block_height, stacks_height_before + 1);
89+
}
90+
91+
fn label(&self) -> String {
92+
"MINE_BITCOIN_BLOCK_AND_TENURE_CHANGE_MINER_1".to_string()
93+
}
94+
95+
fn build(
96+
ctx: Arc<SignerTestContext>,
97+
) -> impl Strategy<Value = CommandWrapper<SignerTestState, SignerTestContext>> {
98+
Just(CommandWrapper::new(
99+
MineBitcoinBlockTenureChangeMiner1::new(ctx.miners.clone()),
100+
))
101+
}
102+
}
103+
104+
pub struct MineBitcoinBlockTenureChangeMiner2 {
105+
miners: Arc<Mutex<MultipleMinerTest>>,
106+
}
107+
108+
impl MineBitcoinBlockTenureChangeMiner2 {
109+
pub fn new(miners: Arc<Mutex<MultipleMinerTest>>) -> Self {
110+
Self { miners }
111+
}
112+
}
113+
114+
impl Command<SignerTestState, SignerTestContext> for MineBitcoinBlockTenureChangeMiner2 {
115+
fn check(&self, state: &SignerTestState) -> bool {
116+
let (conf_1, _) = self.miners.lock().unwrap().get_node_configs();
117+
let burn_height = get_chain_info(&conf_1).burn_block_height;
118+
let miner_1_submitted_commit_last_burn_height = self
119+
.miners
120+
.lock()
121+
.unwrap()
122+
.get_primary_submitted_commit_last_burn_height()
123+
.0
124+
.load(Ordering::SeqCst);
125+
let miner_2_submitted_commit_last_burn_height = self
126+
.miners
127+
.lock()
128+
.unwrap()
129+
.get_secondary_submitted_commit_last_burn_height()
130+
.0
131+
.load(Ordering::SeqCst);
132+
133+
info!(
134+
"Checking: Miner 2 mining Bitcoin block and tenure change tx. Result: {:?} && {:?} && {:?}",
135+
state.is_booted_to_nakamoto, burn_height == miner_1_submitted_commit_last_burn_height, burn_height > miner_2_submitted_commit_last_burn_height
136+
);
137+
state.is_booted_to_nakamoto
138+
&& burn_height == miner_2_submitted_commit_last_burn_height
139+
&& burn_height > miner_1_submitted_commit_last_burn_height
140+
}
141+
142+
fn apply(&self, _state: &mut SignerTestState) {
143+
info!("Applying: Miner 2 mining Bitcoin block and tenure change tx");
144+
145+
let stacks_height_before = self.miners.lock().unwrap().get_peer_stacks_tip_height();
146+
147+
let (conf_1, conf_2) = self.miners.lock().unwrap().get_node_configs();
148+
let burnchain = conf_1.get_burnchain();
149+
let sortdb = burnchain.open_sortition_db(true).unwrap();
150+
self.miners
151+
.lock()
152+
.unwrap()
153+
.mine_bitcoin_block_and_tenure_change_tx(&sortdb, TenureChangeCause::BlockFound, 60)
154+
.expect("Failed to mine BTC block");
155+
156+
let (_, miner_pk_2) = self.miners.lock().unwrap().get_miner_public_keys();
157+
158+
info!(
159+
"Waiting for Nakamoto block {} pushed by miner 2",
160+
stacks_height_before + 1
161+
);
162+
163+
let secondary_miner_block =
164+
wait_for_block_pushed_by_miner_key(30, stacks_height_before + 1, &miner_pk_2)
165+
.expect("Failed to get block N");
166+
167+
let mined_block_height = secondary_miner_block.header.chain_length;
168+
169+
let info_after = get_chain_info(&conf_2);
170+
assert_eq!(
171+
info_after.stacks_tip,
172+
secondary_miner_block.header.block_hash()
173+
);
174+
assert_eq!(info_after.stacks_tip_height, mined_block_height);
175+
assert_eq!(mined_block_height, stacks_height_before + 1);
176+
}
177+
178+
fn label(&self) -> String {
179+
"MINE_BITCOIN_BLOCK_AND_TENURE_CHANGE_MINER_2".to_string()
180+
}
181+
182+
fn build(
183+
ctx: Arc<SignerTestContext>,
184+
) -> impl Strategy<Value = CommandWrapper<SignerTestState, SignerTestContext>> {
185+
Just(CommandWrapper::new(
186+
MineBitcoinBlockTenureChangeMiner2::new(ctx.miners.clone()),
187+
))
188+
}
189+
}
190+
191+
pub struct MineBitcoinBlock {
192+
miners: Arc<Mutex<MultipleMinerTest>>,
193+
timeout_secs: u64,
194+
}
195+
196+
impl MineBitcoinBlock {
197+
pub fn new(miners: Arc<Mutex<MultipleMinerTest>>, timeout_secs: u64) -> Self {
198+
Self {
199+
miners,
200+
timeout_secs,
201+
}
202+
}
203+
}
204+
205+
impl Command<SignerTestState, SignerTestContext> for MineBitcoinBlock {
206+
fn check(&self, _state: &SignerTestState) -> bool {
207+
info!("Checking: Mining tenure. Result: {:?}", true);
208+
true
209+
}
210+
211+
fn apply(&self, _state: &mut SignerTestState) {
212+
info!(
213+
"Applying: Mining tenure and waiting for it for {:?} seconds",
214+
self.timeout_secs
215+
);
216+
217+
let sortdb = {
218+
let miners = self.miners.lock().unwrap();
219+
let (conf_1, _) = miners.get_node_configs();
220+
let burnchain = conf_1.get_burnchain();
221+
let sortdb = burnchain.open_sortition_db(true).unwrap();
222+
sortdb
223+
};
224+
225+
{
226+
let mut miners = self.miners.lock().unwrap();
227+
miners
228+
.mine_bitcoin_blocks_and_confirm(&sortdb, 1, self.timeout_secs)
229+
.expect("Failed to mine BTC block");
230+
}
231+
}
232+
233+
fn label(&self) -> String {
234+
"MINE_BITCOIN_BLOCK".to_string()
235+
}
236+
237+
fn build(
238+
ctx: Arc<SignerTestContext>,
239+
) -> impl Strategy<Value = CommandWrapper<SignerTestState, SignerTestContext>> {
240+
(60u64..90u64).prop_map(move |timeout_secs| {
241+
CommandWrapper::new(MineBitcoinBlock::new(ctx.miners.clone(), timeout_secs))
242+
})
243+
}
244+
}

0 commit comments

Comments
 (0)