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 7 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
45 changes: 33 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,61 @@ 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) {
// first check that the block isn't already in the ready queue
Copy link
Contributor

Choose a reason for hiding this comment

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

nit unneeded

Suggested change
// first check that the block isn't already in the ready queue

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: err=%s", bd.Hash, err)
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
logger.Debugf("failed to check if header is known for hash %s: err=%s", bd.Hash, err)
logger.Debugf("failed to check if header is known for hash %s: %s", bd.Hash, err)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done!

return
}

if has {
// seems we already processed the block, so let's just return
Copy link
Contributor

Choose a reason for hiding this comment

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

nit unneded the log message says it already

Suggested change
// seems we already processed the block, so let's just return

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

Choose a reason for hiding this comment

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

nit does the following work?

Suggested change
type handleReadyBlockFunc = func(*types.BlockData)
type handleReadyBlockFunc func(*types.BlockData)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

yes it does, is there a preference for one?


// 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