Skip to content

Commit 828c3c4

Browse files
committed
Do not vote on potentially GC'ed blocks
1 parent 7ae4447 commit 828c3c4

File tree

5 files changed

+69
-25
lines changed

5 files changed

+69
-25
lines changed

consensus/core/src/commit_finalizer.rs

Lines changed: 63 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -339,9 +339,9 @@ impl CommitFinalizer {
339339
}
340340
}
341341
// Initialize the block state.
342-
blocks_map
343-
.entry(block_ref)
344-
.or_insert_with(|| RwLock::new(BlockState::new(block)));
342+
blocks_map.entry(block_ref).or_insert_with(|| {
343+
RwLock::new(BlockState::new(block, commit_state.commit.commit_ref.index))
344+
});
345345
}
346346
}
347347

@@ -477,6 +477,19 @@ impl CommitFinalizer {
477477
.map(|(k, v)| (*k, v.clone()))
478478
.collect();
479479

480+
let gc_rounds = self
481+
.pending_commits
482+
.iter()
483+
.map(|c| {
484+
(
485+
c.commit.commit_ref.index,
486+
self.dag_state
487+
.read()
488+
.calculate_gc_round(c.commit.leader.round),
489+
)
490+
})
491+
.collect::<Vec<_>>();
492+
480493
// Number of blocks to process in each task.
481494
const BLOCKS_PER_INDIRECT_COMMIT_TASK: usize = 8;
482495

@@ -488,6 +501,7 @@ impl CommitFinalizer {
488501
for chunk in pending_blocks.chunks(BLOCKS_PER_INDIRECT_COMMIT_TASK) {
489502
let context = self.context.clone();
490503
let blocks = self.blocks.clone();
504+
let gc_rounds = gc_rounds.clone();
491505
let chunk: Vec<(BlockRef, BTreeSet<TransactionIndex>)> = chunk.to_vec();
492506

493507
join_set.spawn(tokio::task::spawn_blocking(move || {
@@ -497,6 +511,7 @@ impl CommitFinalizer {
497511
let finalized = Self::try_indirect_finalize_pending_transactions_in_block(
498512
&context,
499513
&blocks,
514+
&gc_rounds,
500515
block_ref,
501516
pending_transactions,
502517
);
@@ -578,6 +593,7 @@ impl CommitFinalizer {
578593
fn try_indirect_finalize_pending_transactions_in_block(
579594
context: &Arc<Context>,
580595
blocks: &Arc<RwLock<BTreeMap<BlockRef, RwLock<BlockState>>>>,
596+
gc_rounds: &[(CommitIndex, Round)],
581597
pending_block_ref: BlockRef,
582598
pending_transactions: BTreeSet<TransactionIndex>,
583599
) -> Vec<TransactionIndex> {
@@ -591,13 +607,11 @@ impl CommitFinalizer {
591607
.collect();
592608
let mut finalized_transactions = vec![];
593609
let blocks_map = blocks.read();
594-
// Use BTreeSet to ensure always visit blocks in the earliest round.
595-
let mut to_visit_blocks = blocks_map
596-
.get(&pending_block_ref)
597-
.unwrap()
598-
.read()
599-
.children
600-
.clone();
610+
// Use BTreeSet for to_visit_blocks, to visit blocks in the earliest round first.
611+
let (pending_commit_index, mut to_visit_blocks) = {
612+
let block_state = blocks_map.get(&pending_block_ref).unwrap().read();
613+
(block_state.commit_index, block_state.children.clone())
614+
};
601615
// Blocks that have been visited.
602616
let mut visited = BTreeSet::new();
603617
// Blocks where votes and origin descendants should be ignored for processing.
@@ -608,15 +622,15 @@ impl CommitFinalizer {
608622
continue;
609623
}
610624
let curr_block_state = blocks_map.get(&curr_block_ref).unwrap_or_else(|| panic!("Block {curr_block_ref} is either incorrectly gc'ed or failed to be recovered after crash.")).read();
611-
// The first ancestor of current block should have the same origin / author as the current block.
612-
// If it is not found in the blocks map but have round higher than the pending block, it might have
613-
// voted on the pending block but have been GC'ed.
614-
// Because the GC'ed block might have voted on the pending block and rejected some of the pending transactions,
615-
// we cannot assume current block is voting to accept transactions from the pending block.
616-
let curr_origin_ancestor_ref = curr_block_state.block.ancestors().first().unwrap();
617-
let skip_votes = curr_block_ref.author == curr_origin_ancestor_ref.author
618-
&& pending_block_ref.round < curr_origin_ancestor_ref.round
619-
&& !blocks_map.contains_key(curr_origin_ancestor_ref);
625+
// When proposing the current block, if it is possible that the pending block has already been GC'ed,
626+
// then it is possible that the current block does not carry votes for the pending block.
627+
// In this case, skip counting votes from the current block.
628+
let skip_votes = Self::check_gc_at_current_commit(
629+
gc_rounds,
630+
pending_block_ref.round,
631+
pending_commit_index,
632+
curr_block_state.commit_index,
633+
);
620634
// Skip counting votes from the block if it has been marked to be ignored.
621635
if ignored.insert(curr_block_ref) {
622636
// Skip collecting votes from origin descendants of current block.
@@ -683,6 +697,32 @@ impl CommitFinalizer {
683697
finalized_transactions
684698
}
685699

700+
// Returns true if the pending block round can be GC'ed when proposing blocks in current commit.
701+
fn check_gc_at_current_commit(
702+
gc_rounds: &[(CommitIndex, Round)],
703+
pending_block_round: Round,
704+
pending_commit_index: CommitIndex,
705+
current_commit_index: CommitIndex,
706+
) -> bool {
707+
assert!(
708+
pending_commit_index <= current_commit_index,
709+
"Pending {pending_commit_index} should be <= current {current_commit_index}"
710+
);
711+
if pending_commit_index == current_commit_index {
712+
return false;
713+
}
714+
// Since GC round only advances after a commit, when proposing blocks for the current commit,
715+
// the GC round is determined by the commit previous to the current commit.
716+
let (commit_index, gc_round) = *gc_rounds
717+
.get((current_commit_index - 1 - pending_commit_index) as usize)
718+
.unwrap();
719+
assert_eq!(
720+
commit_index, current_commit_index - 1,
721+
"Commit index mismatch {commit_index} != {current_commit_index}"
722+
);
723+
pending_block_round <= gc_round
724+
}
725+
686726
fn pop_finalized_commits(&mut self) -> Vec<CommittedSubDag> {
687727
let mut finalized_commits = vec![];
688728

@@ -800,10 +840,12 @@ struct BlockState {
800840
// Other committed blocks that are origin descendants of this block.
801841
// See the comment above append_origin_descendants_from_last_commit() for more details.
802842
origin_descendants: Vec<BlockRef>,
843+
// Commit which contains this block.
844+
commit_index: CommitIndex,
803845
}
804846

805847
impl BlockState {
806-
fn new(block: VerifiedBlock) -> Self {
848+
fn new(block: VerifiedBlock, commit_index: CommitIndex) -> Self {
807849
let reject_votes: BTreeMap<_, _> = block
808850
.transaction_votes()
809851
.iter()
@@ -817,6 +859,7 @@ impl BlockState {
817859
children: BTreeSet::new(),
818860
reject_votes,
819861
origin_descendants,
862+
commit_index,
820863
}
821864
}
822865
}

consensus/core/src/dag_state.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -799,7 +799,8 @@ impl DagState {
799799
let mut targets = VecDeque::new();
800800
targets.push_back(root_block);
801801
while let Some(block_ref) = targets.pop_front() {
802-
// This is only correct with GC enabled.
802+
// No need to collect and mark blocks at or below GC round. These blocks will not be included in new commits
803+
// and do not need their transactions to be voted on.
803804
if block_ref.round <= gc_round {
804805
continue;
805806
}

crates/sui-protocol-config/src/lib.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4287,7 +4287,10 @@ impl ProtocolConfig {
42874287

42884288
cfg.poseidon_bn254_cost_base = Some(260);
42894289

4290-
cfg.feature_flags.consensus_skip_gced_accept_votes = true;
4290+
if chain != Chain::Mainnet && chain != Chain::Testnet {
4291+
cfg.feature_flags.consensus_skip_gced_accept_votes = true;
4292+
}
4293+
42914294
if chain != Chain::Mainnet {
42924295
cfg.feature_flags
42934296
.enable_nitro_attestation_all_nonzero_pcrs_parsing = true;

crates/sui-protocol-config/src/snapshots/sui_protocol_config__test__Mainnet_version_104.snap

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,6 @@ feature_flags:
126126
generate_df_type_layouts: true
127127
private_generics_verifier_v2: true
128128
deprecate_global_storage_ops: true
129-
consensus_skip_gced_accept_votes: true
130129
max_tx_size_bytes: 131072
131130
max_input_objects: 2048
132131
max_size_written_objects: 5000000

crates/sui-protocol-config/src/snapshots/sui_protocol_config__test__Testnet_version_104.snap

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
---
22
source: crates/sui-protocol-config/src/lib.rs
33
expression: "ProtocolConfig::get_for_version(cur, *chain_id)"
4-
snapshot_kind: text
54
---
65
version: 104
76
feature_flags:
@@ -129,7 +128,6 @@ feature_flags:
129128
generate_df_type_layouts: true
130129
private_generics_verifier_v2: true
131130
deprecate_global_storage_ops: true
132-
consensus_skip_gced_accept_votes: true
133131
max_tx_size_bytes: 131072
134132
max_input_objects: 2048
135133
max_size_written_objects: 5000000

0 commit comments

Comments
 (0)