Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore(lib/grandpa): update grandpa message types to match substrate #1534

Merged
merged 8 commits into from
Apr 22, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions lib/grandpa/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand All @@ -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")
19 changes: 17 additions & 2 deletions lib/grandpa/grandpa.go
Original file line number Diff line number Diff line change
Expand Up @@ -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...")
Expand Down Expand Up @@ -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
}
Expand Down
199 changes: 174 additions & 25 deletions lib/grandpa/message.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ package grandpa
import (
"bytes"
"fmt"
"io"
"math/big"

"github.com/ChainSafe/gossamer/dot/network"
"github.com/ChainSafe/gossamer/dot/types"
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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[:])
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

explicitly add length [:PublicKeyLength] or verify length after copy.

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
Expand All @@ -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
}

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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(),
Expand Down
Loading