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

wip: fork choice #168

Closed
wants to merge 2 commits into from
Closed
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
27 changes: 23 additions & 4 deletions beacon_chain/attestation_pool.nim
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,12 @@ import
./beacon_chain_db, ./ssz, ./block_pool,
beacon_node_types


proc init*(T: type AttestationPool, blockPool: BlockPool): T =
T(
slots: initDeque[SlotData](),
blockPool: blockPool,
unresolved: initTable[Eth2Digest, UnresolvedAttestation]()
unresolved: initTable[Eth2Digest, UnresolvedAttestation](),
latestAttestations: initTable[ValidatorPubKey, BlockRef]()
)

proc overlaps(a, b: seq[byte]): bool =
Expand Down Expand Up @@ -183,6 +183,16 @@ proc slotIndex(

int(attestationSlot - pool.startingSlot)

proc updateLatestVotes(
pool: var AttestationPool, state: BeaconState, attestationSlot: Slot,
participants: seq[ValidatorIndex], blck: BlockRef) =
for validator in participants:
let
pubKey = state.validator_registry[validator].pubkey
current = pool.latestAttestations.getOrDefault(pubKey)
if current.isNil or current.slot < attestationSlot:
pool.latestAttestations[pubKey] = blck

proc add*(pool: var AttestationPool,
state: BeaconState,
attestation: Attestation) =
Expand All @@ -199,6 +209,8 @@ proc add*(pool: var AttestationPool,
aggregation_bitfield: attestation.aggregation_bitfield,
custody_bitfield: attestation.custody_bitfield,
aggregate_signature: attestation.aggregate_signature)
participants = get_attestation_participants(
state, attestation.data, validation.aggregation_bitfield)

var found = false
for a in slotData.attestations.mitems():
Expand All @@ -215,13 +227,14 @@ proc add*(pool: var AttestationPool,
debug "Ignoring overlapping attestation",
existingParticipants = get_attestation_participants(
state, a.data, v.aggregation_bitfield),
newParticipants = get_attestation_participants(
state, a.data, validation.aggregation_bitfield)
newParticipants = participants
found = true
break

if not found:
a.validations.add(validation)
pool.updateLatestVotes(state, attestationSlot, participants, a.blck)

info "Attestation resolved",
slot = humaneSlotNum(attestation.data.slot),
shard = attestation.data.shard,
Expand All @@ -243,6 +256,8 @@ proc add*(pool: var AttestationPool,
blck: blck,
validations: @[validation]
))
pool.updateLatestVotes(state, attestationSlot, participants, blck)

info "Attestation resolved",
slot = humaneSlotNum(attestation.data.slot),
shard = attestation.data.shard,
Expand Down Expand Up @@ -332,3 +347,7 @@ proc resolve*(pool: var AttestationPool, state: BeaconState) =

for a in resolved:
pool.add(state, a)

proc latestAttestation*(
pool: AttestationPool, pubKey: ValidatorPubKey): BlockRef =
pool.latestAttestations.getOrDefault(pubKey)
3 changes: 2 additions & 1 deletion beacon_chain/beacon_chain_db.nim
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,13 @@ import

type
BeaconChainDB* = ref object
## DB of finalized blocks
backend: TrieDatabaseRef

DbKeyKind = enum
kHashToState
kHashToBlock
kHeadBlock # Pointer to the most recent block seen
kHeadBlock # Pointer to the most recent finalized block seen
kTailBlock # Pointer to the earliest finalized block

func subkey(kind: DbKeyKind): array[1, byte] =
Expand Down
52 changes: 7 additions & 45 deletions beacon_chain/beacon_node.nim
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,9 @@ import
chronos, chronicles, confutils,
spec/[datatypes, digest, crypto, beaconstate, helpers, validator], conf, time,
state_transition, fork_choice, ssz, beacon_chain_db, validator_pool, extras,
attestation_pool, block_pool, eth2_network,
attestation_pool, block_pool, eth2_network, beacon_node_types,
mainchain_monitor, trusted_state_snapshots,
eth/trie/db, eth/trie/backends/rocksdb_backend,
beacon_node_types
eth/trie/db, eth/trie/backends/rocksdb_backend

const
topicBeaconBlocks = "ethereum/2.1/beacon_chain/blocks"
Expand Down Expand Up @@ -169,41 +168,13 @@ proc getAttachedValidator(node: BeaconNode, idx: int): AttachedValidator =
return node.attachedValidators.getValidator(validatorKey)

proc updateHead(node: BeaconNode) =
# TODO placeholder logic for running the fork choice
var
head = node.state.blck
headSlot = node.state.data.slot

# LRB fork choice - latest resolved block :)
for ph in node.potentialHeads:
let blck = node.blockPool.get(ph)
if blck.isNone():
continue
if blck.get().data.slot >= headSlot:
head = blck.get().refs
headSlot = blck.get().data.slot
node.potentialHeads.setLen(0)

if head.root == node.state.blck.root:
debug "No new head found",
stateRoot = shortLog(node.state.root),
blockRoot = shortLog(node.state.blck.root),
stateSlot = humaneSlotNum(node.state.data.slot)
return

node.blockPool.updateState(node.state, head)
let
justifiedHead = node.blockPool.latestJustifiedBlock()

# TODO this should probably be in blockpool, but what if updateState is
# called with a non-head block?
node.db.putHeadBlock(node.state.blck.root)
node.blockPool.updateState(node.state, justifiedHead)

# TODO we should save the state every now and then, but which state do we
# save? When we receive a block and process it, the state from a
# particular epoch may become finalized - but we no longer have it!
# One thing that would work would be to replay from some earlier
# state (the tail?) to the new finalized state, then save that. Another
# option would be to simply save every epoch start state, and eventually
# point it out as it becomes finalized..
let newHead = lmdGhost(node.attestationPool, node.state.data, justifiedHead)
node.blockPool.updateHead(node.state, newHead)

info "Updated head",
stateRoot = shortLog(node.state.root),
Expand Down Expand Up @@ -524,9 +495,6 @@ proc onAttestation(node: BeaconNode, attestation: Attestation) =

node.attestationPool.add(node.state.data, attestation)

if attestation.data.beacon_block_root notin node.potentialHeads:
node.potentialHeads.add attestation.data.beacon_block_root

proc onBeaconBlock(node: BeaconNode, blck: BeaconBlock) =
# We received a block but don't know much about it yet - in particular, we
# don't know if it's part of the chain we're currently building.
Expand Down Expand Up @@ -556,12 +524,6 @@ proc onBeaconBlock(node: BeaconNode, blck: BeaconBlock) =
# pre-emptively fetched is quite ugly - fix.
node.fetchBlocks(@[blck.parent_root])

# Delay updating the head until the latest moment possible - this makes it
# more likely that we've managed to resolve the block, in case of
# irregularities
if blockRoot notin node.potentialHeads:
node.potentialHeads.add blockRoot

# The block we received contains attestations, and we might not yet know about
# all of them. Let's add them to the attestation pool - in case they block
# is not yet resolved, neither will the attestations be!
Expand Down
14 changes: 14 additions & 0 deletions beacon_chain/beacon_node_types.nim
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,8 @@ type

unresolved*: Table[Eth2Digest, UnresolvedAttestation]

latestAttestations*: Table[ValidatorPubKey, BlockRef]

# #############################################
#
# Block Pool
Expand Down Expand Up @@ -151,6 +153,13 @@ type
tail*: BlockData ##\
## The earliest finalized block we know about

head*: BlockData ##\
## The latest block we know about, that's been chosen as a head by the fork
## choice rule

finalizedHead*: BlockData ##\
## The latest block that was finalized according to the block in head

db*: BeaconChainDB

UnresolvedBlock* = object
Expand All @@ -169,6 +178,11 @@ type

children*: seq[BlockRef]

slot*: Slot # TODO could calculate this by walking to root, but..

justified*: bool
finalized*: bool

BlockData* = object
## Body and graph in one

Expand Down
89 changes: 78 additions & 11 deletions beacon_chain/block_pool.nim
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import
bitops, chronicles, options, tables,
bitops, chronicles, options, sequtils, tables,
ssz, beacon_chain_db, state_transition, extras,
spec/[crypto, datatypes, digest],
beacon_node_types
beacon_node_types,
spec/[crypto, datatypes, digest, helpers]

proc link(parent, child: BlockRef) =
doAssert (not (parent.root == Eth2Digest() or child.root == Eth2Digest())),
Expand All @@ -12,39 +12,52 @@ proc link(parent, child: BlockRef) =
child.parent = parent
parent.children.add(child)

proc init*(T: type BlockRef, root: Eth2Digest, slot: Slot): BlockRef =
BlockRef(
root: root,
slot: slot
)

proc init*(T: type BlockRef, root: Eth2Digest, blck: BeaconBlock): BlockRef =
BlockRef.init(root, blck.slot)

proc init*(T: type BlockPool, db: BeaconChainDB): BlockPool =
# TODO we require that the db contains both a head and a tail block -
# asserting here doesn't seem like the right way to go about it however..
# TODO head is updated outside of block pool but read here - ugly.

let
tail = db.getTailBlock()
head = db.getHeadBlock()
head = db.getHeadBlock() # Note: we assume only finalized blocks are in DB

doAssert tail.isSome(), "Missing tail block, database corrupt?"
doAssert head.isSome(), "Missing head block, database corrupt?"

let
headRoot = head.get()
headBlock = db.getBlock(headRoot)
headRef = BlockRef.init(headRoot, headBlock.get())
tailRoot = tail.get()
tailRef = BlockRef(root: tailRoot)
tailBlock = db.getBlock(tailRoot)
tailRef = BlockRef.init(tailRoot, tailBlock.get())

var blocks = {tailRef.root: tailRef}.toTable()

if headRoot != tailRoot:
var curRef: BlockRef

for root, _ in db.getAncestors(headRoot):
for root, blck in db.getAncestors(headRoot):
if root == tailRef.root:
assert(not curRef.isNil)
link(tailRef, curRef)
curRef = curRef.parent
break

let newRef = BlockRef.init(root, blck)
if curRef == nil:
curRef = BlockRef(root: root)
curRef = newRef
else:
link(BlockRef(root: root), curRef)
link(newRef, curRef)
curRef = curRef.parent
blocks[curRef.root] = curRef

Expand All @@ -65,6 +78,10 @@ proc init*(T: type BlockPool, db: BeaconChainDB): BlockPool =
data: db.getBlock(tailRef.root).get(),
refs: tailRef,
),
finalizedHead: BlockData(
data: db.getBlock(headRef.root).get(),
refs: headRef,
),
db: db
)

