Skip to content

Commit c862dc7

Browse files
samliokyacovm
andauthored
Ensure Non-Ancestors are Pruned When a Future Block is Finalized (#623)
* ensure non-ancestors are pruned when finalized --------- Co-authored-by: yacovm <yacovm@users.noreply.github.com>
1 parent c3889de commit c862dc7

3 files changed

Lines changed: 454 additions & 7 deletions

File tree

‎simplex/epoch.go‎

Lines changed: 41 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -550,6 +550,7 @@ func (e *Epoch) loadFinalizationRecord(r []byte) error {
550550
if err != nil {
551551
return err
552552
}
553+
e.pruneCompetingRounds(finalization)
553554
e.Logger.Info("Finalization Recovered From WAL", zap.Uint64("Round", finalization.Finalization.Round))
554555
return nil
555556
}
@@ -1348,7 +1349,7 @@ func (e *Epoch) persistFinalization(finalization common.Finalization) error {
13481349
e.blockVerificationScheduler.ExecuteBlockDependents(finalization.Finalization.Digest)
13491350
e.blockVerificationScheduler.RemoveOldTasks(finalization.Finalization.Seq)
13501351
e.replicationState.clearBlockDependencyTasks(finalization.Finalization.Digest, finalization.Finalization.Seq, true)
1351-
e.deleteOldEmptyVotes(finalization.Finalization.Round)
1352+
e.pruneCompetingRounds(finalization)
13521353

13531354
if finalization.Finalization.Seq == nextSeqToCommit {
13541355
if err := e.indexFinalizations(finalization.Finalization.Round); err != nil {
@@ -1778,7 +1779,7 @@ func (e *Epoch) handleEmptyNotarizationMessage(emptyNotarization *common.EmptyNo
17781779

17791780
e.Logger.Verbo("Received empty notarization message", zap.Uint64("round", vote.Round))
17801781

1781-
if e.isVoteForFinalizedRound(vote.Round) {
1782+
if e.isBelowHighestFinalizedRound(vote.Round) {
17821783
e.Logger.Debug("Received an empty notarization for a too low round",
17831784
zap.Uint64("round", vote.Round), zap.Uint64("our round", e.round))
17841785

@@ -1814,7 +1815,7 @@ func (e *Epoch) handleEmptyNotarizationMessage(emptyNotarization *common.EmptyNo
18141815
}
18151816

18161817
// we do not care to process votes for rounds where we have finalized
1817-
func (e *Epoch) isVoteForFinalizedRound(round uint64) bool {
1818+
func (e *Epoch) isBelowHighestFinalizedRound(round uint64) bool {
18181819
max := uint64(0)
18191820

18201821
for _, round := range e.rounds {
@@ -1840,7 +1841,7 @@ func (e *Epoch) handleNotarizationMessage(message *common.Notarization, from com
18401841
e.Logger.Verbo("Received notarization message",
18411842
zap.Stringer("from", from), zap.Uint64("round", vote.Round))
18421843

1843-
if e.isVoteForFinalizedRound(vote.Round) {
1844+
if e.isBelowHighestFinalizedRound(vote.Round) {
18441845
return nil
18451846
}
18461847

@@ -2230,6 +2231,12 @@ func (e *Epoch) createBlockVerificationTask(block common.Block, from common.Node
22302231
return common.Digest{}
22312232
}
22322233

2234+
// A higher round was finalized while we were verifying, so this block can no longer be finalized.
2235+
if e.isBelowHighestFinalizedRound(md.Round) {
2236+
e.Logger.Debug("Dropping proposal for a round below a finalized round", zap.Uint64("round", md.Round))
2237+
return common.Digest{}
2238+
}
2239+
22332240
if !e.storeProposal(verifiedBlock) {
22342241
e.Logger.Debug("Unable to store proposed block for the round", zap.Stringer("NodeID", from), zap.Uint64("round", md.Round))
22352242
return common.Digest{}
@@ -2383,6 +2390,12 @@ func (e *Epoch) createNotarizedBlockVerificationTask(block common.Block, notariz
23832390
e.lock.Lock()
23842391
defer e.lock.Unlock()
23852392

2393+
// A higher round was finalized while we were verifying, so this block can no longer be finalized.
2394+
if e.isBelowHighestFinalizedRound(md.Round) {
2395+
e.Logger.Debug("Dropping notarized block for a round below a finalized round", zap.Uint64("round", md.Round))
2396+
return common.Digest{}
2397+
}
2398+
23862399
// we started verifying the block when we didn't have a notarization, however its
23872400
// possible we received a notarization or empty notarization for this block in the meantime.
23882401
round, ok := e.rounds[md.Round]
@@ -3078,9 +3091,30 @@ func (e *Epoch) deleteRounds(round uint64) {
30783091
}
30793092
}
30803093

3081-
func (e *Epoch) deleteOldEmptyVotes(finalizedRound uint64) {
3094+
// pruneCompetingRounds deletes the blocks and empty votes below the finalized round that are not
3095+
// part of the finalized block's chain. Ex. if we have blockchains b1 <- b3 <- b5, and b1 <- b2 <- b4,
3096+
// a call to pruneCompetingRounds(finalization b5), will prune blocks b2 & b4 from the rounds map.
3097+
func (e *Epoch) pruneCompetingRounds(finalization common.Finalization) {
3098+
prev := finalization.Finalization.Prev
3099+
minRound := e.minRoundInRoundsMap()
3100+
for r := finalization.Finalization.Round; r > minRound; r-- {
3101+
round, exists := e.rounds[r-1]
3102+
if !exists {
3103+
continue
3104+
}
3105+
3106+
// don't delete this round, since the block is an ancestor of finalization in our chain
3107+
if header := round.block.BlockHeader(); header.Digest == prev {
3108+
prev = header.Prev
3109+
continue
3110+
}
3111+
3112+
e.Logger.Debug("Pruning block not on the finalized chain", zap.Uint64("round", r-1), zap.Uint64("finalized round", finalization.Finalization.Round))
3113+
delete(e.rounds, r-1)
3114+
}
3115+
30823116
for r := range e.emptyVotes {
3083-
if r < finalizedRound {
3117+
if r < finalization.Finalization.Round {
30843118
delete(e.emptyVotes, r)
30853119
}
30863120
}
@@ -3602,7 +3636,7 @@ func (e *Epoch) processQuorumRound(round *common.QuorumRound, from common.NodeID
36023636
return nil
36033637
}
36043638

3605-
if round.Finalization == nil && e.isVoteForFinalizedRound(round.GetRound()) {
3639+
if round.Finalization == nil && e.isBelowHighestFinalizedRound(round.GetRound()) {
36063640
return fmt.Errorf("received a quorum round for a round that has been finalized. round: %d; seq: %d", round.GetRound(), round.GetSequence())
36073641
}
36083642

0 commit comments

Comments
 (0)