Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions consensus/core/src/dag_state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -790,6 +790,21 @@ impl DagState {
.committed
}

/// Returns true if any block at the given slot has been committed.
/// This is used to prevent double-commit of equivocating blocks where
/// two blocks have the same (Round, Author) but different digests.
pub(crate) fn is_any_block_at_slot_committed(&self, slot: Slot) -> bool {
for (_block_ref, block_info) in self.recent_blocks.range((
Included(BlockRef::new(slot.round, slot.authority, BlockDigest::MIN)),
Included(BlockRef::new(slot.round, slot.authority, BlockDigest::MAX)),
)) {
if block_info.committed {
return true;
}
}
false
}

/// Recursively sets blocks in the causal history of the root block as hard linked, including the root block itself.
/// Returns the list of blocks that are newly linked.
/// The returned blocks are guaranteed to be above the GC round.
Expand Down
5 changes: 5 additions & 0 deletions consensus/core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,11 @@ mod test_dag_parser;
#[path = "tests/randomized_tests.rs"]
mod randomized_tests;

/// Equivocation double-commit prevention tests.
#[cfg(test)]
#[path = "tests/equivocation_commit_test.rs"]
mod equivocation_commit_test;

/// Exported Consensus API.
pub use authority_node::{ConsensusAuthority, NetworkType};
pub use block::{BlockAPI, CertifiedBlock, CertifiedBlocksOutput};
Expand Down
18 changes: 16 additions & 2 deletions consensus/core/src/linearizer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ use itertools::Itertools;
use parking_lot::RwLock;

use crate::{
block::{BlockAPI, VerifiedBlock},
block::{BlockAPI, Slot, VerifiedBlock},
commit::{Commit, CommittedSubDag, TrustedCommit, sort_sub_dag_blocks},
context::Context,
dag_state::DagState,
Expand All @@ -25,6 +25,10 @@ pub(crate) trait BlockStoreAPI {
fn set_committed(&mut self, block_ref: &BlockRef) -> bool;

fn is_committed(&self, block_ref: &BlockRef) -> bool;

/// Returns true if any block at the given slot has been committed.
/// This is used to prevent double-commit of equivocating blocks.
fn is_any_block_at_slot_committed(&self, slot: Slot) -> bool;
}

impl BlockStoreAPI
Expand All @@ -45,6 +49,10 @@ impl BlockStoreAPI
fn is_committed(&self, block_ref: &BlockRef) -> bool {
DagState::is_committed(self, block_ref)
}

fn is_any_block_at_slot_committed(&self, slot: Slot) -> bool {
DagState::is_any_block_at_slot_committed(self, slot)
}
}

/// Expand a committed sequence of leader into a sequence of sub-dags.
Expand Down Expand Up @@ -183,7 +191,13 @@ impl Linearizer {
.iter()
.copied()
.filter(|ancestor| {
ancestor.round > gc_round && !dag_state.is_committed(ancestor)
ancestor.round > gc_round
&& !dag_state.is_committed(ancestor)
// Also check if any block at this slot has been committed.
// This prevents double-commit of equivocating blocks where
// Block A and Block B have the same (Round, Author) but
// different digests.
&& !dag_state.is_any_block_at_slot_committed((*ancestor).into())
})
.collect::<Vec<_>>(),
)
Expand Down
12 changes: 12 additions & 0 deletions consensus/core/src/test_dag_builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,18 @@ impl DagBuilder {
.map(|(_, committed)| *committed)
.expect("Block should be found in store")
}

fn is_any_block_at_slot_committed(&self, slot: Slot) -> bool {
for (block_ref, (_block, committed)) in self.blocks.range((
Included(BlockRef::new(slot.round, slot.authority, BlockDigest::MIN)),
Included(BlockRef::new(slot.round, slot.authority, BlockDigest::MAX)),
)) {
if *committed {
return true;
}
}
false
}
}

let mut storage = BlockStorage {
Expand Down
Loading