Jacek Sieka 2449d4b479
cache empty slot state root (#961)
When replaying state transitions, for the slots that have a block, the
state root is taken from the block. For slots that lack a block, it's
currently calculated using hash_tree_root which is expensive.

Caching the empty slot state roots helps us avoid recalculating this
hash, meaning that for replay, hashes are never calculated. This turns
blocks into fairly lightweight "state-diffs"!

* avoid re-saving state when replaying blocks
* advance empty slots slot-by-slot and save root
* fix sim randomness
* fix sim genesis filename
* introduce `isEpoch` to check if a slot is an epoch slot
2020-05-03 19:44:04 +02:00

204 lines
8.1 KiB
Nim

# 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.
# Uncategorized helper functions from the spec
{.push raises: [Defect].}
import
# Standard lib
math,
# Third-party
stew/endians2,
# Internal
./datatypes, ./digest, ../ssz
# https://github.com/ethereum/eth2.0-specs/blob/v0.11.1/specs/phase0/beacon-chain.md#integer_squareroot
func integer_squareroot*(n: SomeInteger): SomeInteger =
# Return the largest integer ``x`` such that ``x**2 <= n``.
doAssert n >= 0'u64
var
x = n
y = (x + 1) div 2
while y < x:
x = y
y = (x + n div x) div 2
x
# https://github.com/ethereum/eth2.0-specs/blob/v0.11.1/specs/phase0/beacon-chain.md#compute_epoch_at_slot
func compute_epoch_at_slot*(slot: Slot|uint64): Epoch =
# Return the epoch number at ``slot``.
(slot div SLOTS_PER_EPOCH).Epoch
template epoch*(slot: Slot): Epoch =
compute_epoch_at_slot(slot)
template isEpoch*(slot: Slot): bool =
(slot mod SLOTS_PER_EPOCH) == 0
# https://github.com/ethereum/eth2.0-specs/blob/v0.11.1/specs/phase0/beacon-chain.md#compute_start_slot_at_epoch
func compute_start_slot_at_epoch*(epoch: Epoch): Slot =
# Return the start slot of ``epoch``.
(epoch * SLOTS_PER_EPOCH).Slot
# https://github.com/ethereum/eth2.0-specs/blob/v0.11.1/specs/phase0/beacon-chain.md#is_active_validator
func is_active_validator*(validator: Validator, epoch: Epoch): bool =
### Check if ``validator`` is active
validator.activation_epoch <= epoch and epoch < validator.exit_epoch
# https://github.com/ethereum/eth2.0-specs/blob/v0.11.1/specs/phase0/beacon-chain.md#get_active_validator_indices
func get_active_validator_indices*(state: BeaconState, epoch: Epoch):
seq[ValidatorIndex] =
# Return the sequence of active validator indices at ``epoch``.
for idx, val in state.validators:
if is_active_validator(val, epoch):
result.add idx.ValidatorIndex
# https://github.com/ethereum/eth2.0-specs/blob/v0.11.1/specs/phase0/beacon-chain.md#get_committee_count_at_slot
func get_committee_count_at_slot*(state: BeaconState, slot: Slot): uint64 =
# Return the number of committees at ``slot``.
# TODO this is mostly used in for loops which have indexes which then need to
# be converted to CommitteeIndex types for get_beacon_committee(...); replace
# with better and more type-safe use pattern, probably beginning with using a
# CommitteeIndex return type here.
let epoch = compute_epoch_at_slot(slot)
let active_validator_indices = get_active_validator_indices(state, epoch)
let committees_per_slot = clamp(
len(active_validator_indices) div SLOTS_PER_EPOCH div TARGET_COMMITTEE_SIZE,
1, MAX_COMMITTEES_PER_SLOT).uint64
result = committees_per_slot
# Otherwise, get_beacon_committee(...) cannot access some committees.
doAssert (SLOTS_PER_EPOCH * MAX_COMMITTEES_PER_SLOT).uint64 >= result
# https://github.com/ethereum/eth2.0-specs/blob/v0.11.1/specs/phase0/beacon-chain.md#get_current_epoch
func get_current_epoch*(state: BeaconState): Epoch =
# Return the current epoch.
doAssert state.slot >= GENESIS_SLOT, $state.slot
compute_epoch_at_slot(state.slot)
# https://github.com/ethereum/eth2.0-specs/blob/v0.11.1/specs/phase0/beacon-chain.md#get_randao_mix
func get_randao_mix*(state: BeaconState,
epoch: Epoch): Eth2Digest =
## Returns the randao mix at a recent ``epoch``.
state.randao_mixes[epoch mod EPOCHS_PER_HISTORICAL_VECTOR]
func bytes_to_int*(data: openarray[byte]): uint64 =
doAssert data.len == 8
# Little-endian data representation
result = 0
for i in countdown(7, 0):
result = result * 256 + data[i]
# Have 1, 4, 8, and 32-byte versions. 1+ more and maybe worth metaprogramming.
func int_to_bytes32*(x: uint64): array[32, byte] =
## Little-endian data representation
## TODO remove uint64 when those callers fade away
result[0..<7] = x.toBytesLE()
func int_to_bytes32*(x: Epoch): array[32, byte] {.borrow.}
func int_to_bytes8*(x: uint64): array[8, byte] =
x.toBytesLE()
func int_to_bytes1*(x: int): array[1, byte] =
doAssert x >= 0
doAssert x < 256
result[0] = x.byte
func int_to_bytes4*(x: uint64): array[4, byte] =
doAssert x >= 0'u64
doAssert x < 2'u64^32
# Little-endian data representation
result[0] = ((x shr 0) and 0xff).byte
result[1] = ((x shr 8) and 0xff).byte
result[2] = ((x shr 16) and 0xff).byte
result[3] = ((x shr 24) and 0xff).byte
# https://github.com/ethereum/eth2.0-specs/blob/v0.11.1/specs/phase0/beacon-chain.md#compute_fork_data_root
func compute_fork_data_root(current_version: array[4, byte],
genesis_validators_root: Eth2Digest): Eth2Digest =
# Return the 32-byte fork data root for the ``current_version`` and
# ``genesis_validators_root``.
# This is used primarily in signature domains to avoid collisions across
# forks/chains.
hash_tree_root(ForkData(
current_version: current_version,
genesis_validators_root: genesis_validators_root
))
# https://github.com/ethereum/eth2.0-specs/blob/v0.11.1/specs/phase0/beacon-chain.md#compute_fork_digest
func compute_fork_digest*(current_version: array[4, byte],
genesis_validators_root: Eth2Digest): array[4, byte] =
# Return the 4-byte fork digest for the ``current_version`` and
# ``genesis_validators_root``.
# This is a digest primarily used for domain separation on the p2p layer.
# 4-bytes suffices for practical separation of forks/chains.
result[0..3] =
compute_fork_data_root(current_version, genesis_validators_root).data[0..3]
# https://github.com/ethereum/eth2.0-specs/blob/v0.11.1/specs/phase0/beacon-chain.md#compute_domain
func compute_domain*(
domain_type: DomainType,
fork_version: array[4, byte] = [0'u8, 0, 0, 0],
genesis_validators_root: Eth2Digest = ZERO_HASH): Domain =
# Return the domain for the ``domain_type`` and ``fork_version``.
let fork_data_root =
compute_fork_data_root(fork_version, genesis_validators_root)
result[0..3] = int_to_bytes4(domain_type.uint64)
result[4..31] = fork_data_root.data[0..27]
# https://github.com/ethereum/eth2.0-specs/blob/v0.11.1/specs/phase0/beacon-chain.md#get_domain
func get_domain*(
fork: Fork, domain_type: DomainType, epoch: Epoch, genesis_validators_root: Eth2Digest): Domain =
## Return the signature domain (fork version concatenated with domain type)
## of a message.
let fork_version =
if epoch < fork.epoch:
fork.previous_version
else:
fork.current_version
compute_domain(domain_type, fork_version, genesis_validators_root)
func get_domain*(
state: BeaconState, domain_type: DomainType, epoch: Epoch): Domain =
## Return the signature domain (fork version concatenated with domain type)
## of a message.
get_domain(state.fork, domain_type, epoch, state.genesis_validators_root)
# https://github.com/ethereum/eth2.0-specs/blob/v0.11.1/specs/phase0/beacon-chain.md#compute_signing_root
func compute_signing_root*(ssz_object: auto, domain: Domain): Eth2Digest =
# Return the signing root of an object by calculating the root of the
# object-domain tree.
let domain_wrapped_object = SigningRoot(
object_root: hash_tree_root(ssz_object),
domain: domain
)
hash_tree_root(domain_wrapped_object)
# https://github.com/ethereum/eth2.0-specs/blob/v0.11.1/specs/phase0/beacon-chain.md#get_seed
func get_seed*(state: BeaconState, epoch: Epoch, domain_type: DomainType): Eth2Digest =
# Return the seed at ``epoch``.
var seed_input : array[4+8+32, byte]
# Detect potential underflow
static:
doAssert EPOCHS_PER_HISTORICAL_VECTOR > MIN_SEED_LOOKAHEAD
seed_input[0..3] = int_to_bytes4(domain_type.uint64)
seed_input[4..11] = int_to_bytes8(epoch.uint64)
seed_input[12..43] =
get_randao_mix(state, # Avoid underflow
epoch + EPOCHS_PER_HISTORICAL_VECTOR - MIN_SEED_LOOKAHEAD - 1).data
eth2hash(seed_input)