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 Jun 28, 2022
1 parent 914a747 commit 11cc8ce
Show file tree
Hide file tree
Showing 3 changed files with 220 additions and 59 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")
)
36 changes: 13 additions & 23 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 @@ -602,10 +595,7 @@ func (s *Service) VerifyBlockJustification(hash common.Hash, justification []byt
authPubKeys[i] = AuthData{AuthorityID: pcj.AuthorityID}
}

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

var count int

Expand Down
242 changes: 207 additions & 35 deletions lib/grandpa/message_handler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import (
"github.com/ChainSafe/gossamer/lib/keystore"
"github.com/ChainSafe/gossamer/pkg/scale"
"github.com/golang/mock/gomock"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

Expand Down Expand Up @@ -784,44 +784,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 11cc8ce

Please sign in to comment.