Move incoming block from unresolved to pending if it can't be applied (#253)

* Move resolved block from unresolved to pending if it can't be applied

* Renamed unresolved to missing
This commit is contained in:
Yuriy Glukhov 2019-04-26 19:38:56 +03:00 committed by Jacek Sieka
parent ddcdb63430
commit 61ec6fa167
4 changed files with 21 additions and 16 deletions

View File

@ -652,7 +652,7 @@ proc onSlotStart(node: BeaconNode, lastSlot, scheduledSlot: Slot) {.gcsafe, asyn
asyncCheck node.onSlotStart(slot, nextSlot) asyncCheck node.onSlotStart(slot, nextSlot)
proc onSecond(node: BeaconNode, moment: Moment) {.async.} = proc onSecond(node: BeaconNode, moment: Moment) {.async.} =
let missingBlocks = node.blockPool.checkUnresolved() let missingBlocks = node.blockPool.checkMissing()
if missingBlocks.len > 0: if missingBlocks.len > 0:
info "Requesting detected missing blocks", missingBlocks info "Requesting detected missing blocks", missingBlocks
node.requestManager.fetchAncestorBlocks(missingBlocks) do (b: BeaconBlock): node.requestManager.fetchAncestorBlocks(missingBlocks) do (b: BeaconBlock):

View File

@ -158,7 +158,7 @@ type
## for - when we receive a "missing link", we can use this data to build ## for - when we receive a "missing link", we can use this data to build
## an entire branch ## an entire branch
unresolved*: Table[Eth2Digest, UnresolvedBlock] ##\ missing*: Table[Eth2Digest, MissingBlock] ##\
## Roots of blocks that we would like to have (either parent_root of ## Roots of blocks that we would like to have (either parent_root of
## unresolved blocks or block roots of attestations) ## unresolved blocks or block roots of attestations)
@ -180,7 +180,7 @@ type
db*: BeaconChainDB db*: BeaconChainDB
UnresolvedBlock* = object MissingBlock* = object
slots*: uint64 # number of slots that are suspected missing slots*: uint64 # number of slots that are suspected missing
tries*: int tries*: int

View File

@ -122,7 +122,7 @@ proc init*(T: type BlockPool, db: BeaconChainDB): BlockPool =
BlockPool( BlockPool(
pending: initTable[Eth2Digest, BeaconBlock](), pending: initTable[Eth2Digest, BeaconBlock](),
unresolved: initTable[Eth2Digest, UnresolvedBlock](), missing: initTable[Eth2Digest, MissingBlock](),
blocks: blocks, blocks: blocks,
blocksBySlot: blocksBySlot, blocksBySlot: blocksBySlot,
tail: tailRef, tail: tailRef,
@ -157,6 +157,8 @@ proc add*(
return pool.blocks[blockRoot] return pool.blocks[blockRoot]
pool.missing.del(blockRoot)
# If the block we get is older than what we finalized already, we drop it. # If the block we get is older than what we finalized already, we drop it.
# One way this can happen is that we start resolving a block and finalization # One way this can happen is that we start resolving a block and finalization
# happens in the meantime - the block we requested will then be stale # happens in the meantime - the block we requested will then be stale
@ -169,16 +171,17 @@ proc add*(
return return
let parent = pool.blocks.getOrDefault(blck.previous_block_root) let parent = pool.blocks.getOrDefault(blck.previous_block_root)
if parent != nil: if parent != nil:
# The block might have been in either of these - we don't want any more # The block might have been in either of these - we don't want any more
# work done on its behalf # work done on its behalf
pool.unresolved.del(blockRoot)
pool.pending.del(blockRoot) pool.pending.del(blockRoot)
# The block is resolved, now it's time to validate it to ensure that the # The block is resolved, now it's time to validate it to ensure that the
# blocks we add to the database are clean for the given state # blocks we add to the database are clean for the given state
# TODO if the block is from the future, we should not be resolving it (yet), # TODO if the block is from the future, we should not be resolving it (yet),
# but maybe we should use it as a hint that our clock is wrong? # but maybe we should use it as a hint that our clock is wrong?
updateState(pool, state, BlockSlot(blck: parent, slot: blck.slot - 1)) updateState(pool, state, BlockSlot(blck: parent, slot: blck.slot - 1))
@ -231,15 +234,18 @@ proc add*(
return blockRef return blockRef
pool.pending[blockRoot] = blck
# TODO possibly, it makes sense to check the database - that would allow sync # TODO possibly, it makes sense to check the database - that would allow sync
# to simply fill up the database with random blocks the other clients # to simply fill up the database with random blocks the other clients
# think are useful - but, it would also risk filling the database with # think are useful - but, it would also risk filling the database with
# junk that's not part of the block graph # junk that's not part of the block graph
if blck.previous_block_root in pool.unresolved: if blck.previous_block_root in pool.missing or
blck.previous_block_root in pool.pending:
return return
# This is an unresolved block - put it on the unresolved list for now... # This is an unresolved block - put its parent on the missing list for now...
# TODO if we receive spam blocks, one heurestic to implement might be to wait # TODO if we receive spam blocks, one heurestic to implement might be to wait
# for a couple of attestations to appear before fetching parents - this # for a couple of attestations to appear before fetching parents - this
# would help prevent using up network resources for spam - this serves # would help prevent using up network resources for spam - this serves
@ -251,13 +257,13 @@ proc add*(
# filter. # filter.
# TODO when we receive the block, we don't know how many others we're missing # TODO when we receive the block, we don't know how many others we're missing
# from that branch, so right now, we'll just do a blind guess # from that branch, so right now, we'll just do a blind guess
debug "Unresolved block", debug "Unresolved block (parent missing)",
blck = shortLog(blck), blck = shortLog(blck),
blockRoot = shortLog(blockRoot) blockRoot = shortLog(blockRoot)
let parentSlot = blck.slot - 1 let parentSlot = blck.slot - 1
pool.unresolved[blck.previous_block_root] = UnresolvedBlock( pool.missing[blck.previous_block_root] = MissingBlock(
slots: slots:
# The block is at least two slots ahead - try to grab whole history # The block is at least two slots ahead - try to grab whole history
if parentSlot > pool.head.slot: if parentSlot > pool.head.slot:
@ -268,7 +274,6 @@ proc add*(
max(1.uint64, SLOTS_PER_EPOCH.uint64 - max(1.uint64, SLOTS_PER_EPOCH.uint64 -
(parentSlot.uint64 mod SLOTS_PER_EPOCH.uint64)) (parentSlot.uint64 mod SLOTS_PER_EPOCH.uint64))
) )
pool.pending[blockRoot] = blck
proc get*(pool: BlockPool, blck: BlockRef): BlockData = proc get*(pool: BlockPool, blck: BlockRef): BlockData =
## Retrieve the associated block body of a block reference ## Retrieve the associated block body of a block reference
@ -294,18 +299,18 @@ proc getOrResolve*(pool: var BlockPool, root: Eth2Digest): BlockRef =
result = pool.blocks.getOrDefault(root) result = pool.blocks.getOrDefault(root)
if result.isNil: if result.isNil:
pool.unresolved[root] = UnresolvedBlock(slots: 1) pool.missing[root] = MissingBlock(slots: 1)
iterator blockRootsForSlot*(pool: BlockPool, slot: uint64|Slot): Eth2Digest = iterator blockRootsForSlot*(pool: BlockPool, slot: uint64|Slot): Eth2Digest =
for br in pool.blocksBySlot.getOrDefault(slot.uint64, @[]): for br in pool.blocksBySlot.getOrDefault(slot.uint64, @[]):
yield br.root yield br.root
proc checkUnresolved*(pool: var BlockPool): seq[FetchRecord] = proc checkMissing*(pool: var BlockPool): seq[FetchRecord] =
## Return a list of blocks that we should try to resolve from other client - ## Return a list of blocks that we should try to resolve from other client -
## to be called periodically but not too often (once per slot?) ## to be called periodically but not too often (once per slot?)
var done: seq[Eth2Digest] var done: seq[Eth2Digest]
for k, v in pool.unresolved.mpairs(): for k, v in pool.missing.mpairs():
if v.tries > 8: if v.tries > 8:
done.add(k) done.add(k)
else: else:
@ -314,10 +319,10 @@ proc checkUnresolved*(pool: var BlockPool): seq[FetchRecord] =
for k in done: for k in done:
# TODO Need to potentially remove from pool.pending - this is currently a # TODO Need to potentially remove from pool.pending - this is currently a
# memory leak here! # memory leak here!
pool.unresolved.del(k) pool.missing.del(k)
# simple (simplistic?) exponential backoff for retries.. # simple (simplistic?) exponential backoff for retries..
for k, v in pool.unresolved.pairs(): for k, v in pool.missing.pairs():
if v.tries.popcount() == 1: if v.tries.popcount() == 1:
result.add(FetchRecord(root: k, historySlots: v.slots)) result.add(FetchRecord(root: k, historySlots: v.slots))

View File

@ -63,7 +63,7 @@ suite "Block pool processing":
check: check:
pool.get(b2Root).isNone() # Unresolved, shouldn't show up pool.get(b2Root).isNone() # Unresolved, shouldn't show up
FetchRecord(root: b1Root, historySlots: 1) in pool.checkUnresolved() FetchRecord(root: b1Root, historySlots: 1) in pool.checkMissing()
discard pool.add(state, b1Root, b1) discard pool.add(state, b1Root, b1)