Expand Down Expand Up @@ -139,9 +156,9 @@ proc add*(
voluntary_exits = blck.body.voluntary_exits.len,
transfers = blck.body.transfers.len

let blockRef = BlockRef(
root: blockRoot
)
return

let blockRef = BlockRef.init(blockRoot, blck)
link(parent, blockRef)

pool.blocks[blockRoot] = blockRef
Expand Down Expand Up @@ -363,3 +380,53 @@ proc loadTailState*(pool: BlockPool): StateData =
root: pool.tail.data.state_root,
blck: pool.tail.refs
)

proc findAncestorBySlot(blck: BlockRef, slot: Slot): BlockRef =
result = blck

while result != nil and result.slot > slot:
result = result.parent

proc updateHead*(pool: BlockPool, state: var StateData, blck: BlockRef) =
# Start off by making sure we have the right state
updateState(pool, state, blck)

let
# TODO there might not be a block at the epoch boundary - what then?
finalizedHead =
blck.findAncestorBySlot(state.data.finalized_epoch.get_epoch_start_slot())
justifiedHead =
blck.findAncestorBySlot(state.data.justified_epoch.get_epoch_start_slot())

doAssert (not finalizedHead.isNil),
"Block graph should always lead to a finalized block"
doAssert (not justifiedHead.isNil),
"Block graph should always lead to a finalized block"

# Hasn't changed necessarily, but we set it anyway just to be safe
justifiedHead.justified = true

var cur = finalizedHead
while cur != pool.finalizedHead.refs:
cur.parent.children.keepItIf(it == cur)
cur = cur.parent

pool.finalizedHead = BlockData(
data: pool.db.getBlock(finalizedHead.root).get(),
refs: finalizedHead
)

proc findLatestJustifiedBlock(
blck: BlockRef, depth: int, deepest: var tuple[depth: int, blck: BlockRef]) =
if blck.justified and depth > deepest.depth:
deepest = (depth, blck)

for child in blck.children:
findLatestJustifiedBlock(child, depth + 1, deepest)

proc latestJustifiedBlock*(pool: BlockPool): BlockRef =
var deepest = (0, pool.finalizedHead.refs)

findLatestJustifiedBlock(pool.finalizedHead.refs, 0, deepest)

deepest[1]
Loading