Skip to content

fix(dot/sync): fix "block with unknown header is ready" error #2191

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

Merged
merged 8 commits into from
Jan 13, 2022
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
43 changes: 31 additions & 12 deletions dot/sync/chain_sync.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,11 @@ import (
"sync"
"time"

"github.com/ChainSafe/gossamer/dot/peerset"
"github.com/ChainSafe/chaindb"
"github.com/libp2p/go-libp2p-core/peer"

"github.com/ChainSafe/gossamer/dot/network"
"github.com/ChainSafe/gossamer/dot/peerset"
"github.com/ChainSafe/gossamer/dot/types"
"github.com/ChainSafe/gossamer/lib/common"
"github.com/ChainSafe/gossamer/lib/common/variadic"
Expand Down Expand Up @@ -522,7 +523,7 @@ func (cs *chainSync) setMode(mode chainSyncState) {
case bootstrap:
cs.handler = newBootstrapSyncer(cs.blockState)
case tip:
cs.handler = newTipSyncer(cs.blockState, cs.pendingBlocks, cs.readyBlocks)
cs.handler = newTipSyncer(cs.blockState, cs.pendingBlocks, cs.readyBlocks, cs.handleReadyBlock)
}

cs.state = mode
Expand Down Expand Up @@ -692,41 +693,59 @@ func (cs *chainSync) doSync(req *network.BlockRequestMessage, peersTried map[pee
// response was validated! place into ready block queue
for _, bd := range resp.BlockData {
// block is ready to be processed!
handleReadyBlock(bd, cs.pendingBlocks, cs.readyBlocks)
cs.handleReadyBlock(bd)
}

return nil
}

func handleReadyBlock(bd *types.BlockData, pendingBlocks DisjointBlockSet, readyBlocks *blockQueue) {
// see if there are any descendents in the pending queue that are now ready to be processed,
// as we have just become aware of their parent block
func (cs *chainSync) handleReadyBlock(bd *types.BlockData) {
if cs.readyBlocks.has(bd.Hash) {
logger.Tracef("ignoring block %s in response, already in ready queue", bd.Hash)
return
}

// if header was not requested, get it from the pending set
// if we're expecting headers, validate should ensure we have a header
if bd.Header == nil {
block := pendingBlocks.getBlock(bd.Hash)
block := cs.pendingBlocks.getBlock(bd.Hash)
if block == nil {
logger.Criticalf("block with unknown header is ready: hash=%s", bd.Hash)
// block wasn't in the pending set!
// let's check the db as maybe we already processed it
has, err := cs.blockState.HasHeader(bd.Hash)
if err != nil && !errors.Is(err, chaindb.ErrKeyNotFound) {
logger.Debugf("failed to check if header is known for hash %s: %s", bd.Hash, err)
return
}

if has {
logger.Tracef("ignoring block we've already processed, hash=%s", bd.Hash)
return
}

// this is bad and shouldn't happen
logger.Errorf("block with unknown header is ready: hash=%s", bd.Hash)
Comment on lines +726 to +727
Copy link
Contributor

Choose a reason for hiding this comment

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

Would this happen if errors.Is(err, chaindb.ErrKeyNotFound)? 🤔

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@qdm12 yeah this case would be hit if the error is ErrKeyNotFound, maybe I'm misunderstanding the question? haha

return
}

bd.Header = block.header
}

if bd.Header == nil {
logger.Criticalf("new ready block number (unknown) with hash %s", bd.Hash)
logger.Errorf("new ready block number (unknown) with hash %s", bd.Hash)
return
}

logger.Tracef("new ready block number %s with hash %s", bd.Header.Number, bd.Hash)

// see if there are any descendents in the pending queue that are now ready to be processed,
// as we have just become aware of their parent block
ready := []*types.BlockData{bd}
ready = pendingBlocks.getReadyDescendants(bd.Hash, ready)
ready = cs.pendingBlocks.getReadyDescendants(bd.Hash, ready)

for _, rb := range ready {
pendingBlocks.removeBlock(rb.Hash)
readyBlocks.push(rb)
cs.pendingBlocks.removeBlock(rb.Hash)
cs.readyBlocks.push(rb)
}
}

Expand Down
5 changes: 4 additions & 1 deletion dot/sync/chain_sync_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -662,6 +662,7 @@ func TestChainSync_doSync(t *testing.T) {
resp := &network.BlockResponseMessage{
BlockData: []*types.BlockData{
{
Hash: common.Hash{0x1},
Header: &types.Header{
Number: big.NewInt(1),
},
Expand All @@ -687,13 +688,15 @@ func TestChainSync_doSync(t *testing.T) {
resp = &network.BlockResponseMessage{
BlockData: []*types.BlockData{
{
Hash: common.Hash{0x3},
Header: &types.Header{
ParentHash: parent,
Number: big.NewInt(3),
},
Body: &types.Body{},
},
{
Hash: common.Hash{0x2},
Header: &types.Header{
Number: big.NewInt(2),
},
Expand Down Expand Up @@ -762,7 +765,7 @@ func TestHandleReadyBlock(t *testing.T) {
}
cs.pendingBlocks.addBlock(block2NotDescendant)

handleReadyBlock(block1.ToBlockData(), cs.pendingBlocks, cs.readyBlocks)
cs.handleReadyBlock(block1.ToBlockData())

require.False(t, cs.pendingBlocks.hasBlock(header1.Hash()))
require.False(t, cs.pendingBlocks.hasBlock(header2.Hash()))
Expand Down
22 changes: 14 additions & 8 deletions dot/sync/tip_syncer.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,23 +8,29 @@ import (
"math/big"

"github.com/ChainSafe/gossamer/dot/network"
"github.com/ChainSafe/gossamer/dot/types"
"github.com/ChainSafe/gossamer/lib/common"
)

var _ workHandler = &tipSyncer{}

type handleReadyBlockFunc func(*types.BlockData)

// tipSyncer handles workers when syncing at the tip of the chain
type tipSyncer struct {
blockState BlockState
pendingBlocks DisjointBlockSet
readyBlocks *blockQueue
blockState BlockState
pendingBlocks DisjointBlockSet
readyBlocks *blockQueue
handleReadyBlock handleReadyBlockFunc
}

func newTipSyncer(blockState BlockState, pendingBlocks DisjointBlockSet, readyBlocks *blockQueue) *tipSyncer {
func newTipSyncer(blockState BlockState, pendingBlocks DisjointBlockSet, readyBlocks *blockQueue,
handleReadyBlock handleReadyBlockFunc) *tipSyncer {
return &tipSyncer{
blockState: blockState,
pendingBlocks: pendingBlocks,
readyBlocks: readyBlocks,
blockState: blockState,
pendingBlocks: pendingBlocks,
readyBlocks: readyBlocks,
handleReadyBlock: handleReadyBlock,
}
}

Expand Down Expand Up @@ -210,7 +216,7 @@ func (s *tipSyncer) handleTick() ([]*worker, error) {
if has || s.readyBlocks.has(block.header.ParentHash) {
// block is ready, as parent is known!
// also, move any pendingBlocks that are descendants of this block to the ready blocks queue
handleReadyBlock(block.toBlockData(), s.pendingBlocks, s.readyBlocks)
s.handleReadyBlock(block.toBlockData())
continue
}

Expand Down
8 changes: 7 additions & 1 deletion dot/sync/tip_syncer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,13 @@ func newTestTipSyncer(t *testing.T) *tipSyncer {

readyBlocks := newBlockQueue(maxResponseSize)
pendingBlocks := newDisjointBlockSet(pendingBlocksLimit)
return newTipSyncer(bs, pendingBlocks, readyBlocks)
cs := &chainSync{
blockState: bs,
readyBlocks: readyBlocks,
pendingBlocks: pendingBlocks,
}

return newTipSyncer(bs, pendingBlocks, readyBlocks, cs.handleReadyBlock)
}

func TestTipSyncer_handleNewPeerState(t *testing.T) {
Expand Down