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

fix(lib/grandpa): verify equivocatory votes in grandpa justifications #2486

Merged
merged 11 commits into from
May 4, 2022
89 changes: 65 additions & 24 deletions lib/grandpa/message_handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -299,20 +299,43 @@ func getEquivocatoryVoters(votes []AuthData) map[ed25519.PublicKeyBytes]struct{}
return eqvVoters
}

func verifyIfDescendantOfHighestFinalisedBlock(blockState BlockState, hash common.Hash) error {
highestHeader, err := blockState.GetHighestFinalisedHeader()
if err != nil {
return fmt.Errorf("could not get highest finalised header: %w", err)
}

isDescendant, err := blockState.IsDescendantOf(highestHeader.Hash(), hash)
if err != nil {
return fmt.Errorf("could not check if %s is descendant of %s: %w", hash, highestHeader.Hash(), err)
}
if !isDescendant {
// rename this error
return errInvalidVoteBlock
}

return nil

}

func (h *MessageHandler) verifyCommitMessageJustification(fm *CommitMessage) error {
if len(fm.Precommits) != len(fm.AuthData) {
return ErrPrecommitSignatureMismatch
}

if fm.SetID != h.grandpa.state.setID {
return ErrSetIDMismatch
}

err := verifyIfDescendantOfHighestFinalisedBlock(h.blockState, fm.Vote.Hash)
if err != nil {
return fmt.Errorf("cannot verify ancestry of highest finalised block: %w", err)
}

eqvVoters := getEquivocatoryVoters(fm.AuthData)

var count int
for i, pc := range fm.Precommits {
_, ok := eqvVoters[fm.AuthData[i].AuthorityID]
if ok {
continue
}

just := &SignedVote{
Vote: pc,
Signature: fm.AuthData[i].Signature,
Expand All @@ -321,7 +344,7 @@ func (h *MessageHandler) verifyCommitMessageJustification(fm *CommitMessage) err

err := h.verifyJustification(just, fm.Round, h.grandpa.state.setID, precommit)
if err != nil {
logger.Errorf("could not verify justification: %s", err)
logger.Errorf("failed to verify justification for vote from authority id: %s, for block hash: %")
continue
}

Expand All @@ -331,6 +354,15 @@ func (h *MessageHandler) verifyCommitMessageJustification(fm *CommitMessage) err
continue
}

err = verifyBlockHashAgainstBlockNumber(h.blockState, pc.Hash, uint(pc.Number))
if err != nil {
return err
}

if _, ok := eqvVoters[fm.AuthData[i].AuthorityID]; ok {
continue
}

if isDescendant {
count++
}
Expand Down Expand Up @@ -419,36 +451,40 @@ func (h *MessageHandler) verifyPreVoteJustification(msg *CatchUpResponse) (commo
}

func (h *MessageHandler) verifyPreCommitJustification(msg *CatchUpResponse) error {
for _, pcj := range msg.PreCommitJustification {
err := verifyBlockHashAgainstBlockNumber(h.blockState, pcj.Vote.Hash, uint(pcj.Vote.Number))
if err != nil {
if errors.Is(err, chaindb.ErrKeyNotFound) {
h.grandpa.tracker.addCatchUpResponse(msg)
logger.Infof("we might not have synced to the given block %s yet: %s", pcj.Vote.Hash, err)
continue
}
return err
}
}

auths := make([]AuthData, len(msg.PreCommitJustification))
for i, pcj := range msg.PreCommitJustification {
auths[i] = AuthData{AuthorityID: pcj.AuthorityID}
}

err := verifyIfDescendantOfHighestFinalisedBlock(h.blockState, msg.Hash)
if err != nil {
return err
}

eqvVoters := getEquivocatoryVoters(auths)

// verify pre-commit justification
var count uint64
for idx := range msg.PreCommitJustification {
just := &msg.PreCommitJustification[idx]

if _, ok := eqvVoters[just.AuthorityID]; ok {
continue
err = verifyBlockHashAgainstBlockNumber(h.blockState, just.Vote.Hash, uint(just.Vote.Number))
if err != nil {
if errors.Is(err, chaindb.ErrKeyNotFound) {
h.grandpa.tracker.addCatchUpResponse(msg)
logger.Infof("we might not have synced to the given block %s yet: %s", just.Vote.Hash, err)
continue
}
return err
}

err := h.verifyJustification(just, msg.Round, msg.SetID, precommit)
if err != nil {
logger.Errorf("could not verify precommit justification for block %s from authority %s: %s", just.Vote.Hash.String(), just.AuthorityID.String(), err)
continue
}

if _, ok := eqvVoters[just.AuthorityID]; ok {
continue
}

Expand Down Expand Up @@ -531,6 +567,11 @@ func (s *Service) VerifyBlockJustification(hash common.Hash, justification []byt
return fmt.Errorf("already have finalised block with setID=%d and round=%d", setID, fj.Round)
}

err = verifyIfDescendantOfHighestFinalisedBlock(s.blockState, fj.Commit.Hash)
if err != nil {
return err
Copy link
Contributor

Choose a reason for hiding this comment

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

nit maybe wrap the error

}

auths, err := s.grandpaState.GetAuthorities(setID)
if err != nil {
return fmt.Errorf("cannot get authorities for set ID: %w", err)
Expand All @@ -557,10 +598,6 @@ func (s *Service) VerifyBlockJustification(hash common.Hash, justification []byt
setID, fj.Round, fj.Commit.Hash, fj.Commit.Number, len(fj.Commit.Precommits))

for _, just := range fj.Commit.Precommits {
if _, ok := equivocatoryVoters[just.AuthorityID]; ok {
continue
}

// check if vote was for descendant of committed block
isDescendant, err := s.blockState.IsDescendantOf(hash, just.Vote.Hash)
if err != nil {
Expand Down Expand Up @@ -600,6 +637,10 @@ func (s *Service) VerifyBlockJustification(hash common.Hash, justification []byt
return ErrInvalidSignature
}

if _, ok := equivocatoryVoters[just.AuthorityID]; ok {
continue
}

count++
}

Expand Down
1 change: 1 addition & 0 deletions lib/grandpa/state.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ type BlockState interface {
BestBlockHeader() (*types.Header, error)
BestBlockHash() common.Hash
Leaves() []common.Hash
GetHighestFinalisedHeader() (*types.Header, error)
BlocktreeAsString() string
GetImportedBlockNotifierChannel() chan *types.Block
FreeImportedBlockNotifierChannel(ch chan *types.Block)
Expand Down