2019-02-28 21:21:29 +00:00
|
|
|
import
|
initial 0.9.0 spec sync (#509)
* rename compute_epoch_of_slot(...) to compute_epoch_at_slot(...)
* remove some unnecessary imports; remove some crosslink-related code and tests; complete renaming of compute_epoch_of_slot(...) to compute_epoch_at_slot(...)
* rm more transfer-related code and tests; rm more unnecessary strutils imports
* rm remaining unused imports
* remove useless get_empty_per_epoch_cache(...)/compute_start_slot_of_epoch(...) calls
* rename compute_start_slot_of_epoch(...) to compute_start_slot_at_epoch(...)
* rename ACTIVATION_EXIT_DELAY to MAX_SEED_LOOKAHEAD
* update domain types to 0.9.0
* mark AttesterSlashing, IndexedAttestation, AttestationDataAndCustodyBit, DepositData, BeaconBlockHeader, Fork, integer_squareroot(...), and process_voluntary_exit(...) as 0.9.0
* mark increase_balance(...), decrease_balance(...), get_block_root(...), CheckPoint, Deposit, PendingAttestation, HistoricalBatch, is_active_validator(...), and is_slashable_attestation_data(...) as 0.9.0
* mark compute_activation_exit_epoch(...), bls_verify(...), Validator, get_active_validator_indices(...), get_current_epoch(...), get_total_active_balance(...), and get_previous_epoch(...) as 0.9.0
* mark get_block_root_at_slot(...), ProposerSlashing, get_domain(...), VoluntaryExit, mainnet preset Gwei values, minimal preset max operations, process_block_header(...), and is_slashable_validator(...) as 0.9.0
* mark makeWithdrawalCredentials(...), get_validator_churn_limit(...), get_total_balance(...), is_valid_indexed_attestation(...), bls_aggregate_pubkeys(...), initial genesis value/constants, Attestation, get_randao_mix(...), mainnet preset max operations per block constants, minimal preset Gwei values and time parameters, process_eth1_data(...), get_shuffled_seq(...), compute_committee(...), and process_slots(...) as 0.9.0; partially update get_indexed_attestation(...) to 0.9.0 by removing crosslink refs and associated tests
* mark initiate_validator_exit(...), process_registry_updates(...), BeaconBlock, Eth1Data, compute_domain(...), process_randao(...), process_attester_slashing(...), get_base_reward(...), and process_slot(...) as 0.9.0
2019-10-30 19:41:19 +00:00
|
|
|
bitops, chronicles, options, tables,
|
2020-04-20 17:27:52 +00:00
|
|
|
ssz, beacon_chain_db, state_transition, extras, kvstore,
|
2019-10-22 11:57:34 +00:00
|
|
|
beacon_node_types, metrics,
|
2020-02-07 07:13:38 +00:00
|
|
|
spec/[crypto, datatypes, digest, helpers, validator]
|
2019-02-28 21:21:29 +00:00
|
|
|
|
2019-10-22 11:57:34 +00:00
|
|
|
declareCounter beacon_reorgs_total, "Total occurrences of reorganizations of the chain" # On fork choice
|
|
|
|
|
2019-09-12 01:45:04 +00:00
|
|
|
logScope: topics = "blkpool"
|
|
|
|
|
2019-12-19 13:02:28 +00:00
|
|
|
proc updateStateData*(
|
|
|
|
pool: BlockPool, state: var StateData, bs: BlockSlot) {.gcsafe.}
|
|
|
|
proc add*(
|
|
|
|
pool: var BlockPool, blockRoot: Eth2Digest,
|
|
|
|
signedBlock: SignedBeaconBlock): BlockRef {.gcsafe.}
|
|
|
|
|
|
|
|
template withState*(
|
|
|
|
pool: BlockPool, cache: var StateData, blockSlot: BlockSlot, body: untyped): untyped =
|
|
|
|
## Helper template that updates state to a particular BlockSlot - usage of
|
|
|
|
## cache is unsafe outside of block.
|
|
|
|
## TODO async transformations will lead to a race where cache gets updated
|
|
|
|
## while waiting for future to complete - catch this here somehow?
|
|
|
|
|
|
|
|
updateStateData(pool, cache, blockSlot)
|
|
|
|
|
|
|
|
template hashedState(): HashedBeaconState {.inject, used.} = cache.data
|
|
|
|
template state(): BeaconState {.inject, used.} = cache.data.data
|
|
|
|
template blck(): BlockRef {.inject, used.} = cache.blck
|
|
|
|
template root(): Eth2Digest {.inject, used.} = cache.data.root
|
|
|
|
|
|
|
|
body
|
|
|
|
|
2019-11-21 09:15:10 +00:00
|
|
|
func parent*(bs: BlockSlot): BlockSlot =
|
2019-12-23 15:34:09 +00:00
|
|
|
## Return a blockslot representing the previous slot, using the parent block
|
|
|
|
## if the current slot had a block
|
|
|
|
if bs.slot == Slot(0):
|
|
|
|
BlockSlot(blck: nil, slot: Slot(0))
|
|
|
|
else:
|
|
|
|
BlockSlot(
|
|
|
|
blck: if bs.slot > bs.blck.slot: bs.blck else: bs.blck.parent,
|
|
|
|
slot: bs.slot - 1
|
|
|
|
)
|
2019-03-28 06:10:48 +00:00
|
|
|
|
2019-11-21 09:15:10 +00:00
|
|
|
func link(parent, child: BlockRef) =
|
2019-02-28 21:21:29 +00:00
|
|
|
doAssert (not (parent.root == Eth2Digest() or child.root == Eth2Digest())),
|
|
|
|
"blocks missing root!"
|
|
|
|
doAssert parent.root != child.root, "self-references not allowed"
|
|
|
|
|
|
|
|
child.parent = parent
|
|
|
|
parent.children.add(child)
|
|
|
|
|
2019-12-13 13:54:26 +00:00
|
|
|
func isAncestorOf*(a, b: BlockRef): bool =
|
|
|
|
var b = b
|
|
|
|
var depth = 0
|
|
|
|
const maxDepth = (100'i64 * 365 * 24 * 60 * 60 div SECONDS_PER_SLOT.int)
|
|
|
|
while true:
|
|
|
|
if a == b: return true
|
|
|
|
|
|
|
|
# for now, use an assert for block chain length since a chain this long
|
|
|
|
# indicates a circular reference here..
|
|
|
|
doAssert depth < maxDepth
|
|
|
|
depth += 1
|
|
|
|
|
|
|
|
if a.slot >= b.slot or b.parent.isNil:
|
|
|
|
return false
|
|
|
|
|
|
|
|
doAssert b.slot > b.parent.slot
|
|
|
|
b = b.parent
|
|
|
|
|
2019-12-23 15:34:09 +00:00
|
|
|
func getAncestorAt*(blck: BlockRef, slot: Slot): BlockRef =
|
|
|
|
## Return the most recent block as of the time at `slot` that not more recent
|
|
|
|
## than `blck` itself
|
|
|
|
|
|
|
|
var blck = blck
|
|
|
|
|
|
|
|
var depth = 0
|
|
|
|
const maxDepth = (100'i64 * 365 * 24 * 60 * 60 div SECONDS_PER_SLOT.int)
|
|
|
|
|
|
|
|
while true:
|
|
|
|
if blck.slot <= slot:
|
|
|
|
return blck
|
|
|
|
|
|
|
|
if blck.parent.isNil:
|
|
|
|
return nil
|
|
|
|
|
|
|
|
doAssert depth < maxDepth
|
|
|
|
depth += 1
|
|
|
|
|
|
|
|
blck = blck.parent
|
|
|
|
|
|
|
|
func get_ancestor*(blck: BlockRef, slot: Slot): BlockRef =
|
2020-04-11 17:41:50 +00:00
|
|
|
## https://github.com/ethereum/eth2.0-specs/blob/v0.11.1/specs/phase0/fork-choice.md#get_ancestor
|
2019-12-23 15:34:09 +00:00
|
|
|
## Return ancestor at slot, or nil if queried block is older
|
2019-12-19 13:02:28 +00:00
|
|
|
var blck = blck
|
|
|
|
|
|
|
|
var depth = 0
|
|
|
|
const maxDepth = (100'i64 * 365 * 24 * 60 * 60 div SECONDS_PER_SLOT.int)
|
|
|
|
|
|
|
|
while true:
|
|
|
|
if blck.slot == slot:
|
|
|
|
return blck
|
|
|
|
|
|
|
|
if blck.slot < slot:
|
|
|
|
return nil
|
|
|
|
|
2019-12-23 15:34:09 +00:00
|
|
|
if blck.parent.isNil:
|
2019-12-19 13:02:28 +00:00
|
|
|
return nil
|
|
|
|
|
|
|
|
doAssert depth < maxDepth
|
|
|
|
depth += 1
|
|
|
|
|
|
|
|
blck = blck.parent
|
|
|
|
|
2019-12-23 15:34:09 +00:00
|
|
|
func atSlot*(blck: BlockRef, slot: Slot): BlockSlot =
|
|
|
|
## Return a BlockSlot at a given slot, with the block set to the closest block
|
|
|
|
## available. If slot comes from before the block, a suitable block ancestor
|
|
|
|
## will be used, else blck is returned as if all slots after it were empty.
|
|
|
|
## This helper is useful when imagining what the chain looked like at a
|
|
|
|
## particular moment in time, or when imagining what it will look like in the
|
|
|
|
## near future if nothing happens (such as when looking ahead for the next
|
|
|
|
## block proposal)
|
|
|
|
BlockSlot(blck: blck.getAncestorAt(slot), slot: slot)
|
|
|
|
|
2019-11-21 09:15:10 +00:00
|
|
|
func init*(T: type BlockRef, root: Eth2Digest, slot: Slot): BlockRef =
|
2019-03-13 22:59:20 +00:00
|
|
|
BlockRef(
|
|
|
|
root: root,
|
|
|
|
slot: slot
|
|
|
|
)
|
|
|
|
|
2019-11-21 09:15:10 +00:00
|
|
|
func init*(T: type BlockRef, root: Eth2Digest, blck: BeaconBlock): BlockRef =
|
2019-03-13 22:59:20 +00:00
|
|
|
BlockRef.init(root, blck.slot)
|
|
|
|
|
2019-11-21 09:15:10 +00:00
|
|
|
func findAncestorBySlot*(blck: BlockRef, slot: Slot): BlockSlot =
|
2019-03-14 13:33:56 +00:00
|
|
|
## Find the first ancestor that has a slot number less than or equal to `slot`
|
2019-11-22 15:47:08 +00:00
|
|
|
doAssert(not blck.isNil)
|
2019-05-01 09:19:29 +00:00
|
|
|
var ret = blck
|
2019-03-13 22:59:20 +00:00
|
|
|
|
2019-05-01 09:19:29 +00:00
|
|
|
while ret.parent != nil and ret.slot > slot:
|
|
|
|
ret = ret.parent
|
2019-03-13 22:59:20 +00:00
|
|
|
|
2019-05-01 09:19:29 +00:00
|
|
|
BlockSlot(blck: ret, slot: slot)
|
2019-03-22 10:57:19 +00:00
|
|
|
|
2019-02-28 21:21:29 +00:00
|
|
|
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..
|
|
|
|
|
|
|
|
let
|
2019-05-01 09:19:29 +00:00
|
|
|
tailBlockRoot = db.getTailBlock()
|
|
|
|
headBlockRoot = db.getHeadBlock()
|
2019-02-28 21:21:29 +00:00
|
|
|
|
2019-05-01 09:19:29 +00:00
|
|
|
doAssert tailBlockRoot.isSome(), "Missing tail block, database corrupt?"
|
|
|
|
doAssert headBlockRoot.isSome(), "Missing head block, database corrupt?"
|
2019-02-28 21:21:29 +00:00
|
|
|
|
|
|
|
let
|
2019-05-01 09:19:29 +00:00
|
|
|
tailRoot = tailBlockRoot.get()
|
2019-03-13 22:59:20 +00:00
|
|
|
tailBlock = db.getBlock(tailRoot).get()
|
2019-12-16 18:08:50 +00:00
|
|
|
tailRef = BlockRef.init(tailRoot, tailBlock.message)
|
2019-05-01 09:19:29 +00:00
|
|
|
headRoot = headBlockRoot.get()
|
2019-02-28 21:21:29 +00:00
|
|
|
|
2019-03-13 22:59:20 +00:00
|
|
|
var
|
|
|
|
blocks = {tailRef.root: tailRef}.toTable()
|
2020-01-15 11:35:54 +00:00
|
|
|
latestStateRoot = Option[tuple[stateRoot: Eth2Digest, blckRef: BlockRef]]()
|
2019-03-13 22:59:20 +00:00
|
|
|
headRef: BlockRef
|
2019-02-28 21:21:29 +00:00
|
|
|
|
|
|
|
if headRoot != tailRoot:
|
|
|
|
var curRef: BlockRef
|
|
|
|
|
2019-03-13 22:59:20 +00:00
|
|
|
for root, blck in db.getAncestors(headRoot):
|
2019-02-28 21:21:29 +00:00
|
|
|
if root == tailRef.root:
|
2019-03-13 23:04:43 +00:00
|
|
|
doAssert(not curRef.isNil)
|
2019-02-28 21:21:29 +00:00
|
|
|
link(tailRef, curRef)
|
|
|
|
curRef = curRef.parent
|
|
|
|
break
|
|
|
|
|
2019-12-16 18:08:50 +00:00
|
|
|
let newRef = BlockRef.init(root, blck.message)
|
2019-02-28 21:21:29 +00:00
|
|
|
if curRef == nil:
|
2019-03-13 22:59:20 +00:00
|
|
|
curRef = newRef
|
|
|
|
headRef = newRef
|
2019-02-28 21:21:29 +00:00
|
|
|
else:
|
2019-03-13 22:59:20 +00:00
|
|
|
link(newRef, curRef)
|
2019-02-28 21:21:29 +00:00
|
|
|
curRef = curRef.parent
|
|
|
|
blocks[curRef.root] = curRef
|
2019-12-03 11:07:43 +00:00
|
|
|
trace "Populating block pool", key = curRef.root, val = curRef
|
2019-02-28 21:21:29 +00:00
|
|
|
|
2019-12-16 18:08:50 +00:00
|
|
|
if latestStateRoot.isNone() and db.containsState(blck.message.state_root):
|
2020-01-15 11:35:54 +00:00
|
|
|
latestStateRoot = some((blck.message.state_root, curRef))
|
2019-03-13 22:59:20 +00:00
|
|
|
|
2019-02-28 21:21:29 +00:00
|
|
|
doAssert curRef == tailRef,
|
|
|
|
"head block does not lead to tail, database corrupt?"
|
2019-03-13 22:59:20 +00:00
|
|
|
else:
|
|
|
|
headRef = tailRef
|
2019-02-28 21:21:29 +00:00
|
|
|
|
2020-01-15 11:35:54 +00:00
|
|
|
if latestStateRoot.isNone():
|
|
|
|
doAssert db.containsState(tailBlock.message.state_root),
|
|
|
|
"state data missing for tail block, database corrupt?"
|
|
|
|
latestStateRoot = some((tailBlock.message.state_root, tailRef))
|
|
|
|
|
2019-12-19 13:02:28 +00:00
|
|
|
# TODO can't do straight init because in mainnet config, there are too
|
|
|
|
# many live beaconstates on the stack...
|
|
|
|
var tmpState = new Option[BeaconState]
|
|
|
|
|
2020-01-15 11:35:54 +00:00
|
|
|
# We're only saving epoch boundary states in the database right now, so when
|
|
|
|
# we're loading the head block, the corresponding state does not necessarily
|
|
|
|
# exist in the database - we'll load this latest state we know about and use
|
|
|
|
# that as finalization point.
|
|
|
|
tmpState[] = db.getState(latestStateRoot.get().stateRoot)
|
2019-03-13 22:59:20 +00:00
|
|
|
let
|
2020-01-15 11:35:54 +00:00
|
|
|
finalizedSlot =
|
2020-01-23 12:23:57 +00:00
|
|
|
tmpState[].get().finalized_checkpoint.epoch.compute_start_slot_at_epoch()
|
2020-01-15 11:35:54 +00:00
|
|
|
finalizedHead = headRef.findAncestorBySlot(finalizedSlot)
|
More 0.8.0 updates (#311)
* replace BeaconState.finalized_{epoch,root} with BeaconState.finalized_checkpoint; rename get_delayed_activation_exit_epoch(...) to compute_activation_exit_epoch(...) and mark as 0.8.0; update get_churn_limit(...)/get_validator_churn_limit(...) to 0.8.0; update process_registry_updates(...) to 0.8.0
* update process_crosslinks(...) to 0.8.0; mark compute_start_slot_of_epoch(...) and get_committee_count(...) as 0.8.0
* mark Fork, is_slashable_validator(...), and get_beacon_proposer_index(...) as 0.8.0
* rename LATEST_SLASHED_EXIT_LENGTH to EPOCHS_PER_SLASHINGS_VECTOR; update process_slashings(...) to 0.8.0; remove pointless type conversion warning in get_previous_epoch(...)
* convert remaining references to finalized_epoch to finalized_checkpoint.epoch
* update slash_validator(...) to 0.8.0; mark inital value, Gwei, and time constants as 0.8.0; mark hash(...) and processBlockHeader(...) as 0.8.0
* rename WHISTLEBLOWING_REWARD_QUOTIENT to WHISTLEBLOWER_REWARD_QUOTIENT; rename LATEST_ACTIVE_INDEX_ROOTS_LENGTH to EPOCHS_PER_HISTORICAL_VECTOR (randao will also get merged into this); remove get_active_index_root(...); mark time parameter, signature domain types, and max operations per block constants as 0.8.0; update rewards and penalties constants to 0.8.0
* update is_valid_indexed_attestation(...) to 0.8.0; mark process_slot(...) as 0.8.0
* replace BeaconState.{current,previous}_justified_{epoch,root} with BeaconState.{current,previous}_justified_checkpoint
2019-07-05 08:30:05 +00:00
|
|
|
justifiedSlot =
|
2019-12-19 13:02:28 +00:00
|
|
|
tmpState[].get().current_justified_checkpoint.epoch.compute_start_slot_at_epoch()
|
2019-05-01 09:19:29 +00:00
|
|
|
justifiedHead = headRef.findAncestorBySlot(justifiedSlot)
|
|
|
|
head = Head(blck: headRef, justified: justifiedHead)
|
2019-12-19 13:02:28 +00:00
|
|
|
justifiedBlock = db.getBlock(justifiedHead.blck.root).get()
|
|
|
|
justifiedStateRoot = justifiedBlock.message.state_root
|
2019-03-13 22:59:20 +00:00
|
|
|
|
|
|
|
doAssert justifiedHead.slot >= finalizedHead.slot,
|
|
|
|
"justified head comes before finalized head - database corrupt?"
|
|
|
|
|
2019-11-15 14:09:25 +00:00
|
|
|
debug "Block pool initialized",
|
|
|
|
head = head.blck, finalizedHead, tail = tailRef,
|
2020-01-20 17:06:46 +00:00
|
|
|
totalBlocks = blocks.len
|
2019-11-15 14:09:25 +00:00
|
|
|
|
2019-12-19 13:02:28 +00:00
|
|
|
let res = BlockPool(
|
2019-12-16 18:08:50 +00:00
|
|
|
pending: initTable[Eth2Digest, SignedBeaconBlock](),
|
2019-04-26 16:38:56 +00:00
|
|
|
missing: initTable[Eth2Digest, MissingBlock](),
|
2020-04-20 17:27:52 +00:00
|
|
|
|
|
|
|
# Usually one of the other of these will get re-initialized if the pool's
|
|
|
|
# initialized on an epoch boundary, but that is a reasonable readability,
|
|
|
|
# simplicity, and non-special-casing tradeoff for the inefficiency.
|
|
|
|
cachedStates: [
|
|
|
|
init(BeaconChainDB, kvStore MemoryStoreRef.init()),
|
|
|
|
init(BeaconChainDB, kvStore MemoryStoreRef.init())
|
|
|
|
],
|
|
|
|
|
2019-02-28 21:21:29 +00:00
|
|
|
blocks: blocks,
|
2019-03-13 22:59:20 +00:00
|
|
|
tail: tailRef,
|
2019-05-01 09:19:29 +00:00
|
|
|
head: head,
|
2019-03-13 22:59:20 +00:00
|
|
|
finalizedHead: finalizedHead,
|
2019-05-01 09:19:29 +00:00
|
|
|
db: db,
|
2019-12-19 13:02:28 +00:00
|
|
|
heads: @[head],
|
2019-02-28 21:21:29 +00:00
|
|
|
)
|
|
|
|
|
2019-12-19 13:02:28 +00:00
|
|
|
res.headState = StateData(
|
2020-01-15 11:35:54 +00:00
|
|
|
data: HashedBeaconState(
|
|
|
|
data: tmpState[].get(), root: latestStateRoot.get().stateRoot),
|
|
|
|
blck: latestStateRoot.get().blckRef)
|
|
|
|
|
|
|
|
res.updateStateData(res.headState, BlockSlot(blck: head.blck, slot: head.blck.slot))
|
2019-12-19 13:02:28 +00:00
|
|
|
res.tmpState = res.headState
|
|
|
|
|
|
|
|
tmpState[] = db.getState(justifiedStateRoot)
|
|
|
|
res.justifiedState = StateData(
|
|
|
|
data: HashedBeaconState(data: tmpState[].get(), root: justifiedStateRoot),
|
|
|
|
blck: justifiedHead.blck)
|
|
|
|
|
|
|
|
res
|
|
|
|
|
2019-05-01 09:19:29 +00:00
|
|
|
proc addResolvedBlock(
|
2020-02-05 11:41:46 +00:00
|
|
|
pool: var BlockPool, state: BeaconState, blockRoot: Eth2Digest,
|
2019-12-16 18:08:50 +00:00
|
|
|
signedBlock: SignedBeaconBlock, parent: BlockRef): BlockRef =
|
2019-09-12 01:45:04 +00:00
|
|
|
logScope: pcs = "block_resolution"
|
2020-02-05 11:41:46 +00:00
|
|
|
doAssert state.slot == signedBlock.message.slot, "state must match block"
|
2019-09-12 01:45:04 +00:00
|
|
|
|
2019-12-16 18:08:50 +00:00
|
|
|
let blockRef = BlockRef.init(blockRoot, signedBlock.message)
|
2019-05-01 09:19:29 +00:00
|
|
|
link(parent, blockRef)
|
|
|
|
|
|
|
|
pool.blocks[blockRoot] = blockRef
|
2019-12-03 11:07:43 +00:00
|
|
|
trace "Populating block pool", key = blockRoot, val = blockRef
|
2019-05-01 09:19:29 +00:00
|
|
|
|
|
|
|
# Resolved blocks should be stored in database
|
2019-12-16 18:08:50 +00:00
|
|
|
pool.db.putBlock(blockRoot, signedBlock)
|
2019-05-01 09:19:29 +00:00
|
|
|
|
|
|
|
# This block *might* have caused a justification - make sure we stow away
|
|
|
|
# that information:
|
More 0.8.0 updates (#311)
* replace BeaconState.finalized_{epoch,root} with BeaconState.finalized_checkpoint; rename get_delayed_activation_exit_epoch(...) to compute_activation_exit_epoch(...) and mark as 0.8.0; update get_churn_limit(...)/get_validator_churn_limit(...) to 0.8.0; update process_registry_updates(...) to 0.8.0
* update process_crosslinks(...) to 0.8.0; mark compute_start_slot_of_epoch(...) and get_committee_count(...) as 0.8.0
* mark Fork, is_slashable_validator(...), and get_beacon_proposer_index(...) as 0.8.0
* rename LATEST_SLASHED_EXIT_LENGTH to EPOCHS_PER_SLASHINGS_VECTOR; update process_slashings(...) to 0.8.0; remove pointless type conversion warning in get_previous_epoch(...)
* convert remaining references to finalized_epoch to finalized_checkpoint.epoch
* update slash_validator(...) to 0.8.0; mark inital value, Gwei, and time constants as 0.8.0; mark hash(...) and processBlockHeader(...) as 0.8.0
* rename WHISTLEBLOWING_REWARD_QUOTIENT to WHISTLEBLOWER_REWARD_QUOTIENT; rename LATEST_ACTIVE_INDEX_ROOTS_LENGTH to EPOCHS_PER_HISTORICAL_VECTOR (randao will also get merged into this); remove get_active_index_root(...); mark time parameter, signature domain types, and max operations per block constants as 0.8.0; update rewards and penalties constants to 0.8.0
* update is_valid_indexed_attestation(...) to 0.8.0; mark process_slot(...) as 0.8.0
* replace BeaconState.{current,previous}_justified_{epoch,root} with BeaconState.{current,previous}_justified_checkpoint
2019-07-05 08:30:05 +00:00
|
|
|
let justifiedSlot =
|
2020-02-05 11:41:46 +00:00
|
|
|
state.current_justified_checkpoint.epoch.compute_start_slot_at_epoch()
|
2019-05-01 09:19:29 +00:00
|
|
|
|
|
|
|
var foundHead: Option[Head]
|
|
|
|
for head in pool.heads.mitems():
|
2019-12-13 13:54:26 +00:00
|
|
|
if head.blck.isAncestorOf(blockRef):
|
2019-05-01 09:19:29 +00:00
|
|
|
if head.justified.slot != justifiedSlot:
|
|
|
|
head.justified = blockRef.findAncestorBySlot(justifiedSlot)
|
|
|
|
|
2019-12-13 13:54:26 +00:00
|
|
|
head.blck = blockRef
|
|
|
|
|
2019-05-01 09:19:29 +00:00
|
|
|
foundHead = some(head)
|
|
|
|
break
|
|
|
|
|
|
|
|
if foundHead.isNone():
|
|
|
|
foundHead = some(Head(
|
|
|
|
blck: blockRef,
|
|
|
|
justified: blockRef.findAncestorBySlot(justifiedSlot)))
|
|
|
|
pool.heads.add(foundHead.get())
|
|
|
|
|
|
|
|
info "Block resolved",
|
2019-12-16 18:08:50 +00:00
|
|
|
blck = shortLog(signedBlock.message),
|
2019-05-01 09:19:29 +00:00
|
|
|
blockRoot = shortLog(blockRoot),
|
|
|
|
justifiedRoot = shortLog(foundHead.get().justified.blck.root),
|
2019-09-12 01:45:04 +00:00
|
|
|
justifiedSlot = shortLog(foundHead.get().justified.slot),
|
2020-01-20 17:06:46 +00:00
|
|
|
heads = pool.heads.len(),
|
2019-09-12 01:45:04 +00:00
|
|
|
cat = "filtering"
|
2019-05-01 09:19:29 +00:00
|
|
|
|
|
|
|
# Now that we have the new block, we should see if any of the previously
|
|
|
|
# unresolved blocks magically become resolved
|
|
|
|
# TODO there are more efficient ways of doing this that don't risk
|
|
|
|
# running out of stack etc
|
2019-11-27 15:19:41 +00:00
|
|
|
# TODO This code is convoluted because when there are more than ~1.5k
|
|
|
|
# blocks being synced, there's a stack overflow as `add` gets called
|
|
|
|
# for the whole chain of blocks. Instead we use this ugly field in `pool`
|
|
|
|
# which could be avoided by refactoring the code
|
|
|
|
if not pool.inAdd:
|
|
|
|
pool.inAdd = true
|
|
|
|
defer: pool.inAdd = false
|
|
|
|
var keepGoing = true
|
|
|
|
while keepGoing:
|
|
|
|
let retries = pool.pending
|
|
|
|
for k, v in retries:
|
2019-12-19 13:02:28 +00:00
|
|
|
discard pool.add(k, v)
|
2019-11-27 15:19:41 +00:00
|
|
|
# Keep going for as long as the pending pool is shrinking
|
|
|
|
# TODO inefficient! so what?
|
|
|
|
keepGoing = pool.pending.len < retries.len
|
2019-05-01 09:19:29 +00:00
|
|
|
blockRef
|
|
|
|
|
2019-03-08 16:40:17 +00:00
|
|
|
proc add*(
|
2019-12-19 13:02:28 +00:00
|
|
|
pool: var BlockPool, blockRoot: Eth2Digest,
|
2019-12-16 18:08:50 +00:00
|
|
|
signedBlock: SignedBeaconBlock): BlockRef {.gcsafe.} =
|
2019-03-22 15:49:37 +00:00
|
|
|
## return the block, if resolved...
|
2019-03-08 16:40:17 +00:00
|
|
|
## the state parameter may be updated to include the given block, if
|
|
|
|
## everything checks out
|
|
|
|
# TODO reevaluate passing the state in like this
|
2019-12-16 18:08:50 +00:00
|
|
|
let blck = signedBlock.message
|
|
|
|
doAssert blockRoot == hash_tree_root(blck)
|
2019-02-28 21:21:29 +00:00
|
|
|
|
2019-09-12 01:45:04 +00:00
|
|
|
logScope: pcs = "block_addition"
|
|
|
|
|
2019-02-28 21:21:29 +00:00
|
|
|
# Already seen this block??
|
|
|
|
if blockRoot in pool.blocks:
|
|
|
|
debug "Block already exists",
|
2019-03-14 13:33:56 +00:00
|
|
|
blck = shortLog(blck),
|
2019-09-12 01:45:04 +00:00
|
|
|
blockRoot = shortLog(blockRoot),
|
|
|
|
cat = "filtering"
|
2019-02-28 21:21:29 +00:00
|
|
|
|
2019-03-22 15:49:37 +00:00
|
|
|
return pool.blocks[blockRoot]
|
2019-02-28 21:21:29 +00:00
|
|
|
|
2019-04-26 16:38:56 +00:00
|
|
|
pool.missing.del(blockRoot)
|
|
|
|
|
2019-03-13 22:59:20 +00:00
|
|
|
# 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
|
|
|
|
# happens in the meantime - the block we requested will then be stale
|
|
|
|
# by the time it gets here.
|
|
|
|
if blck.slot <= pool.finalizedHead.slot:
|
2019-02-28 21:21:29 +00:00
|
|
|
debug "Old block, dropping",
|
2019-03-14 13:33:56 +00:00
|
|
|
blck = shortLog(blck),
|
2019-08-15 16:01:55 +00:00
|
|
|
tailSlot = shortLog(pool.tail.slot),
|
2019-09-12 01:45:04 +00:00
|
|
|
blockRoot = shortLog(blockRoot),
|
|
|
|
cat = "filtering"
|
2019-02-28 21:21:29 +00:00
|
|
|
|
2019-03-22 15:49:37 +00:00
|
|
|
return
|
2019-02-28 21:21:29 +00:00
|
|
|
|
2019-06-14 13:50:47 +00:00
|
|
|
let parent = pool.blocks.getOrDefault(blck.parent_root)
|
2019-02-28 21:21:29 +00:00
|
|
|
|
|
|
|
if parent != nil:
|
2019-12-19 13:02:28 +00:00
|
|
|
if parent.slot >= blck.slot:
|
|
|
|
# TODO Malicious block? inform peer pool?
|
|
|
|
notice "Invalid block slot",
|
|
|
|
blck = shortLog(blck),
|
|
|
|
blockRoot = shortLog(blockRoot),
|
|
|
|
parentRoot = shortLog(parent.root),
|
|
|
|
parentSlot = shortLog(parent.slot)
|
|
|
|
|
|
|
|
return
|
|
|
|
|
2020-01-20 17:06:46 +00:00
|
|
|
# The block might have been in either of pending or missing - we don't want
|
|
|
|
# any more work done on its behalf
|
2019-03-08 16:40:17 +00:00
|
|
|
pool.pending.del(blockRoot)
|
|
|
|
|
|
|
|
# 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
|
2019-04-26 16:38:56 +00:00
|
|
|
|
2019-03-22 15:49:37 +00:00
|
|
|
# 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?
|
2019-12-19 13:02:28 +00:00
|
|
|
updateStateData(pool, pool.tmpState, BlockSlot(blck: parent, slot: blck.slot - 1))
|
2020-02-29 15:15:44 +00:00
|
|
|
if not state_transition(pool.tmpState.data, signedBlock, {}):
|
2019-03-08 16:40:17 +00:00
|
|
|
# TODO find a better way to log all this block data
|
|
|
|
notice "Invalid block",
|
2019-03-14 13:33:56 +00:00
|
|
|
blck = shortLog(blck),
|
2019-09-12 01:45:04 +00:00
|
|
|
blockRoot = shortLog(blockRoot),
|
|
|
|
cat = "filtering"
|
2019-03-08 16:40:17 +00:00
|
|
|
|
2019-03-13 22:59:20 +00:00
|
|
|
return
|
2020-02-05 11:41:46 +00:00
|
|
|
# Careful, tmpState.data has been updated but not blck - we need to create
|
|
|
|
# the BlockRef first!
|
|
|
|
pool.tmpState.blck = pool.addResolvedBlock(
|
|
|
|
pool.tmpState.data.data, blockRoot, signedBlock, parent)
|
|
|
|
|
|
|
|
return pool.tmpState.blck
|
2019-02-28 21:21:29 +00:00
|
|
|
|
2019-12-16 18:08:50 +00:00
|
|
|
# TODO already checked hash though? main reason to keep this is because
|
|
|
|
# the pending pool calls this function back later in a loop, so as long
|
|
|
|
# as pool.add(...) requires a SignedBeaconBlock, easier to keep them in
|
|
|
|
# pending too.
|
|
|
|
pool.pending[blockRoot] = signedBlock
|
2019-04-26 16:38:56 +00:00
|
|
|
|
2019-02-28 21:21:29 +00:00
|
|
|
# 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
|
|
|
|
# think are useful - but, it would also risk filling the database with
|
|
|
|
# junk that's not part of the block graph
|
|
|
|
|
2019-06-14 13:50:47 +00:00
|
|
|
if blck.parent_root in pool.missing or
|
|
|
|
blck.parent_root in pool.pending:
|
2019-03-22 15:49:37 +00:00
|
|
|
return
|
2019-02-28 21:21:29 +00:00
|
|
|
|
2019-04-26 16:38:56 +00:00
|
|
|
# This is an unresolved block - put its parent on the missing list for now...
|
2019-03-09 04:23:14 +00:00
|
|
|
# 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
|
|
|
|
# would help prevent using up network resources for spam - this serves
|
|
|
|
# two purposes: one is that attestations are likely to appear for the
|
|
|
|
# block only if it's valid / not spam - the other is that malicious
|
|
|
|
# validators that are not proposers can sign invalid blocks and send
|
|
|
|
# them out without penalty - but signing invalid attestations carries
|
|
|
|
# a risk of being slashed, making attestations a more valuable spam
|
|
|
|
# filter.
|
2019-03-27 20:17:01 +00:00
|
|
|
# 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
|
|
|
|
let parentSlot = blck.slot - 1
|
|
|
|
|
2019-06-14 13:50:47 +00:00
|
|
|
pool.missing[blck.parent_root] = MissingBlock(
|
2019-03-27 20:17:01 +00:00
|
|
|
slots:
|
|
|
|
# The block is at least two slots ahead - try to grab whole history
|
2019-05-01 09:19:29 +00:00
|
|
|
if parentSlot > pool.head.blck.slot:
|
|
|
|
parentSlot - pool.head.blck.slot
|
2019-03-27 20:17:01 +00:00
|
|
|
else:
|
|
|
|
# It's a sibling block from a branch that we're missing - fetch one
|
|
|
|
# epoch at a time
|
|
|
|
max(1.uint64, SLOTS_PER_EPOCH.uint64 -
|
|
|
|
(parentSlot.uint64 mod SLOTS_PER_EPOCH.uint64))
|
|
|
|
)
|
2019-02-28 21:21:29 +00:00
|
|
|
|
2020-01-20 17:06:46 +00:00
|
|
|
debug "Unresolved block (parent missing)",
|
|
|
|
blck = shortLog(blck),
|
|
|
|
blockRoot = shortLog(blockRoot),
|
|
|
|
pending = pool.pending.len,
|
|
|
|
missing = pool.missing.len,
|
|
|
|
cat = "filtering"
|
|
|
|
|
2019-11-21 09:15:10 +00:00
|
|
|
func getRef*(pool: BlockPool, root: Eth2Digest): BlockRef =
|
2019-06-10 11:13:53 +00:00
|
|
|
## Retrieve a resolved block reference, if available
|
2019-11-25 23:39:33 +00:00
|
|
|
pool.blocks.getOrDefault(root, nil)
|
2019-06-10 11:13:53 +00:00
|
|
|
|
2020-04-20 17:58:44 +00:00
|
|
|
proc getBlockRange*(
|
2020-04-21 06:43:39 +00:00
|
|
|
pool: BlockPool, startSlot: Slot, skipStep: Natural,
|
|
|
|
output: var openArray[BlockRef]): Natural =
|
2019-09-10 05:50:37 +00:00
|
|
|
## This function populates an `output` buffer of blocks
|
2020-04-21 06:43:39 +00:00
|
|
|
## with a slots ranging from `startSlot` up to, but not including,
|
|
|
|
## `startSlot + skipStep * output.len`, skipping any slots that don't have
|
|
|
|
## a block.
|
2019-09-10 05:50:37 +00:00
|
|
|
##
|
2020-04-21 06:43:39 +00:00
|
|
|
## Blocks will be written to `output` from the end without gaps, even if
|
|
|
|
## a block is missing in a particular slot. The return value shows how
|
|
|
|
## many slots were missing blocks - to iterate over the result, start
|
|
|
|
## at this index.
|
|
|
|
##
|
|
|
|
## If there were no blocks in the range, `output.len` will be returned.
|
|
|
|
let count = output.len
|
2020-04-20 17:58:44 +00:00
|
|
|
trace "getBlockRange entered",
|
|
|
|
head = shortLog(pool.head.blck.root), count, startSlot, skipStep
|
2019-11-26 17:02:27 +00:00
|
|
|
|
2020-04-20 17:58:44 +00:00
|
|
|
let
|
2020-04-21 06:43:39 +00:00
|
|
|
skipStep = max(1, skipStep) # Treat 0 step as 1
|
2020-04-20 17:58:44 +00:00
|
|
|
endSlot = startSlot + uint64(count * skipStep)
|
2019-11-26 17:02:27 +00:00
|
|
|
|
2020-04-20 17:58:44 +00:00
|
|
|
var
|
|
|
|
b = pool.head.blck.atSlot(endSlot)
|
2020-04-21 06:43:39 +00:00
|
|
|
o = count
|
2020-04-20 17:58:44 +00:00
|
|
|
for i in 0..<count:
|
|
|
|
for j in 0..<skipStep:
|
2019-09-10 05:50:37 +00:00
|
|
|
b = b.parent
|
2020-04-20 17:58:44 +00:00
|
|
|
if b.blck.slot == b.slot:
|
2020-04-21 06:43:39 +00:00
|
|
|
output[o - 1] = b.blck
|
|
|
|
dec o
|
|
|
|
|
|
|
|
# Make sure the given input is cleared, just in case
|
|
|
|
for i in 0..<o:
|
|
|
|
output[i] = nil
|
2019-09-10 05:50:37 +00:00
|
|
|
|
2020-04-21 06:43:39 +00:00
|
|
|
o # Return the index of the first non-nil item in the output
|
2019-09-10 05:50:37 +00:00
|
|
|
|
2020-03-16 22:28:54 +00:00
|
|
|
func getBlockBySlot*(pool: BlockPool, slot: Slot): BlockRef =
|
|
|
|
## Retrieves the first block in the current canonical chain
|
|
|
|
## with slot number less or equal to `slot`.
|
|
|
|
pool.head.blck.findAncestorBySlot(slot).blck
|
|
|
|
|
2020-03-18 19:38:34 +00:00
|
|
|
func getBlockByPreciseSlot*(pool: BlockPool, slot: Slot): BlockRef =
|
2020-03-16 22:28:54 +00:00
|
|
|
## Retrieves a block from the canonical chain with a slot
|
|
|
|
## number equal to `slot`.
|
|
|
|
let found = pool.getBlockBySlot(slot)
|
2020-03-18 19:38:34 +00:00
|
|
|
if found.slot != slot: found else: nil
|
2020-03-16 22:28:54 +00:00
|
|
|
|
2019-02-28 21:21:29 +00:00
|
|
|
proc get*(pool: BlockPool, blck: BlockRef): BlockData =
|
|
|
|
## Retrieve the associated block body of a block reference
|
|
|
|
doAssert (not blck.isNil), "Trying to get nil BlockRef"
|
|
|
|
|
|
|
|
let data = pool.db.getBlock(blck.root)
|
|
|
|
doAssert data.isSome, "BlockRef without backing data, database corrupt?"
|
|
|
|
|
|
|
|
BlockData(data: data.get(), refs: blck)
|
|
|
|
|
|
|
|
proc get*(pool: BlockPool, root: Eth2Digest): Option[BlockData] =
|
|
|
|
## Retrieve a resolved block reference and its associated body, if available
|
2019-06-10 11:13:53 +00:00
|
|
|
let refs = pool.getRef(root)
|
2019-02-28 21:21:29 +00:00
|
|
|
|
|
|
|
if not refs.isNil:
|
|
|
|
some(pool.get(refs))
|
|
|
|
else:
|
|
|
|
none(BlockData)
|
|
|
|
|
2019-11-21 09:15:10 +00:00
|
|
|
func getOrResolve*(pool: var BlockPool, root: Eth2Digest): BlockRef =
|
2019-02-28 21:21:29 +00:00
|
|
|
## Fetch a block ref, or nil if not found (will be added to list of
|
|
|
|
## blocks-to-resolve)
|
2019-06-10 11:13:53 +00:00
|
|
|
result = pool.getRef(root)
|
2019-02-28 21:21:29 +00:00
|
|
|
|
|
|
|
if result.isNil:
|
2019-04-26 16:38:56 +00:00
|
|
|
pool.missing[root] = MissingBlock(slots: 1)
|
2019-02-28 21:21:29 +00:00
|
|
|
|
2019-11-21 09:15:10 +00:00
|
|
|
func checkMissing*(pool: var BlockPool): seq[FetchRecord] =
|
2019-02-28 21:21:29 +00:00
|
|
|
## 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?)
|
|
|
|
var done: seq[Eth2Digest]
|
|
|
|
|
2019-04-26 16:38:56 +00:00
|
|
|
for k, v in pool.missing.mpairs():
|
2019-02-28 21:21:29 +00:00
|
|
|
if v.tries > 8:
|
|
|
|
done.add(k)
|
|
|
|
else:
|
|
|
|
inc v.tries
|
|
|
|
|
|
|
|
for k in done:
|
2019-03-08 16:40:17 +00:00
|
|
|
# TODO Need to potentially remove from pool.pending - this is currently a
|
|
|
|
# memory leak here!
|
2019-04-26 16:38:56 +00:00
|
|
|
pool.missing.del(k)
|
2019-02-28 21:21:29 +00:00
|
|
|
|
|
|
|
# simple (simplistic?) exponential backoff for retries..
|
2019-04-26 16:38:56 +00:00
|
|
|
for k, v in pool.missing.pairs():
|
2019-02-28 21:21:29 +00:00
|
|
|
if v.tries.popcount() == 1:
|
2019-03-27 20:17:01 +00:00
|
|
|
result.add(FetchRecord(root: k, historySlots: v.slots))
|
2019-02-28 21:21:29 +00:00
|
|
|
|
2020-02-05 12:04:22 +00:00
|
|
|
proc skipAndUpdateState(
|
|
|
|
state: var HashedBeaconState, slot: Slot,
|
|
|
|
afterUpdate: proc (state: HashedBeaconState)) =
|
|
|
|
while state.data.slot < slot:
|
|
|
|
# Process slots one at a time in case afterUpdate needs to see empty states
|
|
|
|
process_slots(state, state.data.slot + 1)
|
|
|
|
afterUpdate(state)
|
|
|
|
|
2019-02-28 21:21:29 +00:00
|
|
|
proc skipAndUpdateState(
|
2020-02-29 15:15:44 +00:00
|
|
|
state: var HashedBeaconState, signedBlock: SignedBeaconBlock, flags: UpdateFlags,
|
2019-05-04 14:10:45 +00:00
|
|
|
afterUpdate: proc (state: HashedBeaconState)): bool =
|
2019-07-15 21:10:40 +00:00
|
|
|
|
2020-02-29 15:15:44 +00:00
|
|
|
skipAndUpdateState(state, signedBlock.message.slot - 1, afterUpdate)
|
2019-07-15 21:10:40 +00:00
|
|
|
|
2020-02-29 15:15:44 +00:00
|
|
|
let ok = state_transition(state, signedBlock, flags)
|
2019-02-28 21:21:29 +00:00
|
|
|
|
2019-03-08 16:40:17 +00:00
|
|
|
afterUpdate(state)
|
|
|
|
|
|
|
|
ok
|
2019-02-28 21:21:29 +00:00
|
|
|
|
2020-04-20 17:27:52 +00:00
|
|
|
proc putState(pool: BlockPool, state: HashedBeaconState, blck: BlockRef) =
|
2019-03-08 16:40:17 +00:00
|
|
|
# TODO we save state at every epoch start but never remove them - we also
|
|
|
|
# potentially save multiple states per slot if reorgs happen, meaning
|
|
|
|
# we could easily see a state explosion
|
2019-09-12 01:45:04 +00:00
|
|
|
logScope: pcs = "save_state_at_epoch_start"
|
|
|
|
|
2020-04-20 17:27:52 +00:00
|
|
|
var currentCache =
|
|
|
|
pool.cachedStates[state.data.slot.compute_epoch_at_slot.uint64 mod 2]
|
2019-05-04 14:10:45 +00:00
|
|
|
if state.data.slot mod SLOTS_PER_EPOCH == 0:
|
|
|
|
if not pool.db.containsState(state.root):
|
2019-03-22 15:49:37 +00:00
|
|
|
info "Storing state",
|
2020-01-22 12:59:54 +00:00
|
|
|
blockRoot = shortLog(blck.root),
|
|
|
|
blockSlot = shortLog(blck.slot),
|
2019-08-15 16:01:55 +00:00
|
|
|
stateSlot = shortLog(state.data.slot),
|
2019-09-12 01:45:04 +00:00
|
|
|
stateRoot = shortLog(state.root),
|
|
|
|
cat = "caching"
|
2019-05-04 14:10:45 +00:00
|
|
|
pool.db.putState(state.root, state.data)
|
2019-03-28 06:10:48 +00:00
|
|
|
# TODO this should be atomic with the above write..
|
2019-05-04 14:10:45 +00:00
|
|
|
pool.db.putStateRoot(blck.root, state.data.slot, state.root)
|
2019-02-28 21:21:29 +00:00
|
|
|
|
2020-04-20 17:27:52 +00:00
|
|
|
# Because state.data.slot mod SLOTS_PER_EPOCH == 0, wrap back to last
|
|
|
|
# time this was the case i.e. last currentCache. The opposite parity,
|
|
|
|
# by contrast, has just finished filling from the previous epoch. The
|
|
|
|
# resulting lookback window is thus >= SLOTS_PER_EPOCH in size, while
|
|
|
|
# bounded from above by 2*SLOTS_PER_EPOCH.
|
|
|
|
currentCache = init(BeaconChainDB, kvStore MemoryStoreRef.init())
|
|
|
|
else:
|
|
|
|
# Need to be able to efficiently access states for both attestation
|
|
|
|
# aggregation and to process block proposals going back to the last
|
|
|
|
# finalized slot. Ideally to avoid potential combinatiorial forking
|
|
|
|
# storage and/or memory constraints could CoW, up to and including,
|
|
|
|
# in particular, hash_tree_root() which is expensive to do 30 times
|
|
|
|
# since the previous epoch, to efficiently state_transition back to
|
|
|
|
# desired slot. However, none of that's in place, so there are both
|
|
|
|
# expensive, repeated BeaconState copies as well as computationally
|
|
|
|
# time-consuming-near-end-of-epoch hash tree roots. The latter are,
|
|
|
|
# effectively, naïvely O(n^2) in slot number otherwise, so when the
|
|
|
|
# slots become in the mid-to-high-20s it's spending all its time in
|
|
|
|
# pointlessly repeated calculations of prefix-state-transitions. An
|
|
|
|
# intermediate time/memory workaround involves storing only mapping
|
|
|
|
# between BlockRefs, or BlockSlots, and the BeaconState tree roots,
|
|
|
|
# but that still involves tens of megabytes worth of copying, along
|
|
|
|
# with the concomitant memory allocator and GC load. Instead, use a
|
|
|
|
# more memory-intensive (but more conceptually straightforward, and
|
|
|
|
# faster) strategy to just store, for the most recent slots. Keep a
|
|
|
|
# block's StateData of odd-numbered epoch in bucket 1, whilst evens
|
|
|
|
# land in bucket 0 (which is handed back to GC in if branch). There
|
|
|
|
# still is a possibility of combinatorial explosion, but this only,
|
|
|
|
# by a constant-factor, worsens things. TODO the actual solution's,
|
|
|
|
# eventually, to switch to CoW and/or ref objects for state and the
|
|
|
|
# hash_tree_root processing.
|
|
|
|
currentCache.putState(state.root, state.data)
|
|
|
|
# TODO this should be atomic with the above write..
|
|
|
|
currentCache.putStateRoot(blck.root, state.data.slot, state.root)
|
|
|
|
|
2019-03-28 06:10:48 +00:00
|
|
|
proc rewindState(pool: BlockPool, state: var StateData, bs: BlockSlot):
|
|
|
|
seq[BlockData] =
|
2019-09-12 01:45:04 +00:00
|
|
|
logScope: pcs = "replay_state"
|
|
|
|
|
2019-03-28 06:10:48 +00:00
|
|
|
var ancestors = @[pool.get(bs.blck)]
|
|
|
|
# Common case: the last block applied is the parent of the block to apply:
|
|
|
|
if not bs.blck.parent.isNil and state.blck.root == bs.blck.parent.root and
|
2020-02-05 11:41:46 +00:00
|
|
|
state.data.data.slot < bs.blck.slot:
|
2019-03-28 06:10:48 +00:00
|
|
|
return ancestors
|
2019-02-28 21:21:29 +00:00
|
|
|
|
|
|
|
# It appears that the parent root of the proposed new block is different from
|
|
|
|
# what we expected. We will have to rewind the state to a point along the
|
|
|
|
# chain of ancestors of the new block. We will do this by loading each
|
|
|
|
# successive parent block and checking if we can find the corresponding state
|
|
|
|
# in the database.
|
2019-03-28 06:10:48 +00:00
|
|
|
var
|
|
|
|
stateRoot = pool.db.getStateRoot(bs.blck.root, bs.slot)
|
|
|
|
curBs = bs
|
2020-01-22 12:59:54 +00:00
|
|
|
|
|
|
|
# TODO this can happen when state root is saved but state is gone - this would
|
|
|
|
# indicate a corrupt database, but since we're not atomically
|
|
|
|
# writing and deleting state+root mappings in a single transaction, it's
|
|
|
|
# likely to happen and we guard against it here.
|
|
|
|
if stateRoot.isSome() and not pool.db.containsState(stateRoot.get()):
|
|
|
|
stateRoot = none(type(stateRoot.get()))
|
|
|
|
|
2019-03-28 06:10:48 +00:00
|
|
|
while stateRoot.isNone():
|
|
|
|
let parBs = curBs.parent()
|
|
|
|
if parBs.blck.isNil:
|
|
|
|
break # Bug probably!
|
|
|
|
|
|
|
|
if parBs.blck != curBs.blck:
|
|
|
|
ancestors.add(pool.get(parBs.blck))
|
|
|
|
|
2020-04-20 17:27:52 +00:00
|
|
|
for db in [pool.db, pool.cachedStates[0], pool.cachedStates[1]]:
|
|
|
|
if (let tmp = db.getStateRoot(parBs.blck.root, parBs.slot); tmp.isSome()):
|
|
|
|
if db.containsState(tmp.get):
|
|
|
|
stateRoot = tmp
|
|
|
|
break
|
|
|
|
|
|
|
|
if stateRoot.isSome:
|
|
|
|
break
|
2019-03-28 06:10:48 +00:00
|
|
|
|
|
|
|
curBs = parBs
|
|
|
|
|
|
|
|
if stateRoot.isNone():
|
|
|
|
# TODO this should only happen if the database is corrupt - we walked the
|
|
|
|
# list of parent blocks and couldn't find a corresponding state in the
|
|
|
|
# database, which should never happen (at least we should have the
|
|
|
|
# tail state in there!)
|
|
|
|
error "Couldn't find ancestor state root!",
|
2019-09-12 01:45:04 +00:00
|
|
|
blockRoot = shortLog(bs.blck.root),
|
2020-01-22 12:59:54 +00:00
|
|
|
blockSlot = shortLog(bs.blck.slot),
|
|
|
|
slot = shortLog(bs.slot),
|
2019-09-12 01:45:04 +00:00
|
|
|
cat = "crash"
|
2019-03-28 06:10:48 +00:00
|
|
|
doAssert false, "Oh noes, we passed big bang!"
|
2019-02-28 21:21:29 +00:00
|
|
|
|
|
|
|
let
|
2019-12-19 13:02:28 +00:00
|
|
|
ancestor = ancestors.pop()
|
2020-04-20 17:27:52 +00:00
|
|
|
root = stateRoot.get()
|
|
|
|
ancestorState =
|
|
|
|
if pool.db.containsState(root):
|
|
|
|
pool.db.getState(root)
|
|
|
|
elif pool.cachedStates[0].containsState(root):
|
|
|
|
pool.cachedStates[0].getState(root)
|
|
|
|
else:
|
|
|
|
pool.cachedStates[1].getState(root)
|
2019-02-28 21:21:29 +00:00
|
|
|
|
|
|
|
if ancestorState.isNone():
|
|
|
|
# TODO this should only happen if the database is corrupt - we walked the
|
|
|
|
# list of parent blocks and couldn't find a corresponding state in the
|
|
|
|
# database, which should never happen (at least we should have the
|
|
|
|
# tail state in there!)
|
|
|
|
error "Couldn't find ancestor state or block parent missing!",
|
2019-09-12 01:45:04 +00:00
|
|
|
blockRoot = shortLog(bs.blck.root),
|
2020-01-22 12:59:54 +00:00
|
|
|
blockSlot = shortLog(bs.blck.slot),
|
|
|
|
slot = shortLog(bs.slot),
|
2019-09-12 01:45:04 +00:00
|
|
|
cat = "crash"
|
2019-02-28 21:21:29 +00:00
|
|
|
doAssert false, "Oh noes, we passed big bang!"
|
|
|
|
|
2019-09-12 01:45:04 +00:00
|
|
|
trace "Replaying state transitions",
|
2019-08-15 16:01:55 +00:00
|
|
|
stateSlot = shortLog(state.data.data.slot),
|
2019-12-17 09:52:04 +00:00
|
|
|
ancestorStateRoot = shortLog(ancestor.data.message.state_root),
|
2019-08-15 16:01:55 +00:00
|
|
|
ancestorStateSlot = shortLog(ancestorState.get().slot),
|
|
|
|
slot = shortLog(bs.slot),
|
2019-03-28 06:10:48 +00:00
|
|
|
blockRoot = shortLog(bs.blck.root),
|
2019-09-12 01:45:04 +00:00
|
|
|
ancestors = ancestors.len,
|
|
|
|
cat = "replay_state"
|
2019-02-28 21:21:29 +00:00
|
|
|
|
2019-05-04 14:10:45 +00:00
|
|
|
state.data.data = ancestorState.get()
|
|
|
|
state.data.root = stateRoot.get()
|
|
|
|
state.blck = ancestor.refs
|
2019-02-28 21:21:29 +00:00
|
|
|
|
2019-03-28 06:10:48 +00:00
|
|
|
ancestors
|
|
|
|
|
2019-04-29 08:14:22 +00:00
|
|
|
proc updateStateData*(pool: BlockPool, state: var StateData, bs: BlockSlot) =
|
2019-03-28 06:10:48 +00:00
|
|
|
## Rewind or advance state such that it matches the given block and slot -
|
|
|
|
## this may include replaying from an earlier snapshot if blck is on a
|
|
|
|
## different branch or has advanced to a higher slot number than slot
|
|
|
|
## If slot is higher than blck.slot, replay will fill in with empty/non-block
|
|
|
|
## slots, else it is ignored
|
|
|
|
|
|
|
|
# We need to check the slot because the state might have moved forwards
|
|
|
|
# without blocks
|
2019-05-04 14:10:45 +00:00
|
|
|
if state.blck.root == bs.blck.root and state.data.data.slot <= bs.slot:
|
|
|
|
if state.data.data.slot != bs.slot:
|
2019-04-29 08:14:22 +00:00
|
|
|
# Might be that we're moving to the same block but later slot
|
2020-02-05 12:04:22 +00:00
|
|
|
skipAndUpdateState(state.data, bs.slot) do(state: HashedBeaconState):
|
2020-04-20 17:27:52 +00:00
|
|
|
pool.putState(state, bs.blck)
|
2019-04-29 08:14:22 +00:00
|
|
|
|
2019-03-28 06:10:48 +00:00
|
|
|
return # State already at the right spot
|
|
|
|
|
|
|
|
let ancestors = rewindState(pool, state, bs)
|
|
|
|
|
2019-02-28 21:21:29 +00:00
|
|
|
# If we come this far, we found the state root. The last block on the stack
|
|
|
|
# is the one that produced this particular state, so we can pop it
|
|
|
|
# TODO it might be possible to use the latest block hashes from the state to
|
|
|
|
# do this more efficiently.. whatever!
|
|
|
|
|
2019-03-28 06:10:48 +00:00
|
|
|
# Time to replay all the blocks between then and now. We skip one because
|
2019-02-28 21:21:29 +00:00
|
|
|
# it's the one that we found the state with, and it has already been
|
2020-04-20 17:27:52 +00:00
|
|
|
# applied. Pathologically quadratic in slot number, naïvely.
|
2019-12-19 13:02:28 +00:00
|
|
|
for i in countdown(ancestors.len - 1, 0):
|
2019-03-28 06:10:48 +00:00
|
|
|
let ok =
|
2020-02-05 20:40:14 +00:00
|
|
|
skipAndUpdateState(state.data,
|
2020-02-29 15:15:44 +00:00
|
|
|
ancestors[i].data,
|
2020-03-05 12:52:10 +00:00
|
|
|
{skipBlsValidation, skipMerkleValidation, skipStateRootValidation}) do (state: HashedBeaconState):
|
2020-04-20 17:27:52 +00:00
|
|
|
pool.putState(state, ancestors[i].refs)
|
2019-03-28 06:10:48 +00:00
|
|
|
doAssert ok, "Blocks in database should never fail to apply.."
|
2019-02-28 21:21:29 +00:00
|
|
|
|
2020-02-05 12:04:22 +00:00
|
|
|
skipAndUpdateState(state.data, bs.slot) do(state: HashedBeaconState):
|
2020-04-20 17:27:52 +00:00
|
|
|
pool.putState(state, bs.blck)
|
2019-03-08 16:40:17 +00:00
|
|
|
|
2019-03-28 06:10:48 +00:00
|
|
|
state.blck = bs.blck
|
2019-03-14 13:33:56 +00:00
|
|
|
|
2019-02-28 21:21:29 +00:00
|
|
|
proc loadTailState*(pool: BlockPool): StateData =
|
|
|
|
## Load the state associated with the current tail in the pool
|
2019-12-16 18:08:50 +00:00
|
|
|
let stateRoot = pool.db.getBlock(pool.tail.root).get().message.state_root
|
2019-02-28 21:21:29 +00:00
|
|
|
StateData(
|
2019-05-04 14:10:45 +00:00
|
|
|
data: HashedBeaconState(
|
|
|
|
data: pool.db.getState(stateRoot).get(),
|
|
|
|
root: stateRoot),
|
2019-03-13 22:59:20 +00:00
|
|
|
blck: pool.tail
|
2019-02-28 21:21:29 +00:00
|
|
|
)
|
2019-03-13 22:59:20 +00:00
|
|
|
|
2020-01-22 12:59:54 +00:00
|
|
|
proc delState(pool: BlockPool, bs: BlockSlot) =
|
|
|
|
# Delete state state and mapping for a particular block+slot
|
|
|
|
if (let root = pool.db.getStateRoot(bs.blck.root, bs.slot); root.isSome()):
|
|
|
|
pool.db.delState(root.get())
|
|
|
|
pool.db.delStateRoot(bs.blck.root, bs.slot)
|
2019-11-22 14:14:13 +00:00
|
|
|
|
2019-12-19 13:02:28 +00:00
|
|
|
proc updateHead*(pool: BlockPool, newHead: BlockRef) =
|
2019-03-13 22:59:20 +00:00
|
|
|
## Update what we consider to be the current head, as given by the fork
|
|
|
|
## choice.
|
|
|
|
## The choice of head affects the choice of finalization point - the order
|
|
|
|
## of operations naturally becomes important here - after updating the head,
|
|
|
|
## blocks that were once considered potential candidates for a tree will
|
|
|
|
## now fall from grace, or no longer be considered resolved.
|
2019-12-19 13:02:28 +00:00
|
|
|
doAssert newHead.parent != nil or newHead.slot == 0
|
2019-09-12 01:45:04 +00:00
|
|
|
logScope: pcs = "fork_choice"
|
|
|
|
|
2019-12-19 13:02:28 +00:00
|
|
|
if pool.head.blck == newHead:
|
2019-09-12 01:45:04 +00:00
|
|
|
info "No head block update",
|
2019-12-19 13:02:28 +00:00
|
|
|
headBlockRoot = shortLog(newHead.root),
|
|
|
|
headBlockSlot = shortLog(newHead.slot),
|
2019-09-12 01:45:04 +00:00
|
|
|
cat = "fork_choice"
|
2019-03-22 11:33:10 +00:00
|
|
|
|
2019-03-13 22:59:20 +00:00
|
|
|
return
|
|
|
|
|
2019-03-22 15:49:37 +00:00
|
|
|
let
|
|
|
|
lastHead = pool.head
|
2019-12-19 13:02:28 +00:00
|
|
|
pool.db.putHeadBlock(newHead.root)
|
2019-03-13 22:59:20 +00:00
|
|
|
|
|
|
|
# Start off by making sure we have the right state
|
2019-12-19 13:02:28 +00:00
|
|
|
updateStateData(
|
|
|
|
pool, pool.headState, BlockSlot(blck: newHead, slot: newHead.slot))
|
|
|
|
|
|
|
|
let
|
|
|
|
justifiedSlot = pool.headState.data.data
|
|
|
|
.current_justified_checkpoint
|
|
|
|
.epoch
|
|
|
|
.compute_start_slot_at_epoch()
|
|
|
|
justifiedBS = newHead.findAncestorBySlot(justifiedSlot)
|
|
|
|
|
|
|
|
pool.head = Head(blck: newHead, justified: justifiedBS)
|
|
|
|
updateStateData(pool, pool.justifiedState, justifiedBS)
|
|
|
|
|
|
|
|
# TODO isAncestorOf may be expensive - too expensive?
|
|
|
|
if not lastHead.blck.isAncestorOf(newHead):
|
2019-11-25 17:50:35 +00:00
|
|
|
info "Updated head block (new parent)",
|
2019-05-01 09:19:29 +00:00
|
|
|
lastHeadRoot = shortLog(lastHead.blck.root),
|
2019-12-19 13:02:28 +00:00
|
|
|
parentRoot = shortLog(newHead.parent.root),
|
|
|
|
stateRoot = shortLog(pool.headState.data.root),
|
|
|
|
headBlockRoot = shortLog(pool.headState.blck.root),
|
|
|
|
stateSlot = shortLog(pool.headState.data.data.slot),
|
|
|
|
justifiedEpoch = shortLog(pool.headState.data.data.current_justified_checkpoint.epoch),
|
|
|
|
finalizedEpoch = shortLog(pool.headState.data.data.finalized_checkpoint.epoch),
|
2019-09-12 01:45:04 +00:00
|
|
|
cat = "fork_choice"
|
2019-10-22 11:57:34 +00:00
|
|
|
|
|
|
|
# A reasonable criterion for "reorganizations of the chain"
|
|
|
|
beacon_reorgs_total.inc()
|
2019-03-22 15:49:37 +00:00
|
|
|
else:
|
2019-11-25 17:50:35 +00:00
|
|
|
info "Updated head block",
|
2019-12-19 13:02:28 +00:00
|
|
|
stateRoot = shortLog(pool.headState.data.root),
|
|
|
|
headBlockRoot = shortLog(pool.headState.blck.root),
|
|
|
|
stateSlot = shortLog(pool.headState.data.data.slot),
|
|
|
|
justifiedEpoch = shortLog(pool.headState.data.data.current_justified_checkpoint.epoch),
|
|
|
|
finalizedEpoch = shortLog(pool.headState.data.data.finalized_checkpoint.epoch),
|
2019-09-12 01:45:04 +00:00
|
|
|
cat = "fork_choice"
|
2019-03-13 22:59:20 +00:00
|
|
|
|
|
|
|
let
|
2019-12-19 13:02:28 +00:00
|
|
|
finalizedEpochStartSlot =
|
|
|
|
pool.headState.data.data.finalized_checkpoint.epoch.
|
|
|
|
compute_start_slot_at_epoch()
|
2019-03-13 22:59:20 +00:00
|
|
|
# TODO there might not be a block at the epoch boundary - what then?
|
2019-12-19 13:02:28 +00:00
|
|
|
finalizedHead = newHead.findAncestorBySlot(finalizedEpochStartSlot)
|
2019-03-13 22:59:20 +00:00
|
|
|
|
2019-05-01 09:19:29 +00:00
|
|
|
doAssert (not finalizedHead.blck.isNil),
|
2019-03-13 22:59:20 +00:00
|
|
|
"Block graph should always lead to a finalized block"
|
|
|
|
|
|
|
|
if finalizedHead != pool.finalizedHead:
|
2020-01-22 12:59:54 +00:00
|
|
|
block: # Remove states, walking slot by slot
|
|
|
|
discard
|
|
|
|
# TODO this is very aggressive - in theory all our operations start at
|
|
|
|
# the finalized block so all states before that can be wiped..
|
|
|
|
# TODO this is disabled for now because the logic for initializing the
|
|
|
|
# block pool and potentially a few other places depend on certain
|
|
|
|
# states (like the tail state) being present. It's also problematic
|
|
|
|
# because it is not clear what happens when tail and finalized states
|
|
|
|
# happen on an empty slot..
|
|
|
|
# var cur = finalizedHead
|
|
|
|
# while cur != pool.finalizedHead:
|
|
|
|
# cur = cur.parent
|
|
|
|
# pool.delState(cur)
|
|
|
|
|
|
|
|
block: # Clean up block refs, walking block by block
|
|
|
|
var cur = finalizedHead.blck
|
|
|
|
while cur != pool.finalizedHead.blck:
|
|
|
|
# Finalization means that we choose a single chain as the canonical one -
|
|
|
|
# it also means we're no longer interested in any branches from that chain
|
|
|
|
# up to the finalization point.
|
|
|
|
# The new finalized head should not be cleaned! We start at its parent and
|
|
|
|
# clean everything including the old finalized head.
|
|
|
|
cur = cur.parent
|
|
|
|
|
|
|
|
# TODO what about attestations? we need to drop those too, though they
|
|
|
|
# *should* be pretty harmless
|
|
|
|
if cur.parent != nil: # This happens for the genesis / tail block
|
|
|
|
for child in cur.parent.children:
|
|
|
|
if child != cur:
|
|
|
|
# TODO also remove states associated with the unviable forks!
|
|
|
|
# TODO the easiest thing to do here would probably be to use
|
|
|
|
# pool.heads to find unviable heads, then walk those chains
|
|
|
|
# and remove everything.. currently, if there's a child with
|
|
|
|
# children of its own, those children will not be pruned
|
|
|
|
# correctly from the database
|
|
|
|
pool.blocks.del(child.root)
|
|
|
|
pool.db.delBlock(child.root)
|
|
|
|
cur.parent.children = @[cur]
|
2020-01-21 09:22:13 +00:00
|
|
|
|
|
|
|
pool.finalizedHead = finalizedHead
|
2019-05-01 09:19:29 +00:00
|
|
|
|
|
|
|
let hlen = pool.heads.len
|
|
|
|
for i in 0..<hlen:
|
|
|
|
let n = hlen - i - 1
|
2020-01-20 17:06:46 +00:00
|
|
|
if not pool.finalizedHead.blck.isAncestorOf(pool.heads[n].blck):
|
|
|
|
# Any heads that are not derived from the newly finalized block are no
|
|
|
|
# longer viable candidates for future head selection
|
2019-05-01 09:19:29 +00:00
|
|
|
pool.heads.del(n)
|
2019-03-13 22:59:20 +00:00
|
|
|
|
2020-01-20 17:06:46 +00:00
|
|
|
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"
|
|
|
|
|
2020-01-22 12:59:54 +00:00
|
|
|
# TODO prune everything before weak subjectivity period
|
2019-11-22 14:14:13 +00:00
|
|
|
|
2019-11-21 09:15:10 +00:00
|
|
|
func latestJustifiedBlock*(pool: BlockPool): BlockSlot =
|
2019-03-13 22:59:20 +00:00
|
|
|
## Return the most recent block that is justified and at least as recent
|
|
|
|
## as the latest finalized block
|
|
|
|
|
2019-05-01 09:19:29 +00:00
|
|
|
doAssert pool.heads.len > 0,
|
|
|
|
"We should have at least the genesis block in heaads"
|
|
|
|
doAssert (not pool.head.blck.isNil()),
|
|
|
|
"Genesis block will be head, if nothing else"
|
2019-03-28 06:10:48 +00:00
|
|
|
|
2019-05-01 09:19:29 +00:00
|
|
|
# Prefer stability: use justified block from current head to break ties!
|
2019-09-01 15:02:49 +00:00
|
|
|
result = pool.head.justified
|
2019-05-01 09:19:29 +00:00
|
|
|
for head in pool.heads[1 ..< ^0]:
|
2019-09-01 15:02:49 +00:00
|
|
|
if head.justified.slot > result.slot:
|
|
|
|
result = head.justified
|
2019-03-28 06:10:48 +00:00
|
|
|
|
2020-01-17 13:44:01 +00:00
|
|
|
proc isInitialized*(T: type BlockPool, db: BeaconChainDB): bool =
|
|
|
|
let
|
|
|
|
headBlockRoot = db.getHeadBlock()
|
|
|
|
tailBlockRoot = db.getTailBlock()
|
|
|
|
|
|
|
|
if not (headBlockRoot.isSome() and tailBlockRoot.isSome()):
|
|
|
|
return false
|
|
|
|
|
|
|
|
let
|
|
|
|
headBlock = db.getBlock(headBlockRoot.get())
|
|
|
|
tailBlock = db.getBlock(tailBlockRoot.get())
|
|
|
|
|
|
|
|
if not (headBlock.isSome() and tailBlock.isSome()):
|
|
|
|
return false
|
|
|
|
|
|
|
|
if not db.containsState(tailBlock.get().message.state_root):
|
|
|
|
return false
|
|
|
|
|
|
|
|
return true
|
|
|
|
|
2019-03-28 06:10:48 +00:00
|
|
|
proc preInit*(
|
2019-12-16 18:08:50 +00:00
|
|
|
T: type BlockPool, db: BeaconChainDB, state: BeaconState,
|
|
|
|
signedBlock: SignedBeaconBlock) =
|
2019-03-28 06:10:48 +00:00
|
|
|
# write a genesis state, the way the BlockPool expects it to be stored in
|
|
|
|
# database
|
|
|
|
# TODO probably should just init a blockpool with the freshly written
|
|
|
|
# state - but there's more refactoring needed to make it nice - doing
|
|
|
|
# a minimal patch for now..
|
|
|
|
let
|
2019-12-16 18:08:50 +00:00
|
|
|
blockRoot = hash_tree_root(signedBlock.message)
|
2019-03-28 06:10:48 +00:00
|
|
|
|
2020-01-15 11:35:54 +00:00
|
|
|
doAssert signedBlock.message.state_root == hash_tree_root(state)
|
2019-09-12 01:45:04 +00:00
|
|
|
notice "New database from snapshot",
|
2019-03-28 11:22:11 +00:00
|
|
|
blockRoot = shortLog(blockRoot),
|
2019-12-16 18:08:50 +00:00
|
|
|
stateRoot = shortLog(signedBlock.message.state_root),
|
2019-03-28 11:22:11 +00:00
|
|
|
fork = state.fork,
|
2019-09-12 01:45:04 +00:00
|
|
|
validators = state.validators.len(),
|
|
|
|
cat = "initialization"
|
2019-03-28 06:10:48 +00:00
|
|
|
|
|
|
|
db.putState(state)
|
2019-12-16 18:08:50 +00:00
|
|
|
db.putBlock(signedBlock)
|
2019-03-28 06:10:48 +00:00
|
|
|
db.putTailBlock(blockRoot)
|
|
|
|
db.putHeadBlock(blockRoot)
|
2020-01-22 12:59:54 +00:00
|
|
|
db.putStateRoot(blockRoot, state.slot, signedBlock.message.state_root)
|
2020-02-07 07:13:38 +00:00
|
|
|
|
|
|
|
proc getProposer*(pool: BlockPool, head: BlockRef, slot: Slot): Option[ValidatorPubKey] =
|
|
|
|
pool.withState(pool.tmpState, head.atSlot(slot)):
|
|
|
|
var cache = get_empty_per_epoch_cache()
|
|
|
|
|
2020-04-06 18:55:47 +00:00
|
|
|
# https://github.com/ethereum/eth2.0-specs/blob/v0.11.1/specs/phase0/validator.md#validator-assignments
|
2020-02-07 07:13:38 +00:00
|
|
|
let proposerIdx = get_beacon_proposer_index(state, cache)
|
|
|
|
if proposerIdx.isNone:
|
|
|
|
warn "Missing proposer index",
|
|
|
|
slot=slot,
|
|
|
|
epoch=slot.compute_epoch_at_slot,
|
|
|
|
num_validators=state.validators.len,
|
|
|
|
active_validators=
|
|
|
|
get_active_validator_indices(state, slot.compute_epoch_at_slot),
|
|
|
|
balances=state.balances
|
|
|
|
return
|
|
|
|
|
|
|
|
return some(state.validators[proposerIdx.get()].pubkey)
|
2020-03-31 18:39:02 +00:00
|
|
|
|
|
|
|
# https://github.com/ethereum/eth2.0-specs/blob/v0.11.1/specs/phase0/p2p-interface.md#global-topics
|
|
|
|
proc isValidBeaconBlock*(pool: BlockPool,
|
|
|
|
signed_beacon_block: SignedBeaconBlock, current_slot: Slot,
|
|
|
|
flags: UpdateFlags): bool =
|
|
|
|
# In general, checks are ordered from cheap to expensive. Especially, crypto
|
|
|
|
# verification could be quite a bit more expensive than the rest. This is an
|
|
|
|
# externally easy-to-invoke function by tossing network packets at the node.
|
|
|
|
|
|
|
|
# The block is not from a future slot
|
|
|
|
# TODO allow `MAXIMUM_GOSSIP_CLOCK_DISPARITY` leniency, especially towards
|
|
|
|
# seemingly future slots.
|
|
|
|
if not (signed_beacon_block.message.slot <= current_slot):
|
|
|
|
debug "isValidBeaconBlock: block is from a future slot",
|
|
|
|
signed_beacon_block_message_slot = signed_beacon_block.message.slot,
|
|
|
|
current_slot = current_slot
|
|
|
|
return false
|
|
|
|
|
|
|
|
# The block is from a slot greater than the latest finalized slot (with a
|
|
|
|
# MAXIMUM_GOSSIP_CLOCK_DISPARITY allowance) -- i.e. validate that
|
|
|
|
# signed_beacon_block.message.slot >
|
|
|
|
# compute_start_slot_at_epoch(state.finalized_checkpoint.epoch)
|
|
|
|
if not (signed_beacon_block.message.slot > pool.finalizedHead.slot):
|
|
|
|
debug "isValidBeaconBlock: block is not from a slot greater than the latest finalized slot"
|
|
|
|
return false
|
|
|
|
|
2020-04-03 17:49:46 +00:00
|
|
|
# The proposer signature, signed_beacon_block.signature, is valid with
|
|
|
|
# respect to the proposer_index pubkey.
|
|
|
|
|
|
|
|
# TODO resolve following two checks' robustness and remove this early exit.
|
|
|
|
const alwaysTrue = true
|
|
|
|
if alwaysTrue:
|
|
|
|
return true
|
|
|
|
|
|
|
|
# TODO because this check depends on the proposer aspect, and see the comment
|
|
|
|
# there for that issue, the fallout is this check isn't reliable anymore.
|
2020-03-31 18:39:02 +00:00
|
|
|
# The block is the first block with valid signature received for the proposer
|
|
|
|
# for the slot, signed_beacon_block.message.slot.
|
2020-04-01 11:41:39 +00:00
|
|
|
#
|
|
|
|
# While this condition is similar to the proposer slashing condition at
|
|
|
|
# https://github.com/ethereum/eth2.0-specs/blob/v0.11.1/specs/phase0/validator.md#proposer-slashing
|
|
|
|
# it's not identical, and this check does not address slashing:
|
|
|
|
#
|
|
|
|
# (1) The beacon blocks must be conflicting, i.e. different, for the same
|
|
|
|
# slot and proposer. This check also catches identical blocks.
|
|
|
|
#
|
|
|
|
# (2) By this point in the function, it's not been checked whether they're
|
|
|
|
# signed yet. As in general, expensive checks should be deferred, this
|
|
|
|
# would add complexity not directly relevant this function.
|
|
|
|
#
|
|
|
|
# (3) As evidenced by point (1), the similarity in the validation condition
|
|
|
|
# and slashing condition, while not coincidental, aren't similar enough
|
|
|
|
# to combine, as one or the other might drift.
|
|
|
|
#
|
|
|
|
# (4) Furthermore, this function, as much as possible, simply returns a yes
|
|
|
|
# or no answer, without modifying other state for p2p network interface
|
|
|
|
# validation. Complicating this interface, for the sake of sharing only
|
|
|
|
# couple lines of code, wouldn't be worthwhile.
|
|
|
|
#
|
2020-03-31 18:39:02 +00:00
|
|
|
# TODO might check unresolved/orphaned blocks too, and this might not see all
|
2020-04-01 11:41:39 +00:00
|
|
|
# blocks at a given slot (though, in theory, those get checked elsewhere), or
|
|
|
|
# adding metrics that count how often these conditions occur.
|
2020-03-31 18:39:02 +00:00
|
|
|
let slotBlockRef =
|
|
|
|
getBlockByPreciseSlot(pool, signed_beacon_block.message.slot)
|
|
|
|
if (not slotBlockRef.isNil) and
|
|
|
|
pool.get(slotBlockRef).data.message.proposer_index ==
|
|
|
|
signed_beacon_block.message.proposer_index:
|
|
|
|
debug "isValidBeaconBlock: block isn't first block with valid signature received for the proposer",
|
|
|
|
signed_beacon_block_message_slot = signed_beacon_block.message.slot,
|
|
|
|
blckRef = getBlockByPreciseSlot(pool, signed_beacon_block.message.slot)
|
|
|
|
|
|
|
|
return false
|
|
|
|
|
|
|
|
# If this block doesn't have a parent we know about, we can't/don't really
|
|
|
|
# trace it back to a known-good state/checkpoint to verify its prevenance;
|
|
|
|
# while one could getOrResolve to queue up searching for missing parent it
|
|
|
|
# might not be the best place. As much as feasible, this function aims for
|
|
|
|
# answering yes/no, not queuing other action or otherwise altering state.
|
|
|
|
let parent_ref = pool.getRef(signed_beacon_block.message.parent_root)
|
|
|
|
if parent_ref.isNil:
|
2020-04-03 17:49:46 +00:00
|
|
|
# TODO find where incorrect block's being produced at/around epoch 20,
|
|
|
|
# nim-beacon-chain commit 708ac80daef5e05e01d4fc84576f8692adc256a3, at
|
|
|
|
# 2020-04-02, running `make eth2_network_simulation`, or, alternately,
|
|
|
|
# why correctly produced ancestor block isn't found. By appearances, a
|
|
|
|
# chain is being forked, probably by node 0, as nodes 1/2/3 die first,
|
|
|
|
# then node 0 only dies eventually then nodes 1/2/3 are not around, to
|
|
|
|
# help it in turn finalize. So node 0 is probably culprit, around/near
|
|
|
|
# the end of epoch 19, in its block proposal(s). BlockPool.add() later
|
|
|
|
# discovers this same missing parent. The missing step here is that we
|
|
|
|
# need to be able to receive this block and store it in unresolved but
|
|
|
|
# without passing it on to other nodes (which is what EV actually does
|
|
|
|
# specify). The other BeaconBlock validation conditions cannot change,
|
|
|
|
# just because later blocks fill in gaps, but this one can. My read of
|
|
|
|
# the intent here is that only nodes which know about the parentage of
|
|
|
|
# a block should pass it on. That doesn't mean we shouldn't process it
|
|
|
|
# though, just not rebroadcast it.
|
|
|
|
# Debug output: isValidBeaconBlock: incorrectly skipping BLS validation when parent block unknown topics="blkpool" tid=2111475 file=block_pool.nim:1040 current_epoch=22 current_slot=133 parent_root=72b5b0f1 pool_head_slot=131 pool_head_state_root=48e9f4b8 proposed_block_slot=133 proposed_block_state_root=ed7b1ddd proposer_index=42 node=3
|
|
|
|
# So it's missing a head update, probably, at slot 132.
|
|
|
|
debug "isValidBeaconBlock: incorrectly skipping BLS validation when parent block unknown",
|
|
|
|
current_slot = current_slot,
|
|
|
|
current_epoch = compute_epoch_at_slot(current_slot),
|
|
|
|
parent_root = signed_beacon_block.message.parent_root,
|
|
|
|
proposed_block_slot = signed_beacon_block.message.slot,
|
|
|
|
proposer_index = signed_beacon_block.message.proposer_index,
|
|
|
|
proposed_block_state_root = signed_beacon_block.message.state_root,
|
|
|
|
pool_head_slot = pool.headState.data.data.slot,
|
|
|
|
pool_head_state_root = pool.headState.data.root
|
|
|
|
|
2020-03-31 18:39:02 +00:00
|
|
|
return false
|
|
|
|
|
|
|
|
let bs =
|
|
|
|
BlockSlot(blck: parent_ref, slot: pool.get(parent_ref).data.message.slot)
|
|
|
|
pool.withState(pool.tmpState, bs):
|
|
|
|
let
|
|
|
|
blockRoot = hash_tree_root(signed_beacon_block.message)
|
|
|
|
domain = get_domain(pool.headState.data.data, DOMAIN_BEACON_PROPOSER,
|
|
|
|
compute_epoch_at_slot(signed_beacon_block.message.slot))
|
|
|
|
signing_root = compute_signing_root(blockRoot, domain)
|
|
|
|
proposer_index = signed_beacon_block.message.proposer_index
|
|
|
|
|
|
|
|
if proposer_index >= pool.headState.data.data.validators.len.uint64:
|
|
|
|
return false
|
|
|
|
if not blsVerify(pool.headState.data.data.validators[proposer_index].pubkey,
|
|
|
|
signing_root.data, signed_beacon_block.signature):
|
|
|
|
debug "isValidBeaconBlock: block failed signature verification"
|
|
|
|
return false
|
|
|
|
|
|
|
|
true
|