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

task(lib/grandpa): Verify block hashes and number #2468

Merged
merged 17 commits into from
Apr 26, 2022
Merged
Show file tree
Hide file tree
Changes from 12 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
3 changes: 1 addition & 2 deletions dot/state/block.go
Original file line number Diff line number Diff line change
Expand Up @@ -205,8 +205,6 @@ func (bs *BlockState) GetHeader(hash common.Hash) (header *types.Header, err err
return header, nil
}

result := types.NewEmptyHeader()

if bs.db == nil {
return nil, fmt.Errorf("database is nil")
}
Expand All @@ -220,6 +218,7 @@ func (bs *BlockState) GetHeader(hash common.Hash) (header *types.Header, err err
return nil, err
}

result := types.NewEmptyHeader()
err = scale.Unmarshal(data, result)
if err != nil {
return nil, err
Expand Down
5 changes: 4 additions & 1 deletion dot/state/block_finalisation.go
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,10 @@ func (bs *BlockState) SetFinalisedHash(hash common.Hash, round, setID uint64) er
bs.Lock()
defer bs.Unlock()

has, _ := bs.HasHeader(hash)
has, err := bs.HasHeader(hash)
if err != nil {
return fmt.Errorf("could not check header for hash %s: %w", hash, err)
}
if !has {
return fmt.Errorf("cannot finalise unknown block %s", hash)
}
Expand Down
11 changes: 6 additions & 5 deletions dot/state/grandpa.go
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,8 @@ func (s *GrandpaState) GetLatestRound() (uint64, error) {
return round, nil
}

// SetNextChange sets the next authority change
// SetNextChange sets the next authority change at the given block number.
// NOTE: This block number will be the last block in the current set and not part of the next set.
func (s *GrandpaState) SetNextChange(authorities []types.GrandpaVoter, number uint) error {
currSetID, err := s.GetCurrentSetID()
if err != nil {
Expand Down Expand Up @@ -180,7 +181,7 @@ func (s *GrandpaState) setSetIDChangeAtBlock(setID uint64, number uint) error {
return s.db.Put(setIDChangeKey(setID), common.UintToBytes(number))
}

// GetSetIDChange returs the block number where the set ID was updated
// GetSetIDChange returns the block number where the set ID was updated
func (s *GrandpaState) GetSetIDChange(setID uint64) (blockNumber uint, err error) {
num, err := s.db.Get(setIDChangeKey(setID))
if err != nil {
Expand All @@ -191,7 +192,7 @@ func (s *GrandpaState) GetSetIDChange(setID uint64) (blockNumber uint, err error
}

// GetSetIDByBlockNumber returns the set ID for a given block number
func (s *GrandpaState) GetSetIDByBlockNumber(num uint) (uint64, error) {
func (s *GrandpaState) GetSetIDByBlockNumber(blockNumber uint) (uint64, error) {
curr, err := s.GetCurrentSetID()
if err != nil {
return 0, err
Expand All @@ -217,11 +218,11 @@ func (s *GrandpaState) GetSetIDByBlockNumber(num uint) (uint64, error) {

// if the given block number is greater or equal to the block number of the set ID change,
// return the current set ID
if num <= changeUpper && num > changeLower {
if blockNumber <= changeUpper && blockNumber > changeLower {
kishansagathiya marked this conversation as resolved.
Show resolved Hide resolved
return curr, nil
}

if num > changeUpper {
if blockNumber > changeUpper {
kishansagathiya marked this conversation as resolved.
Show resolved Hide resolved
return curr + 1, nil
}

Expand Down
2 changes: 2 additions & 0 deletions lib/grandpa/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,8 @@ var (
// ErrNoJustification is returned when no justification can be found for a block, ie. it has not been finalised
ErrNoJustification = errors.New("no justification found for block")

ErrBlockHashMismatch = errors.New("block hash does not correspond to given block number")
qdm12 marked this conversation as resolved.
Show resolved Hide resolved

// ErrMinVotesNotMet is returned when the number of votes is less than the required minimum in a Justification
ErrMinVotesNotMet = errors.New("minimum number of votes not met in a Justification")

Expand Down
73 changes: 72 additions & 1 deletion lib/grandpa/message_handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"fmt"
"reflect"

"github.com/ChainSafe/chaindb"
"github.com/ChainSafe/gossamer/dot/network"
"github.com/ChainSafe/gossamer/dot/telemetry"
"github.com/ChainSafe/gossamer/dot/types"
Expand Down Expand Up @@ -104,6 +105,17 @@ func (h *MessageHandler) handleNeighbourMessage(msg *NeighbourMessage) error {
func (h *MessageHandler) handleCommitMessage(msg *CommitMessage) error {
logger.Debugf("received commit message, msg: %+v", msg)

err := verifyBlockHashAgainstBlockNumber(h.blockState, msg.Vote.Hash, uint(msg.Vote.Number))
if errors.Is(err, chaindb.ErrKeyNotFound) {
h.grandpa.tracker.addCommit(msg)
logger.Infof("we might not have synced to the given block %s yet: %s", msg.Vote.Hash, err)
return nil
}

if err != nil {
return err
qdm12 marked this conversation as resolved.
Show resolved Hide resolved
}
kishansagathiya marked this conversation as resolved.
Show resolved Hide resolved

containsPrecommitsSignedBy := make([]string, len(msg.AuthData))
for i, authData := range msg.AuthData {
containsPrecommitsSignedBy[i] = authData.AuthorityID.String()
Expand Down Expand Up @@ -184,6 +196,16 @@ func (h *MessageHandler) handleCatchUpResponse(msg *CatchUpResponse) error {
"received catch up response with hash %s for round %d and set id %d",
msg.Hash, msg.Round, msg.SetID)

err := verifyBlockHashAgainstBlockNumber(h.blockState, msg.Hash, uint(msg.Number))
if errors.Is(err, chaindb.ErrKeyNotFound) {
h.grandpa.tracker.addCatchUpResponse(msg)
logger.Infof("we might not have synced to the given block %s yet: %s", msg.Hash, err)
return nil
}
if err != nil {
return err
qdm12 marked this conversation as resolved.
Show resolved Hide resolved
}
kishansagathiya marked this conversation as resolved.
Show resolved Hide resolved

// TODO: re-add catch-up logic (#1531)
if true {
return nil
Expand Down Expand Up @@ -300,12 +322,13 @@ func (h *MessageHandler) verifyCommitMessageJustification(fm *CommitMessage) err

err := h.verifyJustification(just, fm.Round, h.grandpa.state.setID, precommit)
if err != nil {
logger.Errorf("could not verify justification: %s", err)
continue
}

isDescendant, err := h.blockState.IsDescendantOf(fm.Vote.Hash, just.Vote.Hash)
if err != nil {
logger.Warnf("verifyCommitMessageJustification: %s", err)
logger.Warnf("could not check for descendant: %s", err)
qdm12 marked this conversation as resolved.
Show resolved Hide resolved
continue
}

Expand All @@ -330,6 +353,17 @@ func (h *MessageHandler) verifyPreVoteJustification(msg *CatchUpResponse) (commo
voters := make(map[ed25519.PublicKeyBytes]map[common.Hash]int, len(msg.PreVoteJustification))
eqVotesByHash := make(map[common.Hash]map[ed25519.PublicKeyBytes]struct{})

for _, pvj := range msg.PreVoteJustification {
err := verifyBlockHashAgainstBlockNumber(h.blockState, pvj.Vote.Hash, uint(pvj.Vote.Number))
if errors.Is(err, chaindb.ErrKeyNotFound) {
h.grandpa.tracker.addCatchUpResponse(msg)
logger.Infof("we might not have synced to the given block %s yet: %s", pvj.Vote.Hash, err)
continue
}
if err != nil {
return common.Hash{}, err
qdm12 marked this conversation as resolved.
Show resolved Hide resolved
}
}
// identify equivocatory votes by hash
for _, justification := range msg.PreVoteJustification {
hashsToCount, ok := voters[justification.AuthorityID]
Expand Down Expand Up @@ -386,6 +420,18 @@ func (h *MessageHandler) verifyPreVoteJustification(msg *CatchUpResponse) (commo
}

func (h *MessageHandler) verifyPreCommitJustification(msg *CatchUpResponse) error {
for _, pcj := range msg.PreCommitJustification {
err := verifyBlockHashAgainstBlockNumber(h.blockState, pcj.Vote.Hash, uint(pcj.Vote.Number))
if errors.Is(err, chaindb.ErrKeyNotFound) {
h.grandpa.tracker.addCatchUpResponse(msg)
logger.Infof("we might not have synced to the given block %s yet: %s", pcj.Vote.Hash, err)
continue
}
if err != nil {
return err
qdm12 marked this conversation as resolved.
Show resolved Hide resolved
}
}

auths := make([]AuthData, len(msg.PreCommitJustification))
for i, pcj := range msg.PreCommitJustification {
auths[i] = AuthData{AuthorityID: pcj.AuthorityID}
Expand Down Expand Up @@ -562,6 +608,18 @@ func (s *Service) VerifyBlockJustification(hash common.Hash, justification []byt
return ErrMinVotesNotMet
}

err = verifyBlockHashAgainstBlockNumber(s.blockState, fj.Commit.Hash, uint(fj.Commit.Number))
if err != nil {
return err
qdm12 marked this conversation as resolved.
Show resolved Hide resolved
}

for _, preCommit := range fj.Commit.Precommits {
err := verifyBlockHashAgainstBlockNumber(s.blockState, preCommit.Vote.Hash, uint(preCommit.Vote.Number))
if err != nil {
return err
qdm12 marked this conversation as resolved.
Show resolved Hide resolved
}
}

err = s.blockState.SetFinalisedHash(hash, fj.Round, setID)
if err != nil {
return err
Expand All @@ -573,6 +631,19 @@ func (s *Service) VerifyBlockJustification(hash common.Hash, justification []byt
return nil
}

func verifyBlockHashAgainstBlockNumber(bs BlockState, hash common.Hash, number uint) error {
timwu20 marked this conversation as resolved.
Show resolved Hide resolved
timwu20 marked this conversation as resolved.
Show resolved Hide resolved
header, err := bs.GetHeader(hash)
if err != nil {
return fmt.Errorf("could not get header from block hash: %w", err)
kishansagathiya marked this conversation as resolved.
Show resolved Hide resolved
}

if header.Number != number {
return fmt.Errorf("%w: expected number %d from header but got number %d",
kishansagathiya marked this conversation as resolved.
Show resolved Hide resolved
ErrBlockHashMismatch, header.Number, number)
}
return nil
}

func isInAuthSet(auth *ed25519.PublicKey, set []types.GrandpaVoter) bool {
for _, a := range set {
if bytes.Equal(a.Key.Encode(), auth.Encode()) {
Expand Down
42 changes: 33 additions & 9 deletions lib/grandpa/message_handler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -250,7 +250,7 @@ func TestMessageHandler_VerifyJustification_InvalidSig(t *testing.T) {
func TestMessageHandler_CommitMessage_NoCatchUpRequest_ValidSig(t *testing.T) {
gs, st := newTestService(t)

round := uint64(77)
round := uint64(1)
gs.state.round = round
just := buildTestJustification(t, int(gs.state.threshold()), round, gs.state.setID, kr, precommit)
err := st.Grandpa.SetPrecommits(round, gs.state.setID, just)
Expand Down Expand Up @@ -505,6 +505,18 @@ func TestMessageHandler_VerifyPreVoteJustification(t *testing.T) {
telemetryMock.EXPECT().SendMessage(gomock.Any()).AnyTimes()

gs, st := newTestService(t)

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)

h := NewMessageHandler(gs, st.Block, telemetryMock)

just := buildTestJustification(t, int(gs.state.threshold()), 1, gs.state.setID, kr, prevote)
Expand All @@ -525,6 +537,18 @@ func TestMessageHandler_VerifyPreCommitJustification(t *testing.T) {
telemetryMock.EXPECT().SendMessage(gomock.Any()).AnyTimes()

gs, st := newTestService(t)

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)

h := NewMessageHandler(gs, st.Block, telemetryMock)

round := uint64(1)
Expand All @@ -537,7 +561,7 @@ func TestMessageHandler_VerifyPreCommitJustification(t *testing.T) {
Number: uint32(round),
}

err := h.verifyPreCommitJustification(msg)
err = h.verifyPreCommitJustification(msg)
require.NoError(t, err)
}

Expand All @@ -553,7 +577,7 @@ func TestMessageHandler_HandleCatchUpResponse(t *testing.T) {

h := NewMessageHandler(gs, st.Block, telemetryMock)

round := uint64(77)
round := uint64(1)
kishansagathiya marked this conversation as resolved.
Show resolved Hide resolved
gs.state.round = round + 1

pvJust := buildTestJustification(t, int(gs.state.threshold()), round, gs.state.setID, kr, prevote)
Expand Down Expand Up @@ -605,7 +629,7 @@ func TestMessageHandler_VerifyBlockJustification_WithEquivocatoryVotes(t *testin
}

gs, st := newTestService(t)
err := st.Grandpa.SetNextChange(auths, 1)
err := st.Grandpa.SetNextChange(auths, 0)
require.NoError(t, err)

body, err := types.NewBodyFromBytes([]byte{0})
Expand All @@ -623,8 +647,8 @@ func TestMessageHandler_VerifyBlockJustification_WithEquivocatoryVotes(t *testin
require.NoError(t, err)
require.Equal(t, uint64(1), setID)

round := uint64(2)
number := uint32(2)
round := uint64(1)
number := uint32(1)
precommits := buildTestJustification(t, 20, round, setID, kr, precommit)
just := newJustification(round, testHash, number, precommits)
data, err := scale.Marshal(*just)
Expand All @@ -647,7 +671,7 @@ func TestMessageHandler_VerifyBlockJustification(t *testing.T) {
}

gs, st := newTestService(t)
err := st.Grandpa.SetNextChange(auths, 1)
err := st.Grandpa.SetNextChange(auths, 0)
require.NoError(t, err)

body, err := types.NewBodyFromBytes([]byte{0})
Expand All @@ -667,8 +691,8 @@ func TestMessageHandler_VerifyBlockJustification(t *testing.T) {

genhash := st.Block.GenesisHash()

round := uint64(2)
number := uint32(2)
round := uint64(1)
number := uint32(1)
precommits := buildTestJustification(t, 2, round, setID, kr, precommit)
just := newJustification(round, testHash, number, precommits)
data, err := scale.Marshal(*just)
Expand Down