keep cache of per-epoch items in block pool (#1068)
* plumbing between block pool and state transition functions around active validator indices and committees * have shared epochrefs followed by blockref tree while allowing for skipped slots * factor out the epoch info extraction; document how the EpochRef follows forks
This commit is contained in:
parent
4b731b266d
commit
b5f45db5e9
|
@ -116,6 +116,10 @@ type
|
||||||
|
|
||||||
updateFlags*: UpdateFlags
|
updateFlags*: UpdateFlags
|
||||||
|
|
||||||
|
EpochRef* = ref object
|
||||||
|
shuffled_active_validator_indices*: seq[ValidatorIndex]
|
||||||
|
epoch*: Epoch
|
||||||
|
|
||||||
BlockRef* = ref object
|
BlockRef* = ref object
|
||||||
## Node in object graph guaranteed to lead back to tail block, and to have
|
## Node in object graph guaranteed to lead back to tail block, and to have
|
||||||
## a corresponding entry in database.
|
## a corresponding entry in database.
|
||||||
|
@ -132,6 +136,11 @@ type
|
||||||
|
|
||||||
slot*: Slot # TODO could calculate this by walking to root, but..
|
slot*: Slot # TODO could calculate this by walking to root, but..
|
||||||
|
|
||||||
|
epochsInfo*: seq[EpochRef]
|
||||||
|
## Could be multiple, since blocks could skip slots, but usually, not many
|
||||||
|
## Even if competing forks happen later during this epoch, potential empty
|
||||||
|
## slots beforehand must all be from this fork.
|
||||||
|
|
||||||
BlockData* = object
|
BlockData* = object
|
||||||
## Body and graph in one
|
## Body and graph in one
|
||||||
|
|
||||||
|
|
|
@ -8,7 +8,7 @@
|
||||||
{.push raises: [Defect].}
|
{.push raises: [Defect].}
|
||||||
|
|
||||||
import
|
import
|
||||||
chronicles, options, tables,
|
chronicles, options, sequtils, tables,
|
||||||
metrics,
|
metrics,
|
||||||
../ssz, ../beacon_chain_db, ../state_transition, ../extras,
|
../ssz, ../beacon_chain_db, ../state_transition, ../extras,
|
||||||
../spec/[crypto, datatypes, digest, helpers, validator],
|
../spec/[crypto, datatypes, digest, helpers, validator],
|
||||||
|
@ -53,6 +53,12 @@ func parent*(bs: BlockSlot): BlockSlot =
|
||||||
slot: bs.slot - 1
|
slot: bs.slot - 1
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func populateEpochCache*(state: BeaconState, epoch: Epoch): EpochRef =
|
||||||
|
result = (EpochRef)(
|
||||||
|
epoch: state.slot.compute_epoch_at_slot,
|
||||||
|
shuffled_active_validator_indices:
|
||||||
|
get_shuffled_active_validator_indices(state, epoch))
|
||||||
|
|
||||||
func link*(parent, child: BlockRef) =
|
func link*(parent, child: BlockRef) =
|
||||||
doAssert (not (parent.root == Eth2Digest() or child.root == Eth2Digest())),
|
doAssert (not (parent.root == Eth2Digest() or child.root == Eth2Digest())),
|
||||||
"blocks missing root!"
|
"blocks missing root!"
|
||||||
|
@ -133,6 +139,23 @@ func atSlot*(blck: BlockRef, slot: Slot): BlockSlot =
|
||||||
## block proposal)
|
## block proposal)
|
||||||
BlockSlot(blck: blck.getAncestorAt(slot), slot: slot)
|
BlockSlot(blck: blck.getAncestorAt(slot), slot: slot)
|
||||||
|
|
||||||
|
func getEpochInfo*(blck: BlockRef, state: BeaconState): EpochRef =
|
||||||
|
# This is the only intended mechanism by which to get an EpochRef
|
||||||
|
let
|
||||||
|
state_epoch = state.slot.compute_epoch_at_slot
|
||||||
|
matching_epochinfo = blck.epochsInfo.filterIt(it.epoch == state_epoch)
|
||||||
|
|
||||||
|
if matching_epochinfo.len == 0:
|
||||||
|
let cache = populateEpochCache(state, state_epoch)
|
||||||
|
blck.epochsInfo.add(cache)
|
||||||
|
trace "candidate_chains.skipAndUpdateState(): back-filling parent.epochInfo",
|
||||||
|
state_slot = state.slot
|
||||||
|
cache
|
||||||
|
elif matching_epochinfo.len == 1:
|
||||||
|
matching_epochinfo[0]
|
||||||
|
else:
|
||||||
|
raiseAssert "multiple EpochRefs per epoch per BlockRef invalid"
|
||||||
|
|
||||||
func init(T: type BlockRef, root: Eth2Digest, slot: Slot): BlockRef =
|
func init(T: type BlockRef, root: Eth2Digest, slot: Slot): BlockRef =
|
||||||
BlockRef(
|
BlockRef(
|
||||||
root: root,
|
root: root,
|
||||||
|
@ -441,8 +464,17 @@ proc skipAndUpdateState(
|
||||||
doAssert (addr(statePtr.data) == addr v)
|
doAssert (addr(statePtr.data) == addr v)
|
||||||
statePtr[] = dag.headState
|
statePtr[] = dag.headState
|
||||||
|
|
||||||
|
# TODO it's probably not the right way to convey this, but for now, avoids
|
||||||
|
# death-by-dozens-of-pointless-changes in developing this
|
||||||
|
let epochInfo = getEpochInfo(blck.refs, state.data.data)
|
||||||
|
var stateCache = get_empty_per_epoch_cache()
|
||||||
|
stateCache.shuffled_active_validator_indices[
|
||||||
|
state.data.data.slot.compute_epoch_at_slot] =
|
||||||
|
epochInfo.shuffled_active_validator_indices
|
||||||
|
|
||||||
let ok = state_transition(
|
let ok = state_transition(
|
||||||
state.data, blck.data, flags + dag.updateFlags, restore)
|
state.data, blck.data, stateCache, flags + dag.updateFlags, restore)
|
||||||
|
|
||||||
if ok and save:
|
if ok and save:
|
||||||
dag.putState(state.data, blck.refs)
|
dag.putState(state.data, blck.refs)
|
||||||
|
|
||||||
|
|
|
@ -9,7 +9,7 @@ import
|
||||||
chronicles, tables,
|
chronicles, tables,
|
||||||
metrics, stew/results,
|
metrics, stew/results,
|
||||||
../ssz, ../state_transition, ../extras,
|
../ssz, ../state_transition, ../extras,
|
||||||
../spec/[crypto, datatypes, digest, helpers],
|
../spec/[crypto, datatypes, digest, helpers, validator],
|
||||||
|
|
||||||
block_pools_types, candidate_chains
|
block_pools_types, candidate_chains
|
||||||
|
|
||||||
|
@ -46,6 +46,8 @@ proc addResolvedBlock(
|
||||||
doAssert state.slot == signedBlock.message.slot, "state must match block"
|
doAssert state.slot == signedBlock.message.slot, "state must match block"
|
||||||
|
|
||||||
let blockRef = BlockRef.init(blockRoot, signedBlock.message)
|
let blockRef = BlockRef.init(blockRoot, signedBlock.message)
|
||||||
|
parent.epochsInfo =
|
||||||
|
@[populateEpochCache(state, state.slot.compute_epoch_at_slot)]
|
||||||
link(parent, blockRef)
|
link(parent, blockRef)
|
||||||
|
|
||||||
dag.blocks[blockRoot] = blockRef
|
dag.blocks[blockRoot] = blockRef
|
||||||
|
@ -175,8 +177,19 @@ proc add*(
|
||||||
doAssert v.addr == addr poolPtr.tmpState.data
|
doAssert v.addr == addr poolPtr.tmpState.data
|
||||||
poolPtr.tmpState = poolPtr.headState
|
poolPtr.tmpState = poolPtr.headState
|
||||||
|
|
||||||
|
# TODO it's probably not the right way to convey this, but for now, avoids
|
||||||
|
# death-by-dozens-of-pointless-changes in developing this
|
||||||
|
# TODO rename these, since now, the two "state cache"s are juxtaposed
|
||||||
|
# directly
|
||||||
|
let epochInfo = getEpochInfo(parent, dag.tmpState.data.data)
|
||||||
|
var stateCache = get_empty_per_epoch_cache()
|
||||||
|
stateCache.shuffled_active_validator_indices[
|
||||||
|
dag.tmpState.data.data.slot.compute_epoch_at_slot] =
|
||||||
|
epochInfo.shuffled_active_validator_indices
|
||||||
|
# End of section to refactor/combine
|
||||||
|
|
||||||
if not state_transition(
|
if not state_transition(
|
||||||
dag.tmpState.data, signedBlock, dag.updateFlags, restore):
|
dag.tmpState.data, signedBlock, stateCache, dag.updateFlags, restore):
|
||||||
# TODO find a better way to log all this block data
|
# TODO find a better way to log all this block data
|
||||||
notice "Invalid block",
|
notice "Invalid block",
|
||||||
blck = shortLog(blck),
|
blck = shortLog(blck),
|
||||||
|
|
|
@ -399,11 +399,14 @@ type
|
||||||
data*: BeaconState
|
data*: BeaconState
|
||||||
root*: Eth2Digest # hash_tree_root(data)
|
root*: Eth2Digest # hash_tree_root(data)
|
||||||
|
|
||||||
|
# TODO remove some of these, or otherwise coordinate with EpochRef
|
||||||
StateCache* = object
|
StateCache* = object
|
||||||
beacon_committee_cache*:
|
beacon_committee_cache*:
|
||||||
Table[tuple[a: int, b: Eth2Digest], seq[ValidatorIndex]]
|
Table[tuple[a: int, b: Eth2Digest], seq[ValidatorIndex]]
|
||||||
active_validator_indices_cache*:
|
active_validator_indices_cache*:
|
||||||
Table[Epoch, seq[ValidatorIndex]]
|
Table[Epoch, seq[ValidatorIndex]]
|
||||||
|
shuffled_active_validator_indices*:
|
||||||
|
Table[Epoch, seq[ValidatorIndex]]
|
||||||
committee_count_cache*: Table[Epoch, uint64]
|
committee_count_cache*: Table[Epoch, uint64]
|
||||||
|
|
||||||
func shortValidatorKey*(state: BeaconState, validatorIdx: int): string =
|
func shortValidatorKey*(state: BeaconState, validatorIdx: int): string =
|
||||||
|
|
|
@ -79,6 +79,17 @@ func get_shuffled_seq*(seed: Eth2Digest,
|
||||||
|
|
||||||
result = shuffled_active_validator_indices
|
result = shuffled_active_validator_indices
|
||||||
|
|
||||||
|
func get_shuffled_active_validator_indices*(state: BeaconState, epoch: Epoch):
|
||||||
|
seq[ValidatorIndex] =
|
||||||
|
# Non-spec function, to cache a data structure from which one can cheaply
|
||||||
|
# compute both get_active_validator_indexes() and get_beacon_committee().
|
||||||
|
let active_validator_indices = get_active_validator_indices(state, epoch)
|
||||||
|
mapIt(
|
||||||
|
get_shuffled_seq(
|
||||||
|
get_seed(state, epoch, DOMAIN_BEACON_ATTESTER),
|
||||||
|
active_validator_indices.len.uint64),
|
||||||
|
active_validator_indices[it])
|
||||||
|
|
||||||
# https://github.com/ethereum/eth2.0-specs/blob/v0.11.3/specs/phase0/beacon-chain.md#get_previous_epoch
|
# https://github.com/ethereum/eth2.0-specs/blob/v0.11.3/specs/phase0/beacon-chain.md#get_previous_epoch
|
||||||
func get_previous_epoch*(state: BeaconState): Epoch =
|
func get_previous_epoch*(state: BeaconState): Epoch =
|
||||||
# Return the previous epoch (unless the current epoch is ``GENESIS_EPOCH``).
|
# Return the previous epoch (unless the current epoch is ``GENESIS_EPOCH``).
|
||||||
|
@ -153,6 +164,8 @@ func get_empty_per_epoch_cache*(): StateCache =
|
||||||
initTable[tuple[a: int, b: Eth2Digest], seq[ValidatorIndex]]()
|
initTable[tuple[a: int, b: Eth2Digest], seq[ValidatorIndex]]()
|
||||||
result.active_validator_indices_cache =
|
result.active_validator_indices_cache =
|
||||||
initTable[Epoch, seq[ValidatorIndex]]()
|
initTable[Epoch, seq[ValidatorIndex]]()
|
||||||
|
result.shuffled_active_validator_indices =
|
||||||
|
initTable[Epoch, seq[ValidatorIndex]]()
|
||||||
result.committee_count_cache = initTable[Epoch, uint64]()
|
result.committee_count_cache = initTable[Epoch, uint64]()
|
||||||
|
|
||||||
# https://github.com/ethereum/eth2.0-specs/blob/v0.11.3/specs/phase0/beacon-chain.md#compute_proposer_index
|
# https://github.com/ethereum/eth2.0-specs/blob/v0.11.3/specs/phase0/beacon-chain.md#compute_proposer_index
|
||||||
|
|
|
@ -28,6 +28,7 @@
|
||||||
{.push raises: [Defect].}
|
{.push raises: [Defect].}
|
||||||
|
|
||||||
import
|
import
|
||||||
|
tables,
|
||||||
chronicles,
|
chronicles,
|
||||||
stew/results,
|
stew/results,
|
||||||
./extras, ./ssz, metrics,
|
./extras, ./ssz, metrics,
|
||||||
|
@ -177,6 +178,8 @@ proc noRollback*(state: var HashedBeaconState) =
|
||||||
|
|
||||||
proc state_transition*(
|
proc state_transition*(
|
||||||
state: var HashedBeaconState, signedBlock: SignedBeaconBlock,
|
state: var HashedBeaconState, signedBlock: SignedBeaconBlock,
|
||||||
|
# TODO this is ... okay, but not perfect; align with EpochRef
|
||||||
|
stateCache: var StateCache,
|
||||||
flags: UpdateFlags, rollback: RollbackHashedProc): bool {.nbench.} =
|
flags: UpdateFlags, rollback: RollbackHashedProc): bool {.nbench.} =
|
||||||
## Time in the beacon chain moves by slots. Every time (haha.) that happens,
|
## Time in the beacon chain moves by slots. Every time (haha.) that happens,
|
||||||
## we will update the beacon state. Normally, the state updates will be driven
|
## we will update the beacon state. Normally, the state updates will be driven
|
||||||
|
@ -209,6 +212,7 @@ proc state_transition*(
|
||||||
# the changes in case of failure (look out for `var BeaconState` and
|
# the changes in case of failure (look out for `var BeaconState` and
|
||||||
# bool return values...)
|
# bool return values...)
|
||||||
doAssert not rollback.isNil, "use noRollback if it's ok to mess up state"
|
doAssert not rollback.isNil, "use noRollback if it's ok to mess up state"
|
||||||
|
doAssert stateCache.shuffled_active_validator_indices.hasKey(state.data.slot.compute_epoch_at_slot)
|
||||||
|
|
||||||
if not process_slots(state, signedBlock.message.slot, flags):
|
if not process_slots(state, signedBlock.message.slot, flags):
|
||||||
rollback(state)
|
rollback(state)
|
||||||
|
@ -223,11 +227,11 @@ proc state_transition*(
|
||||||
if skipBLSValidation in flags or
|
if skipBLSValidation in flags or
|
||||||
verify_block_signature(state.data, signedBlock):
|
verify_block_signature(state.data, signedBlock):
|
||||||
|
|
||||||
var per_epoch_cache = get_empty_per_epoch_cache()
|
# TODO after checking scaffolding, remove this
|
||||||
trace "in state_transition: processing block, signature passed",
|
trace "in state_transition: processing block, signature passed",
|
||||||
signature = signedBlock.signature,
|
signature = signedBlock.signature,
|
||||||
blockRoot = hash_tree_root(signedBlock.message)
|
blockRoot = hash_tree_root(signedBlock.message)
|
||||||
if processBlock(state.data, signedBlock.message, flags, per_epoch_cache):
|
if processBlock(state.data, signedBlock.message, flags, stateCache):
|
||||||
if skipStateRootValidation in flags or verifyStateRoot(state.data, signedBlock.message):
|
if skipStateRootValidation in flags or verifyStateRoot(state.data, signedBlock.message):
|
||||||
# State root is what it should be - we're done!
|
# State root is what it should be - we're done!
|
||||||
|
|
||||||
|
@ -245,6 +249,18 @@ proc state_transition*(
|
||||||
|
|
||||||
false
|
false
|
||||||
|
|
||||||
|
proc state_transition*(
|
||||||
|
state: var HashedBeaconState, signedBlock: SignedBeaconBlock,
|
||||||
|
flags: UpdateFlags, rollback: RollbackHashedProc): bool {.nbench.} =
|
||||||
|
# TODO consider moving this to testutils or similar, since non-testing
|
||||||
|
# and fuzzing code should always be coming from blockpool which should
|
||||||
|
# always be providing cache or equivalent
|
||||||
|
var cache = get_empty_per_epoch_cache()
|
||||||
|
cache.shuffled_active_validator_indices[state.data.slot.compute_epoch_at_slot] =
|
||||||
|
get_shuffled_active_validator_indices(
|
||||||
|
state.data, state.data.slot.compute_epoch_at_slot)
|
||||||
|
state_transition(state, signedBlock, cache, flags, rollback)
|
||||||
|
|
||||||
# https://github.com/ethereum/eth2.0-specs/blob/v0.11.1/specs/phase0/validator.md
|
# https://github.com/ethereum/eth2.0-specs/blob/v0.11.1/specs/phase0/validator.md
|
||||||
# TODO There's more to do here - the spec has helpers that deal set up some of
|
# TODO There's more to do here - the spec has helpers that deal set up some of
|
||||||
# the fields in here!
|
# the fields in here!
|
||||||
|
|
Loading…
Reference in New Issue