diff --git a/lib/grandpa/errors.go b/lib/grandpa/errors.go index 1908f785cf..9b2ce98b72 100644 --- a/lib/grandpa/errors.go +++ b/lib/grandpa/errors.go @@ -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") ) diff --git a/lib/grandpa/message_handler.go b/lib/grandpa/message_handler.go index 766ca26b29..1afd5dbf7d 100644 --- a/lib/grandpa/message_handler.go +++ b/lib/grandpa/message_handler.go @@ -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) { @@ -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 { @@ -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 @@ -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 diff --git a/lib/grandpa/message_handler_test.go b/lib/grandpa/message_handler_test.go index 2f43675ed0..73ebe16bee 100644 --- a/lib/grandpa/message_handler_test.go +++ b/lib/grandpa/message_handler_test.go @@ -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) {