nimbus-eth2/beacon_chain/spec/datatypes.nim

833 lines
29 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.
# This file contains data types that are part of the spec and thus subject to
# serialization and spec updates.
#
# The spec folder in general contains code that has been hoisted from the
# specification and that follows the spec as closely as possible, so as to make
# it easy to keep up-to-date.
#
# These datatypes are used as specifications for serialization - thus should not
# be altered outside of what the spec says. Likewise, they should not be made
# `ref` - this can be achieved by wrapping them in higher-level
# types / composition
{.experimental: "notnil".}
{.push raises: [Defect].}
import
macros, hashes, json, strutils, tables, typetraits,
stew/[byteutils], chronicles,
json_serialization/types as jsonTypes,
../version, ../ssz/types as sszTypes, ./crypto, ./digest, ./presets
export
sszTypes, presets
# TODO Data types:
# Presently, we're reusing the data types from the serialization (uint64) in the
# objects we pass around to the beacon chain logic, thus keeping the two
# similar. This is convenient for keeping up with the specification, but
# will eventually need a more robust approach such that we don't run into
# over- and underflows.
# Some of the open questions are being tracked here:
# https://github.com/ethereum/eth2.0-specs/issues/224
#
# The present approach causes some problems due to how Nim treats unsigned
# integers - here's no high(uint64), arithmetic support is incomplete, there's
# no over/underflow checking available
#
# Eventually, we could also differentiate between user/tainted data and
# internal state that's gone through sanity checks already.
const
SPEC_VERSION* = "0.12.3" ## \
## Spec version we're aiming to be compatible with, right now
GENESIS_SLOT* = Slot(0)
GENESIS_EPOCH* = (GENESIS_SLOT.uint64 div SLOTS_PER_EPOCH).Epoch ##\
## compute_epoch_at_slot(GENESIS_SLOT)
FAR_FUTURE_EPOCH* = (not 0'u64).Epoch # 2^64 - 1 in spec
# Not part of spec. Still useful, pending removing usage if appropriate.
ZERO_HASH* = Eth2Digest()
# Not part of spec
WEAK_SUBJECTVITY_PERIOD* =
Slot(uint64(4 * 30 * 24 * 60 * 60) div SECONDS_PER_SLOT)
# TODO: This needs revisiting.
# Why was the validator WITHDRAWAL_PERIOD altered in the spec?
# https://github.com/ethereum/eth2.0-specs/blob/v0.12.2/specs/phase0/p2p-interface.md#configuration
ATTESTATION_PROPAGATION_SLOT_RANGE* = 32
SLOTS_PER_ETH1_VOTING_PERIOD* = EPOCHS_PER_ETH1_VOTING_PERIOD * SLOTS_PER_EPOCH
DEPOSIT_CONTRACT_TREE_DEPTH* = 32
BASE_REWARDS_PER_EPOCH* = 4
template maxSize*(n: int) {.pragma.}
type
# Domains
# ---------------------------------------------------------------
# https://github.com/ethereum/eth2.0-specs/blob/v0.12.2/specs/phase0/beacon-chain.md#domain-types
DomainType* = enum
DOMAIN_BEACON_PROPOSER = 0
DOMAIN_BEACON_ATTESTER = 1
DOMAIN_RANDAO = 2
DOMAIN_DEPOSIT = 3
DOMAIN_VOLUNTARY_EXIT = 4
DOMAIN_SELECTION_PROOF = 5
DOMAIN_AGGREGATE_AND_PROOF = 6
# https://github.com/ethereum/eth2.0-specs/blob/v0.12.2/specs/phase0/beacon-chain.md#custom-types
Domain* = array[32, byte]
# https://github.com/nim-lang/Nim/issues/574 and be consistent across
# 32-bit and 64-bit word platforms.
# TODO VALIDATOR_REGISTRY_LIMIT is 1 shl 40 in 0.12.1, and
# proc newSeq(typ: PNimType, len: int): pointer {.compilerRtl.}
# in Nim/lib/system/gc.nim quite tightly ties seq addressibility
# to the system wordsize. This lifts smaller, and now incorrect,
# range-limit.
ValidatorIndex* = distinct uint32
Gwei* = uint64
CommitteeIndex* = distinct uint64
# https://github.com/ethereum/eth2.0-specs/blob/v0.12.2/specs/phase0/beacon-chain.md#proposerslashing
ProposerSlashing* = object
signed_header_1*: SignedBeaconBlockHeader
signed_header_2*: SignedBeaconBlockHeader
# https://github.com/ethereum/eth2.0-specs/blob/v0.12.2/specs/phase0/beacon-chain.md#attesterslashing
AttesterSlashing* = object
attestation_1*: IndexedAttestation
attestation_2*: IndexedAttestation
# https://github.com/ethereum/eth2.0-specs/blob/v0.12.2/specs/phase0/beacon-chain.md#indexedattestation
IndexedAttestation* = object
# TODO ValidatorIndex, but that doesn't serialize properly
attesting_indices*: List[uint64, Limit MAX_VALIDATORS_PER_COMMITTEE]
data*: AttestationData
signature*: ValidatorSig
TrustedIndexedAttestation* = object
# TODO ValidatorIndex, but that doesn't serialize properly
attesting_indices*: List[uint64, Limit MAX_VALIDATORS_PER_COMMITTEE]
data*: AttestationData
signature*: TrustedSig
CommitteeValidatorsBits* = BitList[Limit MAX_VALIDATORS_PER_COMMITTEE]
# https://github.com/ethereum/eth2.0-specs/blob/v0.12.2/specs/phase0/beacon-chain.md#attestation
Attestation* = object
aggregation_bits*: CommitteeValidatorsBits
data*: AttestationData
signature*: ValidatorSig
TrustedAttestation* = object
aggregation_bits*: CommitteeValidatorsBits
data*: AttestationData
signature*: TrustedSig
ForkDigest* = distinct array[4, byte]
# https://github.com/ethereum/eth2.0-specs/blob/v0.12.2/specs/phase0/beacon-chain.md#forkdata
ForkData* = object
current_version*: Version
genesis_validators_root*: Eth2Digest
# https://github.com/ethereum/eth2.0-specs/blob/v0.12.2/specs/phase0/beacon-chain.md#checkpoint
Checkpoint* = object
epoch*: Epoch
root*: Eth2Digest
# https://github.com/ethereum/eth2.0-specs/blob/v0.12.2/specs/phase0/beacon-chain.md#AttestationData
AttestationData* = object
slot*: Slot
# TODO this is actually a CommitteeIndex; remove some conversions by
# allowing SSZ to directly handle this
index*: uint64
# LMD GHOST vote
beacon_block_root*: Eth2Digest
# FFG vote
source*: Checkpoint
target*: Checkpoint
# https://github.com/ethereum/eth2.0-specs/blob/v0.12.2/specs/phase0/beacon-chain.md#deposit
Deposit* = object
proof*: array[DEPOSIT_CONTRACT_TREE_DEPTH + 1, Eth2Digest] ##\
## Merkle path to deposit root
data*: DepositData
# https://github.com/ethereum/eth2.0-specs/blob/v0.12.2/specs/phase0/beacon-chain.md#depositmessage
DepositMessage* = object
pubkey*: ValidatorPubKey
withdrawal_credentials*: Eth2Digest
amount*: Gwei
# https://github.com/ethereum/eth2.0-specs/blob/v0.12.2/specs/phase0/beacon-chain.md#depositdata
DepositData* = object
pubkey*: ValidatorPubKey
withdrawal_credentials*: Eth2Digest
amount*: Gwei
# Cannot use TrustedSig here as invalid signatures are possible and determine
# if the deposit should be added or not during processing
signature*: ValidatorSig # Signing over DepositMessage
# https://github.com/ethereum/eth2.0-specs/blob/v0.12.2/specs/phase0/beacon-chain.md#voluntaryexit
VoluntaryExit* = object
epoch*: Epoch ##\
## Earliest epoch when voluntary exit can be processed
validator_index*: uint64
# https://github.com/ethereum/eth2.0-specs/blob/v0.12.2/specs/phase0/beacon-chain.md#beaconblock
BeaconBlock* = object
## For each slot, a proposer is chosen from the validator pool to propose
## a new block. Once the block as been proposed, it is transmitted to
## validators that will have a chance to vote on it through attestations.
## Each block collects attestations, or votes, on past blocks, thus a chain
## is formed.
slot*: Slot
proposer_index*: uint64
parent_root*: Eth2Digest ##\
## Root hash of the previous block
state_root*: Eth2Digest ##\
## The state root, _after_ this block has been processed
body*: BeaconBlockBody
TrustedBeaconBlock* = object
## When we receive blocks from outside sources, they are untrusted and go
## through several layers of validation. Blocks that have gone through
## validations can be trusted to be well-formed, with a correct signature,
## having a parent and applying cleanly to the state that their parent
## left them with.
##
## When loading such blocks from the database, to rewind states for example,
## it is expensive to redo the validations (in particular, the signature
## checks), thus `TrustedBlock` uses a `TrustedSig` type to mark that these
## checks can be skipped.
##
## TODO this could probably be solved with some type trickery, but there
## too many bugs in nim around generics handling, and we've used up
## the trickery budget in the serialization library already. Until
## then, the type must be manually kept compatible with its untrusted
## cousin.
slot*: Slot
proposer_index*: uint64
parent_root*: Eth2Digest ##\
state_root*: Eth2Digest ##\
body*: TrustedBeaconBlockBody
# https://github.com/ethereum/eth2.0-specs/blob/v0.12.2/specs/phase0/beacon-chain.md#beaconblockheader
BeaconBlockHeader* = object
slot*: Slot
proposer_index*: uint64
parent_root*: Eth2Digest
state_root*: Eth2Digest
body_root*: Eth2Digest
# https://github.com/ethereum/eth2.0-specs/blob/v0.12.2/specs/phase0/beacon-chain.md#signingdata
SigningData* = object
object_root*: Eth2Digest
domain*: Domain
GraffitiBytes* = distinct array[32, byte]
# https://github.com/ethereum/eth2.0-specs/blob/v0.12.2/specs/phase0/beacon-chain.md#beaconblockbody
BeaconBlockBody* = object
randao_reveal*: ValidatorSig
eth1_data*: Eth1Data
graffiti*: GraffitiBytes
# Operations
proposer_slashings*: List[ProposerSlashing, Limit MAX_PROPOSER_SLASHINGS]
attester_slashings*: List[AttesterSlashing, Limit MAX_ATTESTER_SLASHINGS]
attestations*: List[Attestation, Limit MAX_ATTESTATIONS]
deposits*: List[Deposit, Limit MAX_DEPOSITS]
voluntary_exits*: List[SignedVoluntaryExit, Limit MAX_VOLUNTARY_EXITS]
TrustedBeaconBlockBody* = object
randao_reveal*: TrustedSig
eth1_data*: Eth1Data
graffiti*: GraffitiBytes
# Operations
proposer_slashings*: List[ProposerSlashing, Limit MAX_PROPOSER_SLASHINGS]
attester_slashings*: List[AttesterSlashing, Limit MAX_ATTESTER_SLASHINGS]
attestations*: List[TrustedAttestation, Limit MAX_ATTESTATIONS]
deposits*: List[Deposit, Limit MAX_DEPOSITS]
voluntary_exits*: List[SignedVoluntaryExit, Limit MAX_VOLUNTARY_EXITS]
SomeSignedBeaconBlock* = SignedBeaconBlock | TrustedSignedBeaconBlock
SomeBeaconBlock* = BeaconBlock | TrustedBeaconBlock
SomeBeaconBlockBody* = BeaconBlockBody | TrustedBeaconBlockBody
SomeAttestation* = Attestation | TrustedAttestation
SomeIndexedAttestation* = IndexedAttestation | TrustedIndexedAttestation
# https://github.com/ethereum/eth2.0-specs/blob/v0.12.2/specs/phase0/beacon-chain.md#beaconstate
BeaconState* = object
# Versioning
genesis_time*: uint64
genesis_validators_root*: Eth2Digest
slot*: Slot
fork*: Fork
# History
latest_block_header*: BeaconBlockHeader ##\
## `latest_block_header.state_root == ZERO_HASH` temporarily
block_roots*: HashArray[Limit SLOTS_PER_HISTORICAL_ROOT, Eth2Digest] ##\
## Needed to process attestations, older to newer
state_roots*: HashArray[Limit SLOTS_PER_HISTORICAL_ROOT, Eth2Digest]
historical_roots*: HashList[Eth2Digest, Limit HISTORICAL_ROOTS_LIMIT]
# Eth1
eth1_data*: Eth1Data
eth1_data_votes*:
HashList[Eth1Data, Limit(EPOCHS_PER_ETH1_VOTING_PERIOD * SLOTS_PER_EPOCH)]
eth1_deposit_index*: uint64
# Registry
validators*: HashList[Validator, Limit VALIDATOR_REGISTRY_LIMIT]
balances*: HashList[uint64, Limit VALIDATOR_REGISTRY_LIMIT]
# Randomness
randao_mixes*: HashArray[Limit EPOCHS_PER_HISTORICAL_VECTOR, Eth2Digest]
# Slashings
slashings*: HashArray[Limit EPOCHS_PER_SLASHINGS_VECTOR, uint64] ##\
## Per-epoch sums of slashed effective balances
# Attestations
previous_epoch_attestations*:
HashList[PendingAttestation, Limit(MAX_ATTESTATIONS * SLOTS_PER_EPOCH)]
current_epoch_attestations*:
HashList[PendingAttestation, Limit(MAX_ATTESTATIONS * SLOTS_PER_EPOCH)]
# Finality
justification_bits*: uint8 ##\
## Bit set for every recent justified epoch
## Model a Bitvector[4] as a one-byte uint, which should remain consistent
## with ssz/hashing.
previous_justified_checkpoint*: Checkpoint ##\
## Previous epoch snapshot
current_justified_checkpoint*: Checkpoint
finalized_checkpoint*: Checkpoint
BeaconStateRef* = ref BeaconState not nil
NilableBeaconStateRef* = ref BeaconState
# https://github.com/ethereum/eth2.0-specs/blob/v0.12.2/specs/phase0/beacon-chain.md#validator
Validator* = object
pubkey*: ValidatorPubKey
withdrawal_credentials*: Eth2Digest ##\
## Commitment to pubkey for withdrawals and transfers
effective_balance*: uint64 ##\
## Balance at stake
slashed*: bool
# Status epochs
activation_eligibility_epoch*: Epoch ##\
## When criteria for activation were met
activation_epoch*: Epoch
exit_epoch*: Epoch
withdrawable_epoch*: Epoch ##\
## When validator can withdraw or transfer funds
# https://github.com/ethereum/eth2.0-specs/blob/v0.12.2/specs/phase0/beacon-chain.md#pendingattestation
PendingAttestation* = object
aggregation_bits*: CommitteeValidatorsBits
data*: AttestationData
# TODO this is a Slot
inclusion_delay*: uint64
proposer_index*: uint64
# https://github.com/ethereum/eth2.0-specs/blob/v0.12.2/specs/phase0/beacon-chain.md#historicalbatch
HistoricalBatch* = object
block_roots* : array[SLOTS_PER_HISTORICAL_ROOT, Eth2Digest]
state_roots* : array[SLOTS_PER_HISTORICAL_ROOT, Eth2Digest]
# https://github.com/ethereum/eth2.0-specs/blob/v0.12.2/specs/phase0/beacon-chain.md#fork
Fork* = object
# TODO: Spec introduced an alias for Version = array[4, byte]
# and a default parameter to compute_domain
previous_version*: Version
current_version*: Version
epoch*: Epoch ##\
## Epoch of latest fork
# https://github.com/ethereum/eth2.0-specs/blob/v0.12.2/specs/phase0/beacon-chain.md#eth1data
Eth1Data* = object
deposit_root*: Eth2Digest
deposit_count*: uint64
block_hash*: Eth2Digest
# https://github.com/ethereum/eth2.0-specs/blob/v0.12.2/specs/phase0/validator.md#eth1block
Eth1Block* = object
timestamp*: uint64
deposit_root*: Eth2Digest
deposit_count*: uint64
# All other eth1 block fields
# https://github.com/ethereum/eth2.0-specs/blob/v0.12.2/specs/phase0/beacon-chain.md#signedvoluntaryexit
SignedVoluntaryExit* = object
message*: VoluntaryExit
signature*: ValidatorSig
# https://github.com/ethereum/eth2.0-specs/blob/v0.12.2/specs/phase0/beacon-chain.md#signedbeaconblock
SignedBeaconBlock* = object
message*: BeaconBlock
signature*: ValidatorSig
root* {.dontSerialize.}: Eth2Digest # cached root of signed beacon block
TrustedSignedBeaconBlock* = object
message*: TrustedBeaconBlock
signature*: TrustedSig
root* {.dontSerialize.}: Eth2Digest # cached root of signed beacon block
# https://github.com/ethereum/eth2.0-specs/blob/v0.12.2/specs/phase0/beacon-chain.md#signedbeaconblockheader
SignedBeaconBlockHeader* = object
message*: BeaconBlockHeader
signature*: ValidatorSig
# https://github.com/ethereum/eth2.0-specs/blob/v0.12.2/specs/phase0/validator.md#aggregateandproof
AggregateAndProof* = object
aggregator_index*: uint64
aggregate*: Attestation
selection_proof*: ValidatorSig
# https://github.com/ethereum/eth2.0-specs/blob/v0.12.2/specs/phase0/validator.md#signedaggregateandproof
SignedAggregateAndProof* = object
message*: AggregateAndProof
signature*: ValidatorSig
# TODO to be replaced with some magic hash caching
HashedBeaconState* = object
data*: BeaconState
root*: Eth2Digest # hash_tree_root(data)
# This doesn't know about forks or branches in the DAG. It's for straight,
# linear chunks of the chain.
StateCache* = object
shuffled_active_validator_indices*:
Table[Epoch, seq[ValidatorIndex]]
beacon_proposer_indices*: Table[Slot, Option[ValidatorIndex]]
AttestationSubnets* = object
subscribedSubnets*: array[2, set[uint8]]
stabilitySubnet*: uint64
stabilitySubnetExpirationEpoch*: Epoch
# https://github.com/ethereum/eth2.0-specs/blob/v0.12.2/specs/phase0/p2p-interface.md#topics-and-messages
ValidationResult* = enum
EVRESULT_ACCEPT = 0
EVRESULT_REJECT = 1
EVRESULT_IGNORE = 2
func shortValidatorKey*(state: BeaconState, validatorIdx: int): string =
($state.validators[validatorIdx].pubkey)[0..7]
func getDepositMessage*(depositData: DepositData): DepositMessage =
result.pubkey = depositData.pubkey
result.amount = depositData.amount
result.withdrawal_credentials = depositData.withdrawal_credentials
func getDepositMessage*(deposit: Deposit): DepositMessage =
deposit.data.getDepositMessage
# TODO when https://github.com/nim-lang/Nim/issues/14440 lands in Status's Nim,
# switch proc {.noSideEffect.} to func.
template ethTimeUnit(typ: type) {.dirty.} =
proc `+`*(x: typ, y: uint64): typ {.borrow, noSideEffect.}
proc `-`*(x: typ, y: uint64): typ {.borrow, noSideEffect.}
proc `-`*(x: uint64, y: typ): typ {.borrow, noSideEffect.}
# Not closed over type in question (Slot or Epoch)
proc `mod`*(x: typ, y: uint64): uint64 {.borrow, noSideEffect.}
proc `div`*(x: typ, y: uint64): uint64 {.borrow, noSideEffect.}
proc `div`*(x: uint64, y: typ): uint64 {.borrow, noSideEffect.}
proc `-`*(x: typ, y: typ): uint64 {.borrow, noSideEffect.}
proc `*`*(x: typ, y: uint64): uint64 {.borrow, noSideEffect.}
proc `+=`*(x: var typ, y: typ) {.borrow, noSideEffect.}
proc `+=`*(x: var typ, y: uint64) {.borrow, noSideEffect.}
proc `-=`*(x: var typ, y: typ) {.borrow, noSideEffect.}
proc `-=`*(x: var typ, y: uint64) {.borrow, noSideEffect.}
# Comparison operators
proc `<`*(x: typ, y: typ): bool {.borrow, noSideEffect.}
proc `<`*(x: typ, y: uint64): bool {.borrow, noSideEffect.}
proc `<`*(x: uint64, y: typ): bool {.borrow, noSideEffect.}
proc `<=`*(x: typ, y: typ): bool {.borrow, noSideEffect.}
proc `<=`*(x: typ, y: uint64): bool {.borrow, noSideEffect.}
proc `<=`*(x: uint64, y: typ): bool {.borrow, noSideEffect.}
proc `==`*(x: typ, y: typ): bool {.borrow, noSideEffect.}
proc `==`*(x: typ, y: uint64): bool {.borrow, noSideEffect.}
proc `==`*(x: uint64, y: typ): bool {.borrow, noSideEffect.}
# Nim integration
proc `$`*(x: typ): string {.borrow, noSideEffect.}
proc hash*(x: typ): Hash {.borrow, noSideEffect.}
proc `%`*(x: typ): JsonNode {.borrow, noSideEffect.}
# Serialization
proc writeValue*(writer: var JsonWriter, value: typ)
{.raises: [IOError, Defect].}=
writeValue(writer, uint64 value)
proc readValue*(reader: var JsonReader, value: var typ)
{.raises: [IOError, SerializationError, Defect].} =
value = typ reader.readValue(uint64)
proc writeValue*(writer: var JsonWriter, value: ValidatorIndex)
{.raises: [IOError, Defect].} =
writeValue(writer, uint32 value)
proc readValue*(reader: var JsonReader, value: var ValidatorIndex)
{.raises: [IOError, SerializationError, Defect].} =
value = ValidatorIndex reader.readValue(uint32)
template writeValue*(writer: var JsonWriter, value: Version | ForkDigest) =
writeValue(writer, $value)
proc readValue*(reader: var JsonReader, value: var Version)
{.raises: [IOError, SerializationError, Defect].} =
let hex = reader.readValue(string)
try:
hexToByteArray(hex, array[4, byte](value))
except ValueError:
raiseUnexpectedValue(reader, "Hex string of 4 bytes expected")
proc readValue*(reader: var JsonReader, value: var ForkDigest)
{.raises: [IOError, SerializationError, Defect].} =
let hex = reader.readValue(string)
try:
hexToByteArray(hex, array[4, byte](value))
except ValueError:
raiseUnexpectedValue(reader, "Hex string of 4 bytes expected")
# `ValidatorIndex` seq handling.
# TODO harden these against uint32/uint64 to int type conversion risks
func max*(a: ValidatorIndex, b: int) : auto =
max(a.int, b)
func `[]`*[T](a: var seq[T], b: ValidatorIndex): var T =
a[b.int]
func `[]`*[T](a: seq[T], b: ValidatorIndex): auto =
a[b.int]
func `[]=`*[T](a: var seq[T], b: ValidatorIndex, c: T) =
a[b.int] = c
# `ValidatorIndex` Nim integration
proc `==`*(x, y: ValidatorIndex) : bool {.borrow, noSideEffect.}
proc `<`*(x, y: ValidatorIndex) : bool {.borrow, noSideEffect.}
proc hash*(x: ValidatorIndex): Hash {.borrow, noSideEffect.}
func `$`*(x: ValidatorIndex): auto = $(x.int64)
func `as`*(d: DepositData, T: type DepositMessage): T =
T(pubkey: d.pubkey,
withdrawal_credentials: d.withdrawal_credentials,
amount: d.amount)
ethTimeUnit Slot
ethTimeUnit Epoch
Json.useCustomSerialization(BeaconState.justification_bits):
read:
let s = reader.readValue(string)
if s.len != 4:
raiseUnexpectedValue(reader, "A string with 4 characters expected")
try:
s.parseHexInt.uint8
except ValueError:
raiseUnexpectedValue(reader, "The `justification_bits` value must be a hex string")
write:
writer.writeValue "0x" & value.toHex
Json.useCustomSerialization(BitSeq):
read:
try:
BitSeq reader.readValue(string).hexToSeqByte
except ValueError:
raiseUnexpectedValue(reader, "A BitSeq value should be a valid hex string")
write:
writer.writeValue "0x" & seq[byte](value).toHex
template readValue*(reader: var JsonReader, value: var List) =
value = type(value)(readValue(reader, seq[type value[0]]))
template writeValue*(writer: var JsonWriter, value: List) =
writeValue(writer, asSeq value)
template readValue*(reader: var JsonReader, value: var BitList) =
type T = type(value)
value = T readValue(reader, BitSeq)
template writeValue*(writer: var JsonWriter, value: BitList) =
writeValue(writer, BitSeq value)
template newClone*[T: not ref](x: T): ref T =
# TODO not nil in return type: https://github.com/nim-lang/Nim/issues/14146
# TODO use only when x is a function call that returns a new instance!
let res = new typeof(x) # TODO safe to do noinit here?
res[] = x
res
template assignClone*[T: not ref](x: T): ref T =
# This is a bit of a mess: if x is an rvalue (temporary), RVO kicks in for
# newClone - if it's not, `genericAssign` will be called which is ridiculously
# slow - so `assignClone` should be used when RVO doesn't work. sigh.
let res = new typeof(x) # TODO safe to do noinit here?
assign(res[], x)
res
template newClone*[T](x: ref T not nil): ref T =
newClone(x[])
template lenu64*(x: untyped): untyped =
x.len.uint64
func `$`*(v: ForkDigest | Version): string =
toHex(array[4, byte](v))
# TODO where's borrow support when you need it
func `==`*(a, b: ForkDigest | Version): bool =
array[4, byte](a) == array[4, byte](b)
func len*(v: ForkDigest | Version): int = sizeof(v)
func low*(v: ForkDigest | Version): int = 0
func high*(v: ForkDigest | Version): int = len(v) - 1
func `[]`*(v: ForkDigest | Version, idx: int): byte = array[4, byte](v)[idx]
func shortLog*(s: Slot): uint64 =
s - GENESIS_SLOT
func shortLog*(e: Epoch): uint64 =
e - GENESIS_EPOCH
func shortLog*(v: SomeBeaconBlock): auto =
(
slot: shortLog(v.slot),
proposer_index: v.proposer_index,
parent_root: shortLog(v.parent_root),
state_root: shortLog(v.state_root),
proposer_slashings_len: v.body.proposer_slashings.len(),
attester_slashings_len: v.body.attester_slashings.len(),
attestations_len: v.body.attestations.len(),
deposits_len: v.body.deposits.len(),
voluntary_exits_len: v.body.voluntary_exits.len(),
)
func shortLog*(v: SomeSignedBeaconBlock): auto =
(
blck: shortLog(v.message),
signature: shortLog(v.signature)
)
func shortLog*(v: BeaconBlockHeader): auto =
(
slot: shortLog(v.slot),
proposer_index: v.proposer_index,
parent_root: shortLog(v.parent_root),
state_root: shortLog(v.state_root)
)
func shortLog*(v: SignedBeaconBlockHeader): auto =
(
message: shortLog(v.message),
signature: shortLog(v.signature)
)
func shortLog*(v: DepositData): auto =
(
pubkey: shortLog(v.pubkey),
withdrawal_credentials: shortlog(v.withdrawal_credentials),
amount: v.amount,
signature: shortLog(v.signature)
)
func shortLog*(v: Checkpoint): auto =
(
epoch: shortLog(v.epoch),
root: shortLog(v.root),
)
func shortLog*(v: AttestationData): auto =
(
slot: shortLog(v.slot),
index: v.index,
beacon_block_root: shortLog(v.beacon_block_root),
source: shortLog(v.source),
target: shortLog(v.target),
)
func shortLog*(v: PendingAttestation): auto =
(
aggregation_bits: v.aggregation_bits,
data: shortLog(v.data),
inclusion_delay: v.inclusion_delay,
proposer_index: v.proposer_index
)
func shortLog*(v: SomeAttestation): auto =
(
aggregation_bits: v.aggregation_bits,
data: shortLog(v.data),
signature: shortLog(v.signature)
)
func shortLog*(v: SomeIndexedAttestation): auto =
(
attestating_indices: v.attesting_indices,
data: shortLog(v.data),
signature: shortLog(v.signature)
)
func shortLog*(v: AttesterSlashing): auto =
(
attestation_1: shortLog(v.attestation_1),
attestation_2: shortLog(v.attestation_2),
)
func shortLog*(v: ProposerSlashing): auto =
(
signed_header_1: shortLog(v.signed_header_1),
signed_header_2: shortLog(v.signed_header_2)
)
func shortLog*(v: VoluntaryExit): auto =
(
epoch: shortLog(v.epoch),
validator_index: v.validator_index
)
chronicles.formatIt Slot: it.shortLog
chronicles.formatIt Epoch: it.shortLog
chronicles.formatIt BeaconBlock: it.shortLog
chronicles.formatIt AttestationData: it.shortLog
chronicles.formatIt Attestation: it.shortLog
chronicles.formatIt Checkpoint: it.shortLog
import json_serialization
export json_serialization
export writeValue, readValue
const
# http://facweb.cs.depaul.edu/sjost/it212/documents/ascii-pr.htm
PrintableAsciiChars = {'!'..'~'}
func `$`*(value: GraffitiBytes): string =
result = strip(string.fromBytes(distinctBase value),
leading = false,
chars = Whitespace + {'\0'})
# TODO: Perhaps handle UTF-8 at some point
if not allCharsInSet(result, PrintableAsciiChars):
result = "0x" & toHex(distinctBase value)
func init*(T: type GraffitiBytes, input: string): GraffitiBytes
{.raises: [ValueError, Defect].} =
if input.len > 2 and input[0] == '0' and input[1] == 'x':
if input.len > sizeof(GraffitiBytes) * 2 + 2:
raise newException(ValueError, "The graffiti bytes should be less than 32")
elif input.len mod 2 != 0:
raise newException(ValueError, "The graffiti hex string should have an even length")
hexToByteArray(input, distinctBase(result))
else:
if input.len > 32:
raise newException(ValueError, "The graffiti value should be 32 characters or less")
distinctBase(result)[0 ..< input.len] = toBytes(input)
func defaultGraffitiBytes*(): GraffitiBytes =
let graffitiBytes = toBytes("Nimbus " & fullVersionStr)
distinctBase(result)[0 ..< graffitiBytes.len] = graffitiBytes
proc writeValue*(w: var JsonWriter, value: GraffitiBytes)
{.raises: [IOError, Defect].} =
w.writeValue $value
template `==`*(lhs, rhs: GraffitiBytes): bool =
distinctBase(lhs) == distinctBase(rhs)
proc readValue*(r: var JsonReader, T: type GraffitiBytes): T
{.raises: [IOError, SerializationError, Defect].} =
try:
init(GraffitiBytes, r.readValue(string))
except ValueError as err:
r.raiseUnexpectedValue err.msg
static:
# Sanity checks - these types should be trivial enough to copy with memcpy
doAssert supportsCopyMem(Validator)
doAssert supportsCopyMem(Eth2Digest)
func assign*[T](tgt: var T, src: T) =
# The default `genericAssignAux` that gets generated for assignments in nim
# is ridiculously slow. When syncing, the application was spending 50%+ CPU
# time in it - `assign`, in the same test, doesn't even show in the perf trace
when supportsCopyMem(T):
when sizeof(src) <= sizeof(int):
tgt = src
else:
copyMem(addr tgt, unsafeAddr src, sizeof(tgt))
elif T is object|tuple:
for t, s in fields(tgt, src):
when supportsCopyMem(type s) and sizeof(s) <= sizeof(int) * 2:
t = s # Shortcut
else:
assign(t, s)
elif T is List|BitList:
assign(distinctBase tgt, distinctBase src)
elif T is seq:
tgt.setLen(src.len)
when supportsCopyMem(type(tgt[0])):
if tgt.len > 0:
copyMem(addr tgt[0], unsafeAddr src[0], sizeof(tgt[0]) * tgt.len)
else:
for i in 0..<tgt.len:
assign(tgt[i], src[i])
elif T is ref:
tgt = src
else:
unsupported T