Skip to content

Commit 42b10da

Browse files
committed
Handle pre-commit messages
Signed-off-by: Jacinta Ferrant <[email protected]>
1 parent 9712344 commit 42b10da

File tree

1 file changed

+161
-52
lines changed

1 file changed

+161
-52
lines changed

stacks-signer/src/v0/signer.rs

Lines changed: 161 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -519,7 +519,13 @@ impl Signer {
519519
SignerMessage::StateMachineUpdate(update) => self
520520
.handle_state_machine_update(signer_public_key, update, received_time),
521521
SignerMessage::BlockPreCommit(signer_signature_hash) => {
522-
todo!("Unable to handle block precommit message from {signer_public_key:?}: {signer_signature_hash}");
522+
let stacker_address =
523+
StacksAddress::p2pkh(self.mainnet, signer_public_key);
524+
self.handle_block_pre_commit(
525+
stacks_client,
526+
&stacker_address,
527+
signer_signature_hash,
528+
)
523529
}
524530
_ => {}
525531
}
@@ -869,13 +875,9 @@ impl Signer {
869875

870876
/// The actual `send_block_response` implementation. Declared so that we do
871877
/// not need to duplicate in testing.
872-
fn impl_send_block_response(
873-
&mut self,
874-
block: Option<&NakamotoBlock>,
875-
block_response: BlockResponse,
876-
) {
878+
fn impl_send_block_response(&mut self, block: &NakamotoBlock, block_response: BlockResponse) {
877879
info!(
878-
"{self}: Broadcasting a block response to stacks node: {block_response:?}";
880+
"{self}: Broadcasting block response to stacks node: {block_response:?}";
879881
);
880882
let accepted = matches!(block_response, BlockResponse::Accepted(..));
881883
match self
@@ -890,9 +892,7 @@ impl Signer {
890892
);
891893
}
892894
crate::monitoring::actions::increment_block_responses_sent(accepted);
893-
if let Some(block) = block {
894-
crate::monitoring::actions::record_block_response_latency(block);
895-
}
895+
crate::monitoring::actions::record_block_response_latency(block);
896896
}
897897
Err(e) => {
898898
warn!("{self}: Failed to send block response to stacker-db: {e:?}",);
@@ -901,11 +901,7 @@ impl Signer {
901901
}
902902

903903
#[cfg(any(test, feature = "testing"))]
904-
fn send_block_response(
905-
&mut self,
906-
block: Option<&NakamotoBlock>,
907-
block_response: BlockResponse,
908-
) {
904+
fn send_block_response(&mut self, block: &NakamotoBlock, block_response: BlockResponse) {
909905
const NUM_REPEATS: usize = 1;
910906
let mut count = 0;
911907
let public_keys = TEST_REPEAT_PROPOSAL_RESPONSE.get();
@@ -923,22 +919,44 @@ impl Signer {
923919
}
924920

925921
#[cfg(not(any(test, feature = "testing")))]
926-
fn send_block_response(
927-
&mut self,
928-
block: Option<&NakamotoBlock>,
929-
block_response: BlockResponse,
930-
) {
922+
fn send_block_response(&mut self, block: &NakamotoBlock, block_response: BlockResponse) {
931923
self.impl_send_block_response(block, block_response)
932924
}
933925

926+
/// Send a pre block commit message to signers to indicate that we will be signing the proposed block
927+
fn send_block_pre_commit(&mut self, signer_signature_hash: Sha512Trunc256Sum) {
928+
info!(
929+
"{self}: Broadcasting block pre-commit to stacks node for {signer_signature_hash}";
930+
);
931+
match self
932+
.stackerdb
933+
.send_message_with_retry(SignerMessage::BlockPreCommit(signer_signature_hash))
934+
{
935+
Ok(ack) => {
936+
if !ack.accepted {
937+
warn!(
938+
"{self}: Block pre-commit not accepted by stacker-db: {:?}",
939+
ack.reason
940+
);
941+
}
942+
crate::monitoring::actions::increment_block_pre_commits_sent();
943+
}
944+
Err(e) => {
945+
warn!("{self}: Failed to send block pre-commit to stacker-db: {e:?}",);
946+
}
947+
}
948+
}
949+
934950
/// Handle signer state update message
935951
fn handle_state_machine_update(
936952
&mut self,
937953
signer_public_key: &Secp256k1PublicKey,
938954
update: &StateMachineUpdate,
939955
received_time: &SystemTime,
940956
) {
941-
info!("{self}: Received a new state machine update from signer {signer_public_key:?}: {update:?}");
957+
info!(
958+
"{self}: Received state machine update from signer {signer_public_key:?}: {update:?}"
959+
);
942960
let address = StacksAddress::p2pkh(self.mainnet, signer_public_key);
943961
// Store the state machine update so we can reload it if we crash
944962
if let Err(e) = self.signer_db.insert_state_machine_update(
@@ -953,6 +971,96 @@ impl Signer {
953971
.insert_update(address, update.clone());
954972
}
955973

974+
/// Handle pre-commit message from another signer
975+
fn handle_block_pre_commit(
976+
&mut self,
977+
stacks_client: &StacksClient,
978+
stacker_address: &StacksAddress,
979+
block_hash: &Sha512Trunc256Sum,
980+
) {
981+
debug!(
982+
"{self}: Received pre-commit from signer ({stacker_address:?}) for block ({block_hash})",
983+
);
984+
let Some(mut block_info) = self.block_lookup_by_reward_cycle(block_hash) else {
985+
debug!("{self}: Received pre-commit for a block we have not seen before. Ignoring...");
986+
return;
987+
};
988+
if block_info.has_reached_consensus() {
989+
debug!(
990+
"{self}: Received pre-commit for a block that is already marked as {}. Ignoring...",
991+
block_info.state
992+
);
993+
return;
994+
};
995+
// Make sure the sender is part of our signing set
996+
let is_valid_sender = self.signer_addresses.iter().any(|addr| {
997+
// it only matters that the address hash bytes match
998+
stacker_address.bytes() == addr.bytes()
999+
});
1000+
1001+
if !is_valid_sender {
1002+
debug!("{self}: Received pre-commit message from an unknown sender {stacker_address:?}. Will not store.");
1003+
return;
1004+
}
1005+
1006+
if self.signer_db.has_committed(block_hash, stacker_address).inspect_err(|e| warn!("Failed to check if pre-commit message already considered for {stacker_address:?} for {block_hash}: {e}")).unwrap_or(false) {
1007+
debug!("{self}: Already considered pre-commit message from {stacker_address:?} for {block_hash}. Ignoring...");
1008+
return;
1009+
}
1010+
// commit message is from a valid sender! store it
1011+
self.signer_db
1012+
.add_block_pre_commit(block_hash, stacker_address)
1013+
.unwrap_or_else(|_| panic!("{self}: Failed to save block pre-commit"));
1014+
1015+
// do we have enough pre-commits to reach consensus?
1016+
// i.e. is the threshold reached?
1017+
let committers = self
1018+
.signer_db
1019+
.get_block_pre_committers(block_hash)
1020+
.unwrap_or_else(|_| panic!("{self}: Failed to load block commits"));
1021+
1022+
let commit_weight = self.compute_signature_signing_weight(committers.iter());
1023+
let total_weight = self.compute_signature_total_weight();
1024+
1025+
let min_weight = NakamotoBlockHeader::compute_voting_weight_threshold(total_weight)
1026+
.unwrap_or_else(|_| {
1027+
panic!("{self}: Failed to compute threshold weight for {total_weight}")
1028+
});
1029+
1030+
if min_weight > commit_weight {
1031+
debug!(
1032+
"{self}: Not enough pre-committed to block {block_hash} (have {commit_weight}, need at least {min_weight}/{total_weight})"
1033+
);
1034+
return;
1035+
}
1036+
1037+
// have enough commits, so maybe we should actually broadcast our signature...
1038+
if block_info.valid == Some(false) {
1039+
// We already marked this block as invalid. We should not do anything further as we do not change our votes on rejected blocks.
1040+
debug!(
1041+
"{self}: Enough pre-committed to block {block_hash}, but we do not view the block as valid. Doing nothing."
1042+
);
1043+
return;
1044+
}
1045+
// It is only considered globally accepted IFF we receive a new block event confirming it OR see the chain tip of the node advance to it.
1046+
if let Err(e) = block_info.mark_locally_accepted(false) {
1047+
if !block_info.has_reached_consensus() {
1048+
warn!("{self}: Failed to mark block as locally accepted: {e:?}",);
1049+
}
1050+
block_info.signed_self.get_or_insert(get_epoch_time_secs());
1051+
}
1052+
1053+
self.signer_db
1054+
.insert_block(&block_info)
1055+
.unwrap_or_else(|e| self.handle_insert_block_error(e));
1056+
let block_response = self.create_block_acceptance(&block_info.block);
1057+
if let Some(accepted) = block_response.as_block_accepted() {
1058+
// have to save the signature _after_ the block info
1059+
self.handle_block_signature(stacks_client, accepted);
1060+
}
1061+
self.impl_send_block_response(&block_info.block, block_response);
1062+
}
1063+
9561064
/// Handle block proposal messages submitted to signers stackerdb
9571065
fn handle_block_proposal(
9581066
&mut self,
@@ -1050,7 +1158,7 @@ impl Signer {
10501158

10511159
if let Some(block_response) = block_response {
10521160
// We know proposal is invalid. Send rejection message, do not do further validation and do not store it.
1053-
self.send_block_response(Some(&block_info.block), block_response);
1161+
self.send_block_response(&block_info.block, block_response);
10541162
} else {
10551163
// Just in case check if the last block validation submission timed out.
10561164
self.check_submitted_block_proposal();
@@ -1104,7 +1212,7 @@ impl Signer {
11041212
return;
11051213
};
11061214

1107-
self.impl_send_block_response(Some(&block_info.block), block_response);
1215+
self.impl_send_block_response(&block_info.block, block_response);
11081216
}
11091217

11101218
/// Handle block response messages from a signer
@@ -1207,13 +1315,13 @@ impl Signer {
12071315
}
12081316
}
12091317

1210-
/// Handle the block validate ok response. Returns our block response if we have one
1318+
/// Handle the block validate ok response
12111319
fn handle_block_validate_ok(
12121320
&mut self,
12131321
stacks_client: &StacksClient,
12141322
block_validate_ok: &BlockValidateOk,
12151323
sortition_state: &mut Option<SortitionsView>,
1216-
) -> Option<BlockResponse> {
1324+
) {
12171325
crate::monitoring::actions::increment_block_validation_responses(true);
12181326
let signer_signature_hash = block_validate_ok.signer_signature_hash;
12191327
if self
@@ -1242,17 +1350,19 @@ impl Signer {
12421350
let Some(mut block_info) = self.block_lookup_by_reward_cycle(&signer_signature_hash) else {
12431351
// We have not seen this block before. Why are we getting a response for it?
12441352
debug!("{self}: Received a block validate response for a block we have not seen before. Ignoring...");
1245-
return None;
1353+
return;
12461354
};
12471355
if block_info.is_locally_finalized() {
12481356
debug!("{self}: Received block validation for a block that is already marked as {}. Ignoring...", block_info.state);
1249-
return None;
1357+
return;
12501358
}
12511359

12521360
if let Some(block_response) =
12531361
self.check_block_against_signer_db_state(stacks_client, &block_info.block)
12541362
{
1255-
let block_rejection = block_response.as_block_rejection()?;
1363+
let Some(block_rejection) = block_response.as_block_rejection() else {
1364+
return;
1365+
};
12561366
// The signer db state has changed. We no longer view this block as valid. Override the validation response.
12571367
if let Err(e) = block_info.mark_locally_rejected() {
12581368
if !block_info.has_reached_consensus() {
@@ -1263,12 +1373,15 @@ impl Signer {
12631373
.insert_block(&block_info)
12641374
.unwrap_or_else(|e| self.handle_insert_block_error(e));
12651375
self.handle_block_rejection(block_rejection, sortition_state);
1266-
Some(block_response)
1376+
self.impl_send_block_response(
1377+
&block_info.block,
1378+
BlockResponse::Rejected(block_rejection.clone()),
1379+
);
12671380
} else {
12681381
if let Err(e) = block_info.mark_locally_accepted(false) {
12691382
if !block_info.has_reached_consensus() {
12701383
warn!("{self}: Failed to mark block as locally accepted: {e:?}",);
1271-
return None;
1384+
return;
12721385
}
12731386
block_info.signed_self.get_or_insert(get_epoch_time_secs());
12741387
}
@@ -1282,19 +1395,19 @@ impl Signer {
12821395
self.signer_db
12831396
.insert_block(&block_info)
12841397
.unwrap_or_else(|e| self.handle_insert_block_error(e));
1285-
let block_response = self.create_block_acceptance(&block_info.block);
1398+
self.send_block_pre_commit(signer_signature_hash);
12861399
// have to save the signature _after_ the block info
1287-
self.handle_block_signature(stacks_client, block_response.as_block_accepted()?);
1288-
Some(block_response)
1400+
let address = self.stacks_address;
1401+
self.handle_block_pre_commit(stacks_client, &address, &signer_signature_hash);
12891402
}
12901403
}
12911404

1292-
/// Handle the block validate reject response. Returns our block response if we have one
1405+
/// Handle the block validate reject response
12931406
fn handle_block_validate_reject(
12941407
&mut self,
12951408
block_validate_reject: &BlockValidateReject,
12961409
sortition_state: &mut Option<SortitionsView>,
1297-
) -> Option<BlockResponse> {
1410+
) {
12981411
crate::monitoring::actions::increment_block_validation_responses(false);
12991412
let signer_signature_hash = block_validate_reject.signer_signature_hash;
13001413
if self
@@ -1307,16 +1420,16 @@ impl Signer {
13071420
let Some(mut block_info) = self.block_lookup_by_reward_cycle(&signer_signature_hash) else {
13081421
// We have not seen this block before. Why are we getting a response for it?
13091422
debug!("{self}: Received a block validate response for a block we have not seen before. Ignoring...");
1310-
return None;
1423+
return;
13111424
};
13121425
if block_info.is_locally_finalized() {
13131426
debug!("{self}: Received block validation for a block that is already marked as {}. Ignoring...", block_info.state);
1314-
return None;
1427+
return;
13151428
}
13161429
if let Err(e) = block_info.mark_locally_rejected() {
13171430
if !block_info.has_reached_consensus() {
13181431
warn!("{self}: Failed to mark block as locally rejected: {e:?}",);
1319-
return None;
1432+
return;
13201433
}
13211434
}
13221435
let block_rejection = BlockRejection::from_validate_rejection(
@@ -1337,7 +1450,7 @@ impl Signer {
13371450
.insert_block(&block_info)
13381451
.unwrap_or_else(|e| self.handle_insert_block_error(e));
13391452
self.handle_block_rejection(&block_rejection, sortition_state);
1340-
Some(BlockResponse::Rejected(block_rejection))
1453+
self.impl_send_block_response(&block_info.block, BlockResponse::Rejected(block_rejection));
13411454
}
13421455

13431456
/// Handle the block validate response returned from our prior calls to submit a block for validation
@@ -1348,15 +1461,15 @@ impl Signer {
13481461
sortition_state: &mut Option<SortitionsView>,
13491462
) {
13501463
info!("{self}: Received a block validate response: {block_validate_response:?}");
1351-
let block_response = match block_validate_response {
1464+
match block_validate_response {
13521465
BlockValidateResponse::Ok(block_validate_ok) => {
13531466
crate::monitoring::actions::record_block_validation_latency(
13541467
block_validate_ok.validation_time_ms,
13551468
);
1356-
self.handle_block_validate_ok(stacks_client, block_validate_ok, sortition_state)
1469+
self.handle_block_validate_ok(stacks_client, block_validate_ok, sortition_state);
13571470
}
13581471
BlockValidateResponse::Reject(block_validate_reject) => {
1359-
self.handle_block_validate_reject(block_validate_reject, sortition_state)
1472+
self.handle_block_validate_reject(block_validate_reject, sortition_state);
13601473
}
13611474
};
13621475
// Remove this block validation from the pending table
@@ -1365,15 +1478,6 @@ impl Signer {
13651478
.remove_pending_block_validation(&signer_sig_hash)
13661479
.unwrap_or_else(|e| warn!("{self}: Failed to remove pending block validation: {e:?}"));
13671480

1368-
if let Some(response) = block_response {
1369-
let block = self
1370-
.signer_db
1371-
.block_lookup(&signer_sig_hash)
1372-
.unwrap_or_default()
1373-
.map(|info| info.block);
1374-
self.impl_send_block_response(block.as_ref(), response);
1375-
};
1376-
13771481
// Check if there is a pending block validation that we need to submit to the node
13781482
self.check_pending_block_validations(stacks_client);
13791483
}
@@ -1464,7 +1568,7 @@ impl Signer {
14641568
warn!("{self}: Failed to mark block as locally rejected: {e:?}");
14651569
}
14661570
};
1467-
self.impl_send_block_response(Some(&block_info.block), rejection);
1571+
self.impl_send_block_response(&block_info.block, rejection);
14681572

14691573
self.signer_db
14701574
.insert_block(&block_info)
@@ -1701,6 +1805,11 @@ impl Signer {
17011805
return;
17021806
}
17031807

1808+
// If this isn't our own signature, try treating it as a pre-commit in case the caller is running an outdated version
1809+
if signer_address != self.stacks_address {
1810+
self.handle_block_pre_commit(stacks_client, &signer_address, block_hash);
1811+
}
1812+
17041813
// do we have enough signatures to broadcast?
17051814
// i.e. is the threshold reached?
17061815
let signatures = self

0 commit comments

Comments
 (0)