Skip to content

Commit

Permalink
fix(lib/grandpa): Duplicate votes is GRANDPA are counted as equivocat…
Browse files Browse the repository at this point in the history
…ory votes (GSR-11) (#2624)
  • Loading branch information
edwardmack authored Jul 8, 2022
1 parent 2bb2609 commit 422e7b3
Show file tree
Hide file tree
Showing 3 changed files with 217 additions and 57 deletions.
1 change: 0 additions & 1 deletion lib/grandpa/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -99,5 +99,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")
)
33 changes: 11 additions & 22 deletions lib/grandpa/message_handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -284,22 +284,20 @@ 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:
signature, present := voters[v.AuthorityID]
if present && !bytes.Equal(signature[:], v.Signature[:]) {
eqvVoters[v.AuthorityID] = struct{}{}
default:
return nil, fmt.Errorf("%w: authority id %x has %d votes",
errInvalidMultiplicity, v.AuthorityID, voters[v.AuthorityID])
} 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 +327,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 +460,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 @@ -608,10 +600,7 @@ func (s *Service) VerifyBlockJustification(hash common.Hash, justification []byt
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 Down
240 changes: 206 additions & 34 deletions lib/grandpa/message_handler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -807,44 +807,216 @@ func TestMessageHandler_VerifyBlockJustification_invalid(t *testing.T) {
}

func Test_getEquivocatoryVoters(t *testing.T) {
// many of equivocatory votes
t.Parallel()

ed25519Keyring, err := keystore.NewEd25519Keyring()
require.NoError(t, err)
fakeAuthorities := []*ed25519.Keypair{
ed25519Keyring.Alice().(*ed25519.Keypair),
ed25519Keyring.Alice().(*ed25519.Keypair),
ed25519Keyring.Bob().(*ed25519.Keypair),
ed25519Keyring.Charlie().(*ed25519.Keypair),
ed25519Keyring.Charlie().(*ed25519.Keypair),
ed25519Keyring.Dave().(*ed25519.Keypair),
ed25519Keyring.Dave().(*ed25519.Keypair),
ed25519Keyring.Eve().(*ed25519.Keypair),
ed25519Keyring.Ferdie().(*ed25519.Keypair),
ed25519Keyring.Heather().(*ed25519.Keypair),
ed25519Keyring.Heather().(*ed25519.Keypair),
ed25519Keyring.Ian().(*ed25519.Keypair),
ed25519Keyring.Ian().(*ed25519.Keypair),
tests := map[string]struct {
votes []AuthData
want map[ed25519.PublicKeyBytes]struct{}
}{
"no votes": {
votes: []AuthData{},
want: map[ed25519.PublicKeyBytes]struct{}{},
},
"one vote": {
votes: []AuthData{
{
AuthorityID: ed25519Keyring.Alice().Public().(*ed25519.PublicKey).AsBytes(),
Signature: [64]byte{1, 2, 3, 4},
},
},
want: map[ed25519.PublicKeyBytes]struct{}{},
},
"two votes different authorities": {
votes: []AuthData{
{
AuthorityID: ed25519Keyring.Alice().Public().(*ed25519.PublicKey).AsBytes(),
Signature: [64]byte{1, 2, 3, 4},
},
{
AuthorityID: ed25519Keyring.Bob().Public().(*ed25519.PublicKey).AsBytes(),
Signature: [64]byte{1, 2, 3, 4},
},
},
want: map[ed25519.PublicKeyBytes]struct{}{},
},
"duplicate votes": {
votes: []AuthData{
{
AuthorityID: ed25519Keyring.Alice().Public().(*ed25519.PublicKey).AsBytes(),
Signature: [64]byte{1, 2, 3, 4},
},
{
AuthorityID: ed25519Keyring.Alice().Public().(*ed25519.PublicKey).AsBytes(),
Signature: [64]byte{1, 2, 3, 4},
},
},
want: map[ed25519.PublicKeyBytes]struct{}{},
},
"equivocatory vote": {
votes: []AuthData{
{
AuthorityID: ed25519Keyring.Alice().Public().(*ed25519.PublicKey).AsBytes(),
Signature: [64]byte{1, 2, 3, 4},
},
{
AuthorityID: ed25519Keyring.Alice().Public().(*ed25519.PublicKey).AsBytes(),
Signature: [64]byte{5, 6, 7, 8},
},
},
want: map[ed25519.PublicKeyBytes]struct{}{
ed25519Keyring.Alice().Public().(*ed25519.PublicKey).AsBytes(): {},
},
},
"equivocatory vote with duplicate": {
votes: []AuthData{
{
AuthorityID: ed25519Keyring.Alice().Public().(*ed25519.PublicKey).AsBytes(),
Signature: [64]byte{1, 2, 3, 4},
},
{
AuthorityID: ed25519Keyring.Alice().Public().(*ed25519.PublicKey).AsBytes(),
Signature: [64]byte{5, 6, 7, 8},
},
{
AuthorityID: ed25519Keyring.Alice().Public().(*ed25519.PublicKey).AsBytes(),
Signature: [64]byte{1, 2, 3, 4},
},
},
want: map[ed25519.PublicKeyBytes]struct{}{
ed25519Keyring.Alice().Public().(*ed25519.PublicKey).AsBytes(): {},
},
},
"three voters one equivocatory": {
votes: []AuthData{
{
AuthorityID: ed25519Keyring.Alice().Public().(*ed25519.PublicKey).AsBytes(),
Signature: [64]byte{1, 2, 3, 4},
},
{
AuthorityID: ed25519Keyring.Bob().Public().(*ed25519.PublicKey).AsBytes(),
Signature: [64]byte{1, 2, 3, 4},
},
{
AuthorityID: ed25519Keyring.Bob().Public().(*ed25519.PublicKey).AsBytes(),
Signature: [64]byte{5, 6, 7, 8},
},
{
AuthorityID: ed25519Keyring.Charlie().Public().(*ed25519.PublicKey).AsBytes(),
Signature: [64]byte{5, 6, 7, 8},
},
},
want: map[ed25519.PublicKeyBytes]struct{}{
ed25519Keyring.Bob().Public().(*ed25519.PublicKey).AsBytes(): {},
},
},
"three voters one equivocatory one duplicate": {
votes: []AuthData{
{
AuthorityID: ed25519Keyring.Alice().Public().(*ed25519.PublicKey).AsBytes(),
Signature: [64]byte{1, 2, 3, 4},
},
{
AuthorityID: ed25519Keyring.Alice().Public().(*ed25519.PublicKey).AsBytes(),
Signature: [64]byte{5, 6, 7, 8},
},
{
AuthorityID: ed25519Keyring.Bob().Public().(*ed25519.PublicKey).AsBytes(),
Signature: [64]byte{5, 6, 7, 8},
},
{
AuthorityID: ed25519Keyring.Bob().Public().(*ed25519.PublicKey).AsBytes(),
Signature: [64]byte{5, 6, 7, 8},
},
{
AuthorityID: ed25519Keyring.Charlie().Public().(*ed25519.PublicKey).AsBytes(),
Signature: [64]byte{5, 6, 7, 8},
},
},
want: map[ed25519.PublicKeyBytes]struct{}{
ed25519Keyring.Alice().Public().(*ed25519.PublicKey).AsBytes(): {},
},
},
"three voters two equivocatory": {
votes: []AuthData{
{
AuthorityID: ed25519Keyring.Alice().Public().(*ed25519.PublicKey).AsBytes(),
Signature: [64]byte{1, 2, 3, 4},
},
{
AuthorityID: ed25519Keyring.Alice().Public().(*ed25519.PublicKey).AsBytes(),
Signature: [64]byte{5, 6, 7, 8},
},
{
AuthorityID: ed25519Keyring.Bob().Public().(*ed25519.PublicKey).AsBytes(),
Signature: [64]byte{1, 2, 3, 4},
},
{
AuthorityID: ed25519Keyring.Bob().Public().(*ed25519.PublicKey).AsBytes(),
Signature: [64]byte{5, 6, 7, 8},
},
{
AuthorityID: ed25519Keyring.Charlie().Public().(*ed25519.PublicKey).AsBytes(),
Signature: [64]byte{5, 6, 7, 8},
},
},
want: map[ed25519.PublicKeyBytes]struct{}{
ed25519Keyring.Alice().Public().(*ed25519.PublicKey).AsBytes(): {},
ed25519Keyring.Bob().Public().(*ed25519.PublicKey).AsBytes(): {},
},
},
"three voters two duplicate": {
votes: []AuthData{
{
AuthorityID: ed25519Keyring.Alice().Public().(*ed25519.PublicKey).AsBytes(),
Signature: [64]byte{1, 2, 3, 4},
},
{
AuthorityID: ed25519Keyring.Alice().Public().(*ed25519.PublicKey).AsBytes(),
Signature: [64]byte{1, 2, 3, 4},
},
{
AuthorityID: ed25519Keyring.Bob().Public().(*ed25519.PublicKey).AsBytes(),
Signature: [64]byte{1, 2, 3, 4},
},
{
AuthorityID: ed25519Keyring.Bob().Public().(*ed25519.PublicKey).AsBytes(),
Signature: [64]byte{1, 2, 3, 4},
},
{
AuthorityID: ed25519Keyring.Charlie().Public().(*ed25519.PublicKey).AsBytes(),
Signature: [64]byte{5, 6, 7, 8},
},
},
want: map[ed25519.PublicKeyBytes]struct{}{},
},
"three voters": {
votes: []AuthData{
{
AuthorityID: ed25519Keyring.Alice().Public().(*ed25519.PublicKey).AsBytes(),
Signature: [64]byte{1, 2, 3, 4},
},
{
AuthorityID: ed25519Keyring.Bob().Public().(*ed25519.PublicKey).AsBytes(),
Signature: [64]byte{1, 2, 3, 4},
},
{
AuthorityID: ed25519Keyring.Charlie().Public().(*ed25519.PublicKey).AsBytes(),
Signature: [64]byte{5, 6, 7, 8},
},
},
want: map[ed25519.PublicKeyBytes]struct{}{},
},
}

authData := make([]AuthData, len(fakeAuthorities))

for i, auth := range fakeAuthorities {
authData[i] = AuthData{
AuthorityID: auth.Public().(*ed25519.PublicKey).AsBytes(),
}
for name, tt := range tests {
tt := tt
t.Run(name, func(t *testing.T) {
t.Parallel()
got := getEquivocatoryVoters(tt.votes)
assert.Equalf(t, tt.want, got, "getEquivocatoryVoters(%v)", tt.votes)
})
}

eqv, err := getEquivocatoryVoters(authData)
require.NoError(t, err)
require.Len(t, eqv, 5)

// test that getEquivocatoryVoters returns an error if a voter has more than two equivocatory votes
authData = append(authData, AuthData{
AuthorityID: ed25519Keyring.Alice().Public().(*ed25519.PublicKey).AsBytes(),
})

_, err = getEquivocatoryVoters(authData)
require.ErrorIs(t, err, errInvalidMultiplicity)
}

func Test_VerifyCommitMessageJustification_ShouldRemoveEquivocatoryVotes(t *testing.T) {
Expand Down

0 comments on commit 422e7b3

Please sign in to comment.