Skip to content

Commit ec98152

Browse files
committed
[Consensus Observer] Handle state sync notification race condition.
1 parent 9d2998e commit ec98152

File tree

2 files changed

+28
-51
lines changed

2 files changed

+28
-51
lines changed

consensus/src/consensus_observer/observer/block_data.rs

Lines changed: 0 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -89,17 +89,6 @@ impl ObserverBlockData {
8989
self.block_payload_store.all_payloads_exist(blocks)
9090
}
9191

92-
/// Returns true iff the root epoch and round match the given values
93-
pub fn check_root_epoch_and_round(&self, epoch: u64, round: Round) -> bool {
94-
// Get the expected epoch and round
95-
let root = self.root();
96-
let expected_epoch = root.commit_info().epoch();
97-
let expected_round = root.commit_info().round();
98-
99-
// Check if the expected epoch and round match the given values
100-
expected_epoch == epoch && expected_round == round
101-
}
102-
10392
/// Clears all block data and returns the root ledger info
10493
pub fn clear_block_data(&mut self) -> LedgerInfoWithSignatures {
10594
// Clear the payload store
@@ -403,33 +392,6 @@ mod test {
403392
assert!(observer_block_data.all_payloads_exist(&[pipelined_block]));
404393
}
405394

406-
#[test]
407-
fn test_check_root_epoch_and_round() {
408-
// Create a root ledger info
409-
let epoch = 10;
410-
let round = 5;
411-
let root = create_ledger_info(epoch, round);
412-
413-
// Create the observer block data
414-
let mut observer_block_data =
415-
ObserverBlockData::new_with_root(ConsensusObserverConfig::default(), root);
416-
417-
// Check the root epoch and round
418-
assert!(observer_block_data.check_root_epoch_and_round(epoch, round));
419-
assert!(!observer_block_data.check_root_epoch_and_round(epoch, round + 1));
420-
assert!(!observer_block_data.check_root_epoch_and_round(epoch + 1, round));
421-
422-
// Update the root ledger info
423-
let new_epoch = epoch + 10;
424-
let new_round = round + 100;
425-
let new_root = create_ledger_info(new_epoch, new_round);
426-
observer_block_data.update_root(new_root.clone());
427-
428-
// Check the updated root epoch and round
429-
assert!(!observer_block_data.check_root_epoch_and_round(epoch, round));
430-
assert!(observer_block_data.check_root_epoch_and_round(new_epoch, new_round));
431-
}
432-
433395
#[test]
434396
fn test_clear_block_data() {
435397
// Create a root ledger info

consensus/src/consensus_observer/observer/consensus_observer.rs

Lines changed: 28 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -972,14 +972,14 @@ impl ConsensusObserver {
972972
) {
973973
// Get the epoch and round for the synced commit decision
974974
let ledger_info = latest_synced_ledger_info.ledger_info();
975-
let epoch = ledger_info.epoch();
976-
let round = ledger_info.round();
975+
let synced_epoch = ledger_info.epoch();
976+
let synced_round = ledger_info.round();
977977

978978
// Log the state sync notification
979979
info!(
980980
LogSchema::new(LogEntry::ConsensusObserver).message(&format!(
981-
"Received state sync notification for commit completion! Epoch {}, round: {}!",
982-
epoch, round
981+
"Received state sync notification for commit completion! Synced epoch {}, round: {}!",
982+
synced_epoch, synced_round
983983
))
984984
);
985985

@@ -992,26 +992,41 @@ impl ConsensusObserver {
992992
return;
993993
}
994994

995-
// Verify that the state sync notification is for the current epoch and round
996-
if !self
997-
.observer_block_data
998-
.lock()
999-
.check_root_epoch_and_round(epoch, round)
1000-
{
995+
// Get the block data root epoch and round
996+
let block_data_root = self.observer_block_data.lock().root();
997+
let block_data_epoch = block_data_root.ledger_info().epoch();
998+
let block_data_round = block_data_root.ledger_info().round();
999+
1000+
// If the commit sync notification is behind the block data root, ignore it. This
1001+
// is possible due to a race condition where we started syncing to a newer commit
1002+
// at the same time that state sync sent the notification for a previous commit.
1003+
if (synced_epoch, synced_round) < (block_data_epoch, block_data_round) {
1004+
info!(
1005+
LogSchema::new(LogEntry::ConsensusObserver).message(&format!(
1006+
"Ignoring old commit sync notification for epoch: {}, round: {}! Current root: {:?}",
1007+
synced_epoch, synced_round, block_data_root
1008+
))
1009+
);
1010+
return;
1011+
}
1012+
1013+
// If the commit sync notification is ahead the block data root, something has gone wrong!
1014+
if (synced_epoch, synced_round) > (block_data_epoch, block_data_round) {
10011015
// Log the error, reset the state sync manager and return early
10021016
error!(
10031017
LogSchema::new(LogEntry::ConsensusObserver).message(&format!(
10041018
"Received invalid commit sync notification for epoch: {}, round: {}! Current root: {:?}",
1005-
epoch, round, self.observer_block_data.lock().root()
1019+
synced_epoch, synced_round, block_data_root
10061020
))
10071021
);
10081022
self.state_sync_manager.clear_active_commit_sync();
10091023
return;
10101024
}
10111025

1012-
// If the epoch has changed, end the current epoch and start the latest one
1026+
// Otherwise, the commit sync notification matches the block data root.
1027+
// If the epoch has changed, end the current epoch and start the latest one.
10131028
let current_epoch_state = self.get_epoch_state();
1014-
if epoch > current_epoch_state.epoch {
1029+
if synced_epoch > current_epoch_state.epoch {
10151030
// Wait for the latest epoch to start
10161031
self.execution_client.end_epoch().await;
10171032
self.wait_for_epoch_start().await;

0 commit comments

Comments
 (0)