introduce database support for Altair (#2667)
* introduce immutable Altair BeaconState * add database support for Altair blocks and states * add tests for Altair get/put/contains/delete state * enable blockchain_dag Altair state database storing * properly return error on getting missing altair block
This commit is contained in:
parent
ae1abf24af
commit
41e0a7abc0
|
@ -82,13 +82,16 @@ OK: 11/11 Fail: 0/11 Skip: 0/11
|
||||||
```diff
|
```diff
|
||||||
+ empty database [Preset: mainnet] OK
|
+ empty database [Preset: mainnet] OK
|
||||||
+ find ancestors [Preset: mainnet] OK
|
+ find ancestors [Preset: mainnet] OK
|
||||||
+ sanity check blocks [Preset: mainnet] OK
|
+ sanity check Altair blocks [Preset: mainnet] OK
|
||||||
|
+ sanity check Altair states [Preset: mainnet] OK
|
||||||
|
+ sanity check Altair states, reusing buffers [Preset: mainnet] OK
|
||||||
+ sanity check genesis roundtrip [Preset: mainnet] OK
|
+ sanity check genesis roundtrip [Preset: mainnet] OK
|
||||||
|
+ sanity check phase 0 blocks [Preset: mainnet] OK
|
||||||
|
+ sanity check phase 0 states [Preset: mainnet] OK
|
||||||
|
+ sanity check phase 0 states, reusing buffers [Preset: mainnet] OK
|
||||||
+ sanity check state diff roundtrip [Preset: mainnet] OK
|
+ sanity check state diff roundtrip [Preset: mainnet] OK
|
||||||
+ sanity check states [Preset: mainnet] OK
|
|
||||||
+ sanity check states, reusing buffers [Preset: mainnet] OK
|
|
||||||
```
|
```
|
||||||
OK: 7/7 Fail: 0/7 Skip: 0/7
|
OK: 10/10 Fail: 0/10 Skip: 0/10
|
||||||
## Beacon state [Preset: mainnet]
|
## Beacon state [Preset: mainnet]
|
||||||
```diff
|
```diff
|
||||||
+ Smoke test initialize_beacon_state_from_eth1 [Preset: mainnet] OK
|
+ Smoke test initialize_beacon_state_from_eth1 [Preset: mainnet] OK
|
||||||
|
@ -308,4 +311,4 @@ OK: 3/3 Fail: 0/3 Skip: 0/3
|
||||||
OK: 1/1 Fail: 0/1 Skip: 0/1
|
OK: 1/1 Fail: 0/1 Skip: 0/1
|
||||||
|
|
||||||
---TOTAL---
|
---TOTAL---
|
||||||
OK: 174/182 Fail: 0/182 Skip: 8/182
|
OK: 177/185 Fail: 0/185 Skip: 8/185
|
||||||
|
|
|
@ -89,9 +89,11 @@ type
|
||||||
checkpoint*: proc() {.gcsafe, raises: [Defect].}
|
checkpoint*: proc() {.gcsafe, raises: [Defect].}
|
||||||
|
|
||||||
keyValues: KvStoreRef # Random stuff using DbKeyKind - suitable for small values mainly!
|
keyValues: KvStoreRef # Random stuff using DbKeyKind - suitable for small values mainly!
|
||||||
blocks: KvStoreRef # BlockRoot -> TrustedBeaconBlock
|
blocks: KvStoreRef # BlockRoot -> phase0.TrustedBeaconBlock
|
||||||
|
altairBlocks: KvStoreRef # BlockRoot -> altair.TrustedBeaconBlock
|
||||||
stateRoots: KvStoreRef # (Slot, BlockRoot) -> StateRoot
|
stateRoots: KvStoreRef # (Slot, BlockRoot) -> StateRoot
|
||||||
statesNoVal: KvStoreRef # StateRoot -> BeaconStateNoImmutableValidators
|
statesNoVal: KvStoreRef # StateRoot -> BeaconStateNoImmutableValidators
|
||||||
|
altairStatesNoVal: KvStoreRef # StateRoot -> AltairBeaconStateNoImmutableValidators
|
||||||
stateDiffs: KvStoreRef ##\
|
stateDiffs: KvStoreRef ##\
|
||||||
## StateRoot -> BeaconStateDiff
|
## StateRoot -> BeaconStateDiff
|
||||||
## Instead of storing full BeaconStates, one can store only the diff from
|
## Instead of storing full BeaconStates, one can store only the diff from
|
||||||
|
@ -301,8 +303,10 @@ proc new*(T: type BeaconChainDB,
|
||||||
# V1 - expected-to-be small rows get without rowid optimizations
|
# V1 - expected-to-be small rows get without rowid optimizations
|
||||||
keyValues = kvStore db.openKvStore("key_values", true).expectDb()
|
keyValues = kvStore db.openKvStore("key_values", true).expectDb()
|
||||||
blocks = kvStore db.openKvStore("blocks").expectDb()
|
blocks = kvStore db.openKvStore("blocks").expectDb()
|
||||||
|
altairBlocks = kvStore db.openKvStore("altair_blocks").expectDb()
|
||||||
stateRoots = kvStore db.openKvStore("state_roots", true).expectDb()
|
stateRoots = kvStore db.openKvStore("state_roots", true).expectDb()
|
||||||
statesNoVal = kvStore db.openKvStore("state_no_validators2").expectDb()
|
statesNoVal = kvStore db.openKvStore("state_no_validators2").expectDb()
|
||||||
|
altairStatesNoVal = kvStore db.openKvStore("altair_state_no_validators").expectDb()
|
||||||
stateDiffs = kvStore db.openKvStore("state_diffs").expectDb()
|
stateDiffs = kvStore db.openKvStore("state_diffs").expectDb()
|
||||||
summaries = kvStore db.openKvStore("beacon_block_summaries", true).expectDb()
|
summaries = kvStore db.openKvStore("beacon_block_summaries", true).expectDb()
|
||||||
|
|
||||||
|
@ -337,8 +341,10 @@ proc new*(T: type BeaconChainDB,
|
||||||
checkpoint: proc() = db.checkpoint(),
|
checkpoint: proc() = db.checkpoint(),
|
||||||
keyValues: keyValues,
|
keyValues: keyValues,
|
||||||
blocks: blocks,
|
blocks: blocks,
|
||||||
|
altair_blocks: altair_blocks,
|
||||||
stateRoots: stateRoots,
|
stateRoots: stateRoots,
|
||||||
statesNoVal: statesNoVal,
|
statesNoVal: statesNoVal,
|
||||||
|
altairStatesNoVal: statesNoVal,
|
||||||
stateDiffs: stateDiffs,
|
stateDiffs: stateDiffs,
|
||||||
summaries: summaries,
|
summaries: summaries,
|
||||||
)
|
)
|
||||||
|
@ -449,8 +455,10 @@ proc close*(db: BeaconchainDB) =
|
||||||
# Close things in reverse order
|
# Close things in reverse order
|
||||||
discard db.summaries.close()
|
discard db.summaries.close()
|
||||||
discard db.stateDiffs.close()
|
discard db.stateDiffs.close()
|
||||||
|
discard db.altairStatesNoVal.close()
|
||||||
discard db.statesNoVal.close()
|
discard db.statesNoVal.close()
|
||||||
discard db.stateRoots.close()
|
discard db.stateRoots.close()
|
||||||
|
discard db.altairBlocks.close()
|
||||||
discard db.blocks.close()
|
discard db.blocks.close()
|
||||||
discard db.keyValues.close()
|
discard db.keyValues.close()
|
||||||
db.immutableValidatorsDb.close()
|
db.immutableValidatorsDb.close()
|
||||||
|
@ -475,6 +483,10 @@ proc putBlock*(db: BeaconChainDB, value: phase0.TrustedSignedBeaconBlock) =
|
||||||
db.blocks.putSnappySSZ(value.root.data, value)
|
db.blocks.putSnappySSZ(value.root.data, value)
|
||||||
db.putBeaconBlockSummary(value.root, value.message.toBeaconBlockSummary())
|
db.putBeaconBlockSummary(value.root, value.message.toBeaconBlockSummary())
|
||||||
|
|
||||||
|
proc putBlock*(db: BeaconChainDB, value: altair.TrustedSignedBeaconBlock) =
|
||||||
|
db.altairBlocks.putSnappySSZ(value.root.data, value)
|
||||||
|
db.putBeaconBlockSummary(value.root, value.message.toBeaconBlockSummary())
|
||||||
|
|
||||||
proc updateImmutableValidators*(
|
proc updateImmutableValidators*(
|
||||||
db: BeaconChainDB, validators: openArray[Validator]) =
|
db: BeaconChainDB, validators: openArray[Validator]) =
|
||||||
# Must be called before storing a state that references the new validators
|
# Must be called before storing a state that references the new validators
|
||||||
|
@ -492,7 +504,14 @@ proc putState*(db: BeaconChainDB, key: Eth2Digest, value: phase0.BeaconState) =
|
||||||
key.data,
|
key.data,
|
||||||
isomorphicCast[BeaconStateNoImmutableValidators](value))
|
isomorphicCast[BeaconStateNoImmutableValidators](value))
|
||||||
|
|
||||||
proc putState*(db: BeaconChainDB, value: phase0.BeaconState) =
|
proc putState*(db: BeaconChainDB, key: Eth2Digest, value: altair.BeaconState) =
|
||||||
|
db.updateImmutableValidators(value.validators.asSeq())
|
||||||
|
db.altairStatesNoVal.putSnappySSZ(
|
||||||
|
key.data,
|
||||||
|
isomorphicCast[AltairBeaconStateNoImmutableValidators](value))
|
||||||
|
|
||||||
|
proc putState*(
|
||||||
|
db: BeaconChainDB, value: phase0.BeaconState | altair.BeaconState) =
|
||||||
db.putState(hash_tree_root(value), value)
|
db.putState(hash_tree_root(value), value)
|
||||||
|
|
||||||
func stateRootKey(root: Eth2Digest, slot: Slot): array[40, byte] =
|
func stateRootKey(root: Eth2Digest, slot: Slot): array[40, byte] =
|
||||||
|
@ -512,10 +531,12 @@ proc putStateDiff*(db: BeaconChainDB, root: Eth2Digest, value: BeaconStateDiff)
|
||||||
|
|
||||||
proc delBlock*(db: BeaconChainDB, key: Eth2Digest) =
|
proc delBlock*(db: BeaconChainDB, key: Eth2Digest) =
|
||||||
db.blocks.del(key.data).expectDb()
|
db.blocks.del(key.data).expectDb()
|
||||||
|
db.altairBlocks.del(key.data).expectDb()
|
||||||
db.summaries.del(key.data).expectDb()
|
db.summaries.del(key.data).expectDb()
|
||||||
|
|
||||||
proc delState*(db: BeaconChainDB, key: Eth2Digest) =
|
proc delState*(db: BeaconChainDB, key: Eth2Digest) =
|
||||||
db.statesNoVal.del(key.data).expectDb()
|
db.statesNoVal.del(key.data).expectDb()
|
||||||
|
db.altairStatesNoVal.del(key.data).expectDb()
|
||||||
|
|
||||||
proc delStateRoot*(db: BeaconChainDB, root: Eth2Digest, slot: Slot) =
|
proc delStateRoot*(db: BeaconChainDB, root: Eth2Digest, slot: Slot) =
|
||||||
db.stateRoots.del(stateRootKey(root, slot)).expectDb()
|
db.stateRoots.del(stateRootKey(root, slot)).expectDb()
|
||||||
|
@ -556,6 +577,16 @@ proc getBlock*(db: BeaconChainDB, key: Eth2Digest):
|
||||||
# set root after deserializing (so it doesn't get zeroed)
|
# set root after deserializing (so it doesn't get zeroed)
|
||||||
result.get().root = key
|
result.get().root = key
|
||||||
|
|
||||||
|
proc getAltairBlock*(db: BeaconChainDB, key: Eth2Digest):
|
||||||
|
Opt[altair.TrustedSignedBeaconBlock] =
|
||||||
|
# We only store blocks that we trust in the database
|
||||||
|
result.ok(default(altair.TrustedSignedBeaconBlock))
|
||||||
|
if db.altairBlocks.getSnappySSZ(key.data, result.get) == GetResult.found:
|
||||||
|
# set root after deserializing (so it doesn't get zeroed)
|
||||||
|
result.get().root = key
|
||||||
|
else:
|
||||||
|
result.err()
|
||||||
|
|
||||||
proc getStateOnlyMutableValidators(
|
proc getStateOnlyMutableValidators(
|
||||||
immutableValidators: openArray[ImmutableValidatorData2],
|
immutableValidators: openArray[ImmutableValidatorData2],
|
||||||
store: KvStoreRef, key: openArray[byte], output: var phase0.BeaconState,
|
store: KvStoreRef, key: openArray[byte], output: var phase0.BeaconState,
|
||||||
|
@ -598,6 +629,48 @@ proc getStateOnlyMutableValidators(
|
||||||
rollback(output)
|
rollback(output)
|
||||||
false
|
false
|
||||||
|
|
||||||
|
proc getAltairStateOnlyMutableValidators(
|
||||||
|
immutableValidators: openArray[ImmutableValidatorData2],
|
||||||
|
store: KvStoreRef, key: openArray[byte], output: var altair.BeaconState,
|
||||||
|
rollback: AltairRollbackProc): bool =
|
||||||
|
## Load state into `output` - BeaconState is large so we want to avoid
|
||||||
|
## re-allocating it if possible
|
||||||
|
## Return `true` iff the entry was found in the database and `output` was
|
||||||
|
## overwritten.
|
||||||
|
## Rollback will be called only if output was partially written - if it was
|
||||||
|
## not found at all, rollback will not be called
|
||||||
|
# TODO rollback is needed to deal with bug - use `noRollback` to ignore:
|
||||||
|
# https://github.com/nim-lang/Nim/issues/14126
|
||||||
|
# TODO RVO is inefficient for large objects:
|
||||||
|
# https://github.com/nim-lang/Nim/issues/13879
|
||||||
|
|
||||||
|
case store.getSnappySSZ(
|
||||||
|
key, isomorphicCast[AltairBeaconStateNoImmutableValidators](output))
|
||||||
|
of GetResult.found:
|
||||||
|
let numValidators = output.validators.len
|
||||||
|
doAssert immutableValidators.len >= numValidators
|
||||||
|
|
||||||
|
for i in 0 ..< numValidators:
|
||||||
|
let
|
||||||
|
# Bypass hash cache invalidation
|
||||||
|
dstValidator = addr output.validators.data[i]
|
||||||
|
|
||||||
|
assign(
|
||||||
|
dstValidator.pubkey,
|
||||||
|
immutableValidators[i].pubkey.loadValid().toPubKey())
|
||||||
|
assign(
|
||||||
|
dstValidator.withdrawal_credentials,
|
||||||
|
immutableValidators[i].withdrawal_credentials)
|
||||||
|
|
||||||
|
output.validators.resetCache()
|
||||||
|
|
||||||
|
true
|
||||||
|
of GetResult.notFound:
|
||||||
|
false
|
||||||
|
of GetResult.corrupted:
|
||||||
|
rollback(output)
|
||||||
|
false
|
||||||
|
|
||||||
proc getState(
|
proc getState(
|
||||||
db: BeaconChainDBV0,
|
db: BeaconChainDBV0,
|
||||||
immutableValidators: openArray[ImmutableValidatorData2],
|
immutableValidators: openArray[ImmutableValidatorData2],
|
||||||
|
@ -646,6 +719,22 @@ proc getState*(
|
||||||
else:
|
else:
|
||||||
true
|
true
|
||||||
|
|
||||||
|
proc getAltairState*(
|
||||||
|
db: BeaconChainDB, key: Eth2Digest, output: var altair.BeaconState,
|
||||||
|
rollback: AltairRollbackProc): bool =
|
||||||
|
## Load state into `output` - BeaconState is large so we want to avoid
|
||||||
|
## re-allocating it if possible
|
||||||
|
## Return `true` iff the entry was found in the database and `output` was
|
||||||
|
## overwritten.
|
||||||
|
## Rollback will be called only if output was partially written - if it was
|
||||||
|
## not found at all, rollback will not be called
|
||||||
|
# TODO rollback is needed to deal with bug - use `noRollback` to ignore:
|
||||||
|
# https://github.com/nim-lang/Nim/issues/14126
|
||||||
|
# TODO RVO is inefficient for large objects:
|
||||||
|
# https://github.com/nim-lang/Nim/issues/13879
|
||||||
|
getAltairStateOnlyMutableValidators(
|
||||||
|
db.immutableValidators, db.altairStatesNoVal, key.data, output, rollback)
|
||||||
|
|
||||||
proc getStateRoot(db: BeaconChainDBV0,
|
proc getStateRoot(db: BeaconChainDBV0,
|
||||||
root: Eth2Digest,
|
root: Eth2Digest,
|
||||||
slot: Slot): Opt[Eth2Digest] =
|
slot: Slot): Opt[Eth2Digest] =
|
||||||
|
@ -698,7 +787,9 @@ proc containsBlock*(db: BeaconChainDBV0, key: Eth2Digest): bool =
|
||||||
db.backend.contains(subkey(phase0.SignedBeaconBlock, key)).expectDb()
|
db.backend.contains(subkey(phase0.SignedBeaconBlock, key)).expectDb()
|
||||||
|
|
||||||
proc containsBlock*(db: BeaconChainDB, key: Eth2Digest): bool =
|
proc containsBlock*(db: BeaconChainDB, key: Eth2Digest): bool =
|
||||||
db.blocks.contains(key.data).expectDb() or db.v0.containsBlock(key)
|
db.altairBlocks.contains(key.data).expectDb() or
|
||||||
|
db.blocks.contains(key.data).expectDb() or
|
||||||
|
db.v0.containsBlock(key)
|
||||||
|
|
||||||
proc containsState*(db: BeaconChainDBV0, key: Eth2Digest): bool =
|
proc containsState*(db: BeaconChainDBV0, key: Eth2Digest): bool =
|
||||||
let sk = subkey(BeaconStateNoImmutableValidators, key)
|
let sk = subkey(BeaconStateNoImmutableValidators, key)
|
||||||
|
@ -707,6 +798,7 @@ proc containsState*(db: BeaconChainDBV0, key: Eth2Digest): bool =
|
||||||
db.backend.contains(subkey(phase0.BeaconState, key)).expectDb()
|
db.backend.contains(subkey(phase0.BeaconState, key)).expectDb()
|
||||||
|
|
||||||
proc containsState*(db: BeaconChainDB, key: Eth2Digest, legacy: bool = true): bool =
|
proc containsState*(db: BeaconChainDB, key: Eth2Digest, legacy: bool = true): bool =
|
||||||
|
db.altairStatesNoVal.contains(key.data).expectDb or
|
||||||
db.statesNoVal.contains(key.data).expectDb or
|
db.statesNoVal.contains(key.data).expectDb or
|
||||||
(legacy and db.v0.containsState(key))
|
(legacy and db.v0.containsState(key))
|
||||||
|
|
||||||
|
|
|
@ -12,13 +12,14 @@ import
|
||||||
stew/[assign2, io2, objects, results],
|
stew/[assign2, io2, objects, results],
|
||||||
serialization,
|
serialization,
|
||||||
eth/db/[kvstore, kvstore_sqlite3],
|
eth/db/[kvstore, kvstore_sqlite3],
|
||||||
./spec/[crypto, datatypes, digest],
|
./spec/[crypto, digest],
|
||||||
|
./spec/datatypes/[base, altair],
|
||||||
./ssz/[ssz_serialization, merkleization],
|
./ssz/[ssz_serialization, merkleization],
|
||||||
filepath
|
filepath
|
||||||
|
|
||||||
type
|
type
|
||||||
# https://github.com/ethereum/eth2.0-specs/blob/v1.0.1/specs/phase0/beacon-chain.md#beaconstate
|
# https://github.com/ethereum/eth2.0-specs/blob/v1.0.1/specs/phase0/beacon-chain.md#beaconstate
|
||||||
# Memory-representation-equivalent to a v1.0.1 BeaconState for in-place SSZ reading and writing
|
# Memory-representation-equivalent to a phase0 BeaconState for in-place SSZ reading and writing
|
||||||
BeaconStateNoImmutableValidators* = object
|
BeaconStateNoImmutableValidators* = object
|
||||||
# Versioning
|
# Versioning
|
||||||
genesis_time*: uint64
|
genesis_time*: uint64
|
||||||
|
@ -71,6 +72,68 @@ type
|
||||||
current_justified_checkpoint*: Checkpoint
|
current_justified_checkpoint*: Checkpoint
|
||||||
finalized_checkpoint*: Checkpoint
|
finalized_checkpoint*: Checkpoint
|
||||||
|
|
||||||
|
# https://github.com/ethereum/eth2.0-specs/blob/v1.1.0-alpha.7/specs/altair/beacon-chain.md#beaconstate
|
||||||
|
# Memory-representation-equivalent to an Altair BeaconState for in-place SSZ
|
||||||
|
# reading and writing
|
||||||
|
AltairBeaconStateNoImmutableValidators* = 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[ValidatorStatus, 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
|
||||||
|
|
||||||
|
# Participation
|
||||||
|
previous_epoch_participation*:
|
||||||
|
HashList[ParticipationFlags, Limit VALIDATOR_REGISTRY_LIMIT]
|
||||||
|
current_epoch_participation*:
|
||||||
|
HashList[ParticipationFlags, Limit VALIDATOR_REGISTRY_LIMIT]
|
||||||
|
|
||||||
|
# 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
|
||||||
|
|
||||||
|
# Inactivity
|
||||||
|
inactivity_scores*: HashList[uint64, Limit VALIDATOR_REGISTRY_LIMIT] # [New in Altair]
|
||||||
|
|
||||||
|
# Light client sync committees
|
||||||
|
current_sync_committee*: SyncCommittee # [New in Altair]
|
||||||
|
next_sync_committee*: SyncCommittee # [New in Altair]
|
||||||
|
|
||||||
func getSizeofSig(x: auto, n: int = 0): seq[(string, int, int)] =
|
func getSizeofSig(x: auto, n: int = 0): seq[(string, int, int)] =
|
||||||
for name, value in x.fieldPairs:
|
for name, value in x.fieldPairs:
|
||||||
when value is tuple|object:
|
when value is tuple|object:
|
||||||
|
|
|
@ -559,9 +559,11 @@ proc putState(dag: ChainDAGRef, state: var StateData) =
|
||||||
# Ideally we would save the state and the root lookup cache in a single
|
# Ideally we would save the state and the root lookup cache in a single
|
||||||
# transaction to prevent database inconsistencies, but the state loading code
|
# transaction to prevent database inconsistencies, but the state loading code
|
||||||
# is resilient against one or the other going missing
|
# is resilient against one or the other going missing
|
||||||
if state.data.beaconStateFork != forkAltair:
|
case state.data.beaconStateFork:
|
||||||
# TODO re-enable for Altair
|
of forkPhase0:
|
||||||
dag.db.putState(getStateRoot(state.data), state.data.hbsPhase0.data)
|
dag.db.putState(getStateRoot(state.data), state.data.hbsPhase0.data)
|
||||||
|
of forkAltair:
|
||||||
|
dag.db.putState(getStateRoot(state.data), state.data.hbsAltair.data)
|
||||||
|
|
||||||
dag.db.putStateRoot(
|
dag.db.putStateRoot(
|
||||||
state.blck.root, getStateField(state.data, slot), getStateRoot(state.data))
|
state.blck.root, getStateField(state.data, slot), getStateRoot(state.data))
|
||||||
|
|
|
@ -105,9 +105,13 @@ func verifyStateRoot(state: phase0.BeaconState, blck: altair.TrustedBeaconBlock)
|
||||||
|
|
||||||
type
|
type
|
||||||
RollbackProc* = proc(v: var phase0.BeaconState) {.gcsafe, raises: [Defect].}
|
RollbackProc* = proc(v: var phase0.BeaconState) {.gcsafe, raises: [Defect].}
|
||||||
|
AltairRollbackProc* = proc(v: var altair.BeaconState) {.gcsafe, raises: [Defect].}
|
||||||
|
|
||||||
func noRollback*(state: var phase0.BeaconState) =
|
func noRollback*(state: var phase0.BeaconState) =
|
||||||
trace "Skipping rollback of broken state"
|
trace "Skipping rollback of broken phase 0 state"
|
||||||
|
|
||||||
|
func noRollback*(state: var altair.BeaconState) =
|
||||||
|
trace "Skipping rollback of broken Altair state"
|
||||||
|
|
||||||
type
|
type
|
||||||
RollbackHashedProc* = proc(state: var phase0.HashedBeaconState) {.gcsafe, raises: [Defect].}
|
RollbackHashedProc* = proc(state: var phase0.HashedBeaconState) {.gcsafe, raises: [Defect].}
|
||||||
|
@ -170,7 +174,7 @@ func noRollback*(state: var phase0.HashedBeaconState) =
|
||||||
func noRollback*(state: var altair.HashedBeaconState) =
|
func noRollback*(state: var altair.HashedBeaconState) =
|
||||||
trace "Skipping rollback of broken Altair state"
|
trace "Skipping rollback of broken Altair state"
|
||||||
|
|
||||||
proc maybeUpgradeStateToAltair(
|
proc maybeUpgradeStateToAltair*(
|
||||||
state: var ForkedHashedBeaconState, altairForkSlot: Slot) =
|
state: var ForkedHashedBeaconState, altairForkSlot: Slot) =
|
||||||
# Both process_slots() and state_transition_block() call this, so only run it
|
# Both process_slots() and state_transition_block() call this, so only run it
|
||||||
# once by checking for existing fork.
|
# once by checking for existing fork.
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
# beacon_chain
|
# beacon_chain
|
||||||
# Copyright (c) 2018-2020 Status Research & Development GmbH
|
# Copyright (c) 2018-2021 Status Research & Development GmbH
|
||||||
# Licensed and distributed under either of
|
# Licensed and distributed under either of
|
||||||
# * MIT license (license terms in the root directory or at https://opensource.org/licenses/MIT).
|
# * 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).
|
# * Apache v2 license (license terms in the root directory or at https://www.apache.org/licenses/LICENSE-2.0).
|
||||||
|
@ -13,8 +13,8 @@ import
|
||||||
math,
|
math,
|
||||||
|
|
||||||
# Specs
|
# Specs
|
||||||
../../beacon_chain/spec/[datatypes, crypto, digest,
|
../../beacon_chain/spec/[crypto, digest, keystore, signatures, presets],
|
||||||
keystore, signatures, presets],
|
../../beacon_chain/spec/datatypes/base,
|
||||||
|
|
||||||
# Internals
|
# Internals
|
||||||
../../beacon_chain/extras,
|
../../beacon_chain/extras,
|
||||||
|
@ -103,8 +103,8 @@ proc mockGenesisBalancedDeposits*(
|
||||||
mockGenesisDepositsImpl(result, validatorCount,amount,flags):
|
mockGenesisDepositsImpl(result, validatorCount,amount,flags):
|
||||||
discard
|
discard
|
||||||
|
|
||||||
proc mockUpdateStateForNewDeposit*(
|
proc mockUpdateStateForNewDeposit*[T](
|
||||||
state: var BeaconState,
|
state: var T,
|
||||||
validator_index: uint64,
|
validator_index: uint64,
|
||||||
amount: uint64,
|
amount: uint64,
|
||||||
# withdrawal_credentials: Eth2Digest
|
# withdrawal_credentials: Eth2Digest
|
||||||
|
|
|
@ -12,8 +12,9 @@ import
|
||||||
unittest2,
|
unittest2,
|
||||||
../beacon_chain/[beacon_chain_db, extras, interop, ssz],
|
../beacon_chain/[beacon_chain_db, extras, interop, ssz],
|
||||||
../beacon_chain/spec/[
|
../beacon_chain/spec/[
|
||||||
beaconstate, crypto, datatypes, digest, forkedbeaconstate_helpers, presets,
|
beaconstate, crypto, digest, forkedbeaconstate_helpers, presets,
|
||||||
state_transition],
|
state_transition],
|
||||||
|
../beacon_chain/spec/datatypes/[phase0, altair],
|
||||||
../beacon_chain/consensus_object_pools/blockchain_dag,
|
../beacon_chain/consensus_object_pools/blockchain_dag,
|
||||||
eth/db/kvstore,
|
eth/db/kvstore,
|
||||||
# test utilies
|
# test utilies
|
||||||
|
@ -22,14 +23,30 @@ import
|
||||||
when isMainModule:
|
when isMainModule:
|
||||||
import chronicles # or some random compile error happens...
|
import chronicles # or some random compile error happens...
|
||||||
|
|
||||||
proc getStateRef(db: BeaconChainDB, root: Eth2Digest): NilableBeaconStateRef =
|
proc getPhase0StateRef(db: BeaconChainDB, root: Eth2Digest):
|
||||||
|
phase0.NilableBeaconStateRef =
|
||||||
# load beaconstate the way the block pool does it - into an existing instance
|
# load beaconstate the way the block pool does it - into an existing instance
|
||||||
let res = BeaconStateRef()
|
let res = (phase0.BeaconStateRef)()
|
||||||
if db.getState(root, res[], noRollback):
|
if db.getState(root, res[], noRollback):
|
||||||
return res
|
return res
|
||||||
|
|
||||||
func withDigest(blck: TrustedBeaconBlock): TrustedSignedBeaconBlock =
|
proc getAltairStateRef(db: BeaconChainDB, root: Eth2Digest):
|
||||||
TrustedSignedBeaconBlock(
|
altair.NilableBeaconStateRef =
|
||||||
|
# load beaconstate the way the block pool does it - into an existing instance
|
||||||
|
let res = (altair.BeaconStateRef)()
|
||||||
|
if db.getAltairState(root, res[], noRollback):
|
||||||
|
return res
|
||||||
|
|
||||||
|
func withDigest(blck: phase0.TrustedBeaconBlock):
|
||||||
|
phase0.TrustedSignedBeaconBlock =
|
||||||
|
phase0.TrustedSignedBeaconBlock(
|
||||||
|
message: blck,
|
||||||
|
root: hash_tree_root(blck)
|
||||||
|
)
|
||||||
|
|
||||||
|
func withDigest(blck: altair.TrustedBeaconBlock):
|
||||||
|
altair.TrustedSignedBeaconBlock =
|
||||||
|
altair.TrustedSignedBeaconBlock(
|
||||||
message: blck,
|
message: blck,
|
||||||
root: hash_tree_root(blck)
|
root: hash_tree_root(blck)
|
||||||
)
|
)
|
||||||
|
@ -39,15 +56,15 @@ suite "Beacon chain DB" & preset():
|
||||||
var
|
var
|
||||||
db = BeaconChainDB.new(defaultRuntimePreset, "", inMemory = true)
|
db = BeaconChainDB.new(defaultRuntimePreset, "", inMemory = true)
|
||||||
check:
|
check:
|
||||||
db.getStateRef(Eth2Digest()).isNil
|
db.getPhase0StateRef(Eth2Digest()).isNil
|
||||||
db.getBlock(Eth2Digest()).isNone
|
db.getBlock(Eth2Digest()).isNone
|
||||||
|
|
||||||
test "sanity check blocks" & preset():
|
test "sanity check phase 0 blocks" & preset():
|
||||||
var
|
var
|
||||||
db = BeaconChainDB.new(defaultRuntimePreset, "", inMemory = true)
|
db = BeaconChainDB.new(defaultRuntimePreset, "", inMemory = true)
|
||||||
|
|
||||||
let
|
let
|
||||||
signedBlock = withDigest(TrustedBeaconBlock())
|
signedBlock = withDigest((phase0.TrustedBeaconBlock)())
|
||||||
root = hash_tree_root(signedBlock.message)
|
root = hash_tree_root(signedBlock.message)
|
||||||
|
|
||||||
db.putBlock(signedBlock)
|
db.putBlock(signedBlock)
|
||||||
|
@ -56,6 +73,11 @@ suite "Beacon chain DB" & preset():
|
||||||
db.containsBlock(root)
|
db.containsBlock(root)
|
||||||
db.getBlock(root).get() == signedBlock
|
db.getBlock(root).get() == signedBlock
|
||||||
|
|
||||||
|
db.delBlock(root)
|
||||||
|
check:
|
||||||
|
not db.containsBlock(root)
|
||||||
|
db.getBlock(root).isErr()
|
||||||
|
|
||||||
db.putStateRoot(root, signedBlock.message.slot, root)
|
db.putStateRoot(root, signedBlock.message.slot, root)
|
||||||
var root2 = root
|
var root2 = root
|
||||||
root2.data[0] = root.data[0] + 1
|
root2.data[0] = root.data[0] + 1
|
||||||
|
@ -67,7 +89,37 @@ suite "Beacon chain DB" & preset():
|
||||||
|
|
||||||
db.close()
|
db.close()
|
||||||
|
|
||||||
test "sanity check states" & preset():
|
test "sanity check Altair blocks" & preset():
|
||||||
|
var
|
||||||
|
db = BeaconChainDB.new(defaultRuntimePreset, "", inMemory = true)
|
||||||
|
|
||||||
|
let
|
||||||
|
signedBlock = withDigest((altair.TrustedBeaconBlock)())
|
||||||
|
root = hash_tree_root(signedBlock.message)
|
||||||
|
|
||||||
|
db.putBlock(signedBlock)
|
||||||
|
|
||||||
|
check:
|
||||||
|
db.containsBlock(root)
|
||||||
|
db.getAltairBlock(root).get() == signedBlock
|
||||||
|
|
||||||
|
db.delBlock(root)
|
||||||
|
check:
|
||||||
|
not db.containsBlock(root)
|
||||||
|
db.getAltairBlock(root).isErr()
|
||||||
|
|
||||||
|
db.putStateRoot(root, signedBlock.message.slot, root)
|
||||||
|
var root2 = root
|
||||||
|
root2.data[0] = root.data[0] + 1
|
||||||
|
db.putStateRoot(root, signedBlock.message.slot + 1, root2)
|
||||||
|
|
||||||
|
check:
|
||||||
|
db.getStateRoot(root, signedBlock.message.slot).get() == root
|
||||||
|
db.getStateRoot(root, signedBlock.message.slot + 1).get() == root2
|
||||||
|
|
||||||
|
db.close()
|
||||||
|
|
||||||
|
test "sanity check phase 0 states" & preset():
|
||||||
var
|
var
|
||||||
db = makeTestDB(SLOTS_PER_EPOCH)
|
db = makeTestDB(SLOTS_PER_EPOCH)
|
||||||
dag = init(ChainDAGRef, defaultRuntimePreset, db)
|
dag = init(ChainDAGRef, defaultRuntimePreset, db)
|
||||||
|
@ -83,19 +135,46 @@ suite "Beacon chain DB" & preset():
|
||||||
|
|
||||||
check:
|
check:
|
||||||
db.containsState(root)
|
db.containsState(root)
|
||||||
hash_tree_root(db.getStateRef(root)[]) == root
|
hash_tree_root(db.getPhase0StateRef(root)[]) == root
|
||||||
|
|
||||||
db.delState(root)
|
db.delState(root)
|
||||||
check: not db.containsState(root)
|
check:
|
||||||
|
not db.containsState(root)
|
||||||
|
db.getPhase0StateRef(root).isNil
|
||||||
|
|
||||||
db.close()
|
db.close()
|
||||||
|
|
||||||
test "sanity check states, reusing buffers" & preset():
|
test "sanity check Altair states" & preset():
|
||||||
|
var
|
||||||
|
db = makeTestDB(SLOTS_PER_EPOCH)
|
||||||
|
dag = init(ChainDAGRef, defaultRuntimePreset, db)
|
||||||
|
testStates = getTestStates(dag.headState.data, true)
|
||||||
|
|
||||||
|
# Ensure transitions beyond just adding validators and increasing slots
|
||||||
|
sort(testStates) do (x, y: ref ForkedHashedBeaconState) -> int:
|
||||||
|
cmp($getStateRoot(x[]), $getStateRoot(y[]))
|
||||||
|
|
||||||
|
for state in testStates:
|
||||||
|
db.putState(state[].hbsAltair.data)
|
||||||
|
let root = hash_tree_root(state[])
|
||||||
|
|
||||||
|
check:
|
||||||
|
db.containsState(root)
|
||||||
|
hash_tree_root(db.getAltairStateRef(root)[]) == root
|
||||||
|
|
||||||
|
db.delState(root)
|
||||||
|
check:
|
||||||
|
not db.containsState(root)
|
||||||
|
db.getAltairStateRef(root).isNil
|
||||||
|
|
||||||
|
db.close()
|
||||||
|
|
||||||
|
test "sanity check phase 0 states, reusing buffers" & preset():
|
||||||
var
|
var
|
||||||
db = makeTestDB(SLOTS_PER_EPOCH)
|
db = makeTestDB(SLOTS_PER_EPOCH)
|
||||||
dag = init(ChainDAGRef, defaultRuntimePreset, db)
|
dag = init(ChainDAGRef, defaultRuntimePreset, db)
|
||||||
|
|
||||||
let stateBuffer = BeaconStateRef()
|
let stateBuffer = (phase0.BeaconStateRef)()
|
||||||
var testStates = getTestStates(dag.headState.data)
|
var testStates = getTestStates(dag.headState.data)
|
||||||
|
|
||||||
# Ensure transitions beyond just adding validators and increasing slots
|
# Ensure transitions beyond just adding validators and increasing slots
|
||||||
|
@ -112,7 +191,37 @@ suite "Beacon chain DB" & preset():
|
||||||
hash_tree_root(stateBuffer[]) == root
|
hash_tree_root(stateBuffer[]) == root
|
||||||
|
|
||||||
db.delState(root)
|
db.delState(root)
|
||||||
check: not db.containsState(root)
|
check:
|
||||||
|
not db.containsState(root)
|
||||||
|
not db.getState(root, stateBuffer[], noRollback)
|
||||||
|
|
||||||
|
db.close()
|
||||||
|
|
||||||
|
test "sanity check Altair states, reusing buffers" & preset():
|
||||||
|
var
|
||||||
|
db = makeTestDB(SLOTS_PER_EPOCH)
|
||||||
|
dag = init(ChainDAGRef, defaultRuntimePreset, db)
|
||||||
|
|
||||||
|
let stateBuffer = (altair.BeaconStateRef)()
|
||||||
|
var testStates = getTestStates(dag.headState.data, true)
|
||||||
|
|
||||||
|
# Ensure transitions beyond just adding validators and increasing slots
|
||||||
|
sort(testStates) do (x, y: ref ForkedHashedBeaconState) -> int:
|
||||||
|
cmp($getStateRoot(x[]), $getStateRoot(y[]))
|
||||||
|
|
||||||
|
for state in testStates:
|
||||||
|
db.putState(state[].hbsAltair.data)
|
||||||
|
let root = hash_tree_root(state[])
|
||||||
|
|
||||||
|
check:
|
||||||
|
db.getAltairState(root, stateBuffer[], noRollback)
|
||||||
|
db.containsState(root)
|
||||||
|
hash_tree_root(stateBuffer[]) == root
|
||||||
|
|
||||||
|
db.delState(root)
|
||||||
|
check:
|
||||||
|
not db.containsState(root)
|
||||||
|
not db.getAltairState(root, stateBuffer[], noRollback)
|
||||||
|
|
||||||
db.close()
|
db.close()
|
||||||
|
|
||||||
|
@ -122,11 +231,11 @@ suite "Beacon chain DB" & preset():
|
||||||
|
|
||||||
let
|
let
|
||||||
a0 = withDigest(
|
a0 = withDigest(
|
||||||
TrustedBeaconBlock(slot: GENESIS_SLOT + 0))
|
(phase0.TrustedBeaconBlock)(slot: GENESIS_SLOT + 0))
|
||||||
a1 = withDigest(
|
a1 = withDigest(
|
||||||
TrustedBeaconBlock(slot: GENESIS_SLOT + 1, parent_root: a0.root))
|
(phase0.TrustedBeaconBlock)(slot: GENESIS_SLOT + 1, parent_root: a0.root))
|
||||||
a2 = withDigest(
|
a2 = withDigest(
|
||||||
TrustedBeaconBlock(slot: GENESIS_SLOT + 2, parent_root: a1.root))
|
(phase0.TrustedBeaconBlock)(slot: GENESIS_SLOT + 2, parent_root: a1.root))
|
||||||
|
|
||||||
doAssert toSeq(db.getAncestors(a0.root)) == []
|
doAssert toSeq(db.getAncestors(a0.root)) == []
|
||||||
doAssert toSeq(db.getAncestors(a2.root)) == []
|
doAssert toSeq(db.getAncestors(a2.root)) == []
|
||||||
|
@ -175,7 +284,7 @@ suite "Beacon chain DB" & preset():
|
||||||
db.putState(state[])
|
db.putState(state[])
|
||||||
|
|
||||||
check db.containsState(root)
|
check db.containsState(root)
|
||||||
let state2 = db.getStateRef(root)
|
let state2 = db.getPhase0StateRef(root)
|
||||||
db.delState(root)
|
db.delState(root)
|
||||||
check not db.containsState(root)
|
check not db.containsState(root)
|
||||||
db.close()
|
db.close()
|
||||||
|
|
|
@ -16,7 +16,7 @@ import
|
||||||
beaconstate, crypto, datatypes, forkedbeaconstate_helpers, helpers,
|
beaconstate, crypto, datatypes, forkedbeaconstate_helpers, helpers,
|
||||||
presets, state_transition]
|
presets, state_transition]
|
||||||
|
|
||||||
proc valid_deposit(state: var BeaconState) =
|
proc valid_deposit[T](state: var T) =
|
||||||
const deposit_amount = MAX_EFFECTIVE_BALANCE
|
const deposit_amount = MAX_EFFECTIVE_BALANCE
|
||||||
let validator_index = state.validators.len
|
let validator_index = state.validators.len
|
||||||
let deposit = mockUpdateStateForNewDeposit(
|
let deposit = mockUpdateStateForNewDeposit(
|
||||||
|
@ -41,7 +41,8 @@ proc valid_deposit(state: var BeaconState) =
|
||||||
EFFECTIVE_BALANCE_INCREMENT
|
EFFECTIVE_BALANCE_INCREMENT
|
||||||
)
|
)
|
||||||
|
|
||||||
proc getTestStates*(initialState: ForkedHashedBeaconState):
|
proc getTestStates*(
|
||||||
|
initialState: ForkedHashedBeaconState, useAltair: bool = false):
|
||||||
seq[ref ForkedHashedBeaconState] =
|
seq[ref ForkedHashedBeaconState] =
|
||||||
# Randomly generated slot numbers, with a jump to around
|
# Randomly generated slot numbers, with a jump to around
|
||||||
# SLOTS_PER_HISTORICAL_ROOT to force wraparound of those
|
# SLOTS_PER_HISTORICAL_ROOT to force wraparound of those
|
||||||
|
@ -68,7 +69,16 @@ proc getTestStates*(initialState: ForkedHashedBeaconState):
|
||||||
if getStateField(tmpState[], slot) < slot:
|
if getStateField(tmpState[], slot) < slot:
|
||||||
doAssert process_slots(
|
doAssert process_slots(
|
||||||
tmpState[], slot, cache, rewards, {}, FAR_FUTURE_SLOT)
|
tmpState[], slot, cache, rewards, {}, FAR_FUTURE_SLOT)
|
||||||
|
|
||||||
|
if useAltair and epoch == 1:
|
||||||
|
maybeUpgradeStateToAltair(tmpState[], slot)
|
||||||
|
|
||||||
if i mod 3 == 0:
|
if i mod 3 == 0:
|
||||||
valid_deposit(tmpState.hbsPhase0.data)
|
if tmpState[].beaconStateFork == forkPhase0:
|
||||||
|
valid_deposit(tmpState[].hbsPhase0.data)
|
||||||
|
else:
|
||||||
|
valid_deposit(tmpState[].hbsAltair.data)
|
||||||
doAssert getStateField(tmpState[], slot) == slot
|
doAssert getStateField(tmpState[], slot) == slot
|
||||||
result.add assignClone(tmpState[])
|
|
||||||
|
if useAltair == (tmpState[].beaconStateFork == forkAltair):
|
||||||
|
result.add assignClone(tmpState[])
|
||||||
|
|
Loading…
Reference in New Issue