From 4f8a37bc0a4d8708e62834d7ca4a795a5d0ce815 Mon Sep 17 00:00:00 2001 From: noot <36753753+noot@users.noreply.github.com> Date: Thu, 22 Apr 2021 15:25:15 -0400 Subject: [PATCH] chore(lib/grandpa): update grandpa message types to match substrate (#1534) --- lib/grandpa/errors.go | 7 +- lib/grandpa/grandpa.go | 19 ++- lib/grandpa/message.go | 199 ++++++++++++++++++++++++---- lib/grandpa/message_handler.go | 54 ++++---- lib/grandpa/message_handler_test.go | 38 +++--- lib/grandpa/message_test.go | 18 +-- lib/grandpa/network_test.go | 2 +- lib/grandpa/round_test.go | 61 +++++---- lib/grandpa/types.go | 7 +- lib/grandpa/vote_message.go | 16 +-- 10 files changed, 306 insertions(+), 115 deletions(-) diff --git a/lib/grandpa/errors.go b/lib/grandpa/errors.go index 34902002ac..73042e6b32 100644 --- a/lib/grandpa/errors.go +++ b/lib/grandpa/errors.go @@ -69,8 +69,8 @@ var ErrCannotDecodeSubround = errors.New("cannot decode invalid subround value") // ErrInvalidMessageType is returned when a network.Message cannot be decoded var ErrInvalidMessageType = errors.New("cannot decode invalid message type") -// ErrNotFinalizationMessage is returned when calling GetFinalizedHash on a message that isn't a FinalizationMessage -var ErrNotFinalizationMessage = errors.New("cannot get finalized hash from VoteMessage") +// ErrNotCommitMessage is returned when calling GetFinalizedHash on a message that isn't a CommitMessage +var ErrNotCommitMessage = errors.New("cannot get finalized hash from VoteMessage") // ErrNoJustification is returned when no justification can be found for a block, ie. it has not been finalized var ErrNoJustification = errors.New("no justification found for block") @@ -92,3 +92,6 @@ var ErrCatchUpResponseNotCompletable = errors.New("catch up response is not comp // ErrServicePaused is returned if the service is paused and waiting for catch up messages var ErrServicePaused = errors.New("service is paused") + +// ErrPrecommitSignatureMismatch is returned when the number of precommits and signatures in a CommitMessage do not match +var ErrPrecommitSignatureMismatch = errors.New("number of precommits does not match number of signatures") diff --git a/lib/grandpa/grandpa.go b/lib/grandpa/grandpa.go index bb4feabc73..135e8494c2 100644 --- a/lib/grandpa/grandpa.go +++ b/lib/grandpa/grandpa.go @@ -387,12 +387,27 @@ func (s *Service) playGrandpaRound() error { // if primary, broadcast the best final candidate from the previous round if bytes.Equal(primary.key.Encode(), s.keypair.Public().Encode()) { - msg, err := s.newFinalizationMessage(s.head, s.state.round-1).ToConsensusMessage() + msg, err := s.newCommitMessage(s.head, s.state.round-1).ToConsensusMessage() if err != nil { logger.Error("failed to encode finalization message", "error", err) } else { s.network.SendMessage(msg) } + + primProposal, err := s.createVoteMessage(&Vote{ + hash: s.head.Hash(), + number: uint32(s.head.Number.Int64()), + }, primaryProposal, s.keypair) + if err != nil { + logger.Error("failed to create primary proposal message", "error", err) + } else { + msg, err = primProposal.ToConsensusMessage() + if err != nil { + logger.Error("failed to encode finalization message", "error", err) + } else { + s.network.SendMessage(msg) + } + } } logger.Debug("receiving pre-vote messages...") @@ -584,7 +599,7 @@ func (s *Service) attemptToFinalize() error { votes := s.getDirectVotes(precommit) logger.Debug("finalized block!!!", "setID", s.state.setID, "round", s.state.round, "hash", s.head.Hash(), "precommits #", pc, "votes for bfc #", votes[*bfc], "total votes for bfc", pc, "precommits", s.precommits) - msg, err := s.newFinalizationMessage(s.head, s.state.round).ToConsensusMessage() + msg, err := s.newCommitMessage(s.head, s.state.round).ToConsensusMessage() if err != nil { return err } diff --git a/lib/grandpa/message.go b/lib/grandpa/message.go index 0b76d4603d..8fe22a927c 100644 --- a/lib/grandpa/message.go +++ b/lib/grandpa/message.go @@ -19,6 +19,8 @@ package grandpa import ( "bytes" "fmt" + "io" + "math/big" "github.com/ChainSafe/gossamer/dot/network" "github.com/ChainSafe/gossamer/dot/types" @@ -36,11 +38,10 @@ type GrandpaMessage interface { //nolint var ( voteType byte = 0 - precommitType byte = 1 // TODO: precommitType is now part of voteType + commitType byte = 1 neighbourType byte = 2 catchUpRequestType byte = 3 catchUpResponseType byte = 4 - finalizationType byte = 5 // TODO: this is actually 1 ) // FullVote represents a vote with additional information about the state @@ -54,6 +55,7 @@ type FullVote struct { // SignedMessage represents a block hash and number signed by an authority type SignedMessage struct { + Stage subround // 0 for pre-vote, 1 for pre-commit, 2 for primary proposal Hash common.Hash Number uint32 Signature [64]byte // ed25519.SignatureLength @@ -65,18 +67,65 @@ func (m *SignedMessage) String() string { return fmt.Sprintf("hash=%s number=%d authorityID=0x%x", m.Hash, m.Number, m.AuthorityID) } +// Decode SCALE decodes the data into a SignedMessage +func (m *SignedMessage) Decode(r io.Reader) (err error) { + m.Stage, err = subround(0).Decode(r) + if err != nil { + return err + } + + vote, err := new(Vote).Decode(r) + if err != nil { + return err + } + + m.Hash = vote.hash + m.Number = vote.number + + sig, err := common.Read64Bytes(r) + if err != nil { + return err + } + + copy(m.Signature[:], sig[:]) + + id, err := common.Read32Bytes(r) + if err != nil { + return err + } + + copy(m.AuthorityID[:], id[:]) + return nil +} + // VoteMessage represents a network-level vote message // https://github.com/paritytech/substrate/blob/master/client/finality-grandpa/src/communication/gossip.rs#L336 type VoteMessage struct { Round uint64 SetID uint64 - Stage subround // 0 for pre-vote, 1 for pre-commit Message *SignedMessage } -// Type returns voteType or precommitType +// Decode SCALE decodes the data into a VoteMessage +func (v *VoteMessage) Decode(r io.Reader) (err error) { + v.Round, err = common.ReadUint64(r) + if err != nil { + return err + } + + v.SetID, err = common.ReadUint64(r) + if err != nil { + return err + } + + v.Message = new(SignedMessage) + err = v.Message.Decode(r) + return err +} + +// Type returns voteType func (v *VoteMessage) Type() byte { - return byte(v.Stage) + return voteType } // ToConsensusMessage converts the VoteMessage into a network-level consensus message @@ -86,9 +135,8 @@ func (v *VoteMessage) ToConsensusMessage() (*ConsensusMessage, error) { return nil, err } - typ := byte(v.Stage) return &ConsensusMessage{ - Data: append([]byte{typ}, enc...), + Data: append([]byte{voteType}, enc...), }, nil } @@ -117,38 +165,139 @@ func (m *NeighbourMessage) Type() byte { return neighbourType } -// FinalizationMessage represents a network finalization message -type FinalizationMessage struct { - Round uint64 - Vote *Vote - Justification []*SignedPrecommit +// AuthData represents signature data within a CommitMessage to be paired with a Precommit +type AuthData struct { + Signature [64]byte + AuthorityID ed25519.PublicKeyBytes +} + +// Encode SCALE encodes the AuthData +func (d *AuthData) Encode() ([]byte, error) { + return append(d.Signature[:], d.AuthorityID[:]...), nil +} + +// Decode SCALE decodes the data into an AuthData +func (d *AuthData) Decode(r io.Reader) error { + sig, err := common.Read64Bytes(r) + if err != nil { + return err + } + + copy(d.Signature[:], sig[:]) + + id, err := common.Read32Bytes(r) + if err != nil { + return err + } + + copy(d.AuthorityID[:], id[:]) + return nil } -// Type returns finalizationType -func (f *FinalizationMessage) Type() byte { - return finalizationType +// CommitMessage represents a network finalization message +type CommitMessage struct { + Round uint64 + SetID uint64 + Vote *Vote + Precommits []*Vote + AuthData []*AuthData } -// ToConsensusMessage converts the FinalizationMessage into a network-level consensus message -func (f *FinalizationMessage) ToConsensusMessage() (*ConsensusMessage, error) { +// Decode SCALE decodes the data into a CommitMessage +func (f *CommitMessage) Decode(r io.Reader) (err error) { + f.Round, err = common.ReadUint64(r) + if err != nil { + return err + } + + f.SetID, err = common.ReadUint64(r) + if err != nil { + return err + } + + f.Vote, err = new(Vote).Decode(r) + if err != nil { + return err + } + + sd := &scale.Decoder{Reader: r} + numPrecommits, err := sd.Decode(new(big.Int)) + if err != nil { + return err + } + + f.Precommits = make([]*Vote, numPrecommits.(*big.Int).Int64()) + for i := range f.Precommits { + f.Precommits[i], err = new(Vote).Decode(r) + if err != nil { + return err + } + } + + numAuthData, err := sd.Decode(new(big.Int)) + if err != nil { + return err + } + + if numAuthData.(*big.Int).Cmp(numPrecommits.(*big.Int)) != 0 { + return ErrPrecommitSignatureMismatch + } + + f.AuthData = make([]*AuthData, numAuthData.(*big.Int).Int64()) + for i := range f.AuthData { + f.AuthData[i] = new(AuthData) + err = f.AuthData[i].Decode(r) + if err != nil { + return err + } + } + + return nil +} + +// Type returns commitType +func (f *CommitMessage) Type() byte { + return commitType +} + +// ToConsensusMessage converts the CommitMessage into a network-level consensus message +func (f *CommitMessage) ToConsensusMessage() (*ConsensusMessage, error) { enc, err := scale.Encode(f) if err != nil { return nil, err } return &ConsensusMessage{ - Data: append([]byte{finalizationType}, enc...), + Data: append([]byte{commitType}, enc...), }, nil } -func (s *Service) newFinalizationMessage(header *types.Header, round uint64) *FinalizationMessage { - return &FinalizationMessage{ - Round: round, - Vote: NewVoteFromHeader(header), - Justification: s.justification[round], +func (s *Service) newCommitMessage(header *types.Header, round uint64) *CommitMessage { + just := s.justification[round] + precommits, authData := justificationToCompact(just) + return &CommitMessage{ + Round: round, + Vote: NewVoteFromHeader(header), + Precommits: precommits, + AuthData: authData, } } +func justificationToCompact(just []*SignedPrecommit) ([]*Vote, []*AuthData) { + precommits := make([]*Vote, len(just)) + authData := make([]*AuthData, len(just)) + + for i, j := range just { + precommits[i] = j.Vote + authData[i] = &AuthData{ + Signature: j.Signature, + AuthorityID: j.AuthorityID, + } + } + + return precommits, authData +} + type catchUpRequest struct { Round uint64 SetID uint64 @@ -179,8 +328,8 @@ func (r *catchUpRequest) ToConsensusMessage() (*ConsensusMessage, error) { } type catchUpResponse struct { - Round uint64 SetID uint64 + Round uint64 PreVoteJustification []*SignedPrecommit PreCommitJustification []*SignedPrecommit Hash common.Hash @@ -227,8 +376,8 @@ func (s *Service) newCatchUpResponse(round, setID uint64) (*catchUpResponse, err pcj := d.([]*SignedPrecommit) return &catchUpResponse{ - Round: round, SetID: setID, + Round: round, PreVoteJustification: pvj, PreCommitJustification: pcj, Hash: header.Hash(), diff --git a/lib/grandpa/message_handler.go b/lib/grandpa/message_handler.go index 2d31704b56..e30f655735 100644 --- a/lib/grandpa/message_handler.go +++ b/lib/grandpa/message_handler.go @@ -47,7 +47,7 @@ func NewMessageHandler(grandpa *Service, blockState BlockState) *MessageHandler } // HandleMessage handles a GRANDPA consensus message -// if it is a FinalizationMessage, it updates the BlockState +// if it is a CommitMessage, it updates the BlockState // if it is a VoteMessage, it sends it to the GRANDPA service func (h *MessageHandler) handleMessage(from peer.ID, msg *ConsensusMessage) (network.NotificationsMessage, error) { if msg == nil || len(msg.Data) == 0 { @@ -63,15 +63,15 @@ func (h *MessageHandler) handleMessage(from peer.ID, msg *ConsensusMessage) (net logger.Debug("handling grandpa message", "msg", m) switch m.Type() { - case voteType, precommitType: + case voteType: vm, ok := m.(*VoteMessage) if h.grandpa != nil && ok { // send vote message to grandpa service h.grandpa.in <- vm } - case finalizationType: - if fm, ok := m.(*FinalizationMessage); ok { - return h.handleFinalizationMessage(fm) + case commitType: + if fm, ok := m.(*CommitMessage); ok { + return h.handleCommitMessage(fm) } case neighbourType: nm, ok := m.(*NeighbourMessage) @@ -142,7 +142,7 @@ func (h *MessageHandler) handleNeighbourMessage(from peer.ID, msg *NeighbourMess return nil } -func (h *MessageHandler) handleFinalizationMessage(msg *FinalizationMessage) (*ConsensusMessage, error) { +func (h *MessageHandler) handleCommitMessage(msg *CommitMessage) (*ConsensusMessage, error) { logger.Debug("received finalization message", "round", msg.Round, "hash", msg.Vote.hash) if has, _ := h.blockState.HasFinalizedBlock(msg.Round, h.grandpa.state.setID); has { @@ -150,7 +150,7 @@ func (h *MessageHandler) handleFinalizationMessage(msg *FinalizationMessage) (*C } // check justification here - err := h.verifyFinalizationMessageJustification(msg) + err := h.verifyCommitMessageJustification(msg) if err != nil { return nil, err } @@ -168,7 +168,7 @@ func (h *MessageHandler) handleFinalizationMessage(msg *FinalizationMessage) (*C } // check if msg has same setID but is 2 or more rounds ahead of us, if so, return catch-up request to send - if msg.Round > h.grandpa.state.round+1 && !h.grandpa.paused.Load().(bool) { // TODO: FinalizationMessage does not have setID, confirm this is correct + if msg.Round > h.grandpa.state.round+1 && !h.grandpa.paused.Load().(bool) { // TODO: CommitMessage does not have setID, confirm this is correct h.grandpa.paused.Store(true) h.grandpa.state.round = msg.Round + 1 req := newCatchUpRequest(msg.Round, h.grandpa.state.setID) @@ -266,7 +266,7 @@ func (h *MessageHandler) verifyCatchUpResponseCompletability(prevote, precommit return nil } -// decodeMessage decodes a network-level consensus message into a GRANDPA VoteMessage or FinalizationMessage +// decodeMessage decodes a network-level consensus message into a GRANDPA VoteMessage or CommitMessage func decodeMessage(msg *ConsensusMessage) (m GrandpaMessage, err error) { var ( mi interface{} @@ -274,16 +274,15 @@ func decodeMessage(msg *ConsensusMessage) (m GrandpaMessage, err error) { ) switch msg.Data[0] { - case voteType, precommitType: - mi, err = scale.Decode(msg.Data[1:], &VoteMessage{Message: new(SignedMessage)}) - if m, ok = mi.(*VoteMessage); !ok { - return nil, ErrInvalidMessageType - } - case finalizationType: - mi, err = scale.Decode(msg.Data[1:], &FinalizationMessage{Justification: []*SignedPrecommit{}}) - if m, ok = mi.(*FinalizationMessage); !ok { - return nil, ErrInvalidMessageType - } + case voteType: + m = &VoteMessage{} + _, err = scale.Decode(msg.Data[1:], m) + case commitType: + r := &bytes.Buffer{} + _, _ = r.Write(msg.Data[1:]) + cm := &CommitMessage{} + err = cm.Decode(r) + m = cm case neighbourType: mi, err = scale.Decode(msg.Data[1:], &NeighbourMessage{}) if m, ok = mi.(*NeighbourMessage); !ok { @@ -310,10 +309,19 @@ func decodeMessage(msg *ConsensusMessage) (m GrandpaMessage, err error) { return m, nil } -func (h *MessageHandler) verifyFinalizationMessageJustification(fm *FinalizationMessage) error { - // verify justifications +func (h *MessageHandler) verifyCommitMessageJustification(fm *CommitMessage) error { + if len(fm.Precommits) != len(fm.AuthData) { + return ErrPrecommitSignatureMismatch + } + count := 0 - for _, just := range fm.Justification { + for i, pc := range fm.Precommits { + just := &SignedPrecommit{ + Vote: pc, + Signature: fm.AuthData[i].Signature, + AuthorityID: fm.AuthData[i].AuthorityID, + } + err := h.verifyJustification(just, fm.Round, h.grandpa.state.setID, precommit) if err != nil { continue @@ -327,7 +335,7 @@ func (h *MessageHandler) verifyFinalizationMessageJustification(fm *Finalization // confirm total # signatures >= grandpa threshold if uint64(count) < h.grandpa.state.threshold() { logger.Error("minimum votes not met for finalization message", "votes needed", h.grandpa.state.threshold(), - "votes", fm.Justification) + "votes received", len(fm.Precommits)) return ErrMinVotesNotMet } return nil diff --git a/lib/grandpa/message_handler_test.go b/lib/grandpa/message_handler_test.go index 42271383ec..c00bf08af1 100644 --- a/lib/grandpa/message_handler_test.go +++ b/lib/grandpa/message_handler_test.go @@ -86,8 +86,8 @@ func TestDecodeMessage_VoteMessage(t *testing.T) { expected := &VoteMessage{ Round: 77, SetID: 99, - Stage: precommit, Message: &SignedMessage{ + Stage: precommit, Hash: common.MustHexToHash("0x7db9db5ed9967b80143100189ba69d9e4deab85ac3570e5df25686cabe32964a"), Number: 0x7777, Signature: sig, @@ -98,29 +98,29 @@ func TestDecodeMessage_VoteMessage(t *testing.T) { require.Equal(t, expected, msg) } -func TestDecodeMessage_FinalizationMessage(t *testing.T) { - cm := &ConsensusMessage{ - Data: common.MustHexToBytes("0x054d000000000000007db9db5ed9967b80143100189ba69d9e4deab85ac3570e5df25686cabe32964a00000000040a0b0c0d00000000000000000000000000000000000000000000000000000000e70300000102030400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000034602b88f60513f1c805d87ef52896934baf6a662bc37414dbdbf69356b1a691"), - } - - msg, err := decodeMessage(cm) - require.NoError(t, err) - - expected := &FinalizationMessage{ +func TestDecodeMessage_CommitMessage(t *testing.T) { + expected := &CommitMessage{ Round: 77, + SetID: 1, Vote: &Vote{ hash: common.MustHexToHash("0x7db9db5ed9967b80143100189ba69d9e4deab85ac3570e5df25686cabe32964a"), - number: 0, + number: 99, + }, + Precommits: []*Vote{ + testVote, }, - Justification: []*SignedPrecommit{ + AuthData: []*AuthData{ { - Vote: testVote, Signature: testSignature, AuthorityID: kr.Alice().Public().(*ed25519.PublicKey).AsBytes(), }, }, } + cm, err := expected.ToConsensusMessage() + require.NoError(t, err) + msg, err := decodeMessage(cm) + require.NoError(t, err) require.Equal(t, expected, msg) } @@ -237,14 +237,14 @@ func TestMessageHandler_VerifyJustification_InvalidSig(t *testing.T) { require.Equal(t, err, ErrInvalidSignature) } -func TestMessageHandler_FinalizationMessage_NoCatchUpRequest_ValidSig(t *testing.T) { +func TestMessageHandler_CommitMessage_NoCatchUpRequest_ValidSig(t *testing.T) { gs, st := newTestService(t) round := uint64(77) gs.state.round = round gs.justification[round] = buildTestJustification(t, int(gs.state.threshold()), round, gs.state.setID, kr, precommit) - fm := gs.newFinalizationMessage(gs.head, round) + fm := gs.newCommitMessage(gs.head, round) fm.Vote = NewVote(testHash, uint32(round)) cm, err := fm.ToConsensusMessage() require.NoError(t, err) @@ -263,7 +263,7 @@ func TestMessageHandler_FinalizationMessage_NoCatchUpRequest_ValidSig(t *testing require.Equal(t, fm.Vote.hash, hash) } -func TestMessageHandler_FinalizationMessage_NoCatchUpRequest_MinVoteError(t *testing.T) { +func TestMessageHandler_CommitMessage_NoCatchUpRequest_MinVoteError(t *testing.T) { gs, st := newTestService(t) round := uint64(77) @@ -271,7 +271,7 @@ func TestMessageHandler_FinalizationMessage_NoCatchUpRequest_MinVoteError(t *tes gs.justification[round] = buildTestJustification(t, int(gs.state.threshold()), round, gs.state.setID, kr, precommit) - fm := gs.newFinalizationMessage(gs.head, round) + fm := gs.newCommitMessage(gs.head, round) cm, err := fm.ToConsensusMessage() require.NoError(t, err) @@ -281,7 +281,7 @@ func TestMessageHandler_FinalizationMessage_NoCatchUpRequest_MinVoteError(t *tes require.Nil(t, out) } -func TestMessageHandler_FinalizationMessage_WithCatchUpRequest(t *testing.T) { +func TestMessageHandler_CommitMessage_WithCatchUpRequest(t *testing.T) { gs, st := newTestService(t) gs.justification[77] = []*SignedPrecommit{ @@ -292,7 +292,7 @@ func TestMessageHandler_FinalizationMessage_WithCatchUpRequest(t *testing.T) { }, } - fm := gs.newFinalizationMessage(gs.head, 77) + fm := gs.newCommitMessage(gs.head, 77) cm, err := fm.ToConsensusMessage() require.NoError(t, err) gs.state.voters = gs.state.voters[:1] diff --git a/lib/grandpa/message_test.go b/lib/grandpa/message_test.go index 0b39ad9ce1..f9b61282ec 100644 --- a/lib/grandpa/message_test.go +++ b/lib/grandpa/message_test.go @@ -44,8 +44,8 @@ func TestVoteMessageToConsensusMessage(t *testing.T) { expected := &VoteMessage{ Round: gs.state.round, SetID: gs.state.setID, - Stage: precommit, Message: &SignedMessage{ + Stage: precommit, Hash: v.hash, Number: v.number, AuthorityID: gs.keypair.Public().(*ed25519.PublicKey).AsBytes(), @@ -62,8 +62,8 @@ func TestVoteMessageToConsensusMessage(t *testing.T) { expected = &VoteMessage{ Round: gs.state.round, SetID: gs.state.setID, - Stage: prevote, Message: &SignedMessage{ + Stage: prevote, Hash: v.hash, Number: v.number, AuthorityID: gs.keypair.Public().(*ed25519.PublicKey).AsBytes(), @@ -73,7 +73,7 @@ func TestVoteMessageToConsensusMessage(t *testing.T) { require.Equal(t, expected, vm) } -func TestFinalizationMessageToConsensusMessage(t *testing.T) { +func TestCommitMessageToConsensusMessage(t *testing.T) { gs, _ := newTestService(t) gs.justification[77] = []*SignedPrecommit{ { @@ -83,12 +83,14 @@ func TestFinalizationMessageToConsensusMessage(t *testing.T) { }, } - fm := gs.newFinalizationMessage(gs.head, 77) + fm := gs.newCommitMessage(gs.head, 77) + precommits, authData := justificationToCompact(gs.justification[77]) - expected := &FinalizationMessage{ - Round: 77, - Vote: NewVoteFromHeader(gs.head), - Justification: gs.justification[77], + expected := &CommitMessage{ + Round: 77, + Vote: NewVoteFromHeader(gs.head), + Precommits: precommits, + AuthData: authData, } require.Equal(t, expected, fm) diff --git a/lib/grandpa/network_test.go b/lib/grandpa/network_test.go index 58c9352506..1bcd81ed43 100644 --- a/lib/grandpa/network_test.go +++ b/lib/grandpa/network_test.go @@ -54,7 +54,7 @@ func TestHandleNetworkMessage(t *testing.T) { }, } - fm := gs.newFinalizationMessage(gs.head, 77) + fm := gs.newCommitMessage(gs.head, 77) cm, err := fm.ToConsensusMessage() require.NoError(t, err) gs.state.voters = gs.state.voters[:1] diff --git a/lib/grandpa/round_test.go b/lib/grandpa/round_test.go index 815da4473e..ef5357dd98 100644 --- a/lib/grandpa/round_test.go +++ b/lib/grandpa/round_test.go @@ -58,7 +58,7 @@ func (n *testNetwork) SendMessage(msg NotificationsMessage) { gmsg, err := decodeMessage(cm) require.NoError(n.t, err) - if gmsg.Type() == finalizationType { + if gmsg.Type() == commitType { n.finalized <- gmsg } else { n.out <- gmsg @@ -259,7 +259,7 @@ func TestPlayGrandpaRound_BaseCase(t *testing.T) { wg := sync.WaitGroup{} wg.Add(len(kr.Keys)) - finalized := make([]*FinalizationMessage, len(kr.Keys)) + finalized := make([]*CommitMessage, len(kr.Keys)) for i, fin := range fins { go func(i int, fin <-chan GrandpaMessage) { @@ -267,7 +267,7 @@ func TestPlayGrandpaRound_BaseCase(t *testing.T) { case f := <-fin: // receive first message, which is finalized block from previous round - if f.(*FinalizationMessage).Round == 0 { + if f.(*CommitMessage).Round == 0 { select { case f = <-fin: case <-time.After(testTimeout): @@ -275,7 +275,7 @@ func TestPlayGrandpaRound_BaseCase(t *testing.T) { } } - finalized[i] = f.(*FinalizationMessage) + finalized[i] = f.(*CommitMessage) case <-time.After(testTimeout): t.Errorf("did not receive finalized block from %d", i) @@ -289,9 +289,11 @@ func TestPlayGrandpaRound_BaseCase(t *testing.T) { for _, fb := range finalized { require.NotNil(t, fb) - require.GreaterOrEqual(t, len(fb.Justification), len(kr.Keys)/2) - finalized[0].Justification = []*SignedPrecommit{} - fb.Justification = []*SignedPrecommit{} + require.GreaterOrEqual(t, len(fb.Precommits), len(kr.Keys)/2) + finalized[0].Precommits = []*Vote{} + finalized[0].AuthData = []*AuthData{} + fb.Precommits = []*Vote{} + fb.AuthData = []*AuthData{} require.Equal(t, finalized[0], fb) } } @@ -357,7 +359,7 @@ func TestPlayGrandpaRound_VaryingChain(t *testing.T) { wg := sync.WaitGroup{} wg.Add(len(kr.Keys)) - finalized := make([]*FinalizationMessage, len(kr.Keys)) + finalized := make([]*CommitMessage, len(kr.Keys)) for i, fin := range fins { @@ -366,7 +368,7 @@ func TestPlayGrandpaRound_VaryingChain(t *testing.T) { case f := <-fin: // receive first message, which is finalized block from previous round - if f.(*FinalizationMessage).Round == 0 { + if f.(*CommitMessage).Round == 0 { select { case f = <-fin: case <-time.After(testTimeout): @@ -374,7 +376,7 @@ func TestPlayGrandpaRound_VaryingChain(t *testing.T) { } } - finalized[i] = f.(*FinalizationMessage) + finalized[i] = f.(*CommitMessage) case <-time.After(testTimeout): t.Errorf("did not receive finalized block from %d", i) } @@ -387,9 +389,12 @@ func TestPlayGrandpaRound_VaryingChain(t *testing.T) { for _, fb := range finalized { require.NotNil(t, fb) - require.GreaterOrEqual(t, len(fb.Justification), len(kr.Keys)/2) - finalized[0].Justification = []*SignedPrecommit{} - fb.Justification = []*SignedPrecommit{} + require.GreaterOrEqual(t, len(fb.Precommits), len(kr.Keys)/2) + require.GreaterOrEqual(t, len(fb.AuthData), len(kr.Keys)/2) + finalized[0].Precommits = []*Vote{} + finalized[0].AuthData = []*AuthData{} + fb.Precommits = []*Vote{} + fb.AuthData = []*AuthData{} require.Equal(t, finalized[0], fb) } } @@ -454,7 +459,7 @@ func TestPlayGrandpaRound_OneThirdEquivocating(t *testing.T) { wg := sync.WaitGroup{} wg.Add(len(kr.Keys)) - finalized := make([]*FinalizationMessage, len(kr.Keys)) + finalized := make([]*CommitMessage, len(kr.Keys)) for i, fin := range fins { @@ -463,7 +468,7 @@ func TestPlayGrandpaRound_OneThirdEquivocating(t *testing.T) { case f := <-fin: // receive first message, which is finalized block from previous round - if f.(*FinalizationMessage).Round == 0 { + if f.(*CommitMessage).Round == 0 { select { case f = <-fin: case <-time.After(testTimeout): @@ -471,7 +476,7 @@ func TestPlayGrandpaRound_OneThirdEquivocating(t *testing.T) { } } - finalized[i] = f.(*FinalizationMessage) + finalized[i] = f.(*CommitMessage) case <-time.After(testTimeout): t.Errorf("did not receive finalized block from %d", i) } @@ -484,9 +489,12 @@ func TestPlayGrandpaRound_OneThirdEquivocating(t *testing.T) { for _, fb := range finalized { require.NotNil(t, fb) - require.GreaterOrEqual(t, len(fb.Justification), len(kr.Keys)/2) - finalized[0].Justification = []*SignedPrecommit{} - fb.Justification = []*SignedPrecommit{} + require.GreaterOrEqual(t, len(fb.Precommits), len(kr.Keys)/2) + require.GreaterOrEqual(t, len(fb.AuthData), len(kr.Keys)/2) + finalized[0].Precommits = []*Vote{} + finalized[0].AuthData = []*AuthData{} + fb.Precommits = []*Vote{} + fb.AuthData = []*AuthData{} require.Equal(t, finalized[0], fb) } } @@ -535,7 +543,7 @@ func TestPlayGrandpaRound_MultipleRounds(t *testing.T) { wg := sync.WaitGroup{} wg.Add(len(kr.Keys)) - finalized := make([]*FinalizationMessage, len(kr.Keys)) + finalized := make([]*CommitMessage, len(kr.Keys)) for i, fin := range fins { @@ -544,7 +552,7 @@ func TestPlayGrandpaRound_MultipleRounds(t *testing.T) { case f := <-fin: // receive first message, which is finalized block from previous round - if f.(*FinalizationMessage).Round == uint64(j) { + if f.(*CommitMessage).Round == uint64(j) { select { case f = <-fin: case <-time.After(testTimeout): @@ -552,7 +560,7 @@ func TestPlayGrandpaRound_MultipleRounds(t *testing.T) { } } - finalized[i] = f.(*FinalizationMessage) + finalized[i] = f.(*CommitMessage) case <-time.After(testTimeout): t.Errorf("did not receive finalized block from %d", i) } @@ -567,9 +575,12 @@ func TestPlayGrandpaRound_MultipleRounds(t *testing.T) { for _, fb := range finalized { require.NotNil(t, fb) require.Equal(t, head, fb.Vote.hash) - require.GreaterOrEqual(t, len(fb.Justification), len(kr.Keys)/2) - finalized[0].Justification = []*SignedPrecommit{} - fb.Justification = []*SignedPrecommit{} + require.GreaterOrEqual(t, len(fb.Precommits), len(kr.Keys)/2) + require.GreaterOrEqual(t, len(fb.AuthData), len(kr.Keys)/2) + finalized[0].Precommits = []*Vote{} + finalized[0].AuthData = []*AuthData{} + fb.Precommits = []*Vote{} + fb.AuthData = []*AuthData{} require.Equal(t, finalized[0], fb) } diff --git a/lib/grandpa/types.go b/lib/grandpa/types.go index 45484818b3..ceb25ea801 100644 --- a/lib/grandpa/types.go +++ b/lib/grandpa/types.go @@ -30,8 +30,11 @@ import ( type subround byte -var prevote subround = 0 -var precommit subround = 1 +var ( + prevote subround = 0 + precommit subround = 1 + primaryProposal subround = 2 +) func (s subround) Encode() ([]byte, error) { return []byte{byte(s)}, nil diff --git a/lib/grandpa/vote_message.go b/lib/grandpa/vote_message.go index 2dbc085026..3b46659204 100644 --- a/lib/grandpa/vote_message.go +++ b/lib/grandpa/vote_message.go @@ -52,7 +52,7 @@ func (s *Service) receiveMessages(cond func() bool) { continue } - logger.Debug("validated vote message", "vote", v, "round", vm.Round, "subround", vm.Stage, "precommits", s.precommits) + logger.Debug("validated vote message", "vote", v, "round", vm.Round, "subround", vm.Message.Stage, "precommits", s.precommits) case <-ctx.Done(): logger.Trace("returning from receiveMessages") return @@ -113,6 +113,7 @@ func (s *Service) createVoteMessage(vote *Vote, stage subround, kp crypto.Keypai } sm := &SignedMessage{ + Stage: stage, Hash: vote.hash, Number: vote.number, Signature: ed25519.NewSignatureBytes(sig), @@ -122,7 +123,6 @@ func (s *Service) createVoteMessage(vote *Vote, stage subround, kp crypto.Keypai return &VoteMessage{ Round: s.state.round, SetID: s.state.setID, - Stage: stage, Message: sm, }, nil } @@ -191,20 +191,20 @@ func (s *Service) validateMessage(m *VoteMessage) (*Vote, error) { } // add justification before checking for equivocation, since equivocatory vote may still be used in justification - if m.Stage == prevote { + if m.Message.Stage == prevote { s.pvJustifications[m.Message.Hash] = append(s.pvJustifications[m.Message.Hash], just) - } else if m.Stage == precommit { + } else if m.Message.Stage == precommit { s.pcJustifications[m.Message.Hash] = append(s.pcJustifications[m.Message.Hash], just) } - equivocated := s.checkForEquivocation(voter, vote, m.Stage) + equivocated := s.checkForEquivocation(voter, vote, m.Message.Stage) if equivocated { return nil, ErrEquivocation } - if m.Stage == prevote { + if m.Message.Stage == prevote { s.prevotes[pk.AsBytes()] = vote - } else if m.Stage == precommit { + } else if m.Message.Stage == precommit { s.precommits[pk.AsBytes()] = vote } @@ -273,7 +273,7 @@ func (s *Service) validateVote(v *Vote) error { func validateMessageSignature(pk *ed25519.PublicKey, m *VoteMessage) error { msg, err := scale.Encode(&FullVote{ - Stage: m.Stage, + Stage: m.Message.Stage, Vote: NewVote(m.Message.Hash, m.Message.Number), Round: m.Round, SetID: m.SetID,