Skip to content

Commit 9186ffe

Browse files
committed
CRC: Move signer_state tests to a dedicated test module
Signed-off-by: Jacinta Ferrant <jacinta.ferrant@gmail.com>
1 parent 1ebdaaa commit 9186ffe

File tree

3 files changed

+367
-364
lines changed

3 files changed

+367
-364
lines changed

stacks-signer/src/tests/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
mod chainstate;
2+
mod signer_state;
Lines changed: 362 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,362 @@
1+
use std::collections::HashMap;
2+
3+
use clarity::types::chainstate::{
4+
ConsensusHash, StacksAddress, StacksBlockId, StacksPrivateKey, StacksPublicKey,
5+
};
6+
use clarity::util::hash::Hash160;
7+
use clarity::util::secp256k1::MessageSignature;
8+
use libsigner::v0::messages::{
9+
StateMachineUpdate as StateMachineUpdateMessage, StateMachineUpdateContent,
10+
StateMachineUpdateMinerState,
11+
};
12+
13+
use crate::signerdb::tests::{create_block_override, tmp_db_path};
14+
use crate::signerdb::SignerDb;
15+
use crate::v0::signer_state::{GlobalStateEvaluator, SignerStateMachine};
16+
17+
fn generate_global_state_evaluator(num_addresses: u32) -> GlobalStateEvaluator {
18+
let address_weights = generate_random_address_with_equal_weights(num_addresses);
19+
let active_protocol_version = 0;
20+
let local_supported_signer_protocol_version = 1;
21+
22+
let update = StateMachineUpdateMessage::new(
23+
active_protocol_version,
24+
local_supported_signer_protocol_version,
25+
StateMachineUpdateContent::V0 {
26+
burn_block: ConsensusHash([0x55; 20]),
27+
burn_block_height: 100,
28+
current_miner: StateMachineUpdateMinerState::ActiveMiner {
29+
current_miner_pkh: Hash160([0xab; 20]),
30+
tenure_id: ConsensusHash([0x44; 20]),
31+
parent_tenure_id: ConsensusHash([0x22; 20]),
32+
parent_tenure_last_block: StacksBlockId([0x33; 32]),
33+
parent_tenure_last_block_height: 1,
34+
},
35+
},
36+
)
37+
.unwrap();
38+
39+
let mut address_updates = HashMap::new();
40+
for address in address_weights.keys() {
41+
address_updates.insert(*address, update.clone());
42+
}
43+
GlobalStateEvaluator::new(address_updates, address_weights)
44+
}
45+
46+
fn generate_random_address_with_equal_weights(num_addresses: u32) -> HashMap<StacksAddress, u32> {
47+
let mut address_weights = HashMap::new();
48+
for _ in 0..num_addresses {
49+
let stacks_address = StacksAddress::p2pkh(
50+
false,
51+
&StacksPublicKey::from_private(&StacksPrivateKey::random()),
52+
);
53+
address_weights.insert(stacks_address, 10);
54+
}
55+
address_weights
56+
}
57+
58+
#[test]
59+
fn determine_latest_supported_signer_protocol_versions() {
60+
let mut global_eval = generate_global_state_evaluator(5);
61+
62+
let addresses: Vec<_> = global_eval.address_weights.keys().cloned().collect();
63+
let local_address = addresses[0];
64+
65+
let local_update = global_eval
66+
.address_updates
67+
.get(&local_address)
68+
.unwrap()
69+
.clone();
70+
assert_eq!(
71+
global_eval
72+
.determine_latest_supported_signer_protocol_version(local_address, &local_update,)
73+
.unwrap(),
74+
local_update.local_supported_signer_protocol_version
75+
);
76+
77+
let StateMachineUpdateMessage {
78+
active_signer_protocol_version,
79+
local_supported_signer_protocol_version,
80+
content:
81+
StateMachineUpdateContent::V0 {
82+
burn_block,
83+
burn_block_height,
84+
current_miner,
85+
},
86+
..
87+
} = local_update.clone();
88+
89+
// Let's update 3 signers (60 percent) to support seperate but greater protocol versions
90+
for (i, address) in addresses.into_iter().skip(1).take(3).enumerate() {
91+
let new_version = local_update.local_supported_signer_protocol_version + i as u64 + 1;
92+
let new_update = StateMachineUpdateMessage::new(
93+
active_signer_protocol_version,
94+
new_version,
95+
StateMachineUpdateContent::V0 {
96+
burn_block,
97+
burn_block_height,
98+
current_miner: current_miner.clone(),
99+
},
100+
)
101+
.unwrap();
102+
global_eval.insert_update(address, new_update);
103+
}
104+
105+
assert_eq!(
106+
global_eval
107+
.determine_latest_supported_signer_protocol_version(local_address, &local_update)
108+
.unwrap(),
109+
local_supported_signer_protocol_version
110+
);
111+
112+
// Let's tip the scales over to version number 2 by updating the local signer's version...
113+
// i.e. > 70% will have version 2 or higher in their map
114+
let local_update = StateMachineUpdateMessage::new(
115+
active_signer_protocol_version,
116+
3,
117+
StateMachineUpdateContent::V0 {
118+
burn_block,
119+
burn_block_height,
120+
current_miner,
121+
},
122+
)
123+
.unwrap();
124+
125+
assert_eq!(
126+
global_eval
127+
.determine_latest_supported_signer_protocol_version(local_address, &local_update)
128+
.unwrap(),
129+
local_supported_signer_protocol_version + 1
130+
);
131+
}
132+
133+
#[test]
134+
fn determine_global_burn_views() {
135+
let mut global_eval = generate_global_state_evaluator(5);
136+
137+
let addresses: Vec<_> = global_eval.address_weights.keys().cloned().collect();
138+
let local_address = addresses[0];
139+
let local_update = global_eval
140+
.address_updates
141+
.get(&local_address)
142+
.unwrap()
143+
.clone();
144+
let StateMachineUpdateMessage {
145+
active_signer_protocol_version,
146+
local_supported_signer_protocol_version,
147+
content:
148+
StateMachineUpdateContent::V0 {
149+
burn_block,
150+
burn_block_height,
151+
current_miner,
152+
},
153+
..
154+
} = local_update.clone();
155+
156+
assert_eq!(
157+
global_eval
158+
.determine_global_burn_view(local_address, &local_update)
159+
.unwrap(),
160+
(burn_block, burn_block_height)
161+
);
162+
163+
// Let's update 3 signers (60 percent) to support a new burn block view
164+
let new_update = StateMachineUpdateMessage::new(
165+
active_signer_protocol_version,
166+
local_supported_signer_protocol_version,
167+
StateMachineUpdateContent::V0 {
168+
burn_block,
169+
burn_block_height: burn_block_height.wrapping_add(1),
170+
current_miner: current_miner.clone(),
171+
},
172+
)
173+
.unwrap();
174+
for address in addresses.into_iter().skip(1).take(3) {
175+
global_eval.insert_update(address, new_update.clone());
176+
}
177+
178+
assert!(
179+
global_eval
180+
.determine_global_burn_view(local_address, &local_update)
181+
.is_none(),
182+
"We should not have reached agreement on the burn block height"
183+
);
184+
185+
// Let's tip the scales over to burn block height + 1
186+
assert_eq!(
187+
global_eval
188+
.determine_global_burn_view(local_address, &new_update)
189+
.unwrap(),
190+
(burn_block, burn_block_height.wrapping_add(1))
191+
);
192+
}
193+
194+
#[test]
195+
fn determine_global_states() {
196+
let mut global_eval = generate_global_state_evaluator(5);
197+
198+
let addresses: Vec<_> = global_eval.address_weights.keys().cloned().collect();
199+
let local_address = addresses[0];
200+
let local_update = global_eval
201+
.address_updates
202+
.get(&local_address)
203+
.unwrap()
204+
.clone();
205+
let StateMachineUpdateMessage {
206+
active_signer_protocol_version,
207+
local_supported_signer_protocol_version,
208+
content:
209+
StateMachineUpdateContent::V0 {
210+
burn_block,
211+
burn_block_height,
212+
current_miner,
213+
},
214+
..
215+
} = local_update.clone();
216+
217+
let state_machine = SignerStateMachine {
218+
burn_block,
219+
burn_block_height,
220+
current_miner: (&current_miner).into(),
221+
active_signer_protocol_version: local_supported_signer_protocol_version, // a majority of signers are saying they support version the same local_supported_signer_protocol_version, so update it here...
222+
};
223+
224+
assert_eq!(
225+
global_eval
226+
.determine_global_state(local_address, &local_update)
227+
.unwrap(),
228+
state_machine
229+
);
230+
let new_miner = StateMachineUpdateMinerState::ActiveMiner {
231+
current_miner_pkh: Hash160([0x00; 20]),
232+
tenure_id: ConsensusHash([0x44; 20]),
233+
parent_tenure_id: ConsensusHash([0x22; 20]),
234+
parent_tenure_last_block: StacksBlockId([0x33; 32]),
235+
parent_tenure_last_block_height: 1,
236+
};
237+
238+
let new_update = StateMachineUpdateMessage::new(
239+
active_signer_protocol_version,
240+
local_supported_signer_protocol_version,
241+
StateMachineUpdateContent::V0 {
242+
burn_block,
243+
burn_block_height,
244+
current_miner: new_miner.clone(),
245+
},
246+
)
247+
.unwrap();
248+
249+
// Let's update 3 signers to some new miner key (60 percent)
250+
for address in addresses.into_iter().skip(1).take(3) {
251+
global_eval.insert_update(address, new_update.clone());
252+
}
253+
254+
assert!(
255+
global_eval
256+
.determine_global_state(local_address, &local_update)
257+
.is_none(),
258+
"We should have a disagreement about the current miner"
259+
);
260+
261+
let state_machine = SignerStateMachine {
262+
burn_block,
263+
burn_block_height,
264+
current_miner: (&new_miner).into(),
265+
active_signer_protocol_version: local_supported_signer_protocol_version, // a majority of signers are saying they support version the same local_supported_signer_protocol_version, so update it here...
266+
};
267+
268+
// Let's tip the scales over to a different miner
269+
assert_eq!(
270+
global_eval
271+
.determine_global_state(local_address, &new_update)
272+
.unwrap(),
273+
state_machine
274+
)
275+
}
276+
277+
#[test]
278+
fn check_capitulate_miner_view() {
279+
let mut global_eval = generate_global_state_evaluator(5);
280+
281+
let addresses: Vec<_> = global_eval.address_weights.keys().cloned().collect();
282+
let local_address = addresses[0];
283+
let local_update = global_eval
284+
.address_updates
285+
.get(&local_address)
286+
.unwrap()
287+
.clone();
288+
let StateMachineUpdateMessage {
289+
active_signer_protocol_version,
290+
local_supported_signer_protocol_version,
291+
content:
292+
StateMachineUpdateContent::V0 {
293+
burn_block,
294+
burn_block_height,
295+
current_miner,
296+
},
297+
..
298+
} = local_update.clone();
299+
// Let's create a new miner view
300+
let new_tenure_id = ConsensusHash([0x00; 20]);
301+
let new_miner = StateMachineUpdateMinerState::ActiveMiner {
302+
current_miner_pkh: Hash160([0x00; 20]),
303+
tenure_id: new_tenure_id,
304+
parent_tenure_id: ConsensusHash([0x22; 20]),
305+
parent_tenure_last_block: StacksBlockId([0x33; 32]),
306+
parent_tenure_last_block_height: 1,
307+
};
308+
309+
let new_update = StateMachineUpdateMessage::new(
310+
active_signer_protocol_version,
311+
local_supported_signer_protocol_version,
312+
StateMachineUpdateContent::V0 {
313+
burn_block,
314+
burn_block_height,
315+
current_miner: new_miner.clone(),
316+
},
317+
)
318+
.unwrap();
319+
320+
let db_path = tmp_db_path();
321+
let mut db = SignerDb::new(db_path).expect("Failed to create signer db");
322+
let (mut block_info_1, _block_proposal) = create_block_override(|b| {
323+
b.block.header.consensus_hash = new_tenure_id;
324+
b.block.header.miner_signature = MessageSignature([0x01; 65]);
325+
b.block.header.chain_length = 1;
326+
b.burn_height = 1;
327+
});
328+
329+
db.insert_block(&block_info_1).unwrap();
330+
// Let's update only our own view: the evaluator will tell me to revert my viewpoint to the original miner
331+
assert_eq!(
332+
global_eval
333+
.capitulate_miner_view(&mut db, local_address, &new_update)
334+
.unwrap(),
335+
current_miner
336+
);
337+
338+
// Let's set a blocking minority to this different view: evaluator should see no global blocks for the blocking majority and return none
339+
// I.e. only if the blocking minority is attempting to reject an reorg should it take priority over the rest.
340+
// Let's update 1 other signer to some new miner key (60 percent)
341+
for address in addresses.into_iter().skip(1).take(1) {
342+
global_eval.insert_update(address, new_update.clone());
343+
}
344+
assert!(
345+
global_eval
346+
.capitulate_miner_view(&mut db, local_address, &new_update)
347+
.is_none(),
348+
"Evaluator should have been unable to determine a majority view and return none"
349+
);
350+
351+
db.mark_block_globally_accepted(&mut block_info_1).unwrap();
352+
353+
db.insert_block(&block_info_1).unwrap();
354+
355+
// Now that the blocking minority references a tenure which would actually get reorged, lets capitulate to their view
356+
assert_eq!(
357+
global_eval
358+
.capitulate_miner_view(&mut db, local_address, &new_update)
359+
.unwrap(),
360+
new_miner
361+
);
362+
}

0 commit comments

Comments
 (0)