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): Duplicate votes is GRANDPA are counted as equivocatory votes (GSR-11) #2624

Merged
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
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")
)
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:
Copy link
Contributor

Choose a reason for hiding this comment

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

Does not this already take cared of the case when a vote appears twice in the AuthData list?

Copy link
Member Author

Choose a reason for hiding this comment

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

The audit issue and spec say that duplicate votes are not equivocatory, just when votes don't match they are then equivocatory.

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 @@ -603,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 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 @@ -792,44 +792,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