Pass the test suite with a BeaconState ref type

This commit is contained in:
Zahary Karadjov 2020-04-23 02:35:55 +03:00 committed by zah
parent 740b76d152
commit fdcbfdff05
32 changed files with 284 additions and 297 deletions

View File

@ -1,7 +1,7 @@
{.push raises: [Defect].} {.push raises: [Defect].}
import import
options, typetraits, stew/endians2, options, typetraits, stew/[results, endians2],
serialization, chronicles, serialization, chronicles,
spec/[datatypes, digest, crypto], spec/[datatypes, digest, crypto],
kvstore, ssz kvstore, ssz
@ -68,11 +68,11 @@ proc init*(T: type BeaconChainDB, backend: KVStoreRef): BeaconChainDB =
proc put(db: BeaconChainDB, key: openArray[byte], v: auto) = proc put(db: BeaconChainDB, key: openArray[byte], v: auto) =
db.backend.put(key, SSZ.encode(v)).expect("working database") db.backend.put(key, SSZ.encode(v)).expect("working database")
proc get(db: BeaconChainDB, key: openArray[byte], T: typedesc): Option[T] = proc get(db: BeaconChainDB, key: openArray[byte], T: typedesc): Opt[T] =
var res: Option[T] var res: Opt[T]
proc decode(data: openArray[byte]) = proc decode(data: openArray[byte]) =
try: try:
res = some(SSZ.decode(data, T)) res.ok SSZ.decode(data, T)
except SerializationError as e: except SerializationError as e:
# If the data can't be deserialized, it could be because it's from a # If the data can't be deserialized, it could be because it's from a
# version of the software that uses a different SSZ encoding # version of the software that uses a different SSZ encoding
@ -119,20 +119,21 @@ proc putHeadBlock*(db: BeaconChainDB, key: Eth2Digest) =
proc putTailBlock*(db: BeaconChainDB, key: Eth2Digest) = proc putTailBlock*(db: BeaconChainDB, key: Eth2Digest) =
db.backend.put(subkey(kTailBlock), key.data).expect("working database") db.backend.put(subkey(kTailBlock), key.data).expect("working database")
proc getBlock*(db: BeaconChainDB, key: Eth2Digest): Option[SignedBeaconBlock] = proc getBlock*(db: BeaconChainDB, key: Eth2Digest): Opt[SignedBeaconBlock] =
db.get(subkey(SignedBeaconBlock, key), SignedBeaconBlock) db.get(subkey(SignedBeaconBlock, key), SignedBeaconBlock)
proc getState*(db: BeaconChainDB, key: Eth2Digest): Option[BeaconState] = proc getState*(db: BeaconChainDB, key: Eth2Digest): Opt[BeaconState] =
db.get(subkey(BeaconState, key), BeaconState) db.get(subkey(BeaconState, key), BeaconState)
proc getStateRoot*(db: BeaconChainDB, root: Eth2Digest, slot: Slot): proc getStateRoot*(db: BeaconChainDB,
Option[Eth2Digest] = root: Eth2Digest,
slot: Slot): Opt[Eth2Digest] =
db.get(subkey(root, slot), Eth2Digest) db.get(subkey(root, slot), Eth2Digest)
proc getHeadBlock*(db: BeaconChainDB): Option[Eth2Digest] = proc getHeadBlock*(db: BeaconChainDB): Opt[Eth2Digest] =
db.get(subkey(kHeadBlock), Eth2Digest) db.get(subkey(kHeadBlock), Eth2Digest)
proc getTailBlock*(db: BeaconChainDB): Option[Eth2Digest] = proc getTailBlock*(db: BeaconChainDB): Opt[Eth2Digest] =
db.get(subkey(kTailBlock), Eth2Digest) db.get(subkey(kTailBlock), Eth2Digest)
proc containsBlock*( proc containsBlock*(
@ -151,7 +152,7 @@ iterator getAncestors*(db: BeaconChainDB, root: Eth2Digest):
## The search will go on until the ancestor cannot be found. ## The search will go on until the ancestor cannot be found.
var root = root var root = root
while (let blck = db.getBlock(root); blck.isSome()): while (let blck = db.getBlock(root); blck.isOk()):
yield (root, blck.get()) yield (root, blck.get())
root = blck.get().message.parent_root root = blck.get().message.parent_root

View File

@ -86,7 +86,7 @@ proc saveValidatorKey(keyName, key: string, conf: BeaconNodeConf) =
writeFile(outputFile, key) writeFile(outputFile, key)
info "Imported validator key", file = outputFile info "Imported validator key", file = outputFile
proc getStateFromSnapshot(conf: BeaconNodeConf, state: var BeaconState): bool = proc getStateFromSnapshot(conf: BeaconNodeConf): NilableBeaconState =
var var
genesisPath = conf.dataDir/genesisFile genesisPath = conf.dataDir/genesisFile
snapshotContents: TaintedString snapshotContents: TaintedString
@ -122,7 +122,7 @@ proc getStateFromSnapshot(conf: BeaconNodeConf, state: var BeaconState): bool =
quit 1 quit 1
try: try:
state = SSZ.decode(snapshotContents, BeaconState) result = SSZ.decode(snapshotContents, BeaconState)
except SerializationError: except SerializationError:
error "Failed to import genesis file", path = genesisPath error "Failed to import genesis file", path = genesisPath
quit 1 quit 1
@ -138,8 +138,6 @@ proc getStateFromSnapshot(conf: BeaconNodeConf, state: var BeaconState): bool =
err = err.msg, genesisFile = conf.dataDir/genesisFile err = err.msg, genesisFile = conf.dataDir/genesisFile
quit 1 quit 1
result = true
proc enrForkIdFromState(state: BeaconState): ENRForkID = proc enrForkIdFromState(state: BeaconState): ENRForkID =
let let
forkVer = state.fork.current_version forkVer = state.fork.current_version
@ -161,10 +159,10 @@ proc init*(T: type BeaconNode, conf: BeaconNodeConf): Future[BeaconNode] {.async
if not BlockPool.isInitialized(db): if not BlockPool.isInitialized(db):
# Fresh start - need to load a genesis state from somewhere # Fresh start - need to load a genesis state from somewhere
var genesisState = new BeaconState var genesisState = conf.getStateFromSnapshot()
# Try file from command line first # Try file from command line first
if not conf.getStateFromSnapshot(genesisState[]): if genesisState.isNil:
# Didn't work, try creating a genesis state using main chain monitor # Didn't work, try creating a genesis state using main chain monitor
# TODO Could move this to a separate "GenesisMonitor" process or task # TODO Could move this to a separate "GenesisMonitor" process or task
# that would do only this - see # that would do only this - see
@ -178,23 +176,27 @@ proc init*(T: type BeaconNode, conf: BeaconNodeConf): Future[BeaconNode] {.async
error "No initial state, need genesis state or deposit contract address" error "No initial state, need genesis state or deposit contract address"
quit 1 quit 1
genesisState[] = await mainchainMonitor.getGenesis() genesisState = await mainchainMonitor.getGenesis()
if genesisState[].slot != GENESIS_SLOT: # This is needed to prove the not nil property from here on
# TODO how to get a block from a non-genesis state? if genesisState == nil:
error "Starting from non-genesis state not supported", doAssert false
stateSlot = genesisState[].slot, else:
stateRoot = hash_tree_root(genesisState[]) if genesisState.slot != GENESIS_SLOT:
quit 1 # TODO how to get a block from a non-genesis state?
error "Starting from non-genesis state not supported",
stateSlot = genesisState.slot,
stateRoot = hash_tree_root(genesisState)
quit 1
let tailBlock = get_initial_beacon_block(genesisState[]) let tailBlock = get_initial_beacon_block(genesisState)
try: try:
BlockPool.preInit(db, genesisState[], tailBlock) BlockPool.preInit(db, genesisState, tailBlock)
doAssert BlockPool.isInitialized(db), "preInit should have initialized db" doAssert BlockPool.isInitialized(db), "preInit should have initialized db"
except CatchableError as e: except CatchableError as e:
error "Failed to initialize database", err = e.msg error "Failed to initialize database", err = e.msg
quit 1 quit 1
# TODO check that genesis given on command line (if any) matches database # TODO check that genesis given on command line (if any) matches database
let let
@ -1057,7 +1059,7 @@ proc installBeaconApiHandlers(rpcServer: RpcServer, node: BeaconNode) =
requireOneOf(slot, root) requireOneOf(slot, root)
if slot.isSome: if slot.isSome:
let blk = node.blockPool.head.blck.atSlot(slot.get) let blk = node.blockPool.head.blck.atSlot(slot.get)
var tmpState: StateData var tmpState = emptyStateData()
node.blockPool.withState(tmpState, blk): node.blockPool.withState(tmpState, blk):
return jsonResult(state) return jsonResult(state)
else: else:

View File

@ -225,6 +225,20 @@ type
root*: Eth2Digest root*: Eth2Digest
historySlots*: uint64 historySlots*: uint64
func emptyStateData*: StateData =
StateData(
data: HashedBeaconState(
# Please note that this initialization is needed in order
# to allocate memory for the BeaconState:
data: BeaconState(),
root: default(Eth2Digest)
),
blck: default(BlockRef))
func clone*(other: StateData): StateData =
StateData(data: clone(other.data),
blck: other.blck)
proc shortLog*(v: AttachedValidator): string = shortLog(v.pubKey) proc shortLog*(v: AttachedValidator): string = shortLog(v.pubKey)
chronicles.formatIt BlockSlot: chronicles.formatIt BlockSlot:

View File

@ -9,7 +9,7 @@
import import
bitops, chronicles, options, tables, bitops, chronicles, options, tables,
ssz, beacon_chain_db, state_transition, extras, kvstore, stew/results, ssz, beacon_chain_db, state_transition, extras, kvstore,
beacon_node_types, metrics, beacon_node_types, metrics,
spec/[crypto, datatypes, digest, helpers, validator] spec/[crypto, datatypes, digest, helpers, validator]
@ -204,21 +204,20 @@ proc init*(T: type BlockPool, db: BeaconChainDB): BlockPool =
"state data missing for tail block, database corrupt?" "state data missing for tail block, database corrupt?"
latestStateRoot = some((tailBlock.message.state_root, tailRef)) latestStateRoot = some((tailBlock.message.state_root, tailRef))
# TODO can't do straight init because in mainnet config, there are too
# many live beaconstates on the stack...
var tmpState = new Option[BeaconState]
# We're only saving epoch boundary states in the database right now, so when # We're only saving epoch boundary states in the database right now, so when
# we're loading the head block, the corresponding state does not necessarily # we're loading the head block, the corresponding state does not necessarily
# exist in the database - we'll load this latest state we know about and use # exist in the database - we'll load this latest state we know about and use
# that as finalization point. # that as finalization point.
tmpState[] = db.getState(latestStateRoot.get().stateRoot) let stateOpt = db.getState(latestStateRoot.get().stateRoot)
doAssert stateOpt.isSome, "failed to obtain latest state. database corrupt?"
let tmpState = stateOpt.get
let let
finalizedSlot = finalizedSlot =
tmpState[].get().finalized_checkpoint.epoch.compute_start_slot_at_epoch() tmpState.finalized_checkpoint.epoch.compute_start_slot_at_epoch()
finalizedHead = headRef.findAncestorBySlot(finalizedSlot) finalizedHead = headRef.findAncestorBySlot(finalizedSlot)
justifiedSlot = justifiedSlot =
tmpState[].get().current_justified_checkpoint.epoch.compute_start_slot_at_epoch() tmpState.current_justified_checkpoint.epoch.compute_start_slot_at_epoch()
justifiedHead = headRef.findAncestorBySlot(justifiedSlot) justifiedHead = headRef.findAncestorBySlot(justifiedSlot)
head = Head(blck: headRef, justified: justifiedHead) head = Head(blck: headRef, justified: justifiedHead)
justifiedBlock = db.getBlock(justifiedHead.blck.root).get() justifiedBlock = db.getBlock(justifiedHead.blck.root).get()
@ -231,6 +230,18 @@ proc init*(T: type BlockPool, db: BeaconChainDB): BlockPool =
head = head.blck, finalizedHead, tail = tailRef, head = head.blck, finalizedHead, tail = tailRef,
totalBlocks = blocks.len totalBlocks = blocks.len
let headState = StateData(
data: HashedBeaconState(
data: tmpState, root: latestStateRoot.get().stateRoot),
blck: latestStateRoot.get().blckRef)
let justifiedState = db.getState(justifiedStateRoot)
doAssert justifiedState.isSome,
"failed to obtain latest justified state. database corrupt?"
# For the initialization of `tmpState` below.
# Please note that it's initialized few lines below
{.push warning[UnsafeDefault]: off.}
let res = BlockPool( let res = BlockPool(
pending: initTable[Eth2Digest, SignedBeaconBlock](), pending: initTable[Eth2Digest, SignedBeaconBlock](),
missing: initTable[Eth2Digest, MissingBlock](), missing: initTable[Eth2Digest, MissingBlock](),
@ -249,21 +260,17 @@ proc init*(T: type BlockPool, db: BeaconChainDB): BlockPool =
finalizedHead: finalizedHead, finalizedHead: finalizedHead,
db: db, db: db,
heads: @[head], heads: @[head],
headState: headState,
justifiedState: StateData(
data: HashedBeaconState(data: justifiedState.get, root: justifiedStateRoot),
blck: justifiedHead.blck),
tmpState: default(StateData)
) )
{.pop.}
res.headState = StateData( res.updateStateData(res.headState, BlockSlot(blck: head.blck,
data: HashedBeaconState( slot: head.blck.slot))
data: tmpState[].get(), root: latestStateRoot.get().stateRoot), res.tmpState = clone(res.headState)
blck: latestStateRoot.get().blckRef)
res.updateStateData(res.headState, BlockSlot(blck: head.blck, slot: head.blck.slot))
res.tmpState = res.headState
tmpState[] = db.getState(justifiedStateRoot)
res.justifiedState = StateData(
data: HashedBeaconState(data: tmpState[].get(), root: justifiedStateRoot),
blck: justifiedHead.blck)
res res
proc addResolvedBlock( proc addResolvedBlock(
@ -568,7 +575,7 @@ proc skipAndUpdateState(
skipAndUpdateState(state, signedBlock.message.slot - 1, afterUpdate) skipAndUpdateState(state, signedBlock.message.slot - 1, afterUpdate)
let ok = state_transition(state, signedBlock, flags) let ok = state_transition(state, signedBlock, flags)
afterUpdate(state) afterUpdate(state)
@ -653,7 +660,7 @@ proc rewindState(pool: BlockPool, state: var StateData, bs: BlockSlot):
# writing and deleting state+root mappings in a single transaction, it's # writing and deleting state+root mappings in a single transaction, it's
# likely to happen and we guard against it here. # likely to happen and we guard against it here.
if stateRoot.isSome() and not pool.db.containsState(stateRoot.get()): if stateRoot.isSome() and not pool.db.containsState(stateRoot.get()):
stateRoot = none(type(stateRoot.get())) stateRoot.err()
while stateRoot.isNone(): while stateRoot.isNone():
let parBs = curBs.parent() let parBs = curBs.parent()
@ -718,7 +725,7 @@ proc rewindState(pool: BlockPool, state: var StateData, bs: BlockSlot):
ancestors = ancestors.len, ancestors = ancestors.len,
cat = "replay_state" cat = "replay_state"
state.data.data = ancestorState.get() state.data.data[] = ancestorState.get()[]
state.data.root = stateRoot.get() state.data.root = stateRoot.get()
state.blck = ancestor.refs state.blck = ancestor.refs

View File

@ -41,7 +41,7 @@ type
depositContractAddress: Address depositContractAddress: Address
dataProviderFactory*: DataProviderFactory dataProviderFactory*: DataProviderFactory
genesisState: ref BeaconState genesisState: NilableBeaconState
genesisStateFut: Future[void] genesisStateFut: Future[void]
eth1Chain: Eth1Chain eth1Chain: Eth1Chain
@ -87,6 +87,10 @@ type
const const
reorgDepthLimit = 1000 reorgDepthLimit = 1000
# TODO Nim's analysis on the lock level of the methods in this
# module seems broken. Investigate and file this as an issue.
{.push warning[LockLevel]: off.}
# https://github.com/ethereum/eth2.0-specs/blob/v0.11.1/specs/phase0/validator.md#get_eth1_data # https://github.com/ethereum/eth2.0-specs/blob/v0.11.1/specs/phase0/validator.md#get_eth1_data
func compute_time_at_slot(state: BeaconState, slot: Slot): uint64 = func compute_time_at_slot(state: BeaconState, slot: Slot): uint64 =
return state.genesis_time + slot * SECONDS_PER_SLOT return state.genesis_time + slot * SECONDS_PER_SLOT
@ -346,8 +350,7 @@ proc checkForGenesisEvent(m: MainchainMonitor) =
# https://github.com/ethereum/eth2.0-pm/tree/6e41fcf383ebeb5125938850d8e9b4e9888389b4/interop/mocked_start#create-genesis-state # https://github.com/ethereum/eth2.0-pm/tree/6e41fcf383ebeb5125938850d8e9b4e9888389b4/interop/mocked_start#create-genesis-state
s.genesis_time = startTime s.genesis_time = startTime
m.genesisState.new() m.genesisState = clone(s)
m.genesisState[] = s
if not m.genesisStateFut.isNil: if not m.genesisStateFut.isNil:
m.genesisStateFut.complete() m.genesisStateFut.complete()
m.genesisStateFut = nil m.genesisStateFut = nil
@ -436,8 +439,11 @@ proc getGenesis*(m: MainchainMonitor): Future[BeaconState] {.async.} =
await m.genesisStateFut await m.genesisStateFut
m.genesisStateFut = nil m.genesisStateFut = nil
doAssert(not m.genesisState.isNil) if m.genesisState == nil:
return m.genesisState[] doAssert(false)
return BeaconState()
else:
return m.genesisState
method getBlockByHash*(p: Web3DataProviderRef, hash: BlockHash): Future[BlockObject] = method getBlockByHash*(p: Web3DataProviderRef, hash: BlockHash): Future[BlockObject] =
discard discard
@ -585,7 +591,11 @@ proc stop*(m: MainchainMonitor) =
proc getLatestEth1BlockHash*(url: string): Future[Eth2Digest] {.async.} = proc getLatestEth1BlockHash*(url: string): Future[Eth2Digest] {.async.} =
let web3 = await newWeb3(url) let web3 = await newWeb3(url)
defer: await web3.close() try:
let blk = await web3.provider.eth_getBlockByNumber("latest", false) let blk = await web3.provider.eth_getBlockByNumber("latest", false)
return Eth2Digest(data: array[32, byte](blk.hash)) return Eth2Digest(data: array[32, byte](blk.hash))
finally:
await web3.close()
{.pop.}

View File

@ -20,6 +20,8 @@
# TODO report compiler crash when this is uncommented # TODO report compiler crash when this is uncommented
# {.push raises: [Defect].} # {.push raises: [Defect].}
{.experimental: "notnil".}
import import
macros, hashes, json, strutils, tables, macros, hashes, json, strutils, tables,
stew/[byteutils, bitseqs], chronicles, stew/[byteutils, bitseqs], chronicles,
@ -242,7 +244,7 @@ type
voluntary_exits*: List[SignedVoluntaryExit, MAX_VOLUNTARY_EXITS] voluntary_exits*: List[SignedVoluntaryExit, MAX_VOLUNTARY_EXITS]
# https://github.com/ethereum/eth2.0-specs/blob/v0.11.1/specs/phase0/beacon-chain.md#beaconstate # https://github.com/ethereum/eth2.0-specs/blob/v0.11.1/specs/phase0/beacon-chain.md#beaconstate
BeaconState* = object BeaconStateObj* = object
# Versioning # Versioning
genesis_time*: uint64 genesis_time*: uint64
genesis_validators_root*: Eth2Digest genesis_validators_root*: Eth2Digest
@ -294,6 +296,9 @@ type
current_justified_checkpoint*: Checkpoint current_justified_checkpoint*: Checkpoint
finalized_checkpoint*: Checkpoint finalized_checkpoint*: Checkpoint
BeaconState* = ref BeaconStateObj not nil
NilableBeaconState* = ref BeaconStateObj
# https://github.com/ethereum/eth2.0-specs/blob/v0.11.1/specs/phase0/beacon-chain.md#validator # https://github.com/ethereum/eth2.0-specs/blob/v0.11.1/specs/phase0/beacon-chain.md#validator
Validator* = object Validator* = object
pubkey*: ValidatorPubKey pubkey*: ValidatorPubKey
@ -565,6 +570,15 @@ template readValue*(reader: var JsonReader, value: var BitList) =
template writeValue*(writer: var JsonWriter, value: BitList) = template writeValue*(writer: var JsonWriter, value: BitList) =
writeValue(writer, BitSeq value) writeValue(writer, BitSeq value)
func clone*[T](x: ref T): ref T not nil =
new result
result[] = x[]
func clone*(other: HashedBeaconState): HashedBeaconState =
HashedBeaconState(
data: clone(other.data),
root: other.root)
template init*(T: type BitList, len: int): auto = T init(BitSeq, len) template init*(T: type BitList, len: int): auto = T init(BitSeq, len)
template len*(x: BitList): auto = len(BitSeq(x)) template len*(x: BitList): auto = len(BitSeq(x))
template bytes*(x: BitList): auto = bytes(BitSeq(x)) template bytes*(x: BitList): auto = bytes(BitSeq(x))

View File

@ -170,7 +170,7 @@ func get_domain*(
state: BeaconState, domain_type: DomainType, epoch: Epoch): Domain = state: BeaconState, domain_type: DomainType, epoch: Epoch): Domain =
## Return the signature domain (fork version concatenated with domain type) ## Return the signature domain (fork version concatenated with domain type)
## of a message. ## of a message.
get_domain(state.fork, domain_type, epoch, state. genesis_validators_root) 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 # 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 = func compute_signing_root*(ssz_object: auto, domain: Domain): Eth2Digest =

View File

@ -485,7 +485,7 @@ proc makeBeaconBlock*(
deposits: deposits) deposits: deposits)
) )
var tmpState = state var tmpState = clone(state)
let ok = process_block(tmpState, blck, {skipBlsValidation}, cache) let ok = process_block(tmpState, blck, {skipBlsValidation}, cache)
if not ok: if not ok:

View File

@ -148,7 +148,7 @@ template writeFixedSized(s: OutputStream, x: auto) =
template supports*(_: type SSZ, T: type): bool = template supports*(_: type SSZ, T: type): bool =
mixin toSszType mixin toSszType
anonConst compiles(fixedPortionSize toSszType(default(T))) anonConst compiles(fixedPortionSize toSszType(declval T))
func init*(T: type SszWriter, stream: OutputStream): T {.raises: [Defect].} = func init*(T: type SszWriter, stream: OutputStream): T {.raises: [Defect].} =
result.stream = stream result.stream = stream

View File

@ -84,9 +84,8 @@ func readSszValue*(input: openarray[byte], T: type): T {.raisesssz.} =
result = T readSszValue(input, seq[ElemType]) result = T readSszValue(input, seq[ElemType])
elif result is ptr|ref: elif result is ptr|ref:
if input.len > 0: new result
new result result[] = readSszValue(input, type(result[]))
result[] = readSszValue(input, type(result[]))
elif result is Option: elif result is Option:
if input.len > 0: if input.len > 0:
@ -161,8 +160,8 @@ func readSszValue*(input: openarray[byte], T: type): T {.raisesssz.} =
const boundingOffsets = T.getFieldBoundingOffsets(fieldName) const boundingOffsets = T.getFieldBoundingOffsets(fieldName)
trs "BOUNDING OFFSET FOR FIELD ", fieldName, " = ", boundingOffsets trs "BOUNDING OFFSET FOR FIELD ", fieldName, " = ", boundingOffsets
type FieldType = type field type FieldType = type maybeDeref(field)
type SszType = type toSszType(default(FieldType)) type SszType = type toSszType(declval FieldType)
when isFixedSize(SszType): when isFixedSize(SszType):
const const
@ -185,13 +184,21 @@ func readSszValue*(input: openarray[byte], T: type): T {.raisesssz.} =
# TODO The extra type escaping here is a work-around for a Nim issue: # TODO The extra type escaping here is a work-around for a Nim issue:
when type(FieldType) is type(SszType): when type(FieldType) is type(SszType):
trs "READING NATIVE ", fieldName, ": ", name(SszType) trs "READING NATIVE ", fieldName, ": ", name(SszType)
field = readSszValue(input.toOpenArray(startOffset, endOffset - 1), SszType) maybeDeref(field) = readSszValue(
input.toOpenArray(startOffset, endOffset - 1),
SszType)
trs "READING COMPLETE ", fieldName trs "READING COMPLETE ", fieldName
elif useListType and FieldType is List: elif useListType and FieldType is List:
field = readSszValue(input.toOpenArray(startOffset, endOffset - 1), FieldType) maybeDeref(field) = readSszValue(
input.toOpenArray(startOffset, endOffset - 1),
FieldType)
else: else:
trs "READING FOREIGN ", fieldName, ": ", name(SszType) trs "READING FOREIGN ", fieldName, ": ", name(SszType)
field = fromSszBytes(FieldType, input.toOpenArray(startOffset, endOffset - 1)) maybeDeref(field) = fromSszBytes(
FieldType,
input.toOpenArray(startOffset, endOffset - 1))
else: else:
unsupported T unsupported T

View File

@ -3,7 +3,7 @@
import import
strutils, parseutils, strutils, parseutils,
faststreams/output_stream, json_serialization/writer, stew/objects, faststreams/output_stream, json_serialization/writer,
../spec/datatypes, ../spec/datatypes,
types, bytes_reader, navigator types, bytes_reader, navigator
@ -79,7 +79,7 @@ proc typeInfo*(T: type): TypeInfo =
func genTypeInfo(T: type): TypeInfo = func genTypeInfo(T: type): TypeInfo =
mixin toSszType, enumAllSerializedFields mixin toSszType, enumAllSerializedFields
type SszType = type(toSszType default(T)) type SszType = type toSszType(declval T)
result = when type(SszType) isnot T: result = when type(SszType) isnot T:
TypeInfo(kind: LeafValue) TypeInfo(kind: LeafValue)
elif T is object: elif T is object:

View File

@ -40,7 +40,7 @@ func navigateToField*[T](n: SszNavigator[T],
fieldName: static string, fieldName: static string,
FieldType: type): SszNavigator[FieldType] {.raisesssz.} = FieldType: type): SszNavigator[FieldType] {.raisesssz.} =
mixin toSszType mixin toSszType
type SszFieldType = type toSszType(default FieldType) type SszFieldType = type toSszType(declval FieldType)
const boundingOffsets = getFieldBoundingOffsets(T, fieldName) const boundingOffsets = getFieldBoundingOffsets(T, fieldName)
checkBounds(n.m, boundingOffsets[1]) checkBounds(n.m, boundingOffsets[1])
@ -101,7 +101,7 @@ func indexVarSizeList(m: MemRange, idx: int): MemRange {.raisesssz.} =
template indexList(n, idx, T: untyped): untyped = template indexList(n, idx, T: untyped): untyped =
type R = T type R = T
mixin toSszType mixin toSszType
type ElemType = type toSszType(default R) type ElemType = type toSszType(declval R)
when isFixedSize(ElemType): when isFixedSize(ElemType):
const elemSize = fixedPortionSize(ElemType) const elemSize = fixedPortionSize(ElemType)
let elemPos = idx * elemSize let elemPos = idx * elemSize
@ -119,11 +119,16 @@ template `[]`*[R, T](n: SszNavigator[array[R, T]], idx: int): SszNavigator[T] =
func `[]`*[T](n: SszNavigator[T]): T {.raisesssz.} = func `[]`*[T](n: SszNavigator[T]): T {.raisesssz.} =
mixin toSszType, fromSszBytes mixin toSszType, fromSszBytes
type SszRepr = type(toSszType default(T)) when T is ref:
when type(SszRepr) is type(T): type ObjectType = type(result[])
readSszValue(toOpenArray(n.m), T) new result
result[] = SszNavigator[ObjectType](n)[]
else: else:
fromSszBytes(T, toOpenArray(n.m)) type SszRepr = type toSszType(declval T)
when type(SszRepr) is type(T):
readSszValue(toOpenArray(n.m), T)
else:
fromSszBytes(T, toOpenArray(n.m))
converter derefNavigator*[T](n: SszNavigator[T]): T {.raisesssz.} = converter derefNavigator*[T](n: SszNavigator[T]): T {.raisesssz.} =
n[] n[]

View File

@ -84,13 +84,19 @@ template ElemType*[T](A: type[openarray[T]]): untyped =
template ElemType*(T: type[seq|string|List]): untyped = template ElemType*(T: type[seq|string|List]): untyped =
type(default(T)[0]) type(default(T)[0])
template maybeDeref*(x: auto): auto =
when type(x) is ref|ptr:
x[]
else:
x
func isFixedSize*(T0: type): bool {.compileTime.} = func isFixedSize*(T0: type): bool {.compileTime.} =
mixin toSszType, enumAllSerializedFields mixin toSszType, enumAllSerializedFields
when T0 is openarray|Option|ref|ptr: when T0 is openarray|Option|ref|ptr:
return false return false
else: else:
type T = type toSszType(default T0) type T = type toSszType(declval T0)
when T is BasicType: when T is BasicType:
return true return true
@ -104,7 +110,7 @@ func isFixedSize*(T0: type): bool {.compileTime.} =
func fixedPortionSize*(T0: type): int {.compileTime.} = func fixedPortionSize*(T0: type): int {.compileTime.} =
mixin enumAllSerializedFields, toSszType mixin enumAllSerializedFields, toSszType
type T = type toSszType(default T0) type T = type toSszType(declval T0)
when T is BasicType: sizeof(T) when T is BasicType: sizeof(T)
elif T is array: elif T is array:
@ -123,7 +129,7 @@ func fixedPortionSize*(T0: type): int {.compileTime.} =
func sszSchemaType*(T0: type): SszType {.compileTime.} = func sszSchemaType*(T0: type): SszType {.compileTime.} =
mixin toSszType, enumAllSerializedFields mixin toSszType, enumAllSerializedFields
type T = type toSszType(default T0) type T = type toSszType(declval T0)
when T is bool: when T is bool:
SszType(kind: sszBool) SszType(kind: sszBool)

View File

@ -170,7 +170,7 @@ proc state_transition*(
## TODO, of cacheState/processEpoch/processSlot/processBloc, only the last ## TODO, of cacheState/processEpoch/processSlot/processBloc, only the last
## might fail, so should this bother capturing here, or? ## might fail, so should this bother capturing here, or?
var old_state = state var old_state = clone(state)
# These should never fail. # These should never fail.
process_slots(state, signedBlock.message.slot) process_slots(state, signedBlock.message.slot)
@ -194,7 +194,7 @@ proc state_transition*(
return true return true
# Block processing failed, roll back changes # Block processing failed, roll back changes
state = old_state state[] = old_state[]
false false
# Hashed-state transition functions # Hashed-state transition functions
@ -253,7 +253,7 @@ proc process_slots*(state: var HashedBeaconState, slot: Slot) =
proc state_transition*( proc state_transition*(
state: var HashedBeaconState, signedBlock: SignedBeaconBlock, flags: UpdateFlags): bool = state: var HashedBeaconState, signedBlock: SignedBeaconBlock, flags: UpdateFlags): bool =
# Save for rollback # Save for rollback
var old_state = state var old_state = clone(state)
process_slots(state, signedBlock.message.slot) process_slots(state, signedBlock.message.slot)
@ -275,5 +275,6 @@ proc state_transition*(
return true return true
# Block processing failed, roll back changes # Block processing failed, roll back changes
state = old_state state.data[] = old_state.data[]
state.root = old_state.root
false false

View File

@ -139,10 +139,8 @@ proc parseSSZ(path: string, T: typedesc): T =
proc runFullTransition*(dir, preState, blocksPrefix: string, blocksQty: int, skipBLS: bool) = proc runFullTransition*(dir, preState, blocksPrefix: string, blocksQty: int, skipBLS: bool) =
let prePath = dir / preState & ".ssz" let prePath = dir / preState & ".ssz"
var state: ref BeaconState
new state
echo "Running: ", prePath echo "Running: ", prePath
state[] = parseSSZ(prePath, BeaconState) var state = parseSSZ(prePath, BeaconState)
for i in 0 ..< blocksQty: for i in 0 ..< blocksQty:
let blockPath = dir / blocksPrefix & $i & ".ssz" let blockPath = dir / blocksPrefix & $i & ".ssz"
@ -151,18 +149,16 @@ proc runFullTransition*(dir, preState, blocksPrefix: string, blocksQty: int, ski
let signedBlock = parseSSZ(blockPath, SignedBeaconBlock) let signedBlock = parseSSZ(blockPath, SignedBeaconBlock)
let flags = if skipBLS: {skipBlsValidation} let flags = if skipBLS: {skipBlsValidation}
else: {} else: {}
let success = state_transition(state[], signedBlock.message, flags) let success = state_transition(state, signedBlock.message, flags)
echo "State transition status: ", if success: "SUCCESS ✓" else: "FAILURE ⚠️" echo "State transition status: ", if success: "SUCCESS ✓" else: "FAILURE ⚠️"
proc runProcessSlots*(dir, preState: string, numSlots: uint64) = proc runProcessSlots*(dir, preState: string, numSlots: uint64) =
let prePath = dir / preState & ".ssz" let prePath = dir / preState & ".ssz"
var state: ref BeaconState
new state
echo "Running: ", prePath echo "Running: ", prePath
state[] = parseSSZ(prePath, BeaconState) var state = parseSSZ(prePath, BeaconState)
process_slots(state[], state.slot + numSlots) process_slots(state, state.slot + numSlots)
template processEpochScenarioImpl( template processEpochScenarioImpl(
dir, preState: string, dir, preState: string,
@ -170,19 +166,17 @@ template processEpochScenarioImpl(
needCache: static bool): untyped = needCache: static bool): untyped =
let prePath = dir/preState & ".ssz" let prePath = dir/preState & ".ssz"
var state: ref BeaconState
new state
echo "Running: ", prePath echo "Running: ", prePath
state[] = parseSSZ(prePath, BeaconState) var state = parseSSZ(prePath, BeaconState)
when needCache: when needCache:
var cache = get_empty_per_epoch_cache() var cache = get_empty_per_epoch_cache()
# Epoch transitions can't fail (TODO is this true?) # Epoch transitions can't fail (TODO is this true?)
when needCache: when needCache:
transitionFn(state[], cache) transitionFn(state, cache)
else: else:
transitionFn(state[]) transitionFn(state)
echo astToStr(transitionFn) & " status: ", "Done" # if success: "SUCCESS ✓" else: "FAILURE ⚠️" echo astToStr(transitionFn) & " status: ", "Done" # if success: "SUCCESS ✓" else: "FAILURE ⚠️"
@ -197,10 +191,8 @@ template processBlockScenarioImpl(
needFlags, needCache: static bool): untyped = needFlags, needCache: static bool): untyped =
let prePath = dir/preState & ".ssz" let prePath = dir/preState & ".ssz"
var state: ref BeaconState
new state
echo "Running: ", prePath echo "Running: ", prePath
state[] = parseSSZ(prePath, BeaconState) var state = parseSSZ(prePath, BeaconState)
var consObj: ref `ConsensusObject` var consObj: ref `ConsensusObject`
new consObj new consObj
@ -215,13 +207,13 @@ template processBlockScenarioImpl(
consObj[] = parseSSZ(consObjPath, ConsensusObject) consObj[] = parseSSZ(consObjPath, ConsensusObject)
when needFlags and needCache: when needFlags and needCache:
let success = transitionFn(state[], consObj[], flags, cache) let success = transitionFn(state, consObj[], flags, cache)
elif needFlags: elif needFlags:
let success = transitionFn(state[], consObj[], flags) let success = transitionFn(state, consObj[], flags)
elif needCache: elif needCache:
let success = transitionFn(state[], consObj[], flags, cache) let success = transitionFn(state, consObj[], flags, cache)
else: else:
let success = transitionFn(state[], consObj[]) let success = transitionFn(state, consObj[])
echo astToStr(transitionFn) & " status: ", if success: "SUCCESS ✓" else: "FAILURE ⚠️" echo astToStr(transitionFn) & " status: ", if success: "SUCCESS ✓" else: "FAILURE ⚠️"

View File

@ -37,28 +37,20 @@ proc runTest(identifier: string) =
prefix = "[Invalid] " prefix = "[Invalid] "
timedTest prefix & identifier: timedTest prefix & identifier:
var stateRef, postRef: ref BeaconState
var attestationRef: ref Attestation
new attestationRef
new stateRef
var cache = get_empty_per_epoch_cache() var cache = get_empty_per_epoch_cache()
attestationRef[] = parseTest(testDir/"attestation.ssz", SSZ, Attestation) let attestation = parseTest(testDir/"attestation.ssz", SSZ, Attestation)
stateRef[] = parseTest(testDir/"pre.ssz", SSZ, BeaconState) var preState = parseTest(testDir/"pre.ssz", SSZ, BeaconState)
if existsFile(testDir/"post.ssz"): if existsFile(testDir/"post.ssz"):
new postRef let postState = parseTest(testDir/"post.ssz", SSZ, BeaconState)
postRef[] = parseTest(testDir/"post.ssz", SSZ, BeaconState) let done = process_attestation(preState, attestation, {}, cache)
if postRef.isNil:
let done = process_attestation(stateRef[], attestationRef[], {}, cache)
doAssert done == false, "We didn't expect this invalid attestation to be processed."
else:
let done = process_attestation(stateRef[], attestationRef[], {}, cache)
doAssert done, "Valid attestation not processed" doAssert done, "Valid attestation not processed"
check: stateRef.hash_tree_root() == postRef.hash_tree_root() check: preState.hash_tree_root() == postState.hash_tree_root()
reportDiff(stateRef, postRef) reportDiff(preState, postState)
else:
let done = process_attestation(preState, attestation, {}, cache)
doAssert done == false, "We didn't expect this invalid attestation to be processed."
`testImpl _ operations_attestations _ identifier`() `testImpl _ operations_attestations _ identifier`()

View File

@ -37,30 +37,22 @@ proc runTest(identifier: string) =
prefix = "[Invalid] " prefix = "[Invalid] "
timedTest prefix & identifier: timedTest prefix & identifier:
var stateRef, postRef: ref BeaconState
var attesterSlashingRef: ref AttesterSlashing
new attesterSlashingRef
new stateRef
var cache = get_empty_per_epoch_cache() var cache = get_empty_per_epoch_cache()
attesterSlashingRef[] = parseTest(testDir/"attester_slashing.ssz", SSZ, AttesterSlashing) let attesterSlashing = parseTest(testDir/"attester_slashing.ssz", SSZ, AttesterSlashing)
stateRef[] = parseTest(testDir/"pre.ssz", SSZ, BeaconState) var preState = parseTest(testDir/"pre.ssz", SSZ, BeaconState)
if existsFile(testDir/"post.ssz"): if existsFile(testDir/"post.ssz"):
new postRef let postState = parseTest(testDir/"post.ssz", SSZ, BeaconState)
postRef[] = parseTest(testDir/"post.ssz", SSZ, BeaconState) let done = process_attester_slashing(preState, attesterSlashing,
{}, cache)
if postRef.isNil:
let done = process_attester_slashing(stateRef[], attesterSlashingRef[],
{}, cache)
doAssert done == false, "We didn't expect this invalid attester slashing to be processed."
else:
let done = process_attester_slashing(stateRef[], attesterSlashingRef[],
{}, cache)
doAssert done, "Valid attestater slashing not processed" doAssert done, "Valid attestater slashing not processed"
check: stateRef.hash_tree_root() == postRef.hash_tree_root() check: preState.hash_tree_root() == postState.hash_tree_root()
reportDiff(stateRef, postRef) reportDiff(preState, postState)
else:
let done = process_attester_slashing(preState, attesterSlashing,
{}, cache)
doAssert done == false, "We didn't expect this invalid attester slashing to be processed."
`testImpl _ operations_attester_slashing _ identifier`() `testImpl _ operations_attester_slashing _ identifier`()

View File

@ -37,28 +37,20 @@ proc runTest(identifier: string) =
prefix = "[Invalid] " prefix = "[Invalid] "
timedTest prefix & identifier: timedTest prefix & identifier:
var stateRef, postRef: ref BeaconState
var blck: ref BeaconBlock
new blck
new stateRef
var cache = get_empty_per_epoch_cache() var cache = get_empty_per_epoch_cache()
blck[] = parseTest(testDir/"block.ssz", SSZ, BeaconBlock) let blck = parseTest(testDir/"block.ssz", SSZ, BeaconBlock)
stateRef[] = parseTest(testDir/"pre.ssz", SSZ, BeaconState) var preState = parseTest(testDir/"pre.ssz", SSZ, BeaconState)
if existsFile(testDir/"post.ssz"): if existsFile(testDir/"post.ssz"):
new postRef let postState = parseTest(testDir/"post.ssz", SSZ, BeaconState)
postRef[] = parseTest(testDir/"post.ssz", SSZ, BeaconState) let done = process_block_header(preState, blck, {}, cache)
if postRef.isNil:
let done = process_block_header(stateRef[], blck[], {}, cache)
doAssert done == false, "We didn't expect this invalid block header to be processed."
else:
let done = process_block_header(stateRef[], blck[], {}, cache)
doAssert done, "Valid block header not processed" doAssert done, "Valid block header not processed"
check: stateRef.hash_tree_root() == postRef.hash_tree_root() check: preState.hash_tree_root() == postState.hash_tree_root()
reportDiff(stateRef, postRef) reportDiff(preState, postState)
else:
let done = process_block_header(preState, blck, {}, cache)
doAssert done == false, "We didn't expect this invalid block header to be processed."
`testImpl _ blockheader _ identifier`() `testImpl _ blockheader _ identifier`()

View File

@ -40,23 +40,15 @@ proc runTest(identifier: string) =
prefix = "[Invalid] " prefix = "[Invalid] "
timedTest prefix & " " & identifier: timedTest prefix & " " & identifier:
var stateRef, postRef: ref BeaconState let deposit = parseTest(testDir/"deposit.ssz", SSZ, Deposit)
var depositRef: ref Deposit var preState = parseTest(testDir/"pre.ssz", SSZ, BeaconState)
new depositRef
new stateRef
depositRef[] = parseTest(testDir/"deposit.ssz", SSZ, Deposit)
stateRef[] = parseTest(testDir/"pre.ssz", SSZ, BeaconState)
if existsFile(testDir/"post.ssz"): if existsFile(testDir/"post.ssz"):
new postRef let postState = parseTest(testDir/"post.ssz", SSZ, BeaconState)
postRef[] = parseTest(testDir/"post.ssz", SSZ, BeaconState) discard process_deposit(preState, deposit, flags)
reportDiff(preState, postState)
if postRef.isNil:
check not process_deposit(stateRef[], depositRef[], flags)
else: else:
discard process_deposit(stateRef[], depositRef[], flags) check not process_deposit(preState, deposit, flags)
reportDiff(stateRef, postRef)
`testImpl _ operations_deposits _ identifier`() `testImpl _ operations_deposits _ identifier`()

View File

@ -37,28 +37,20 @@ proc runTest(identifier: string) =
prefix = "[Invalid] " prefix = "[Invalid] "
timedTest prefix & astToStr(identifier): timedTest prefix & astToStr(identifier):
var stateRef, postRef: ref BeaconState let proposerSlashing = parseTest(testDir/"proposer_slashing.ssz", SSZ, ProposerSlashing)
var proposerSlashing: ref ProposerSlashing var preState = parseTest(testDir/"pre.ssz", SSZ, BeaconState)
new proposerSlashing
new stateRef
proposerSlashing[] = parseTest(testDir/"proposer_slashing.ssz", SSZ, ProposerSlashing)
stateRef[] = parseTest(testDir/"pre.ssz", SSZ, BeaconState)
if existsFile(testDir/"post.ssz"):
new postRef
postRef[] = parseTest(testDir/"post.ssz", SSZ, BeaconState)
var cache = get_empty_per_epoch_cache() var cache = get_empty_per_epoch_cache()
if postRef.isNil: if existsFile(testDir/"post.ssz"):
let done = process_proposer_slashing(stateRef[], proposerSlashing[], {}, cache) let postState = parseTest(testDir/"post.ssz", SSZ, BeaconState)
doAssert done == false, "We didn't expect this invalid proposer slashing to be processed." let done = process_proposer_slashing(preState, proposerSlashing, {}, cache)
else:
let done = process_proposer_slashing(stateRef[], proposerSlashing[], {}, cache)
doAssert done, "Valid proposer slashing not processed" doAssert done, "Valid proposer slashing not processed"
check: stateRef.hash_tree_root() == postRef.hash_tree_root() check: preState.hash_tree_root() == postState.hash_tree_root()
reportDiff(stateRef, postRef) reportDiff(preState, postState)
else:
let done = process_proposer_slashing(preState, proposerSlashing, {}, cache)
doAssert done == false, "We didn't expect this invalid proposer slashing to be processed."
`testImpl_proposer_slashing _ identifier`() `testImpl_proposer_slashing _ identifier`()

View File

@ -37,26 +37,18 @@ proc runTest(identifier: string) =
prefix = "[Invalid] " prefix = "[Invalid] "
timedTest prefix & identifier: timedTest prefix & identifier:
var stateRef, postRef: ref BeaconState let voluntaryExit = parseTest(testDir/"voluntary_exit.ssz", SSZ, SignedVoluntaryExit)
var voluntaryExit: ref SignedVoluntaryExit var preState = parseTest(testDir/"pre.ssz", SSZ, BeaconState)
new voluntaryExit
new stateRef
voluntaryExit[] = parseTest(testDir/"voluntary_exit.ssz", SSZ, SignedVoluntaryExit)
stateRef[] = parseTest(testDir/"pre.ssz", SSZ, BeaconState)
if existsFile(testDir/"post.ssz"): if existsFile(testDir/"post.ssz"):
new postRef let postState = parseTest(testDir/"post.ssz", SSZ, BeaconState)
postRef[] = parseTest(testDir/"post.ssz", SSZ, BeaconState) let done = process_voluntary_exit(preState, voluntaryExit, {})
if postRef.isNil:
let done = process_voluntary_exit(stateRef[], voluntaryExit[], {})
doAssert done == false, "We didn't expect this invalid voluntary exit to be processed."
else:
let done = process_voluntary_exit(stateRef[], voluntaryExit[], {})
doAssert done, "Valid voluntary exit not processed" doAssert done, "Valid voluntary exit not processed"
check: stateRef.hash_tree_root() == postRef.hash_tree_root() check: preState.hash_tree_root() == postState.hash_tree_root()
reportDiff(stateRef, postRef) reportDiff(preState, postState)
else:
let done = process_voluntary_exit(preState, voluntaryExit, {})
doAssert done == false, "We didn't expect this invalid voluntary exit to be processed."
`testImpl _ voluntary_exit _ identifier`() `testImpl _ voluntary_exit _ identifier`()

View File

@ -34,32 +34,28 @@ proc runTest(identifier: string) =
"[Invalid] " "[Invalid] "
timedTest prefix & identifier: timedTest prefix & identifier:
var stateRef, postRef: ref BeaconState var preState = parseTest(testDir/"pre.ssz", SSZ, BeaconState)
new stateRef var hasPostState = existsFile(testDir/"post.ssz")
stateRef[] = parseTest(testDir/"pre.ssz", SSZ, BeaconState)
if existsFile(testDir/"post.ssz"):
new postRef
postRef[] = parseTest(testDir/"post.ssz", SSZ, BeaconState)
# In test cases with more than 10 blocks the first 10 aren't 0-prefixed, # In test cases with more than 10 blocks the first 10 aren't 0-prefixed,
# so purely lexicographic sorting wouldn't sort properly. # so purely lexicographic sorting wouldn't sort properly.
for i in 0 ..< toSeq(walkPattern(testDir/"blocks_*.ssz")).len: for i in 0 ..< toSeq(walkPattern(testDir/"blocks_*.ssz")).len:
let blck = parseTest(testDir/"blocks_" & $i & ".ssz", SSZ, SignedBeaconBlock) let blck = parseTest(testDir/"blocks_" & $i & ".ssz", SSZ, SignedBeaconBlock)
if postRef.isNil: if hasPostState:
let success = state_transition(stateRef[], blck, flags = {})
doAssert not success, "We didn't expect this invalid block to be processed"
else:
# TODO: The EF is using invalid BLS keys so we can't verify them # TODO: The EF is using invalid BLS keys so we can't verify them
let success = state_transition(stateRef[], blck, flags = {skipBlsValidation}) let success = state_transition(preState, blck, flags = {skipBlsValidation})
doAssert success, "Failure when applying block " & $i doAssert success, "Failure when applying block " & $i
else:
let success = state_transition(preState, blck, flags = {})
doAssert not success, "We didn't expect this invalid block to be processed"
# check: stateRef.hash_tree_root() == postRef.hash_tree_root() # check: preState.hash_tree_root() == postState.hash_tree_root()
if not postRef.isNil: if hasPostState:
let postState = parseTest(testDir/"post.ssz", SSZ, BeaconState)
when false: when false:
reportDiff(stateRef, postRef) reportDiff(preState, postState)
doAssert stateRef.hash_tree_root() == postRef.hash_tree_root() doAssert preState.hash_tree_root() == postState.hash_tree_root()
`testImpl _ blck _ identifier`() `testImpl _ blck _ identifier`()

View File

@ -31,16 +31,13 @@ proc runTest(identifier: string) =
proc `testImpl _ slots _ identifier`() = proc `testImpl _ slots _ identifier`() =
timedTest "Slots - " & identifier: timedTest "Slots - " & identifier:
var stateRef, postRef: ref BeaconState var preState = parseTest(testDir/"pre.ssz", SSZ, BeaconState)
new stateRef let postState = parseTest(testDir/"post.ssz", SSZ, BeaconState)
new postRef
stateRef[] = parseTest(testDir/"pre.ssz", SSZ, BeaconState)
postRef[] = parseTest(testDir/"post.ssz", SSZ, BeaconState)
process_slots(stateRef[], stateRef.slot + num_slots) process_slots(preState, preState.slot + num_slots)
# check: stateRef.hash_tree_root() == postRef.hash_tree_root() # check: preState.hash_tree_root() == postState.hash_tree_root()
reportDiff(stateRef, postRef) reportDiff(preState, postState)
`testImpl _ slots _ identifier`() `testImpl _ slots _ identifier`()

View File

@ -82,11 +82,7 @@ proc checkBasic(T: typedesc,
var fileContents = readFile(dir/"serialized.ssz") var fileContents = readFile(dir/"serialized.ssz")
var stream = memoryInput(fileContents) var stream = memoryInput(fileContents)
var reader = init(SszReader, stream) var reader = init(SszReader, stream)
var deserialized = reader.readValue(T)
# We are using heap allocation to avoid stack overflow
# issues caused by large objects such as `BeaconState`:
var deserialized = new T
reader.readValue(deserialized[])
if stream.readable: if stream.readable:
raise newException(UnconsumedInput, "Remaining bytes in the input") raise newException(UnconsumedInput, "Remaining bytes in the input")

View File

@ -38,16 +38,16 @@ template runSuite(suiteDir, testName: string, transitionProc: untyped{ident}, us
let unitTestName = testDir.rsplit(DirSep, 1)[1] let unitTestName = testDir.rsplit(DirSep, 1)[1]
timedTest testName & " - " & unitTestName & preset(): timedTest testName & " - " & unitTestName & preset():
let stateRef = parseTest(testDir/"pre.ssz", SSZ, ref BeaconState) var preState = parseTest(testDir/"pre.ssz", SSZ, BeaconState)
let postRef = parseTest(testDir/"post.ssz", SSZ, ref BeaconState) let postState = parseTest(testDir/"post.ssz", SSZ, BeaconState)
when useCache: when useCache:
var cache = get_empty_per_epoch_cache() var cache = get_empty_per_epoch_cache()
transitionProc(stateRef[], cache) transitionProc(preState, cache)
else: else:
transitionProc(stateRef[]) transitionProc(preState)
reportDiff(stateRef, postRef) reportDiff(preState, postState)
`suiteImpl _ transitionProc`() `suiteImpl _ transitionProc`()

View File

@ -33,8 +33,7 @@ suiteReport "[Unit - Spec - Block processing] Attestations " & preset():
# The attestation to process must be named "attestation" in the calling context # The attestation to process must be named "attestation" in the calling context
timedTest name: timedTest name:
var state{.inject.}: BeaconState var state {.inject.} = clone(genesisState)
deepCopy(state, genesisState)
# Attestation setup body # Attestation setup body
# ---------------------------------------- # ----------------------------------------

View File

@ -33,8 +33,7 @@ suiteReport "[Unit - Spec - Block processing] Deposits " & preset():
# TODO: BLS signature # TODO: BLS signature
timedTest "Deposit " & name & " MAX_EFFECTIVE_BALANCE balance (" & timedTest "Deposit " & name & " MAX_EFFECTIVE_BALANCE balance (" &
$(MAX_EFFECTIVE_BALANCE div 10'u64^9) & " ETH)": $(MAX_EFFECTIVE_BALANCE div 10'u64^9) & " ETH)":
var state: BeaconState var state = clone(genesisState)
deepCopy(state, genesisState)
# Test configuration # Test configuration
# ---------------------------------------- # ----------------------------------------
@ -75,9 +74,7 @@ suiteReport "[Unit - Spec - Block processing] Deposits " & preset():
valid_deposit(MAX_EFFECTIVE_BALANCE + 1, "over") valid_deposit(MAX_EFFECTIVE_BALANCE + 1, "over")
timedTest "Validator top-up": timedTest "Validator top-up":
var state = clone(genesisState)
var state: BeaconState
deepCopy(state, genesisState)
# Test configuration # Test configuration
# ---------------------------------------- # ----------------------------------------

View File

@ -212,46 +212,39 @@ proc finalizeOn12(state: var BeaconState, epoch: Epoch, sufficient_support: bool
doAssert state.current_justified_checkpoint == c2 # still old current doAssert state.current_justified_checkpoint == c2 # still old current
doAssert state.finalized_checkpoint == old_finalized # no new finalized checkpoint doAssert state.finalized_checkpoint == old_finalized # no new finalized checkpoint
suiteReport "[Unit - Spec - Epoch processing] Justification and Finalization " & preset(): proc payload =
echo " Finalization rules are detailed at https://github.com/protolambda/eth2-docs#justification-and-finalization" suiteReport "[Unit - Spec - Epoch processing] Justification and Finalization " & preset():
echo " Finalization rules are detailed at https://github.com/protolambda/eth2-docs#justification-and-finalization"
const NumValidators = uint64(8) * SLOTS_PER_EPOCH const NumValidators = uint64(8) * SLOTS_PER_EPOCH
let genesisState = initGenesisState(NumValidators) let genesisState = initGenesisState(NumValidators)
doAssert genesisState.validators.len == int NumValidators doAssert genesisState.validators.len == int NumValidators
var state: BeaconState setup:
template resetState: untyped = var state = clone(genesisState)
deepCopy(state, genesisState)
timedTest " Rule I - 234 finalization with enough support": timedTest " Rule I - 234 finalization with enough support":
resetState() finalizeOn234(state, Epoch 5, sufficient_support = true)
finalizeOn234(state, Epoch 5, sufficient_support = true)
timedTest " Rule I - 234 finalization without support": timedTest " Rule I - 234 finalization without support":
resetState() finalizeOn234(state, Epoch 5, sufficient_support = false)
finalizeOn234(state, Epoch 5, sufficient_support = false)
timedTest " Rule II - 23 finalization with enough support": timedTest " Rule II - 23 finalization with enough support":
resetState() finalizeOn23(state, Epoch 4, sufficient_support = true)
finalizeOn23(state, Epoch 4, sufficient_support = true)
timedTest " Rule II - 23 finalization without support": timedTest " Rule II - 23 finalization without support":
resetState() finalizeOn23(state, Epoch 4, sufficient_support = false)
finalizeOn23(state, Epoch 4, sufficient_support = false)
timedTest " Rule III - 123 finalization with enough support":
finalizeOn123(state, Epoch 6, sufficient_support = true)
timedTest " Rule III - 123 finalization with enough support": timedTest " Rule III - 123 finalization without support":
resetState() finalizeOn123(state, Epoch 6, sufficient_support = false)
finalizeOn123(state, Epoch 6, sufficient_support = true)
timedTest " Rule III - 123 finalization without support": timedTest " Rule IV - 12 finalization with enough support":
resetState() finalizeOn12(state, Epoch 3, sufficient_support = true)
finalizeOn123(state, Epoch 6, sufficient_support = false)
timedTest " Rule IV - 12 finalization with enough support": timedTest " Rule IV - 12 finalization without support":
resetState() finalizeOn12(state, Epoch 3, sufficient_support = false)
finalizeOn12(state, Epoch 3, sufficient_support = true)
timedTest " Rule IV - 12 finalization without support": payload()
resetState()
finalizeOn12(state, Epoch 3, sufficient_support = false)

View File

@ -55,7 +55,7 @@ suiteReport "Beacon chain DB" & preset():
check: check:
db.containsState(root) db.containsState(root)
db.getState(root).get() == state db.getState(root).get[] == state[]
timedTest "find ancestors" & preset(): timedTest "find ancestors" & preset():
var var
@ -106,4 +106,4 @@ suiteReport "Beacon chain DB" & preset():
check: check:
db.containsState(root) db.containsState(root)
db.getState(root).get() == state db.getState(root).get[] == state[]

View File

@ -242,7 +242,7 @@ when const_preset == "minimal": # Too much stack space used on mainnet
bs1_3 = b1Add.atSlot(3.Slot) bs1_3 = b1Add.atSlot(3.Slot)
bs2_3 = b2Add.atSlot(3.Slot) bs2_3 = b2Add.atSlot(3.Slot)
var tmpState = pool.headState var tmpState = clone(pool.headState)
# move to specific block # move to specific block
pool.updateStateData(tmpState, bs1) pool.updateStateData(tmpState, bs1)

View File

@ -26,8 +26,7 @@ suiteReport "Block processing" & preset():
genesisRoot = hash_tree_root(genesisBlock.message) genesisRoot = hash_tree_root(genesisBlock.message)
timedTest "Passes from genesis state, no block" & preset(): timedTest "Passes from genesis state, no block" & preset():
var var state = clone(genesisState)
state = genesisState
process_slots(state, state.slot + 1) process_slots(state, state.slot + 1)
check: check:
@ -35,7 +34,7 @@ suiteReport "Block processing" & preset():
timedTest "Passes from genesis state, empty block" & preset(): timedTest "Passes from genesis state, empty block" & preset():
var var
state = genesisState state = clone(genesisState)
previous_block_root = hash_tree_root(genesisBlock.message) previous_block_root = hash_tree_root(genesisBlock.message)
new_block = makeTestBlock(state, previous_block_root) new_block = makeTestBlock(state, previous_block_root)
@ -47,8 +46,7 @@ suiteReport "Block processing" & preset():
state.slot == genesisState.slot + 1 state.slot == genesisState.slot + 1
timedTest "Passes through epoch update, no block" & preset(): timedTest "Passes through epoch update, no block" & preset():
var var state = clone(genesisState)
state = genesisState
process_slots(state, Slot(SLOTS_PER_EPOCH)) process_slots(state, Slot(SLOTS_PER_EPOCH))
@ -57,7 +55,7 @@ suiteReport "Block processing" & preset():
timedTest "Passes through epoch update, empty block" & preset(): timedTest "Passes through epoch update, empty block" & preset():
var var
state = genesisState state = clone(genesisState)
previous_block_root = genesisRoot previous_block_root = genesisRoot
for i in 1..SLOTS_PER_EPOCH.int: for i in 1..SLOTS_PER_EPOCH.int:
@ -75,7 +73,7 @@ suiteReport "Block processing" & preset():
timedTest "Attestation gets processed at epoch" & preset(): timedTest "Attestation gets processed at epoch" & preset():
var var
state = genesisState state = clone(genesisState)
previous_block_root = genesisRoot previous_block_root = genesisRoot
cache = get_empty_per_epoch_cache() cache = get_empty_per_epoch_cache()

View File

@ -148,7 +148,7 @@ proc makeTestBlock*(
# It's a bit awkward - in order to produce a block for N+1, we need to # It's a bit awkward - in order to produce a block for N+1, we need to
# calculate what the state will look like after that block has been applied, # calculate what the state will look like after that block has been applied,
# because the block includes the state root. # because the block includes the state root.
var tmpState = state var tmpState = clone(state)
addTestBlock( addTestBlock(
tmpState, parent_root, eth1_data, attestations, deposits, graffiti, flags) tmpState, parent_root, eth1_data, attestations, deposits, graffiti, flags)