diff --git a/.golangci.yml b/.golangci.yml index d5aa421ef1..4979ade538 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -157,6 +157,10 @@ issues: source: "^//go:generate " text: "long-lines" + - text: 'G204: Subprocess launched with variable' + linters: + - gosec + # Independently from option `exclude` we use default exclude patterns, # it can be disabled by this option. To list all # excluded by default patterns execute `golangci-lint run --help`. diff --git a/lib/grandpa/grandpa.go b/lib/grandpa/grandpa.go index 4f623cb08a..aa1ed3619f 100644 --- a/lib/grandpa/grandpa.go +++ b/lib/grandpa/grandpa.go @@ -30,6 +30,10 @@ var ( logger = log.NewFromGlobal(log.AddContext("pkg", "grandpa")) ) +var ( + ErrUnsupportedSubround = errors.New("unsupported subround") +) + // Service represents the current state of the grandpa protocol type Service struct { // preliminaries @@ -688,7 +692,7 @@ func (s *Service) determinePreCommit() (*Vote, error) { return &pvb, nil } -// isFinalisable returns true is the round is finalisable, false otherwise. +// isFinalisable returns true if the round is finalisable, false otherwise. func (s *Service) isFinalisable(round uint64) (bool, error) { var pvb Vote var err error @@ -806,16 +810,20 @@ func (s *Service) createJustification(bfc common.Hash, stage Subround) ([]Signed spc *sync.Map err error just []SignedVote + eqv map[ed25519.PublicKeyBytes][]*SignedVote ) switch stage { case prevote: spc = s.prevotes + eqv = s.pvEquivocations case precommit: spc = s.precommits + eqv = s.pcEquivocations + default: + return nil, fmt.Errorf("%w: %s", ErrUnsupportedSubround, stage) } - // TODO: use equivacatory votes to create justification as well (#1667) spc.Range(func(_, value interface{}) bool { pc := value.(*SignedVote) var isDescendant bool @@ -837,6 +845,12 @@ func (s *Service) createJustification(bfc common.Hash, stage Subround) ([]Signed return nil, err } + for _, votes := range eqv { + for _, vote := range votes { + just = append(just, *vote) + } + } + return just, nil } diff --git a/lib/grandpa/grandpa_test.go b/lib/grandpa/grandpa_test.go index 38fc4bac10..bc524b2942 100644 --- a/lib/grandpa/grandpa_test.go +++ b/lib/grandpa/grandpa_test.go @@ -8,6 +8,7 @@ import ( "math/big" "math/rand" "sort" + "sync" "testing" "time" @@ -1264,3 +1265,159 @@ func TestFinalRoundGaugeMetric(t *testing.T) { gauge := ethmetrics.GetOrRegisterGauge(finalityGrandpaRoundMetrics, nil) require.Equal(t, gauge.Value(), int64(180)) } + +func TestGrandpaServiceCreateJustification_ShouldCountEquivocatoryVotes(t *testing.T) { + // setup granpda service + gs, st := newTestService(t) + now := time.Unix(1000, 0) + + const previousBlocksToAdd = 9 + bfcBlock := addBlocksAndReturnTheLastOne(t, st.Block, previousBlocksToAdd, now) + + bfcHash := bfcBlock.Header.Hash() + bfcNumber := bfcBlock.Header.Number.Int64() + + // create fake authorities + ed25519Keyring, err := keystore.NewEd25519Keyring() + require.NoError(t, err) + fakeAuthorities := []*ed25519.Keypair{ + ed25519Keyring.Alice().(*ed25519.Keypair), + ed25519Keyring.Bob().(*ed25519.Keypair), + ed25519Keyring.Charlie().(*ed25519.Keypair), + ed25519Keyring.Dave().(*ed25519.Keypair), + ed25519Keyring.Eve().(*ed25519.Keypair), + ed25519Keyring.Bob().(*ed25519.Keypair), // equivocatory + ed25519Keyring.Dave().(*ed25519.Keypair), // equivocatory + } + + equivocatories := make(map[ed25519.PublicKeyBytes][]*types.GrandpaSignedVote) + prevotes := &sync.Map{} + + var totalLegitVotes int + // voting on + for _, v := range fakeAuthorities { + vote := &SignedVote{ + AuthorityID: v.Public().(*ed25519.PublicKey).AsBytes(), + Vote: types.GrandpaVote{ + Hash: bfcHash, + Number: uint32(bfcNumber), + }, + } + + // to simulate the real world: + // if the voter already has voted, then we remove + // previous vote and add it on the equivocatories with the new vote + previous, ok := prevotes.Load(vote.AuthorityID) + if !ok { + prevotes.Store(vote.AuthorityID, vote) + totalLegitVotes++ + } else { + prevotes.Delete(vote.AuthorityID) + equivocatories[vote.AuthorityID] = []*types.GrandpaSignedVote{ + previous.(*types.GrandpaSignedVote), + vote, + } + totalLegitVotes-- + } + } + + gs.pvEquivocations = equivocatories + gs.prevotes = prevotes + + justifications, err := gs.createJustification(bfcHash, prevote) + require.NoError(t, err) + + var totalEqvVotes int + // checks if the created justification contains all equivocatories votes + for eqvPubKeyBytes, expectedVotes := range equivocatories { + votesOnJustification := 0 + + for _, justification := range justifications { + if justification.AuthorityID == eqvPubKeyBytes { + votesOnJustification++ + } + } + + require.Equal(t, len(expectedVotes), votesOnJustification) + totalEqvVotes += votesOnJustification + } + + require.Len(t, justifications, totalLegitVotes+totalEqvVotes) +} + +// addBlocksToState test helps adding previous blocks +func addBlocksToState(t *testing.T, blockState *state.BlockState, depth int) { + t.Helper() + + previousHash := blockState.BestBlockHash() + + rt, err := blockState.GetRuntime(nil) + require.NoError(t, err) + + head, err := blockState.BestBlockHeader() + require.NoError(t, err) + + startNum := int(head.Number.Int64()) + + for i := startNum + 1; i <= depth; i++ { + arrivalTime := time.Now() + + d, err := types.NewBabePrimaryPreDigest(0, uint64(i), [32]byte{}, [64]byte{}).ToPreRuntimeDigest() + require.NoError(t, err) + require.NotNil(t, d) + digest := types.NewDigest() + err = digest.Add(*d) + require.NoError(t, err) + + block := &types.Block{ + Header: types.Header{ + ParentHash: previousHash, + Number: big.NewInt(int64(i)), + StateRoot: trie.EmptyHash, + Digest: digest, + }, + Body: types.Body{}, + } + + hash := block.Header.Hash() + err = blockState.AddBlockWithArrivalTime(block, arrivalTime) + require.NoError(t, err) + + blockState.StoreRuntime(hash, rt) + previousHash = hash + } +} + +func addBlocksAndReturnTheLastOne(t *testing.T, blockState *state.BlockState, depth int, lastBlockArrivalTime time.Time) *types.Block { + t.Helper() + addBlocksToState(t, blockState, depth) + + // create a new fake block to fake authorities commit on + previousHash := blockState.BestBlockHash() + previousHead, err := blockState.BestBlockHeader() + require.NoError(t, err) + + bfcNumber := int(previousHead.Number.Int64() + 1) + + d, err := types.NewBabePrimaryPreDigest(0, uint64(bfcNumber), [32]byte{}, [64]byte{}).ToPreRuntimeDigest() + require.NoError(t, err) + require.NotNil(t, d) + digest := types.NewDigest() + err = digest.Add(*d) + require.NoError(t, err) + + bfcBlock := &types.Block{ + Header: types.Header{ + ParentHash: previousHash, + Number: big.NewInt(int64(bfcNumber)), + StateRoot: trie.EmptyHash, + Digest: digest, + }, + Body: types.Body{}, + } + + err = blockState.AddBlockWithArrivalTime(bfcBlock, lastBlockArrivalTime) + require.NoError(t, err) + + return bfcBlock +} diff --git a/lib/grandpa/message_handler.go b/lib/grandpa/message_handler.go index b7906745e0..45843f7719 100644 --- a/lib/grandpa/message_handler.go +++ b/lib/grandpa/message_handler.go @@ -238,13 +238,35 @@ func (h *MessageHandler) verifyCatchUpResponseCompletability(prevote, precommit return nil } +func getEquivocatoryVoters(votes []AuthData) map[ed25519.PublicKeyBytes]struct{} { + eqvVoters := make(map[ed25519.PublicKeyBytes]struct{}) + voters := make(map[ed25519.PublicKeyBytes]int, len(votes)) + + for _, v := range votes { + voters[v.AuthorityID]++ + + if voters[v.AuthorityID] > 1 { + eqvVoters[v.AuthorityID] = struct{}{} + } + } + + return eqvVoters +} + func (h *MessageHandler) verifyCommitMessageJustification(fm *CommitMessage) error { if len(fm.Precommits) != len(fm.AuthData) { return ErrPrecommitSignatureMismatch } - count := 0 + eqvVoters := getEquivocatoryVoters(fm.AuthData) + + var count int for i, pc := range fm.Precommits { + _, ok := eqvVoters[fm.AuthData[i].AuthorityID] + if ok { + continue + } + just := &SignedVote{ Vote: pc, Signature: fm.AuthData[i].Signature, @@ -268,7 +290,7 @@ func (h *MessageHandler) verifyCommitMessageJustification(fm *CommitMessage) err } // confirm total # signatures >= grandpa threshold - if uint64(count) < h.grandpa.state.threshold() { + if uint64(count)+uint64(len(eqvVoters)) < h.grandpa.state.threshold() { logger.Debugf( "minimum votes not met for finalisation message. Need %d votes and received %d votes.", h.grandpa.state.threshold(), count) @@ -280,11 +302,40 @@ func (h *MessageHandler) verifyCommitMessageJustification(fm *CommitMessage) err } func (h *MessageHandler) verifyPreVoteJustification(msg *CatchUpResponse) (common.Hash, error) { + voters := make(map[ed25519.PublicKeyBytes]map[common.Hash]int, len(msg.PreVoteJustification)) + eqVotesByHash := make(map[common.Hash]map[ed25519.PublicKeyBytes]struct{}) + + // identify equivocatory votes by hash + for _, justification := range msg.PreVoteJustification { + hashsToCount, ok := voters[justification.AuthorityID] + if !ok { + hashsToCount = make(map[common.Hash]int) + } + + hashsToCount[justification.Vote.Hash]++ + voters[justification.AuthorityID] = hashsToCount + + if hashsToCount[justification.Vote.Hash] > 1 { + pubKeysOnHash, ok := eqVotesByHash[justification.Vote.Hash] + if !ok { + pubKeysOnHash = make(map[ed25519.PublicKeyBytes]struct{}) + } + + pubKeysOnHash[justification.AuthorityID] = struct{}{} + eqVotesByHash[justification.Vote.Hash] = pubKeysOnHash + } + } + // verify pre-vote justification, returning the pre-voted block if there is one votes := make(map[common.Hash]uint64) + for idx := range msg.PreVoteJustification { + just := &msg.PreVoteJustification[idx] + + // if the current voter is on equivocatory map then ignore the vote + if _, ok := eqVotesByHash[just.Vote.Hash][just.AuthorityID]; ok { + continue + } - for i := range msg.PreVoteJustification { - just := &msg.PreVoteJustification[i] err := h.verifyJustification(just, msg.Round, msg.SetID, prevote) if err != nil { continue @@ -295,7 +346,8 @@ func (h *MessageHandler) verifyPreVoteJustification(msg *CatchUpResponse) (commo var prevote common.Hash for hash, count := range votes { - if count >= h.grandpa.state.threshold() { + equivocatoryVotes := eqVotesByHash[hash] + if count+uint64(len(equivocatoryVotes)) >= h.grandpa.state.threshold() { prevote = hash break } @@ -309,10 +361,22 @@ func (h *MessageHandler) verifyPreVoteJustification(msg *CatchUpResponse) (commo } func (h *MessageHandler) verifyPreCommitJustification(msg *CatchUpResponse) error { + auths := make([]AuthData, len(msg.PreCommitJustification)) + for i, pcj := range msg.PreCommitJustification { + auths[i] = AuthData{AuthorityID: pcj.AuthorityID} + } + + eqvVoters := getEquivocatoryVoters(auths) + // verify pre-commit justification - count := 0 - for i := range msg.PreCommitJustification { - just := &msg.PreCommitJustification[i] + var count uint64 + for idx := range msg.PreCommitJustification { + just := &msg.PreCommitJustification[idx] + + if _, ok := eqvVoters[just.AuthorityID]; ok { + continue + } + err := h.verifyJustification(just, msg.Round, msg.SetID, precommit) if err != nil { continue @@ -323,7 +387,7 @@ func (h *MessageHandler) verifyPreCommitJustification(msg *CatchUpResponse) erro } } - if uint64(count) < h.grandpa.state.threshold() { + if count+uint64(len(eqvVoters)) < h.grandpa.state.threshold() { return ErrMinVotesNotMet } @@ -358,6 +422,7 @@ func (h *MessageHandler) verifyJustification(just *SignedVote, round, setID uint // verify authority in justification set authFound := false + for _, auth := range h.grandpa.authorities() { justKey, err := just.AuthorityID.Encode() if err != nil { @@ -401,15 +466,31 @@ func (s *Service) VerifyBlockJustification(hash common.Hash, justification []byt return fmt.Errorf("cannot get authorities for set ID: %w", err) } - logger.Debugf( - "verifying justification: set id %d, round %d, hash %s, number %d, sig count %d", - setID, fj.Round, fj.Commit.Hash, fj.Commit.Number, len(fj.Commit.Precommits)) + // 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) < (2 * len(auths) / 3) { + if len(fj.Commit.Precommits) < threshold { return ErrMinVotesNotMet } + authPubKeys := make([]AuthData, len(fj.Commit.Precommits)) + for i, pcj := range fj.Commit.Precommits { + authPubKeys[i] = AuthData{AuthorityID: pcj.AuthorityID} + } + + equivocatoryVoters := getEquivocatoryVoters(authPubKeys) + var count int + + logger.Debugf( + "verifying justification: set id %d, round %d, hash %s, number %d, sig count %d", + setID, fj.Round, fj.Commit.Hash, fj.Commit.Number, len(fj.Commit.Precommits)) + for _, just := range fj.Commit.Precommits { + if _, ok := equivocatoryVoters[just.AuthorityID]; ok { + continue + } + // check if vote was for descendant of committed block isDescendant, err := s.blockState.IsDescendantOf(hash, just.Vote.Hash) if err != nil { @@ -425,8 +506,7 @@ func (s *Service) VerifyBlockJustification(hash common.Hash, justification []byt return err } - ok := isInAuthSet(pk, auths) - if !ok { + if !isInAuthSet(pk, auths) { return ErrAuthorityNotInSet } @@ -441,7 +521,7 @@ func (s *Service) VerifyBlockJustification(hash common.Hash, justification []byt return err } - ok, err = pk.Verify(msg, just.Signature[:]) + ok, err := pk.Verify(msg, just.Signature[:]) if err != nil { return err } @@ -449,6 +529,12 @@ func (s *Service) VerifyBlockJustification(hash common.Hash, justification []byt if !ok { return ErrInvalidSignature } + + count++ + } + + if count+len(equivocatoryVoters) < threshold { + return ErrMinVotesNotMet } err = s.blockState.SetFinalisedHash(hash, fj.Round, setID) diff --git a/lib/grandpa/message_handler_test.go b/lib/grandpa/message_handler_test.go index 28ad733f50..5ba7b69465 100644 --- a/lib/grandpa/message_handler_test.go +++ b/lib/grandpa/message_handler_test.go @@ -506,6 +506,69 @@ func TestMessageHandler_HandleCatchUpResponse(t *testing.T) { require.Equal(t, round+1, gs.state.round) } +func TestMessageHandler_VerifyBlockJustification_WithEquivocatoryVotes(t *testing.T) { + auths := []types.GrandpaVoter{ + { + Key: *kr.Alice().Public().(*ed25519.PublicKey), + }, + { + Key: *kr.Bob().Public().(*ed25519.PublicKey), + }, + { + Key: *kr.Charlie().Public().(*ed25519.PublicKey), + }, + { + Key: *kr.Dave().Public().(*ed25519.PublicKey), + }, + { + Key: *kr.Eve().Public().(*ed25519.PublicKey), + }, + { + Key: *kr.Ferdie().Public().(*ed25519.PublicKey), + }, + { + Key: *kr.George().Public().(*ed25519.PublicKey), + }, + { + Key: *kr.Heather().Public().(*ed25519.PublicKey), + }, + { + Key: *kr.Ian().Public().(*ed25519.PublicKey), + }, + } + + gs, st := newTestService(t) + err := st.Grandpa.SetNextChange(auths, big.NewInt(1)) + require.NoError(t, err) + + body, err := types.NewBodyFromBytes([]byte{0}) + require.NoError(t, err) + + block := &types.Block{ + Header: *testHeader, + Body: *body, + } + + err = st.Block.AddBlock(block) + require.NoError(t, err) + + err = st.Grandpa.IncrementSetID() + require.NoError(t, err) + + setID, err := st.Grandpa.GetCurrentSetID() + require.NoError(t, err) + require.Equal(t, uint64(1), setID) + + round := uint64(2) + number := uint32(2) + precommits := buildTestJustification(t, 20, round, setID, kr, precommit) + just := newJustification(round, testHash, number, precommits) + data, err := scale.Marshal(*just) + require.NoError(t, err) + err = gs.VerifyBlockJustification(testHash, data) + require.NoError(t, err) +} + func TestMessageHandler_VerifyBlockJustification(t *testing.T) { auths := []types.GrandpaVoter{ { @@ -587,3 +650,256 @@ func TestMessageHandler_VerifyBlockJustification(t *testing.T) { err = gs.VerifyBlockJustification(testHash, data) require.Equal(t, ErrMinVotesNotMet, err) } + +func Test_getEquivocatoryVoters(t *testing.T) { + // many of equivocatory votes + ed25519Keyring, err := keystore.NewEd25519Keyring() + require.NoError(t, err) + fakeAuthorities := []*ed25519.Keypair{ + ed25519Keyring.Alice().(*ed25519.Keypair), + ed25519Keyring.Alice().(*ed25519.Keypair), + ed25519Keyring.Alice().(*ed25519.Keypair), + ed25519Keyring.Bob().(*ed25519.Keypair), + ed25519Keyring.Charlie().(*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), + } + + authData := make([]AuthData, len(fakeAuthorities)) + + for i, auth := range fakeAuthorities { + authData[i] = AuthData{ + AuthorityID: auth.Public().(*ed25519.PublicKey).AsBytes(), + } + } + + eqv := getEquivocatoryVoters(authData) + require.Len(t, eqv, 5) +} + +func Test_VerifyCommitMessageJustification_ShouldRemoveEquivocatoryVotes(t *testing.T) { + const fakeRound = 2 + + gs, st := newTestService(t) + h := NewMessageHandler(gs, st.Block) + + const previousBlocksToAdd = 8 + now := time.Unix(1000, 0) + bfcBlock := addBlocksAndReturnTheLastOne(t, st.Block, previousBlocksToAdd, now) + + bfcHash := bfcBlock.Header.Hash() + bfcNumber := bfcBlock.Header.Number.Int64() + + // many of equivocatory votes + ed25519Keyring, err := keystore.NewEd25519Keyring() + require.NoError(t, err) + fakeAuthorities := []*ed25519.Keypair{ + ed25519Keyring.Alice().(*ed25519.Keypair), + ed25519Keyring.Alice().(*ed25519.Keypair), + ed25519Keyring.Alice().(*ed25519.Keypair), + ed25519Keyring.Bob().(*ed25519.Keypair), + ed25519Keyring.Charlie().(*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), + } + + authData := make([]AuthData, len(fakeAuthorities)) + precommits := make([]Vote, len(fakeAuthorities)) + + for i, auth := range fakeAuthorities { + vote := types.GrandpaVote{ + Hash: bfcHash, + Number: uint32(bfcNumber), + } + + sig := signFakeFullVote( + t, auth, precommit, vote, fakeRound, gs.state.setID, + ) + + authData[i] = AuthData{ + Signature: sig, + AuthorityID: auth.Public().(*ed25519.PublicKey).AsBytes(), + } + precommits[i] = Vote{Hash: bfcHash, Number: uint32(bfcNumber)} + } + + // Charlie has an equivocatory vote + testCommitData := &CommitMessage{ + Round: fakeRound, + Vote: Vote{ + Hash: bfcHash, + Number: uint32(bfcNumber), + }, + Precommits: precommits, + AuthData: authData, + } + + err = h.verifyCommitMessageJustification(testCommitData) + require.NoError(t, err) +} + +func Test_VerifyPrevoteJustification_CountEquivocatoryVoters(t *testing.T) { + gs, st := newTestService(t) + h := NewMessageHandler(gs, st.Block) + + const previousBlocksToAdd = 9 + now := time.Unix(1000, 0) + bfcBlock := addBlocksAndReturnTheLastOne(t, st.Block, previousBlocksToAdd, now) + + bfcHash := bfcBlock.Header.Hash() + bfcNumber := bfcBlock.Header.Number.Int64() + + // many of equivocatory votes + ed25519Keyring, err := keystore.NewEd25519Keyring() + require.NoError(t, err) + + fakeAuthorities := []*ed25519.Keypair{ + ed25519Keyring.Alice().(*ed25519.Keypair), + ed25519Keyring.Alice().(*ed25519.Keypair), + ed25519Keyring.Alice().(*ed25519.Keypair), + ed25519Keyring.Bob().(*ed25519.Keypair), + ed25519Keyring.Charlie().(*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.Ian().(*ed25519.Keypair), + } + + prevotesJustification := make([]SignedVote, len(fakeAuthorities)) + for idx, fakeAuthority := range fakeAuthorities { + var vote types.GrandpaVote + + // put one vote on a different hash + if idx == 1 { + vote = types.GrandpaVote{ + Hash: bfcBlock.Header.ParentHash, + Number: uint32(bfcNumber - 1), + } + } else { + vote = types.GrandpaVote{ + Hash: bfcHash, + Number: uint32(bfcNumber), + } + } + + sig := signFakeFullVote( + t, fakeAuthority, prevote, vote, gs.state.round, gs.state.setID) + + prevotesJustification[idx] = SignedVote{ + Vote: vote, + Signature: sig, + AuthorityID: fakeAuthority.Public().(*ed25519.PublicKey).AsBytes(), + } + } + + testCatchUpResponse := &CatchUpResponse{ + SetID: gs.state.setID, + Round: gs.state.round, + PreVoteJustification: prevotesJustification, + Hash: bfcHash, + Number: uint32(bfcNumber), + } + + hash, err := h.verifyPreVoteJustification(testCatchUpResponse) + require.NoError(t, err) + require.Equal(t, hash, bfcHash) +} + +func Test_VerifyPreCommitJustification(t *testing.T) { + gs, st := newTestService(t) + h := NewMessageHandler(gs, st.Block) + + const previousBlocksToAdd = 7 + now := time.Unix(1000, 0) + bfcBlock := addBlocksAndReturnTheLastOne(t, st.Block, previousBlocksToAdd, now) + + bfcHash := bfcBlock.Header.Hash() + bfcNumber := bfcBlock.Header.Number.Int64() + + // many of equivocatory votes + ed25519Keyring, err := keystore.NewEd25519Keyring() + require.NoError(t, err) + + // Alice, Charlie, David - Equivocatory + // Bob, Eve, Ferdie, Ian - Legit + // total of votes 4 legit + 3 equivocatory + // the threshold for testing is 9, so 2/3 of 9 = 6 + fakeAuthorities := []*ed25519.Keypair{ + ed25519Keyring.Alice().(*ed25519.Keypair), + ed25519Keyring.Alice().(*ed25519.Keypair), + ed25519Keyring.Alice().(*ed25519.Keypair), + ed25519Keyring.Bob().(*ed25519.Keypair), + ed25519Keyring.Charlie().(*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.Ian().(*ed25519.Keypair), + } + + prevotesJustification := make([]SignedVote, len(fakeAuthorities)) + for idx, fakeAuthority := range fakeAuthorities { + vote := types.GrandpaVote{ + Hash: bfcHash, + Number: uint32(bfcNumber), + } + + sig := signFakeFullVote( + t, fakeAuthority, precommit, vote, gs.state.round, gs.state.setID) + + prevotesJustification[idx] = SignedVote{ + Vote: vote, + Signature: sig, + AuthorityID: fakeAuthority.Public().(*ed25519.PublicKey).AsBytes(), + } + } + + testCatchUpResponse := &CatchUpResponse{ + SetID: gs.state.setID, + Round: gs.state.round, + PreCommitJustification: prevotesJustification, + Hash: bfcHash, + Number: uint32(bfcNumber), + } + + err = h.verifyPreCommitJustification(testCatchUpResponse) + require.NoError(t, err) +} + +func signFakeFullVote( + t *testing.T, auth *ed25519.Keypair, + stage Subround, v types.GrandpaVote, + round, setID uint64) [64]byte { + msg, err := scale.Marshal(FullVote{ + Stage: stage, + Vote: v, + Round: round, + SetID: setID, + }) + require.NoError(t, err) + + var sig [64]byte + privSig, err := auth.Private().Sign(msg) + require.NoError(t, err) + + copy(sig[:], privSig) + + return sig +} diff --git a/lib/grandpa/vote_message.go b/lib/grandpa/vote_message.go index 52c696fd84..84bbfdc67a 100644 --- a/lib/grandpa/vote_message.go +++ b/lib/grandpa/vote_message.go @@ -25,14 +25,14 @@ func (s *Service) receiveMessages(ctx context.Context) { for { select { case msg, ok := <-s.in: - if msg == nil || msg.msg == nil { - continue - } - if !ok { return } + if msg == nil || msg.msg == nil { + continue + } + logger.Tracef("received vote message %v from %s", msg.msg, msg.from) vm := msg.msg