Skip to content

Commit 4d88466

Browse files
samliokyacovm
andauthored
Create EmptyVoteMetadata Struct (#215)
* add empty vote metadata * remove canoto * rebase fix * fix check * update if --------- Co-authored-by: yacovm <yacovm@users.noreply.github.com>
1 parent 2d54ea8 commit 4d88466

File tree

8 files changed

+48
-157
lines changed

8 files changed

+48
-157
lines changed

epoch.go

Lines changed: 5 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -664,13 +664,6 @@ func (e *Epoch) handleEmptyVoteMessage(message *EmptyVote, from NodeID) error {
664664
return nil
665665
}
666666

667-
if vote.HasParent() {
668-
e.Logger.Debug("Empty vote has a parent but should not have one",
669-
zap.Uint64("round", vote.Round),
670-
zap.Stringer("Prev", vote.Prev), zap.Uint64("Seq", vote.Seq))
671-
return nil
672-
}
673-
674667
if e.round > vote.Round {
675668
e.Logger.Debug("Got empty vote from a past round",
676669
zap.Uint64("round", vote.Round), zap.Uint64("my round", e.round), zap.Stringer("from", from))
@@ -1311,13 +1304,6 @@ func (e *Epoch) handleEmptyNotarizationMessage(emptyNotarization *EmptyNotarizat
13111304
}
13121305

13131306
func (e *Epoch) verifyEmptyNotarization(emptyNotarization *EmptyNotarization) bool {
1314-
if emptyNotarization.Vote.HasParent() {
1315-
e.Logger.Debug("Empty notarization vote has a parent but should not have one",
1316-
zap.Uint64("round", emptyNotarization.Vote.Round),
1317-
zap.Stringer("Prev", emptyNotarization.Vote.Prev),
1318-
zap.Uint64("Seq", emptyNotarization.Vote.Seq))
1319-
return false
1320-
}
13211307
// Check empty notarization was signed by only eligible nodes
13221308
if emptyNotarization.QC == nil {
13231309
e.Logger.Debug("Empty notarization quorum certificate is nil")
@@ -2086,11 +2072,10 @@ func (e *Epoch) metadata() ProtocolMetadata {
20862072
}
20872073

20882074
func (e *Epoch) triggerEmptyBlockNotarization(round uint64) {
2089-
md := e.metadata()
2090-
md.Seq = 0 // Empty votes have no sequence number because they don't have a parent block.
2091-
md.Prev = emptyDigest // Empty votes are orphans and do not have parent blocks.
2092-
2093-
emptyVote := ToBeSignedEmptyVote{ProtocolMetadata: md}
2075+
emptyVote := ToBeSignedEmptyVote{EmptyVoteMetadata: EmptyVoteMetadata{
2076+
Round: round,
2077+
Epoch: e.Epoch,
2078+
}}
20942079
rawSig, err := emptyVote.Sign(e.Signer)
20952080
if err != nil {
20962081
e.Logger.Error("Failed signing message", zap.Error(err))
@@ -2540,7 +2525,7 @@ func (e *Epoch) handleReplicationResponse(resp *ReplicationResponse, from NodeID
25402525
continue
25412526
}
25422527

2543-
if nextSeqToCommit > data.GetSequence() {
2528+
if data.EmptyNotarization == nil && nextSeqToCommit > data.GetSequence() {
25442529
e.Logger.Debug("Received quorum round for a seq that is too far behind", zap.Uint64("seq", data.GetSequence()))
25452530
continue
25462531
}

epoch_failover_test.go

Lines changed: 9 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ func TestEpochLeaderFailoverWithEmptyNotarization(t *testing.T) {
9494
bb.in <- block.(*testBlock)
9595
}
9696

97-
emptyNotarization := newEmptyNotarization(nodes[:3], 2, 1)
97+
emptyNotarization := newEmptyNotarization(nodes[:3], 2)
9898

9999
e.HandleMessage(&Message{
100100
EmptyNotarization: emptyNotarization,
@@ -116,7 +116,7 @@ func TestEpochLeaderFailoverWithEmptyNotarization(t *testing.T) {
116116
}
117117

118118
// newEmptyNotarization creates a new empty notarization
119-
func newEmptyNotarization(nodes []NodeID, round uint64, seq uint64) *EmptyNotarization {
119+
func newEmptyNotarization(nodes []NodeID, round uint64) *EmptyNotarization {
120120
var qc testQC
121121

122122
for i, node := range nodes {
@@ -125,7 +125,7 @@ func newEmptyNotarization(nodes []NodeID, round uint64, seq uint64) *EmptyNotari
125125

126126
return &EmptyNotarization{
127127
QC: qc,
128-
Vote: ToBeSignedEmptyVote{ProtocolMetadata: ProtocolMetadata{
128+
Vote: ToBeSignedEmptyVote{EmptyVoteMetadata: EmptyVoteMetadata{
129129
Round: round,
130130
}},
131131
}
@@ -212,7 +212,6 @@ func TestEpochLeaderFailoverReceivesEmptyVotesEarly(t *testing.T) {
212212
require.NoError(t, err)
213213
require.Equal(t, emptyVoteFrom1.Vote, emptyNotarization.Vote)
214214
require.Equal(t, uint64(3), emptyNotarization.Vote.Round)
215-
require.Equal(t, uint64(0), emptyNotarization.Vote.Seq)
216215
require.Equal(t, uint64(3), storage.Height())
217216

218217
header, _, err := ParseBlockRecord(rawProposal)
@@ -266,7 +265,7 @@ func TestReceiveEmptyNotarizationWithNoQC(t *testing.T) {
266265

267266
require.NoError(t, e.Start())
268267

269-
emptyNotarization := newEmptyNotarization(nodes[:3], 0, 0)
268+
emptyNotarization := newEmptyNotarization(nodes[:3], 0)
270269

271270
e.HandleMessage(&Message{
272271
EmptyNotarization: &EmptyNotarization{Vote: emptyNotarization.Vote},
@@ -353,7 +352,6 @@ func TestEpochLeaderFailover(t *testing.T) {
353352
require.NoError(t, err)
354353
require.Equal(t, emptyVoteFrom1.Vote, emptyNotarization.Vote)
355354
require.Equal(t, uint64(3), emptyNotarization.Vote.Round)
356-
require.Equal(t, uint64(0), emptyNotarization.Vote.Seq)
357355
require.Equal(t, uint64(3), storage.Height())
358356

359357
nextBlockSeqToCommit := uint64(3)
@@ -571,7 +569,6 @@ func TestEpochLeaderFailoverAfterProposal(t *testing.T) {
571569
require.NoError(t, err)
572570
require.Equal(t, emptyVoteFrom1.Vote, emptyNotarization.Vote)
573571
require.Equal(t, uint64(3), emptyNotarization.Vote.Round)
574-
require.Equal(t, uint64(0), emptyNotarization.Vote.Seq)
575572
require.Equal(t, uint64(4), storage.Height())
576573
})
577574
}
@@ -687,7 +684,6 @@ func TestEpochLeaderFailoverTwice(t *testing.T) {
687684
require.NoError(t, err)
688685
require.Equal(t, emptyVoteFrom1.Vote, emptyNotarization.Vote)
689686
require.Equal(t, uint64(3), emptyNotarization.Vote.Round)
690-
require.Equal(t, uint64(0), emptyNotarization.Vote.Seq)
691687
require.Equal(t, uint64(3), storage.Height())
692688
})
693689
})
@@ -762,14 +758,15 @@ func TestEpochLeaderFailoverBecauseOfBadBlock(t *testing.T) {
762758
}
763759

764760
func createEmptyVote(md ProtocolMetadata, signer NodeID) *EmptyVote {
765-
md.Prev = Digest{}
766-
md.Seq = 0
767761
emptyVoteFrom2 := &EmptyVote{
768762
Signature: Signature{
769763
Signer: signer,
770764
},
771765
Vote: ToBeSignedEmptyVote{
772-
ProtocolMetadata: md,
766+
EmptyVoteMetadata: EmptyVoteMetadata{
767+
Round: md.Round,
768+
Epoch: 0,
769+
},
773770
},
774771
}
775772
return emptyVoteFrom2
@@ -951,7 +948,7 @@ func TestEpochRebroadcastsEmptyVote(t *testing.T) {
951948
wal.assertWALSize(1)
952949
}
953950

954-
emptyNotarization := newEmptyNotarization(nodes, 0, 0)
951+
emptyNotarization := newEmptyNotarization(nodes, 0)
955952
e.HandleMessage(&simplex.Message{
956953
EmptyNotarization: emptyNotarization,
957954
}, nodes[2])

epoch_test.go

Lines changed: 6 additions & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -327,7 +327,7 @@ func TestEpochNotarizeTwiceThenFinalize(t *testing.T) {
327327
wal.assertNotarization(0)
328328

329329
// Round 1
330-
emptyNote := newEmptyNotarization(nodes, 1, 0)
330+
emptyNote := newEmptyNotarization(nodes, 1)
331331
err = e.HandleMessage(&Message{
332332
EmptyNotarization: emptyNote,
333333
}, nodes[1])
@@ -542,7 +542,7 @@ func advanceRoundFromEmpty(t *testing.T, e *Epoch) {
542542
leader := LeaderForRound(e.Comm.Nodes(), e.Metadata().Round)
543543
require.False(t, e.ID.Equals(leader), "epoch cannot be the leader for the empty round")
544544

545-
emptyNote := newEmptyNotarization(e.Comm.Nodes(), e.Metadata().Round, e.Metadata().Seq)
545+
emptyNote := newEmptyNotarization(e.Comm.Nodes(), e.Metadata().Round)
546546
err := e.HandleMessage(&Message{
547547
EmptyNotarization: emptyNote,
548548
}, leader)
@@ -943,9 +943,9 @@ func TestEpochQCSignedByNonExistentNodes(t *testing.T) {
943943

944944
err = e.HandleMessage(&Message{
945945
EmptyNotarization: &EmptyNotarization{
946-
Vote: ToBeSignedEmptyVote{ProtocolMetadata: ProtocolMetadata{
946+
Vote: ToBeSignedEmptyVote{EmptyVoteMetadata: EmptyVoteMetadata{
947947
Round: 0,
948-
Seq: 0,
948+
Epoch: 0,
949949
}},
950950
QC: qc,
951951
},
@@ -963,9 +963,9 @@ func TestEpochQCSignedByNonExistentNodes(t *testing.T) {
963963

964964
err = e.HandleMessage(&Message{
965965
EmptyNotarization: &EmptyNotarization{
966-
Vote: ToBeSignedEmptyVote{ProtocolMetadata: ProtocolMetadata{
966+
Vote: ToBeSignedEmptyVote{EmptyVoteMetadata: EmptyVoteMetadata{
967967
Round: 0,
968-
Seq: 0,
968+
Epoch: 0,
969969
}},
970970
QC: qc,
971971
},
@@ -998,86 +998,6 @@ func TestEpochQCSignedByNonExistentNodes(t *testing.T) {
998998
})
999999
}
10001000

1001-
func TestEpochDiscardEmptyVotesWithParent(t *testing.T) {
1002-
loggedMessages := make(chan string, 100)
1003-
l := testutil.MakeLogger(t, 1)
1004-
l.Intercept(func(entry zapcore.Entry) error {
1005-
loggedMessages <- entry.Message
1006-
return nil
1007-
})
1008-
hasLogged := func(msg string) bool {
1009-
for len(loggedMessages) > 0 {
1010-
loggedMsg := <-loggedMessages
1011-
if loggedMsg == msg {
1012-
return true
1013-
}
1014-
}
1015-
return false
1016-
}
1017-
1018-
bb := &testBlockBuilder{out: make(chan *testBlock, 1)}
1019-
storage := newInMemStorage()
1020-
1021-
nodes := []NodeID{{1}, {2}, {3}, {4}}
1022-
conf := EpochConfig{
1023-
MaxProposalWait: DefaultMaxProposalWaitTime,
1024-
Logger: l,
1025-
ID: nodes[0],
1026-
Signer: &testSigner{},
1027-
WAL: newTestWAL(t),
1028-
Verifier: &testVerifier{},
1029-
Storage: storage,
1030-
Comm: noopComm(nodes),
1031-
BlockBuilder: bb,
1032-
SignatureAggregator: &testSignatureAggregator{},
1033-
}
1034-
1035-
e, err := NewEpoch(conf)
1036-
require.NoError(t, err)
1037-
1038-
require.NoError(t, e.Start())
1039-
1040-
vb, _ := notarizeAndFinalizeRound(t, e, bb)
1041-
require.NotNil(t, vb)
1042-
1043-
emptyBlockMd := e.Metadata()
1044-
emptyBlockMd.Seq--
1045-
1046-
// Receive empty votes from all nodes, but with a parent set to the last block.
1047-
// While this is not a real scenario but an artificial one, we are testing it to ensure that we do not
1048-
// collect an empty notarization.
1049-
for i := 1; i <= 3; i++ {
1050-
emptyVote := createEmptyVote(emptyBlockMd, nodes[i])
1051-
emptyVote.Vote.Prev = vb.BlockHeader().Digest
1052-
1053-
require.False(t, hasLogged("Empty vote has a parent but should not have one"))
1054-
1055-
e.HandleMessage(&Message{
1056-
EmptyVoteMessage: emptyVote,
1057-
}, nodes[i])
1058-
1059-
require.True(t, hasLogged("Empty vote has a parent but should not have one"))
1060-
}
1061-
1062-
emptyNotarization := newEmptyNotarization(nodes, emptyBlockMd.Round, emptyBlockMd.Seq)
1063-
emptyNotarization.Vote.Prev = vb.BlockHeader().Digest
1064-
1065-
require.False(t, hasLogged("Empty notarization vote has a parent but should not have one"))
1066-
1067-
e.HandleMessage(&Message{
1068-
EmptyNotarization: emptyNotarization,
1069-
}, nodes[1])
1070-
1071-
require.True(t, hasLogged("Empty notarization vote has a parent but should not have one"))
1072-
1073-
vb, _ = notarizeAndFinalizeRound(t, e, bb)
1074-
require.NotNil(t, vb)
1075-
1076-
// Ensure the previous empty votes and empty notarization did not have any effect.
1077-
require.Equal(t, uint64(1), vb.BlockHeader().Round)
1078-
1079-
}
1080-
10811001
func TestEpochBlockSentFromNonLeader(t *testing.T) {
10821002
l := testutil.MakeLogger(t, 1)
10831003
nonLeaderMessage := false

msg.go

Lines changed: 18 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ package simplex
66
import (
77
"bytes"
88
"encoding/asn1"
9+
"encoding/binary"
910
"fmt"
1011
)
1112

@@ -23,30 +24,34 @@ type Message struct {
2324
ReplicationRequest *ReplicationRequest
2425
}
2526

26-
type ToBeSignedEmptyVote struct {
27-
ProtocolMetadata
27+
type EmptyVoteMetadata struct {
28+
Round uint64
29+
Epoch uint64
2830
}
2931

30-
func (v *ToBeSignedEmptyVote) HasParent() bool {
31-
return v.ProtocolMetadata.Prev != emptyDigest || v.ProtocolMetadata.Seq != 0
32+
type ToBeSignedEmptyVote struct {
33+
EmptyVoteMetadata
3234
}
3335

3436
func (v *ToBeSignedEmptyVote) Bytes() []byte {
35-
return v.ProtocolMetadata.Bytes()
37+
bytes := make([]byte, 8+8) // Round + Epoch
38+
binary.BigEndian.PutUint64(bytes[0:8], v.EmptyVoteMetadata.Round)
39+
binary.BigEndian.PutUint64(bytes[8:16], v.EmptyVoteMetadata.Epoch)
40+
return bytes
3641
}
3742

3843
func (v *ToBeSignedEmptyVote) FromBytes(buff []byte) error {
39-
if len(buff) != ProtocolMetadataLen {
40-
return fmt.Errorf("invalid buffer length %d, expected %d", len(buff), ProtocolMetadataLen)
41-
}
42-
43-
md, err := ProtocolMetadataFromBytes(buff[:ProtocolMetadataLen])
44-
if err != nil {
45-
return fmt.Errorf("failed to parse ProtocolMetadata: %w", err)
44+
if len(buff) != 16 {
45+
return fmt.Errorf("invalid buffer length")
4646
}
4747

48-
v.ProtocolMetadata = *md
48+
round := binary.BigEndian.Uint64(buff[0:8])
49+
epoch := binary.BigEndian.Uint64(buff[8:16])
4950

51+
v.EmptyVoteMetadata = EmptyVoteMetadata{
52+
Round: round,
53+
Epoch: epoch,
54+
}
5055
return nil
5156
}
5257

@@ -255,10 +260,6 @@ func (q *QuorumRound) GetRound() uint64 {
255260
}
256261

257262
func (q *QuorumRound) GetSequence() uint64 {
258-
if q.EmptyNotarization != nil {
259-
return q.EmptyNotarization.Vote.Seq
260-
}
261-
262263
if q.Block != nil {
263264
return q.Block.BlockHeader().Seq
264265
}

replication.go

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,12 +27,11 @@ func newSignedSequenceFromRound(round QuorumRound) (*signedSequence, error) {
2727
case round.Finalization != nil:
2828
ss.signers = round.Finalization.QC.Signers()
2929
ss.seq = round.Finalization.Finalization.Seq
30-
case round.EmptyNotarization != nil:
31-
ss.signers = round.EmptyNotarization.QC.Signers()
32-
ss.seq = round.EmptyNotarization.Vote.Seq
3330
case round.Notarization != nil:
3431
ss.signers = round.Notarization.QC.Signers()
3532
ss.seq = round.Notarization.Vote.Seq
33+
case round.EmptyNotarization != nil:
34+
return nil, fmt.Errorf("should not create signed sequence from empty notarization")
3635
default:
3736
return nil, fmt.Errorf("round does not contain a finalization, empty notarization, or notarization")
3837
}
@@ -278,7 +277,7 @@ func (r *ReplicationState) StoreQuorumRound(round QuorumRound) {
278277
return
279278
}
280279

281-
if round.GetSequence() > r.highestSequenceObserved.seq {
280+
if round.EmptyNotarization == nil && round.GetSequence() > r.highestSequenceObserved.seq {
282281
signedSeq, err := newSignedSequenceFromRound(round)
283282
if err != nil {
284283
// should never be here since we already checked the QuorumRound was valid

replication_request_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,7 @@ func TestReplicationRequestMixed(t *testing.T) {
138138
leaderForRound := bytes.Equal(simplex.LeaderForRound(nodes, uint64(i)), e.ID)
139139
emptyBlock := !leaderForRound
140140
if emptyBlock {
141-
emptyNotarization := newEmptyNotarization(nodes, uint64(i), uint64(i))
141+
emptyNotarization := newEmptyNotarization(nodes, uint64(i))
142142
e.HandleMessage(&simplex.Message{
143143
EmptyNotarization: emptyNotarization,
144144
}, nodes[1])

util.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@ func GetLatestVerifiedQuorumRound(round *Round, emptyNotarization *EmptyNotariza
113113
verifiedQuorumRound = &VerifiedQuorumRound{
114114
EmptyNotarization: emptyNotarization,
115115
}
116-
highestRound = emptyNotarization.Vote.ProtocolMetadata.Round
116+
highestRound = emptyNotarization.Vote.Round
117117
exists = true
118118
}
119119
}

0 commit comments

Comments
 (0)