Skip to content

Commit 04b1c34

Browse files
committed
CRC: Fix use of weight vs entry in capitulate_miner_view and various cleanups
Signed-off-by: Jacinta Ferrant <[email protected]>
1 parent 6ef6c41 commit 04b1c34

File tree

7 files changed

+95
-66
lines changed

7 files changed

+95
-66
lines changed

libsigner/src/events.rs

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -192,9 +192,14 @@ pub enum SignerEvent<T: SignerEventTrait> {
192192
/// The `Vec<T>` will contain any signer messages made by the miner.
193193
MinerMessages(Vec<T>),
194194
/// The signer messages for other signers and miners to observe
195-
/// - The `u32` represents the signer set to which the message belongs (either 0 or 1).
196-
/// - Each message of type `T` is paired with the `StacksPublicKey` of the slot from which it was retrieved.
197-
SignerMessages(u32, Vec<(StacksPublicKey, T)>),
195+
SignerMessages {
196+
/// The signer set to which the message belongs (either 0 or 1)
197+
signer_set: u32,
198+
/// Each message of type `T` is paired with the `StacksPublicKey` of the slot from which it was retreived
199+
messages: Vec<(StacksPublicKey, T)>,
200+
/// the time at which this event was received by the signer's event processor
201+
received_time: SystemTime,
202+
},
198203
/// A new block proposal validation response from the node
199204
BlockValidationResponse(BlockValidateResponse),
200205
/// Status endpoint request
@@ -519,6 +524,7 @@ impl<T: SignerEventTrait> TryFrom<StackerDBChunksEvent> for SignerEvent<T> {
519524
type Error = EventError;
520525

521526
fn try_from(event: StackerDBChunksEvent) -> Result<Self, Self::Error> {
527+
let received_time = SystemTime::now();
522528
let signer_event = if event.contract_id.name.as_str() == MINERS_NAME
523529
&& event.contract_id.is_boot()
524530
{
@@ -547,7 +553,11 @@ impl<T: SignerEventTrait> TryFrom<StackerDBChunksEvent> for SignerEvent<T> {
547553
))
548554
})
549555
.collect();
550-
SignerEvent::SignerMessages(signer_set, messages)
556+
SignerEvent::SignerMessages {
557+
signer_set,
558+
messages,
559+
received_time,
560+
}
551561
} else {
552562
return Err(EventError::UnrecognizedStackerDBContract(event.contract_id));
553563
};

libsigner/src/tests/mod.rs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ use std::fmt::Debug;
2020
use std::io::{Read, Write};
2121
use std::net::{SocketAddr, TcpStream, ToSocketAddrs};
2222
use std::sync::mpsc::{channel, Receiver, Sender};
23-
use std::time::Duration;
23+
use std::time::{Duration, SystemTime};
2424
use std::{mem, thread};
2525

2626
use blockstack_lib::chainstate::nakamoto::signer_set::NakamotoSigners;
@@ -192,7 +192,11 @@ fn test_simple_signer() {
192192
.recover_pk()
193193
.expect("Faield to recover public key of slot");
194194
let signer_message = read_next::<SignerMessage, _>(&mut &msg[..]).unwrap();
195-
SignerEvent::SignerMessages(0, vec![(pubkey, signer_message)])
195+
SignerEvent::SignerMessages {
196+
signer_set: 0,
197+
messages: vec![(pubkey, signer_message)],
198+
received_time: SystemTime::now(),
199+
}
196200
})
197201
.collect();
198202

libsigner/src/v0/messages.rs

Lines changed: 1 addition & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -574,7 +574,7 @@ pub enum StateMachineUpdateContent {
574574
}
575575

