# beacon_chain # Copyright (c) 2018-2020 Status Research & Development GmbH # Licensed and distributed under either of # * MIT license (license terms in the root directory or at https://opensource.org/licenses/MIT). # * Apache v2 license (license terms in the root directory or at https://www.apache.org/licenses/LICENSE-2.0). # at your option. This file may not be copied, modified, or distributed except according to those terms. {.push raises: [Defect].} import # Standard libraries chronicles, options, sequtils, tables, sets, # Status libraries metrics, # Internals ../ssz/merkleization, ../beacon_chain_db, ../extras, ../spec/[ crypto, datatypes, digest, helpers, validator, state_transition, beaconstate], block_pools_types export block_pools_types declareCounter beacon_reorgs_total, "Total occurrences of reorganizations of the chain" # On fork choice declareCounter beacon_state_data_cache_hits, "EpochRef hits" declareCounter beacon_state_data_cache_misses, "EpochRef misses" declareCounter beacon_state_rewinds, "State database rewinds" declareGauge beacon_pending_deposits, "Number of pending deposits (state.eth1_data.deposit_count - state.eth1_deposit_index)" # On block declareGauge beacon_processed_deposits_total, "Number of total deposits included on chain" # On block logScope: topics = "chaindag" proc putBlock*( dag: var ChainDAGRef, signedBlock: SignedBeaconBlock) = dag.db.putBlock(signedBlock) proc updateStateData*( dag: ChainDAGRef, state: var StateData, bs: BlockSlot, cache: var StateCache) {.gcsafe.} template withState*( dag: ChainDAGRef, stateData: var StateData, blockSlot: BlockSlot, body: untyped): untyped = ## Helper template that updates stateData to a particular BlockSlot - usage of ## stateData is unsafe outside of block. ## TODO async transformations will lead to a race where stateData gets updated ## while waiting for future to complete - catch this here somehow? var cache {.inject.} = blockSlot.blck.getStateCache(blockSlot.slot.epoch()) updateStateData(dag, stateData, blockSlot, cache) template hashedState(): HashedBeaconState {.inject, used.} = stateData.data template state(): BeaconState {.inject, used.} = stateData.data.data template blck(): BlockRef {.inject, used.} = stateData.blck template root(): Eth2Digest {.inject, used.} = stateData.data.root body func parent*(bs: BlockSlot): BlockSlot = ## 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 ) func get_effective_balances*(state: BeaconState): seq[Gwei] = ## Get the balances from a state as counted for fork choice result.newSeq(state.validators.len) # zero-init let epoch = state.get_current_epoch() for i in 0 ..< result.len: # All non-active validators have a 0 balance template validator: Validator = state.validators[i] if validator.is_active_validator(epoch): result[i] = validator.effective_balance proc init*( T: type EpochRef, state: BeaconState, cache: var StateCache, prevEpoch: EpochRef): T = let epoch = state.get_current_epoch() epochRef = EpochRef( epoch: epoch, current_justified_checkpoint: state.current_justified_checkpoint, finalized_checkpoint: state.finalized_checkpoint, shuffled_active_validator_indices: cache.get_shuffled_active_validator_indices(state, epoch)) for i in 0'u64..= finalized.slot: for e in blck.epochRefs: e.validator_key_store = epochRef.validator_key_store blck = blck.parent func link*(parent, child: BlockRef) = doAssert (not (parent.root == Eth2Digest() or child.root == Eth2Digest())), "blocks missing root!" doAssert parent.root != child.root, "self-references not allowed" child.parent = parent 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 func get_ancestor*(blck: BlockRef, slot: Slot): BlockRef = ## https://github.com/ethereum/eth2.0-specs/blob/v0.12.2/specs/phase0/fork-choice.md#get_ancestor ## Return the most recent block as of the time at `slot` that not more recent ## than `blck` itself doAssert not blck.isNil 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 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.get_ancestor(slot), slot: slot) func atEpochStart*(blck: BlockRef, epoch: Epoch): BlockSlot = ## Return the BlockSlot corresponding to the first slot in the given epoch atSlot(blck, epoch.compute_start_slot_at_epoch) func atEpochEnd*(blck: BlockRef, epoch: Epoch): BlockSlot = ## Return the BlockSlot corresponding to the last slot in the given epoch atSlot(blck, (epoch + 1).compute_start_slot_at_epoch - 1) func epochAncestor*(blck: BlockRef, epoch: Epoch): BlockSlot = ## The state transition works by storing information from blocks in a ## "working" area until the epoch transition, then batching work collected ## during the epoch. Thus, last block in the ancestor epochs is the block ## that has an impact on epoch currently considered. ## ## This function returns a BlockSlot pointing to that epoch boundary, ie the ## boundary where the last block has been applied to the state and epoch ## processing has been done - we will store epoch caches in that particular ## block so that any block in the dag that needs it can find it easily. In ## particular, if empty slot processing is done, there may be multiple epoch ## caches found there. var blck = blck while blck.slot.epoch >= epoch and not blck.parent.isNil: blck = blck.parent blck.atEpochStart(epoch) proc getStateCache*(blck: BlockRef, epoch: Epoch): StateCache = # When creating a state cache, we want the current and the previous epoch # information to be preloaded as both of these are used in state transition # functions var res = StateCache() template load(e: Epoch) = let ancestor = blck.epochAncestor(epoch) for epochRef in ancestor.blck.epochRefs: if epochRef.epoch == e: res.shuffled_active_validator_indices[epochRef.epoch] = epochRef.shuffled_active_validator_indices if epochRef.epoch == epoch: for i, idx in epochRef.beacon_proposers: res.beacon_proposer_indices[ epoch.compute_start_slot_at_epoch + i] = if idx.isSome: some(idx.get()[0]) else: none(ValidatorIndex) break load(epoch) if epoch > 0: load(epoch - 1) res func init(T: type BlockRef, root: Eth2Digest, slot: Slot): BlockRef = BlockRef( root: root, slot: slot ) func init*(T: type BlockRef, root: Eth2Digest, blck: SomeBeaconBlock): BlockRef = BlockRef.init(root, blck.slot) proc init*(T: type ChainDAGRef, preset: RuntimePreset, db: BeaconChainDB, updateFlags: UpdateFlags = {}): ChainDAGRef = # 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 tailBlockRoot = db.getTailBlock() headBlockRoot = db.getHeadBlock() doAssert tailBlockRoot.isSome(), "Missing tail block, database corrupt?" doAssert headBlockRoot.isSome(), "Missing head block, database corrupt?" let tailRoot = tailBlockRoot.get() tailBlock = db.getBlock(tailRoot).get() tailRef = BlockRef.init(tailRoot, tailBlock.message) headRoot = headBlockRoot.get() var blocks = {tailRef.root: tailRef}.toTable() headRef: BlockRef if headRoot != tailRoot: var curRef: BlockRef for blck in db.getAncestors(headRoot): if blck.root == tailRef.root: doAssert(not curRef.isNil) link(tailRef, curRef) curRef = curRef.parent break let newRef = BlockRef.init(blck.root, blck.message) if curRef == nil: curRef = newRef headRef = newRef else: link(newRef, curRef) curRef = curRef.parent blocks[curRef.root] = curRef trace "Populating block dag", key = curRef.root, val = curRef doAssert curRef == tailRef, "head block does not lead to tail, database corrupt?" else: headRef = tailRef var cur = headRef.atSlot(headRef.slot) tmpState = (ref StateData)() # Now that we have a head block, we need to find the most recent state that # we have saved in the database while cur.blck != nil: let root = db.getStateRoot(cur.blck.root, cur.slot) if root.isSome(): # TODO load StateData from BeaconChainDB # We save state root separately for empty slots which means we might # sometimes not find a state even though we saved its state root if db.getState(root.get(), tmpState.data.data, noRollback): tmpState.data.root = root.get() tmpState.blck = cur.blck break if cur.blck.parent != nil and cur.blck.slot.epoch != epoch(cur.blck.parent.slot): # We store the state of the parent block with the epoch processing applied # in the database! cur = cur.blck.parent.atEpochStart(cur.blck.slot.epoch) else: # Moves back slot by slot, in case a state for an empty slot was saved cur = cur.parent if tmpState.blck == nil: warn "No state found in head history, database corrupt?" # TODO Potentially we could recover from here instead of crashing - what # would be a good recovery model? raiseAssert "No state found in head history, database corrupt?" let res = ChainDAGRef( blocks: blocks, tail: tailRef, head: headRef, db: db, heads: @[headRef], headState: tmpState[], tmpState: tmpState[], clearanceState: tmpState[], # The only allowed flag right now is verifyFinalization, as the others all # allow skipping some validation. updateFlags: {verifyFinalization} * updateFlags, runtimePreset: preset, ) doAssert res.updateFlags in [{}, {verifyFinalization}] var cache: StateCache res.updateStateData(res.headState, headRef.atSlot(headRef.slot), cache) # We presently save states on the epoch boundary - it means that the latest # state we loaded might be older than head block - nonetheless, it will be # from the same epoch as the head, thus the finalized and justified slots are # the same - these only change on epoch boundaries. res.finalizedHead = headRef.atEpochStart( res.headState.data.data.finalized_checkpoint.epoch) res.clearanceState = res.headState info "Block dag initialized", head = shortLog(headRef), finalizedHead = shortLog(res.finalizedHead), tail = shortLog(tailRef), totalBlocks = blocks.len res proc findEpochRef*(blck: BlockRef, epoch: Epoch): EpochRef = # may return nil! let ancestor = blck.epochAncestor(epoch) for epochRef in ancestor.blck.epochRefs: if epochRef.epoch == epoch: return epochRef proc getEpochRef*(dag: ChainDAGRef, blck: BlockRef, epoch: Epoch): EpochRef = let epochRef = blck.findEpochRef(epoch) if epochRef != nil: beacon_state_data_cache_hits.inc return epochRef beacon_state_data_cache_misses.inc let ancestor = blck.epochAncestor(epoch) dag.withState(dag.tmpState, ancestor): let prevEpochRef = blck.findEpochRef(epoch - 1) newEpochRef = EpochRef.init(state, cache, prevEpochRef) # TODO consider constraining the number of epochrefs per state ancestor.blck.epochRefs.add newEpochRef newEpochRef.updateKeyStores(blck.parent, dag.finalizedHead.blck) newEpochRef proc getState( dag: ChainDAGRef, state: var StateData, stateRoot: Eth2Digest, blck: BlockRef): bool = let stateAddr = unsafeAddr state # local scope func restore(v: var BeaconState) = if stateAddr == (unsafeAddr dag.headState): # TODO seeing the headState in the restore shouldn't happen - we load # head states only when updating the head position, and by that time # the database will have gone through enough sanity checks that # SSZ exceptions shouldn't happen, which is when restore happens. # Nonetheless, this is an ugly workaround that needs to go away doAssert false, "Cannot alias headState" assign(stateAddr[], dag.headState) if not dag.db.getState(stateRoot, state.data.data, restore): return false state.blck = blck state.data.root = stateRoot true proc getState(dag: ChainDAGRef, state: var StateData, bs: BlockSlot): bool = ## Load a state from the database given a block and a slot - this will first ## lookup the state root in the state root table then load the corresponding ## state, if it exists if not bs.slot.isEpoch: return false # We only ever save epoch states - no need to hit database # TODO earlier versions would store the epoch state with a the epoch block # applied - we generally shouldn't hit the database for such states but # will do so in a transitionary upgrade period! if (let stateRoot = dag.db.getStateRoot(bs.blck.root, bs.slot); stateRoot.isSome()): return dag.getState(state, stateRoot.get(), bs.blck) false proc putState*(dag: ChainDAGRef, state: StateData) = # Store a state and its root # 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 logScope: pcs = "save_state_at_epoch_start" # As a policy, we only store epoch boundary states without the epoch block # (if it exists) applied - the rest can be reconstructed by loading an epoch # boundary state and applying the missing blocks if not state.data.data.slot.isEpoch: trace "Not storing non-epoch state" return if state.data.data.slot <= state.blck.slot: trace "Not storing epoch state with block already applied" return if dag.db.containsState(state.data.root): return info "Storing state", blck = shortLog(state.blck), stateSlot = shortLog(state.data.data.slot), stateRoot = shortLog(state.data.root) # Ideally we would save the state and the root lookup cache in a single # transaction to prevent database inconsistencies, but the state loading code # is resilient against one or the other going missing dag.db.putState(state.data.root, state.data.data) dag.db.putStateRoot(state.blck.root, state.data.data.slot, state.data.root) func getRef*(dag: ChainDAGRef, root: Eth2Digest): BlockRef = ## Retrieve a resolved block reference, if available dag.blocks.getOrDefault(root, nil) func getBlockRange*( dag: ChainDAGRef, startSlot: Slot, skipStep: uint64, output: var openArray[BlockRef]): Natural = ## This function populates an `output` buffer of blocks ## with a slots ranging from `startSlot` up to, but not including, ## `startSlot + skipStep * output.len`, skipping any slots that don't have ## a block. ## ## 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 requestedCount = output.lenu64 trace "getBlockRange entered", head = shortLog(dag.head.root), requestedCount, startSlot, skipStep let headSlot = dag.head.slot runway = if headSlot > startSlot: uint64(headSlot - startSlot) else: return output.len # Identical to returning an empty set of block as indicated above skipStep = max(skipStep, 1) # Treat 0 step as 1 count = min(1'u64 + (runway div skipStep), requestedCount) endSlot = startSlot + count * skipStep var b = dag.head.atSlot(endSlot) o = output.len for i in 0..