# 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. import std/[algorithm, sequtils, sets], extras, beacon_chain_db, stew/results, spec/[beaconstate, crypto, datatypes, digest, presets, validator], block_pools/[block_pools_types, clearance, candidate_chains, quarantine] export results, block_pools_types # Block_Pools # -------------------------------------------- # # Compatibility shims to minimize PR breakage # during block_pool refactor type BlockPool* = object quarantine: Quarantine dag*: CandidateChains {.push raises: [Defect], inline.} # Quarantine dispatch # -------------------------------------------- func checkMissing*(pool: var BlockPool): seq[FetchRecord] = checkMissing(pool.quarantine) # CandidateChains # -------------------------------------------- template tail*(pool: BlockPool): BlockRef = pool.dag.tail template heads*(pool: BlockPool): seq[BlockRef] = pool.dag.heads template head*(pool: BlockPool): BlockRef = pool.dag.head template finalizedHead*(pool: BlockPool): BlockSlot = pool.dag.finalizedHead proc addRawBlock*(pool: var BlockPool, signedBlock: SignedBeaconBlock, onBlockAdded: OnBlockAdded ): Result[BlockRef, BlockError] = ## Add a raw block to the blockpool ## Trigger "callback" on success ## Adding a rawblock might unlock a consequent amount of blocks in quarantine # TODO: `addRawBlock` is accumulating significant cruft # and is in dire need of refactoring # - the ugly `inAdd` field # - the callback # - callback may be problematic as it's called in async validator duties result = addRawBlock(pool.dag, pool.quarantine, signedBlock, onBlockAdded) export parent # func parent*(bs: BlockSlot): BlockSlot export isAncestorOf # func isAncestorOf*(a, b: BlockRef): bool export getAncestorAt # func isAncestorOf*(a, b: BlockRef): bool export get_ancestor # func get_ancestor*(blck: BlockRef, slot: Slot): BlockRef export atSlot # func atSlot*(blck: BlockRef, slot: Slot): BlockSlot proc init*(T: type BlockPool, preset: RuntimePreset, db: BeaconChainDB, updateFlags: UpdateFlags = {}): BlockPool = result.dag = init(CandidateChains, preset, db, updateFlags) func addFlags*(pool: BlockPool, flags: UpdateFlags) = ## Add a flag to the block processing ## This is destined for testing to add skipBLSValidation flag pool.dag.updateFlags.incl flags export init # func init*(T: type BlockRef, root: Eth2Digest, blck: BeaconBlock): BlockRef export addFlags func getRef*(pool: BlockPool, root: Eth2Digest): BlockRef = ## Retrieve a resolved block reference, if available pool.dag.getRef(root) func getBlockRange*( pool: BlockPool, startSlot: Slot, skipStep: Natural, 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. pool.dag.getBlockRange(startSlot, skipStep, output) 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.dag.getBlockBySlot(slot) func getBlockByPreciseSlot*(pool: BlockPool, slot: Slot): BlockRef = ## Retrieves a block from the canonical chain with a slot ## number equal to `slot`. pool.dag.getBlockByPreciseSlot(slot) proc get*(pool: BlockPool, blck: BlockRef): BlockData = ## Retrieve the associated block body of a block reference pool.dag.get(blck) proc get*(pool: BlockPool, root: Eth2Digest): Option[BlockData] = ## Retrieve a resolved block reference and its associated body, if available pool.dag.get(root) func getOrResolve*(pool: var BlockPool, root: Eth2Digest): BlockRef = ## Fetch a block ref, or nil if not found (will be added to list of ## blocks-to-resolve) getOrResolve(pool.dag, pool.quarantine, root) proc updateHead*(pool: BlockPool, newHead: BlockRef) = ## 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. updateHead(pool.dag, newHead) proc addMissing*(pool: var BlockPool, broot: Eth2Digest) {.inline.} = pool.quarantine.addMissing(broot) proc isInitialized*(T: type BlockPool, db: BeaconChainDB): bool = isInitialized(CandidateChains, db) proc preInit*( T: type BlockPool, db: BeaconChainDB, state: BeaconState, signedBlock: SignedBeaconBlock) = preInit(CandidateChains, db, state, signedBlock) proc getProposer*(pool: BlockPool, head: BlockRef, slot: Slot): Option[(ValidatorIndex, ValidatorPubKey)] = getProposer(pool.dag, head, slot) # Rewinder / State transitions # -------------------------------------------- template headState*(pool: BlockPool): StateData = pool.dag.headState template tmpState*(pool: BlockPool): StateData = pool.dag.tmpState template balanceState*(pool: BlockPool): StateData = pool.dag.balanceState 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? withState(pool.dag, cache, blockSlot, body) template withEpochState*( pool: BlockPool, cache: var StateData, blockSlot: BlockSlot, body: untyped): untyped = ## Helper template that updates state to a state with an epoch matching the ## epoch of blockSlot. This aims to be at least as fast as withState, quick ## enough to expose to unautheticated, remote use, but trades off that it's ## possible for it to decide that finding a state from a matching epoch may ## provide too expensive for such use cases. ## ## cache is unsafe outside of block. withEpochState(pool.dag, cache, blockSlot, body) proc updateStateData*( pool: BlockPool, state: var StateData, bs: BlockSlot, matchEpoch: bool = false) = ## 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 updateStateData(pool.dag, state, bs) proc loadTailState*(pool: BlockPool): StateData = loadTailState(pool.dag) proc isValidBeaconBlock*( pool: var BlockPool, signed_beacon_block: SignedBeaconBlock, current_slot: Slot, flags: UpdateFlags): Result[void, BlockError] = isValidBeaconBlock( pool.dag, pool.quarantine, signed_beacon_block, current_slot, flags) # Spec functions implemented based on cached values instead of the full state func count_active_validators*(epochInfo: EpochRef): uint64 = epochInfo.shuffled_active_validator_indices.lenu64 func get_committee_count_per_slot*(epochInfo: EpochRef): uint64 = get_committee_count_per_slot(count_active_validators(epochInfo)) func get_beacon_committee*( epochRef: EpochRef, slot: Slot, index: CommitteeIndex): seq[ValidatorIndex] = # Return the beacon committee at ``slot`` for ``index``. let committees_per_slot = get_committee_count_per_slot(epochRef) compute_committee( epochRef.shuffled_active_validator_indices, (slot mod SLOTS_PER_EPOCH) * committees_per_slot + index.uint64, committees_per_slot * SLOTS_PER_EPOCH ) # https://github.com/ethereum/eth2.0-specs/blob/v0.12.2/specs/phase0/beacon-chain.md#get_attesting_indices func get_attesting_indices*(epochRef: EpochRef, data: AttestationData, bits: CommitteeValidatorsBits): HashSet[ValidatorIndex] = get_attesting_indices( bits, get_beacon_committee(epochRef, data.slot, data.index.CommitteeIndex)) # https://github.com/ethereum/eth2.0-specs/blob/v0.12.2/specs/phase0/beacon-chain.md#get_indexed_attestation func get_indexed_attestation*(epochRef: EpochRef, attestation: Attestation): IndexedAttestation = # Return the indexed attestation corresponding to ``attestation``. let attesting_indices = get_attesting_indices( epochRef, attestation.data, attestation.aggregation_bits) IndexedAttestation( attesting_indices: List[uint64, Limit MAX_VALIDATORS_PER_COMMITTEE].init( sorted(mapIt(attesting_indices, it.uint64), system.cmp)), data: attestation.data, signature: attestation.signature )