clean up block pool
* remove BlockPool.blocksBySlot (unused) * simplify head pruning condition * add head list smoke tests * additional logging
This commit is contained in:
parent
14e1e3af52
commit
7a8054d36d
|
@ -119,8 +119,6 @@ type
|
||||||
## Tree of blocks pointing back to a finalized block on the chain we're
|
## Tree of blocks pointing back to a finalized block on the chain we're
|
||||||
## interested in - we call that block the tail
|
## interested in - we call that block the tail
|
||||||
|
|
||||||
blocksBySlot*: Table[Slot, seq[BlockRef]]
|
|
||||||
|
|
||||||
tail*: BlockRef ##\
|
tail*: BlockRef ##\
|
||||||
## The earliest finalized block we know about
|
## The earliest finalized block we know about
|
||||||
|
|
||||||
|
|
|
@ -195,11 +195,6 @@ proc init*(T: type BlockPool, db: BeaconChainDB): BlockPool =
|
||||||
"state data missing for tail block, database corrupt?"
|
"state data missing for tail block, database corrupt?"
|
||||||
latestStateRoot = some((tailBlock.message.state_root, tailRef))
|
latestStateRoot = some((tailBlock.message.state_root, tailRef))
|
||||||
|
|
||||||
var blocksBySlot = initTable[Slot, seq[BlockRef]]()
|
|
||||||
for _, b in tables.pairs(blocks):
|
|
||||||
let slot = db.getBlock(b.root).get().message.slot
|
|
||||||
blocksBySlot.mgetOrPut(slot, @[]).add(b)
|
|
||||||
|
|
||||||
# TODO can't do straight init because in mainnet config, there are too
|
# TODO can't do straight init because in mainnet config, there are too
|
||||||
# many live beaconstates on the stack...
|
# many live beaconstates on the stack...
|
||||||
var tmpState = new Option[BeaconState]
|
var tmpState = new Option[BeaconState]
|
||||||
|
@ -225,13 +220,12 @@ proc init*(T: type BlockPool, db: BeaconChainDB): BlockPool =
|
||||||
|
|
||||||
debug "Block pool initialized",
|
debug "Block pool initialized",
|
||||||
head = head.blck, finalizedHead, tail = tailRef,
|
head = head.blck, finalizedHead, tail = tailRef,
|
||||||
totalBlocks = blocks.len, totalKnownSlots = blocksBySlot.len
|
totalBlocks = blocks.len
|
||||||
|
|
||||||
let res = BlockPool(
|
let res = BlockPool(
|
||||||
pending: initTable[Eth2Digest, SignedBeaconBlock](),
|
pending: initTable[Eth2Digest, SignedBeaconBlock](),
|
||||||
missing: initTable[Eth2Digest, MissingBlock](),
|
missing: initTable[Eth2Digest, MissingBlock](),
|
||||||
blocks: blocks,
|
blocks: blocks,
|
||||||
blocksBySlot: blocksBySlot,
|
|
||||||
tail: tailRef,
|
tail: tailRef,
|
||||||
head: head,
|
head: head,
|
||||||
finalizedHead: finalizedHead,
|
finalizedHead: finalizedHead,
|
||||||
|
@ -254,22 +248,6 @@ proc init*(T: type BlockPool, db: BeaconChainDB): BlockPool =
|
||||||
|
|
||||||
res
|
res
|
||||||
|
|
||||||
proc addSlotMapping(pool: BlockPool, br: BlockRef) =
|
|
||||||
proc addIfMissing(s: var seq[BlockRef], v: BlockRef) =
|
|
||||||
if v notin s:
|
|
||||||
s.add(v)
|
|
||||||
pool.blocksBySlot.mgetOrPut(br.slot, @[]).addIfMissing(br)
|
|
||||||
|
|
||||||
proc delSlotMapping(pool: BlockPool, br: BlockRef) =
|
|
||||||
var blks = pool.blocksBySlot.getOrDefault(br.slot)
|
|
||||||
if blks.len != 0:
|
|
||||||
let i = blks.find(br)
|
|
||||||
if i >= 0: blks.del(i)
|
|
||||||
if blks.len == 0:
|
|
||||||
pool.blocksBySlot.del(br.slot)
|
|
||||||
else:
|
|
||||||
pool.blocksBySlot[br.slot] = blks
|
|
||||||
|
|
||||||
proc addResolvedBlock(
|
proc addResolvedBlock(
|
||||||
pool: var BlockPool, state: var StateData, blockRoot: Eth2Digest,
|
pool: var BlockPool, state: var StateData, blockRoot: Eth2Digest,
|
||||||
signedBlock: SignedBeaconBlock, parent: BlockRef): BlockRef =
|
signedBlock: SignedBeaconBlock, parent: BlockRef): BlockRef =
|
||||||
|
@ -281,8 +259,6 @@ proc addResolvedBlock(
|
||||||
pool.blocks[blockRoot] = blockRef
|
pool.blocks[blockRoot] = blockRef
|
||||||
trace "Populating block pool", key = blockRoot, val = blockRef
|
trace "Populating block pool", key = blockRoot, val = blockRef
|
||||||
|
|
||||||
pool.addSlotMapping(blockRef)
|
|
||||||
|
|
||||||
# Resolved blocks should be stored in database
|
# Resolved blocks should be stored in database
|
||||||
pool.db.putBlock(blockRoot, signedBlock)
|
pool.db.putBlock(blockRoot, signedBlock)
|
||||||
|
|
||||||
|
@ -320,6 +296,7 @@ proc addResolvedBlock(
|
||||||
blockRoot = shortLog(blockRoot),
|
blockRoot = shortLog(blockRoot),
|
||||||
justifiedRoot = shortLog(foundHead.get().justified.blck.root),
|
justifiedRoot = shortLog(foundHead.get().justified.blck.root),
|
||||||
justifiedSlot = shortLog(foundHead.get().justified.slot),
|
justifiedSlot = shortLog(foundHead.get().justified.slot),
|
||||||
|
heads = pool.heads.len(),
|
||||||
cat = "filtering"
|
cat = "filtering"
|
||||||
|
|
||||||
# Now that we have the new block, we should see if any of the previously
|
# Now that we have the new block, we should see if any of the previously
|
||||||
|
@ -392,8 +369,8 @@ proc add*(
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
# The block might have been in either of these - we don't want any more
|
# The block might have been in either of pending or missing - we don't want
|
||||||
# work done on its behalf
|
# any more work done on its behalf
|
||||||
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
|
||||||
|
@ -443,11 +420,6 @@ 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 (parent missing)",
|
|
||||||
blck = shortLog(blck),
|
|
||||||
blockRoot = shortLog(blockRoot),
|
|
||||||
cat = "filtering"
|
|
||||||
|
|
||||||
let parentSlot = blck.slot - 1
|
let parentSlot = blck.slot - 1
|
||||||
|
|
||||||
pool.missing[blck.parent_root] = MissingBlock(
|
pool.missing[blck.parent_root] = MissingBlock(
|
||||||
|
@ -462,6 +434,14 @@ proc add*(
|
||||||
(parentSlot.uint64 mod SLOTS_PER_EPOCH.uint64))
|
(parentSlot.uint64 mod SLOTS_PER_EPOCH.uint64))
|
||||||
)
|
)
|
||||||
|
|
||||||
|
debug "Unresolved block (parent missing)",
|
||||||
|
blck = shortLog(blck),
|
||||||
|
blockRoot = shortLog(blockRoot),
|
||||||
|
pending = pool.pending.len,
|
||||||
|
missing = pool.missing.len,
|
||||||
|
cat = "filtering"
|
||||||
|
|
||||||
|
|
||||||
func getRef*(pool: BlockPool, root: Eth2Digest): BlockRef =
|
func getRef*(pool: BlockPool, root: Eth2Digest): BlockRef =
|
||||||
## Retrieve a resolved block reference, if available
|
## Retrieve a resolved block reference, if available
|
||||||
pool.blocks.getOrDefault(root, nil)
|
pool.blocks.getOrDefault(root, nil)
|
||||||
|
@ -564,10 +544,6 @@ func getOrResolve*(pool: var BlockPool, root: Eth2Digest): BlockRef =
|
||||||
if result.isNil:
|
if result.isNil:
|
||||||
pool.missing[root] = MissingBlock(slots: 1)
|
pool.missing[root] = MissingBlock(slots: 1)
|
||||||
|
|
||||||
iterator blockRootsForSlot*(pool: BlockPool, slot: Slot): Eth2Digest =
|
|
||||||
for br in pool.blocksBySlot.getOrDefault(slot, @[]):
|
|
||||||
yield br.root
|
|
||||||
|
|
||||||
func checkMissing*(pool: var BlockPool): seq[FetchRecord] =
|
func 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?)
|
||||||
|
@ -757,24 +733,27 @@ proc delFinalizedStateIfNeeded(pool: BlockPool, b: BlockRef) =
|
||||||
|
|
||||||
proc setTailBlock(pool: BlockPool, newTail: BlockRef) =
|
proc setTailBlock(pool: BlockPool, newTail: BlockRef) =
|
||||||
## Advance tail block, pruning all the states and blocks with older slots
|
## Advance tail block, pruning all the states and blocks with older slots
|
||||||
let oldTail = pool.tail
|
doAssert pool.tail.isAncestorOf(newTail),
|
||||||
let fromSlot = oldTail.slot.uint64
|
"tail blocks should have linear ancestry to previous tail"
|
||||||
let toSlot = newTail.slot.uint64 - 1
|
|
||||||
assert(toSlot > fromSlot)
|
|
||||||
for s in fromSlot .. toSlot:
|
|
||||||
for b in pool.blocksBySlot.getOrDefault(s.Slot, @[]):
|
|
||||||
pool.delBlockAndState(b.root)
|
|
||||||
b.children = @[]
|
|
||||||
b.parent = nil
|
|
||||||
pool.blocks.del(b.root)
|
|
||||||
pool.pending.del(b.root)
|
|
||||||
pool.missing.del(b.root)
|
|
||||||
|
|
||||||
pool.blocksBySlot.del(s.Slot)
|
var b = newTail
|
||||||
|
while b != pool.tail:
|
||||||
|
doAssert b.slot > pool.tail.slot
|
||||||
|
doAssert b.parent != nil
|
||||||
|
b = b.parent
|
||||||
|
|
||||||
|
pool.delBlockAndState(b.root)
|
||||||
|
|
||||||
|
doAssert b.children.len == 1, "chain to old tail should be finalized"
|
||||||
|
b.children.setLen(0)
|
||||||
|
b.parent = nil
|
||||||
|
|
||||||
|
pool.blocks.del(b.root)
|
||||||
|
|
||||||
pool.db.putTailBlock(newTail.root)
|
pool.db.putTailBlock(newTail.root)
|
||||||
pool.tail = newTail
|
pool.tail = newTail
|
||||||
pool.addSlotMapping(newTail)
|
newTail.parent = nil
|
||||||
|
|
||||||
info "Tail block updated",
|
info "Tail block updated",
|
||||||
slot = newTail.slot,
|
slot = newTail.slot,
|
||||||
root = shortLog(newTail.root)
|
root = shortLog(newTail.root)
|
||||||
|
@ -849,13 +828,6 @@ proc updateHead*(pool: BlockPool, newHead: BlockRef) =
|
||||||
"Block graph should always lead to a finalized block"
|
"Block graph should always lead to a finalized block"
|
||||||
|
|
||||||
if finalizedHead != pool.finalizedHead:
|
if finalizedHead != pool.finalizedHead:
|
||||||
info "Finalized block",
|
|
||||||
finalizedBlockRoot = shortLog(finalizedHead.blck.root),
|
|
||||||
finalizedBlockSlot = shortLog(finalizedHead.slot),
|
|
||||||
headBlockRoot = shortLog(newHead.root),
|
|
||||||
headBlockSlot = shortLog(newHead.slot),
|
|
||||||
cat = "fork_choice"
|
|
||||||
|
|
||||||
pool.finalizedHead = finalizedHead
|
pool.finalizedHead = finalizedHead
|
||||||
|
|
||||||
var cur = finalizedHead.blck
|
var cur = finalizedHead.blck
|
||||||
|
@ -875,7 +847,6 @@ proc updateHead*(pool: BlockPool, newHead: BlockRef) =
|
||||||
if child != cur:
|
if child != cur:
|
||||||
pool.blocks.del(child.root)
|
pool.blocks.del(child.root)
|
||||||
pool.delBlockAndState(child.root)
|
pool.delBlockAndState(child.root)
|
||||||
pool.delSlotMapping(child)
|
|
||||||
else:
|
else:
|
||||||
pool.delFinalizedStateIfNeeded(child)
|
pool.delFinalizedStateIfNeeded(child)
|
||||||
cur.parent.children = @[cur]
|
cur.parent.children = @[cur]
|
||||||
|
@ -884,11 +855,19 @@ proc updateHead*(pool: BlockPool, newHead: BlockRef) =
|
||||||
let hlen = pool.heads.len
|
let hlen = pool.heads.len
|
||||||
for i in 0..<hlen:
|
for i in 0..<hlen:
|
||||||
let n = hlen - i - 1
|
let n = hlen - i - 1
|
||||||
if pool.heads[n].blck.slot < pool.finalizedHead.blck.slot:
|
if not pool.finalizedHead.blck.isAncestorOf(pool.heads[n].blck):
|
||||||
# By definition, the current head should be newer than the finalized
|
# Any heads that are not derived from the newly finalized block are no
|
||||||
# head, so we'll never delete it here
|
# longer viable candidates for future head selection
|
||||||
pool.heads.del(n)
|
pool.heads.del(n)
|
||||||
|
|
||||||
|
info "Finalized block",
|
||||||
|
finalizedBlockRoot = shortLog(finalizedHead.blck.root),
|
||||||
|
finalizedBlockSlot = shortLog(finalizedHead.slot),
|
||||||
|
headBlockRoot = shortLog(newHead.root),
|
||||||
|
headBlockSlot = shortLog(newHead.slot),
|
||||||
|
heads = pool.heads.len,
|
||||||
|
cat = "fork_choice"
|
||||||
|
|
||||||
# Calculate new tail block and set it
|
# Calculate new tail block and set it
|
||||||
# New tail should be WEAK_SUBJECTIVITY_PERIOD * 2 older than finalizedHead
|
# New tail should be WEAK_SUBJECTIVITY_PERIOD * 2 older than finalizedHead
|
||||||
const tailSlotInterval = WEAK_SUBJECTVITY_PERIOD * 2
|
const tailSlotInterval = WEAK_SUBJECTVITY_PERIOD * 2
|
||||||
|
|
|
@ -104,7 +104,6 @@ when const_preset == "minimal": # Too much stack space used on mainnet
|
||||||
|
|
||||||
check:
|
check:
|
||||||
b0.isSome()
|
b0.isSome()
|
||||||
toSeq(pool.blockRootsForSlot(GENESIS_SLOT)) == @[pool.tail.root]
|
|
||||||
|
|
||||||
timedTest "Simple block add&get" & preset():
|
timedTest "Simple block add&get" & preset():
|
||||||
let
|
let
|
||||||
|
@ -115,6 +114,8 @@ when const_preset == "minimal": # Too much stack space used on mainnet
|
||||||
b1Get.isSome()
|
b1Get.isSome()
|
||||||
b1Get.get().refs.root == b1Root
|
b1Get.get().refs.root == b1Root
|
||||||
b1Add.root == b1Get.get().refs.root
|
b1Add.root == b1Get.get().refs.root
|
||||||
|
pool.heads.len == 1
|
||||||
|
pool.heads[0].blck == b1Add
|
||||||
|
|
||||||
let
|
let
|
||||||
b2Add = pool.add(b2Root, b2)
|
b2Add = pool.add(b2Root, b2)
|
||||||
|
@ -124,6 +125,8 @@ when const_preset == "minimal": # Too much stack space used on mainnet
|
||||||
b2Get.isSome()
|
b2Get.isSome()
|
||||||
b2Get.get().refs.root == b2Root
|
b2Get.get().refs.root == b2Root
|
||||||
b2Add.root == b2Get.get().refs.root
|
b2Add.root == b2Get.get().refs.root
|
||||||
|
pool.heads.len == 1
|
||||||
|
pool.heads[0].blck == b2Add
|
||||||
|
|
||||||
timedTest "Reverse order block add & get" & preset():
|
timedTest "Reverse order block add & get" & preset():
|
||||||
discard pool.add(b2Root, b2)
|
discard pool.add(b2Root, b2)
|
||||||
|
@ -144,8 +147,6 @@ when const_preset == "minimal": # Too much stack space used on mainnet
|
||||||
|
|
||||||
b1Get.get().refs.children[0] == b2Get.get().refs
|
b1Get.get().refs.children[0] == b2Get.get().refs
|
||||||
b2Get.get().refs.parent == b1Get.get().refs
|
b2Get.get().refs.parent == b1Get.get().refs
|
||||||
toSeq(pool.blockRootsForSlot(b1.message.slot)) == @[b1Root]
|
|
||||||
toSeq(pool.blockRootsForSlot(b2.message.slot)) == @[b2Root]
|
|
||||||
|
|
||||||
pool.updateHead(b2Get.get().refs)
|
pool.updateHead(b2Get.get().refs)
|
||||||
|
|
||||||
|
@ -164,6 +165,8 @@ when const_preset == "minimal": # Too much stack space used on mainnet
|
||||||
hash_tree_root(pool2.headState.data.data) == b2.message.state_root
|
hash_tree_root(pool2.headState.data.data) == b2.message.state_root
|
||||||
pool2.get(b1Root).isSome()
|
pool2.get(b1Root).isSome()
|
||||||
pool2.get(b2Root).isSome()
|
pool2.get(b2Root).isSome()
|
||||||
|
pool2.heads.len == 1
|
||||||
|
pool2.heads[0].blck.root == b2Root
|
||||||
|
|
||||||
timedTest "Can add same block twice" & preset():
|
timedTest "Can add same block twice" & preset():
|
||||||
let
|
let
|
||||||
|
|
Loading…
Reference in New Issue