Skip to content

Commit 35bd7a2

Browse files
authored
Only verify blocks once their parent is notarized or finalized (#285)
Signed-off-by: Yacov Manevich <[email protected]>
1 parent 745d5c9 commit 35bd7a2

File tree

3 files changed

+122
-18
lines changed

3 files changed

+122
-18
lines changed

epoch.go

Lines changed: 39 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -978,6 +978,9 @@ func (e *Epoch) persistFinalization(finalization Finalization) error {
978978
// or otherwise write it to the WAL in order to commit it later.
979979
startRound := e.round
980980
nextSeqToCommit := e.nextSeqToCommit()
981+
982+
e.sched.ExecuteDependents(finalization.Finalization.Digest)
983+
981984
if finalization.Finalization.Seq == nextSeqToCommit {
982985
if err := e.indexFinalizations(finalization.Finalization.Round); err != nil {
983986
e.Logger.Error("Failed to index finalizations", zap.Error(err))
@@ -1327,6 +1330,8 @@ func (e *Epoch) persistNotarization(notarization Notarization) error {
13271330
return err
13281331
}
13291332

1333+
e.sched.ExecuteDependents(notarization.Vote.Digest)
1334+
13301335
round := notarization.Vote.Round
13311336
for _, signer := range notarization.QC.Signers() {
13321337
if signerIndex := e.nodes.IndexOf(signer); signerIndex != -1 {
@@ -1559,7 +1564,10 @@ func (e *Epoch) handleBlockMessage(message *BlockMessage, from NodeID) error {
15591564

15601565
// Schedule the block to be verified once its direct predecessor have been verified,
15611566
// or if it can be verified immediately.
1562-
e.Logger.Debug("Scheduling block verification", zap.Uint64("round", md.Round))
1567+
e.Logger.Debug("Scheduling block verification",
1568+
zap.Uint64("round", md.Round),
1569+
zap.Uint64("seq", md.Seq),
1570+
zap.Bool("ready", canBeImmediatelyVerified))
15631571
e.sched.Schedule(task, md.Prev, canBeImmediatelyVerified)
15641572

15651573
return nil
@@ -1881,12 +1889,9 @@ func (e *Epoch) createNotarizedBlockVerificationTask(block Block, notarization N
18811889

18821890
func (e *Epoch) isBlockReadyToBeScheduled(seq uint64, prev Digest) bool {
18831891
if seq > 0 {
1884-
// A block can be scheduled if its predecessor either exists in storage,
1885-
// or there exists a round object for it.
1886-
// Since we only create a round object after we verify the block,
1887-
// it means we have verified this block in the past.
1888-
_, ok := e.locateBlock(seq-1, prev[:])
1889-
return ok
1892+
// A block can be scheduled if its predecessor is either notarized or finalized.
1893+
_, notarizedOrFinalized, _ := e.locateBlock(seq-1, prev[:])
1894+
return notarizedOrFinalized != nil
18901895
}
18911896
// The first block is always ready to be scheduled
18921897
return true
@@ -1918,7 +1923,7 @@ func (e *Epoch) verifyProposalMetadataAndBlacklist(block Block) bool {
19181923
// If it's the former, we need to find the parent of the block and ensure it is correct.
19191924
prevBlacklist := NewBlacklist(uint16(len(e.nodes)))
19201925
if bh.Seq > 0 {
1921-
prevBlock, found := e.locateBlock(bh.Seq-1, bh.Prev[:])
1926+
prevBlock, _, found := e.locateBlock(bh.Seq-1, bh.Prev[:])
19221927
if !found {
19231928
e.Logger.Debug("Could not find parent block with given digest",
19241929
zap.Uint64("blockSeq", bh.Seq-1),
@@ -1974,46 +1979,57 @@ func (e *Epoch) verifyProposalMetadataAndBlacklist(block Block) bool {
19741979
// 2) Else, on storage.
19751980
// Compares to the given digest, and if it's the same, returns it.
19761981
// Otherwise, returns false.
1977-
func (e *Epoch) locateBlock(seq uint64, digest []byte) (VerifiedBlock, bool) {
1982+
func (e *Epoch) locateBlock(seq uint64, digest []byte) (VerifiedBlock, *notarizationOrFinalization, bool) {
19781983
// TODO index rounds by digest too to make it quicker
19791984
// TODO: optimize this by building an index from digest to round
19801985
for _, round := range e.rounds {
19811986
dig := round.block.BlockHeader().Digest
19821987
if bytes.Equal(dig[:], digest) {
1983-
return round.block, true
1988+
nof := &notarizationOrFinalization{
1989+
Notarization: round.notarization,
1990+
Finalization: round.finalization,
1991+
}
1992+
if nof.Notarization == nil && nof.Finalization == nil {
1993+
return nil, nil, false
1994+
}
1995+
return round.block, nof, true
19841996
}
19851997
}
19861998

19871999
height := e.nextSeqToCommit()
19882000
// Not in memory, and no block resides in storage.
19892001
if height == 0 {
1990-
return nil, false
2002+
return nil, nil, false
19912003
}
19922004

19932005
// If the given block has a sequence that is higher than the last block we committed to storage,
19942006
// we don't have the block in our storage.
19952007
maxSeq := height - 1
19962008
if maxSeq < seq {
1997-
return nil, false
2009+
return nil, nil, false
19982010
}
19992011

20002012
if seq >= e.nextSeqToCommit() {
20012013
e.Logger.Debug("Requested block sequence we have not yet committed to storage",
20022014
zap.Uint64("requestedSeq", seq), zap.Uint64("numBlocks", e.nextSeqToCommit()))
2003-
return nil, false
2015+
return nil, nil, false
20042016
}
20052017

2006-
block, _, ok := e.retrieveBlockOrHalt(seq)
2018+
block, finalization, ok := e.retrieveBlockOrHalt(seq)
20072019
if !ok {
2008-
return nil, false
2020+
return nil, nil, false
2021+
}
2022+
2023+
nof := &notarizationOrFinalization{
2024+
Finalization: &finalization,
20092025
}
20102026

20112027
dig := block.BlockHeader().Digest
20122028
if bytes.Equal(dig[:], digest) {
2013-
return block, true
2029+
return block, nof, true
20142030
}
20152031

2016-
return nil, false
2032+
return nil, nil, false
20172033
}
20182034

20192035
func (e *Epoch) buildBlock() {
@@ -2071,7 +2087,7 @@ func (e *Epoch) buildBlock() {
20712087
func (e *Epoch) retrieveBlacklistOfParentBlock(metadata ProtocolMetadata) (Blacklist, bool) {
20722088
var blacklist Blacklist
20732089
if metadata.Seq > 0 {
2074-
prevBlock, ok := e.locateBlock(metadata.Seq-1, metadata.Prev[:])
2090+
prevBlock, _, ok := e.locateBlock(metadata.Seq-1, metadata.Prev[:])
20752091
if !ok {
20762092
e.Logger.Error("Failed locating previous block",
20772093
zap.Uint64("round", metadata.Round),
@@ -3020,3 +3036,8 @@ type messagesForRound struct {
30203036
finalization *Finalization
30213037
notarization *Notarization
30223038
}
3039+
3040+
type notarizationOrFinalization struct {
3041+
*Notarization
3042+
*Finalization
3043+
}

epoch_test.go

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import (
1212
rand2 "math/rand"
1313
"strings"
1414
"sync"
15+
"sync/atomic"
1516
"testing"
1617
"time"
1718

@@ -32,6 +33,71 @@ var (
3233
}
3334
)
3435

36+
func TestBlockNotVerifiedIfParentNotNotarized(t *testing.T) {
37+
bb := &testutil.TestBlockBuilder{Out: make(chan *testutil.TestBlock, 1)}
38+
39+
nodes := []NodeID{{1}, {2}, {3}, {4}}
40+
41+
comm := testutil.NewNoopComm(nodes)
42+
conf, _, _ := testutil.DefaultTestNodeEpochConfig(t, nodes[3], comm, bb)
43+
44+
e, err := NewEpoch(conf)
45+
require.NoError(t, err)
46+
47+
require.NoError(t, e.Start())
48+
49+
blocks := createBlocks(t, nodes, 2)
50+
51+
var block1Verified atomic.Bool
52+
53+
var wg sync.WaitGroup
54+
wg.Add(1)
55+
56+
block0 := blocks[0].VerifiedBlock.(*testutil.TestBlock)
57+
block0.OnVerify = func() {
58+
wg.Done()
59+
}
60+
block1 := blocks[1].VerifiedBlock.(*testutil.TestBlock)
61+
block1.OnVerify = func() {
62+
block1Verified.Store(true)
63+
}
64+
65+
v0, err := testutil.NewTestVote(block0, nodes[0])
66+
require.NoError(t, err)
67+
68+
v1, err := testutil.NewTestVote(block1, nodes[1])
69+
require.NoError(t, err)
70+
71+
emptyNotarization := testutil.NewEmptyNotarization(nodes, 0)
72+
73+
err = e.HandleMessage(&Message{
74+
BlockMessage: &BlockMessage{
75+
Vote: *v0,
76+
Block: block0,
77+
},
78+
}, nodes[0])
79+
require.NoError(t, err)
80+
81+
wg.Wait()
82+
83+
err = e.HandleMessage(&Message{
84+
BlockMessage: &BlockMessage{
85+
Vote: *v1,
86+
Block: block1,
87+
},
88+
}, nodes[1])
89+
require.NoError(t, err)
90+
91+
err = e.HandleMessage(&Message{
92+
EmptyNotarization: emptyNotarization,
93+
}, nodes[1])
94+
require.NoError(t, err)
95+
96+
require.Never(t, func() bool {
97+
return block1Verified.Load()
98+
}, time.Second, 100*time.Millisecond)
99+
}
100+
35101
func TestEpochHandleNotarizationFutureRound(t *testing.T) {
36102
bb := &testutil.TestBlockBuilder{}
37103
nodes := []NodeID{{1}, {2}, {3}, {4}}

sched.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,23 @@ func (as *Scheduler) Schedule(f func() Digest, prev Digest, ready bool) {
140140
as.signal.Broadcast() // (11)
141141
}
142142

143+
func (as *Scheduler) ExecuteDependents(dep Digest) {
144+
as.lock.Lock()
145+
defer as.lock.Unlock()
146+
147+
if as.close {
148+
return
149+
}
150+
151+
newlyReadyTasks := as.pending.Remove(dep)
152+
if len(newlyReadyTasks) == 0 {
153+
return
154+
}
155+
as.ready = append(as.ready, newlyReadyTasks...)
156+
157+
as.signal.Broadcast()
158+
}
159+
143160
type Task struct {
144161
F func() Digest
145162
Parent Digest

0 commit comments

Comments
 (0)