576576
/// Message for update the Signer State infos
577-
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, Eq)]
577+
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, Eq, Hash)]
578578
pub enum StateMachineUpdateMinerState {
579579
/// There is an active miner
580580
ActiveMiner {
@@ -595,29 +595,6 @@ pub enum StateMachineUpdateMinerState {
595595
NoValidMiner,
596596
}
597597

598-
impl Hash for StateMachineUpdateMinerState {
599-
fn hash<H: Hasher>(&self, state: &mut H) {
600-
match self {
601-
StateMachineUpdateMinerState::ActiveMiner {
602-
current_miner_pkh,
603-
tenure_id,
604-
parent_tenure_id,
605-
parent_tenure_last_block,
606-
parent_tenure_last_block_height,
607-
} => {
608-
current_miner_pkh.hash(state);
609-
tenure_id.hash(state);
610-
parent_tenure_id.hash(state);
611-
parent_tenure_last_block.hash(state);
612-
parent_tenure_last_block_height.hash(state);
613-
}
614-
StateMachineUpdateMinerState::NoValidMiner => {
615-
0.hash(state);
616-
}
617-
}
618-
}
619-
}
620-
621598
impl StateMachineUpdate {
622599
/// Construct a StateMachineUpdate message, checking to ensure that the
623600
/// supplied content is supported by the supplied protocol versions.

stacks-signer/src/signerdb.rs

Lines changed: 24 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -367,6 +367,10 @@ CREATE INDEX IF NOT EXISTS blocks_consensus_hash_status_height ON blocks (consen
367367
CREATE INDEX IF NOT EXISTS blocks_reward_cycle_state on blocks (reward_cycle, state);
368368
"#;
369369

370+
static CREATE_INDEXES_11: &str = r#"
371+
CREATE INDEX IF NOT EXISTS signer_state_machine_updates_reward_cycle_received_time ON signer_state_machine_updates (reward_cycle, received_time ASC);
372+
"#;
373+
370374
static CREATE_SIGNER_STATE_TABLE: &str = "
371375
CREATE TABLE IF NOT EXISTS signer_states (
372376
reward_cycle INTEGER PRIMARY KEY,
@@ -524,6 +528,7 @@ CREATE TABLE IF NOT EXISTS signer_state_machine_updates (
524528
signer_addr TEXT NOT NULL,
525529
reward_cycle INTEGER NOT NULL,
526530
state_update TEXT NOT NULL,
531+
received_time TEXT NOT NULL,
527532
PRIMARY KEY (signer_addr, reward_cycle)
528533
) STRICT;"#;
529534

@@ -604,6 +609,7 @@ static SCHEMA_10: &[&str] = &[
604609

605610
static SCHEMA_11: &[&str] = &[
606611
CREATE_SIGNER_STATE_MACHINE_UPDATES_TABLE,
612+
CREATE_INDEXES_11,
607613
"INSERT INTO db_config (version) VALUES (11);",
608614
];
609615

@@ -1355,7 +1361,12 @@ impl SignerDb {
13551361
reward_cycle: u64,
13561362
address: &StacksAddress,
13571363
update: &StateMachineUpdate,
1364+
received_time: &SystemTime,
13581365
) -> Result<(), DBError> {
1366+
let received_ts = received_time
1367+
.duration_since(std::time::UNIX_EPOCH)
1368+
.map_err(|e| DBError::Other(format!("Bad system time: {e}")))?
1369+
.as_secs();
13591370
let update_str =
13601371
serde_json::to_string(&update).expect("Unable to serialize state machine update");
13611372
debug!("Inserting update.";
@@ -1364,10 +1375,11 @@ impl SignerDb {
13641375
"active_signer_protocol_version" => update.active_signer_protocol_version,
13651376
"local_supported_signer_protocol_version" => update.local_supported_signer_protocol_version
13661377
);
1367-
self.db.execute("INSERT OR REPLACE INTO signer_state_machine_updates (signer_addr, reward_cycle, state_update) VALUES (?1, ?2, ?3)", params![
1378+
self.db.execute("INSERT OR REPLACE INTO signer_state_machine_updates (signer_addr, reward_cycle, state_update, received_time) VALUES (?1, ?2, ?3, ?4)", params![
13681379
address.to_string(),
13691380
u64_to_sql(reward_cycle)?,
1370-
update_str
1381+
update_str,
1382+
u64_to_sql(received_ts)?
13711383
])?;
13721384
Ok(())
13731385
}
@@ -2450,12 +2462,17 @@ mod tests {
24502462
"The database should be empty for reward_cycle {reward_cycle_1}"
24512463
);
24522464

2453-
db.insert_state_machine_update(reward_cycle_1, &address_1, &update_1)
2465+
db.insert_state_machine_update(reward_cycle_1, &address_1, &update_1, &SystemTime::now())
24542466
.expect("Unable to insert block into db");
2455-
db.insert_state_machine_update(reward_cycle_1, &address_2, &update_2)
2456-
.expect("Unable to insert block into db");
2457-
db.insert_state_machine_update(reward_cycle_1 + 1, &address_3, &update_3)
2467+
db.insert_state_machine_update(reward_cycle_1, &address_2, &update_2, &SystemTime::now())
24582468
.expect("Unable to insert block into db");
2469+
db.insert_state_machine_update(
2470+
reward_cycle_1 + 1,
2471+
&address_3,
2472+
&update_3,
2473+
&SystemTime::now(),
2474+
)
2475+
.expect("Unable to insert block into db");
24592476

24602477
let updates = db.get_signer_state_machine_updates(reward_cycle_1).unwrap();
24612478
assert_eq!(updates.len(), 2);
@@ -2464,7 +2481,7 @@ mod tests {
24642481
assert_eq!(updates.get(&address_2), Some(&update_2));
24652482
assert_eq!(updates.get(&address_3), None);
24662483

2467-
db.insert_state_machine_update(reward_cycle_1, &address_2, &update_3)
2484+
db.insert_state_machine_update(reward_cycle_1, &address_2, &update_3, &SystemTime::now())
24682485
.expect("Unable to insert block into db");
24692486
let updates = db.get_signer_state_machine_updates(reward_cycle_1).unwrap();
24702487
assert_eq!(updates.len(), 2);

stacks-signer/src/v0/signer.rs

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ use std::fmt::Debug;
1717
use std::sync::mpsc::Sender;
1818
#[cfg(any(test, feature = "testing"))]
1919
use std::sync::LazyLock;
20-
use std::time::{Duration, Instant};
20+
use std::time::{Duration, Instant, SystemTime};
2121

2222
use blockstack_lib::chainstate::nakamoto::{NakamotoBlock, NakamotoBlockHeader};
2323
use blockstack_lib::net::api::postblock_proposal::{
@@ -206,7 +206,9 @@ impl SignerTrait<SignerMessage> for Signer {
206206
| Some(SignerEvent::NewBlock { .. })
207207
| Some(SignerEvent::StatusCheck)
208208
| None => None,
209-
Some(SignerEvent::SignerMessages(msg_parity, ..)) => Some(u64::from(*msg_parity) % 2),
209+
Some(SignerEvent::SignerMessages { signer_set, .. }) => {
210+
Some(u64::from(*signer_set) % 2)
211+
}
210212
};
211213
let other_signer_parity = (self.reward_cycle + 1) % 2;
212214
if event_parity == Some(other_signer_parity) {
@@ -246,7 +248,11 @@ impl SignerTrait<SignerMessage> for Signer {
246248
sortition_state,
247249
)
248250
}
249-
SignerEvent::SignerMessages(_, messages) => {
251+
SignerEvent::SignerMessages {
252+
received_time,
253+
messages,
254+
..
255+
} => {
250256
debug!(
251257
"{self}: Received {} messages from the other signers",
252258
messages.len()
@@ -260,7 +266,11 @@ impl SignerTrait<SignerMessage> for Signer {
260266
sortition_state,
261267
),
262268
SignerMessage::StateMachineUpdate(update) => {
263-
self.handle_state_machine_update(signer_public_key, update);
269+
self.handle_state_machine_update(
270+
signer_public_key,
271+
update,
272+
received_time,
273+
);
264274
}
265275
_ => {}
266276
}
@@ -589,13 +599,16 @@ impl Signer {
589599
&mut self,
590600
signer_public_key: &Secp256k1PublicKey,
591601
update: &StateMachineUpdate,
602+
received_time: &SystemTime,
592603
) {
593604
let address = StacksAddress::p2pkh(self.mainnet, signer_public_key);
594605
// Store the state machine update so we can reload it if we crash
595-
if let Err(e) =
596-
self.signer_db
597-
.insert_state_machine_update(self.reward_cycle, &address, update)
598-
{
606+
if let Err(e) = self.signer_db.insert_state_machine_update(
607+
self.reward_cycle,
608+
&address,
609+
update,
610+
received_time,
611+
) {
599612
warn!("{self}: Failed to update global state in signerdb: {e}");
600613
}
601614
self.global_state_evaluator

stacks-signer/src/v0/signer_state.rs

Lines changed: 23 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ impl GlobalStateEvaluator {
104104

105105
let entry = burn_blocks.entry(burn_block).or_insert_with(|| 0);
106106
*entry += weight;
107-
if *entry >= self.total_weight * 7 / 10 {
107+
if self.reached_agreement(*entry) {
108108
return Some((burn_block, burn_block_height));
109109
}
110110
}
@@ -116,8 +116,7 @@ impl GlobalStateEvaluator {
116116
/// NOTE: do not call this function unless the evaluator has already been updated with the current local state
117117
pub fn determine_global_state(&self) -> Option<SignerStateMachine> {
118118
let active_signer_protocol_version = self.determine_active_signer_protocol_version()?;
119-
let (global_burn_block, _) = self.determine_global_burn_view()?;
120-
let mut miner_views = HashMap::new();
119+
let mut state_views = HashMap::new();
121120
for (address, update) in &self.address_updates {
122121
let Some(weight) = self.address_weights.get(address) else {
123122
continue;
@@ -128,20 +127,18 @@ impl GlobalStateEvaluator {
128127
current_miner,
129128
..
130129
} = &update.content;
131-
132-
if *burn_block != global_burn_block {
133-
continue;
134-
}
135-
136-
let entry = miner_views.entry(current_miner).or_insert_with(|| 0);
130+
let state_machine = SignerStateMachine {
131+
burn_block: *burn_block,
132+
burn_block_height: *burn_block_height,
133+
current_miner: current_miner.into(),
134+
active_signer_protocol_version,
135+
};
136+
let entry = state_views
137+
.entry(state_machine.clone())
138+
.or_insert_with(|| 0);
137139
*entry += weight;
138-
if *entry >= self.total_weight * 7 / 10 {
139-
return Some(SignerStateMachine {
140-
burn_block: *burn_block,
141-
burn_block_height: *burn_block_height,
142-
current_miner: current_miner.into(),
143-
active_signer_protocol_version,
144-
});
140+
if self.reached_agreement(*entry) {
141+
return Some(state_machine);
145142
}
146143
}
147144
None
@@ -169,7 +166,7 @@ impl GlobalStateEvaluator {
169166

170167
let entry = miner_views.entry(current_miner).or_insert_with(|| 0);
171168
*entry += weight;
172-
if *entry >= self.total_weight * 7 / 10 {
169+
if self.reached_agreement(*entry) {
173170
return Some(current_miner.clone());
174171
}
175172
}
@@ -217,7 +214,7 @@ impl GlobalStateEvaluator {
217214
let nmb_blocks = signerdb
218215
.get_globally_accepted_block_count_in_tenure(&tenure_id)
219216
.unwrap_or(0);
220-
if nmb_blocks > 0 || *weight >= self.total_weight * 7 / 10 {
217+
if nmb_blocks > 0 || self.reached_agreement(*entry) {
221218
return Some(current_miner.clone());
222219
}
223220
}
@@ -237,12 +234,18 @@ impl GlobalStateEvaluator {
237234
self.address_updates.insert(address, update);
238235
true
239236
}
237+
238+
/// Check if the supplied vote weight crosses the global agreement threshold.
239+
/// Returns true if it has, false otherwise.
240+
fn reached_agreement(&self, vote_weight: u32) -> bool {
241+
vote_weight >= self.total_weight * 7 / 10
242+
}
240243
}
241244

242245
/// A signer state machine view. This struct can
243246
/// be used to encode the local signer's view or
244247
/// the global view.
245-
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
248+
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Eq, Hash)]
246249
pub struct SignerStateMachine {
247250
/// The tip burn block (i.e., the latest bitcoin block) seen by this signer
248251
pub burn_block: ConsensusHash,
@@ -254,7 +257,7 @@ pub struct SignerStateMachine {
254257
pub active_signer_protocol_version: u64,
255258
}
256259

257-
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
260+
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Eq, Hash)]
258261
/// Enum for capturing the signer state machine's view of who
259262
/// should be the active miner and what their tenure should be
260263
/// built on top of.

testnet/stacks-node/src/nakamoto_node/stackerdb_listener.rs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -235,7 +235,12 @@ impl StackerDBListener {
235235
}) else {
236236
continue;
237237
};
238-
let SignerEvent::SignerMessages(signer_set, messages) = signer_event else {
238+
let SignerEvent::SignerMessages {
239+
signer_set,
240+
messages,
241+
..
242+
} = signer_event
243+
else {
239244
debug!("StackerDBListener: Received signer event other than a signer message. Ignoring.");
240245
continue;
241246
};

0 commit comments

Comments
 (0)