Skip to content

Commit

Permalink
implement fix for duplicate votes is GRANDPA
Browse files Browse the repository at this point in the history
  • Loading branch information
edwardmack committed Jul 1, 2022
1 parent 792e53f commit 40d13f4
Show file tree
Hide file tree
Showing 3 changed files with 241 additions and 81 deletions.
1 change: 0 additions & 1 deletion lib/grandpa/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -98,5 +98,4 @@ var (
errVoteToSignatureMismatch = errors.New("votes and authority count mismatch")
errVoteBlockMismatch = errors.New("block in vote is not descendant of previously finalised block")
errVoteFromSelf = errors.New("got vote from ourselves")
errInvalidMultiplicity = errors.New("more than two equivocatory votes for a voter")
)
81 changes: 35 additions & 46 deletions lib/grandpa/message_handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -284,22 +284,21 @@ func (h *MessageHandler) verifyCatchUpResponseCompletability(prevote, precommit
return nil
}

func getEquivocatoryVoters(votes []AuthData) (map[ed25519.PublicKeyBytes]struct{}, error) {
func getEquivocatoryVoters(votes []AuthData) map[ed25519.PublicKeyBytes]struct{} {
eqvVoters := make(map[ed25519.PublicKeyBytes]struct{})
voters := make(map[ed25519.PublicKeyBytes]int, len(votes))
voters := make(map[ed25519.PublicKeyBytes][64]byte, len(votes))

for _, v := range votes {
voters[v.AuthorityID]++
switch voters[v.AuthorityID] {
case 1:
case 2:
eqvVoters[v.AuthorityID] = struct{}{}
default:
return nil, fmt.Errorf("%w: authority id %x has %d votes",
errInvalidMultiplicity, v.AuthorityID, voters[v.AuthorityID])
signature, present := voters[v.AuthorityID]
if present {
if !bytes.Equal(signature[:], v.Signature[:]) {
eqvVoters[v.AuthorityID] = struct{}{}
}
} else {
voters[v.AuthorityID] = v.Signature
}
}
return eqvVoters, nil
return eqvVoters
}

func isDescendantOfHighestFinalisedBlock(blockState BlockState, hash common.Hash) (bool, error) {
Expand Down Expand Up @@ -329,10 +328,7 @@ func (h *MessageHandler) verifyCommitMessageJustification(fm *CommitMessage) err
return errVoteBlockMismatch
}

eqvVoters, err := getEquivocatoryVoters(fm.AuthData)
if err != nil {
return fmt.Errorf("could not get valid equivocatory voters: %w", err)
}
eqvVoters := getEquivocatoryVoters(fm.AuthData)

var count int
for i, pc := range fm.Precommits {
Expand Down Expand Up @@ -465,10 +461,7 @@ func (h *MessageHandler) verifyPreCommitJustification(msg *CatchUpResponse) erro
return errVoteBlockMismatch
}

eqvVoters, err := getEquivocatoryVoters(auths)
if err != nil {
return fmt.Errorf("could not get valid equivocatory voters: %w", err)
}
eqvVoters := getEquivocatoryVoters(auths)

// verify pre-commit justification
var count uint64
Expand Down Expand Up @@ -553,60 +546,56 @@ func (h *MessageHandler) verifyJustification(just *SignedVote, round, setID uint
return nil
}

// VerifyBlockJustification verifies the finality justification for a block, returns scale encoded justification with
// any extra bytes removed.
func (s *Service) VerifyBlockJustification(hash common.Hash, justification []byte) ([]byte, error) {
// VerifyBlockJustification verifies the finality justification for a block
func (s *Service) VerifyBlockJustification(hash common.Hash, justification []byte) error {
fj := Justification{}
err := scale.Unmarshal(justification, &fj)
if err != nil {
return nil, err
return err
}

setID, err := s.grandpaState.GetSetIDByBlockNumber(uint(fj.Commit.Number))
if err != nil {
return nil, fmt.Errorf("cannot get set ID from block number: %w", err)
return fmt.Errorf("cannot get set ID from block number: %w", err)
}

has, err := s.blockState.HasFinalisedBlock(fj.Round, setID)
if err != nil {
return nil, err
return err
}

if has {
return nil, fmt.Errorf("already have finalised block with setID=%d and round=%d", setID, fj.Round)
return fmt.Errorf("already have finalised block with setID=%d and round=%d", setID, fj.Round)
}

isDescendant, err := isDescendantOfHighestFinalisedBlock(s.blockState, fj.Commit.Hash)
if err != nil {
return nil, err
return err
}

if !isDescendant {
return nil, errVoteBlockMismatch
return errVoteBlockMismatch
}

auths, err := s.grandpaState.GetAuthorities(setID)
if err != nil {
return nil, fmt.Errorf("cannot get authorities for set ID: %w", err)
return fmt.Errorf("cannot get authorities for set ID: %w", err)
}

// threshold is two-thirds the number of authorities,
// uses the current set of authorities to define the threshold
threshold := (2 * len(auths) / 3)

if len(fj.Commit.Precommits) < threshold {
return nil, ErrMinVotesNotMet
return ErrMinVotesNotMet
}

authPubKeys := make([]AuthData, len(fj.Commit.Precommits))
for i, pcj := range fj.Commit.Precommits {
authPubKeys[i] = AuthData{AuthorityID: pcj.AuthorityID}
}

equivocatoryVoters, err := getEquivocatoryVoters(authPubKeys)
if err != nil {
return nil, fmt.Errorf("could not get valid equivocatory voters: %w", err)
}
equivocatoryVoters := getEquivocatoryVoters(authPubKeys)

var count int

Expand All @@ -618,20 +607,20 @@ func (s *Service) VerifyBlockJustification(hash common.Hash, justification []byt
// check if vote was for descendant of committed block
isDescendant, err := s.blockState.IsDescendantOf(hash, just.Vote.Hash)
if err != nil {
return nil, err
return err
}

if !isDescendant {
return nil, ErrPrecommitBlockMismatch
return ErrPrecommitBlockMismatch
}

pk, err := ed25519.NewPublicKey(just.AuthorityID[:])
if err != nil {
return nil, err
return err
}

if !isInAuthSet(pk, auths) {
return nil, ErrAuthorityNotInSet
return ErrAuthorityNotInSet
}

// verify signature for each precommit
Expand All @@ -642,16 +631,16 @@ func (s *Service) VerifyBlockJustification(hash common.Hash, justification []byt
SetID: setID,
})
if err != nil {
return nil, err
return err
}

ok, err := pk.Verify(msg, just.Signature[:])
if err != nil {
return nil, err
return err
}

if !ok {
return nil, ErrInvalidSignature
return ErrInvalidSignature
}

if _, ok := equivocatoryVoters[just.AuthorityID]; ok {
Expand All @@ -662,30 +651,30 @@ func (s *Service) VerifyBlockJustification(hash common.Hash, justification []byt
}

if count+len(equivocatoryVoters) < threshold {
return nil, ErrMinVotesNotMet
return ErrMinVotesNotMet
}

err = verifyBlockHashAgainstBlockNumber(s.blockState, fj.Commit.Hash, uint(fj.Commit.Number))
if err != nil {
return nil, err
return err
}

for _, preCommit := range fj.Commit.Precommits {
err := verifyBlockHashAgainstBlockNumber(s.blockState, preCommit.Vote.Hash, uint(preCommit.Vote.Number))
if err != nil {
return nil, err
return err
}
}

err = s.blockState.SetFinalisedHash(hash, fj.Round, setID)
if err != nil {
return nil, err
return err
}

logger.Debugf(
"set finalised block with hash %s, round %d and set id %d",
hash, fj.Round, setID)
return scale.Marshal(fj)
return nil
}

func verifyBlockHashAgainstBlockNumber(bs BlockState, hash common.Hash, number uint) error {
Expand Down
Loading

0 comments on commit 40d13f4

Please sign in to comment.