add blockchain_dag altair database reading; add rollback tests (#2683)

* add blockchain_dag altair database reading; add rollback tests; fix some unnecessary type conversions

* remove debugging scaffolding

* proposeSignedBlock() will need to be async for merge; introduce altair types to VC
This commit is contained in:
tersec 2021-06-29 15:09:29 +00:00 committed by GitHub
parent cc8f7c26a0
commit 7577f8c2ef
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 102 additions and 38 deletions

View File

@ -82,16 +82,18 @@ 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 Altair and cross-fork getState rollback [Preset: mainnet] OK
+ sanity check Altair blocks [Preset: mainnet] OK + sanity check Altair blocks [Preset: mainnet] OK
+ sanity check Altair states [Preset: mainnet] OK + sanity check Altair states [Preset: mainnet] OK
+ sanity check Altair states, reusing buffers [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 blocks [Preset: mainnet] OK
+ sanity check phase 0 getState rollback [Preset: mainnet] OK
+ sanity check phase 0 states [Preset: mainnet] OK + sanity check phase 0 states [Preset: mainnet] OK
+ sanity check phase 0 states, reusing buffers [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
``` ```
OK: 10/10 Fail: 0/10 Skip: 0/10 OK: 12/12 Fail: 0/12 Skip: 0/12
## 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
@ -311,4 +313,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: 177/185 Fail: 0/185 Skip: 8/185 OK: 179/187 Fail: 0/187 Skip: 8/187

View File

@ -344,7 +344,7 @@ proc new*(T: type BeaconChainDB,
altair_blocks: altair_blocks, altair_blocks: altair_blocks,
stateRoots: stateRoots, stateRoots: stateRoots,
statesNoVal: statesNoVal, statesNoVal: statesNoVal,
altairStatesNoVal: statesNoVal, altairStatesNoVal: altairStatesNoVal,
stateDiffs: stateDiffs, stateDiffs: stateDiffs,
summaries: summaries, summaries: summaries,
) )
@ -514,6 +514,13 @@ proc putState*(
db: BeaconChainDB, value: phase0.BeaconState | altair.BeaconState) = db: BeaconChainDB, value: phase0.BeaconState | altair.BeaconState) =
db.putState(hash_tree_root(value), value) db.putState(hash_tree_root(value), value)
# For testing rollback
proc putCorruptPhase0State*(db: BeaconChainDB, key: Eth2Digest) =
db.statesNoVal.putSnappySSZ(key.data, Validator())
proc putCorruptAltairState*(db: BeaconChainDB, key: Eth2Digest) =
db.altairStatesNoVal.putSnappySSZ(key.data, Validator())
func stateRootKey(root: Eth2Digest, slot: Slot): array[40, byte] = func stateRootKey(root: Eth2Digest, slot: Slot): array[40, byte] =
var ret: array[40, byte] var ret: array[40, byte]
# big endian to get a naturally ascending order on slots in sorted indices # big endian to get a naturally ascending order on slots in sorted indices
@ -626,13 +633,13 @@ proc getStateOnlyMutableValidators(
of GetResult.notFound: of GetResult.notFound:
false false
of GetResult.corrupted: of GetResult.corrupted:
rollback(output) rollback()
false false
proc getAltairStateOnlyMutableValidators( proc getAltairStateOnlyMutableValidators(
immutableValidators: openArray[ImmutableValidatorData2], immutableValidators: openArray[ImmutableValidatorData2],
store: KvStoreRef, key: openArray[byte], output: var altair.BeaconState, store: KvStoreRef, key: openArray[byte], output: var altair.BeaconState,
rollback: AltairRollbackProc): bool = rollback: RollbackProc): bool =
## Load state into `output` - BeaconState is large so we want to avoid ## Load state into `output` - BeaconState is large so we want to avoid
## re-allocating it if possible ## re-allocating it if possible
## Return `true` iff the entry was found in the database and `output` was ## Return `true` iff the entry was found in the database and `output` was
@ -668,7 +675,7 @@ proc getAltairStateOnlyMutableValidators(
of GetResult.notFound: of GetResult.notFound:
false false
of GetResult.corrupted: of GetResult.corrupted:
rollback(output) rollback()
false false
proc getState( proc getState(
@ -697,7 +704,7 @@ proc getState(
of GetResult.notFound: of GetResult.notFound:
false false
of GetResult.corrupted: of GetResult.corrupted:
rollback(output) rollback()
false false
proc getState*( proc getState*(
@ -721,7 +728,7 @@ proc getState*(
proc getAltairState*( proc getAltairState*(
db: BeaconChainDB, key: Eth2Digest, output: var altair.BeaconState, db: BeaconChainDB, key: Eth2Digest, output: var altair.BeaconState,
rollback: AltairRollbackProc): bool = rollback: RollbackProc): bool =
## Load state into `output` - BeaconState is large so we want to avoid ## Load state into `output` - BeaconState is large so we want to avoid
## re-allocating it if possible ## re-allocating it if possible
## Return `true` iff the entry was found in the database and `output` was ## Return `true` iff the entry was found in the database and `output` was

View File

@ -510,11 +510,23 @@ proc getState(
else: else:
unsafeAddr dag.headState unsafeAddr dag.headState
func restore(v: var phase0.BeaconState) = let v = addr state.data
assign(v, restoreAddr[].data.hbsPhase0.data)
if not dag.db.getState(stateRoot, state.data.hbsPhase0.data, restore): func restore() =
return false assign(v[], restoreAddr[].data)
if blck.slot < dag.altairTransitionSlot:
if state.data.beaconStateFork != forkPhase0:
state.data = (ref ForkedHashedBeaconState)(beaconStateFork: forkPhase0)[]
if not dag.db.getState(stateRoot, state.data.hbsPhase0.data, restore):
return false
else:
if state.data.beaconStateFork != forkAltair:
state.data = (ref ForkedHashedBeaconState)(beaconStateFork: forkAltair)[]
if not dag.db.getAltairState(stateRoot, state.data.hbsAltair.data, restore):
return false
state.blck = blck state.blck = blck
setStateRoot(state.data, stateRoot) setStateRoot(state.data, stateRoot)

View File

@ -157,7 +157,7 @@ proc process_attestation_queue(self: var ForkChoice) =
if it.slot < self.checkpoints.time: if it.slot < self.checkpoints.time:
for validator_index in it.attesting_indices: for validator_index in it.attesting_indices:
self.backend.process_attestation( self.backend.process_attestation(
validator_index.ValidatorIndex, it.block_root, it.slot.epoch()) validator_index, it.block_root, it.slot.epoch())
false false
else: else:
true true

View File

@ -18,7 +18,8 @@ import
json_serialization/std/[options, net], json_serialization/std/[options, net],
# Local modules # Local modules
./spec/[datatypes, digest, crypto, helpers, network, signatures], ./spec/datatypes/[phase0, altair],
./spec/[digest, crypto, helpers, network, signatures],
./spec/eth2_apis/beacon_rpc_client, ./spec/eth2_apis/beacon_rpc_client,
./sync/sync_manager, ./sync/sync_manager,
"."/[conf, beacon_clock, version], "."/[conf, beacon_clock, version],
@ -140,7 +141,7 @@ proc onSlotStart(vc: ValidatorClient, lastSlot, scheduledSlot: Slot) {.gcsafe, a
let validator = vc.attachedValidators.validators[public_key] let validator = vc.attachedValidators.validators[public_key]
let randao_reveal = await validator.genRandaoReveal( let randao_reveal = await validator.genRandaoReveal(
vc.fork, vc.beaconGenesis.genesis_validators_root, slot) vc.fork, vc.beaconGenesis.genesis_validators_root, slot)
var newBlock = SignedBeaconBlock( var newBlock = phase0.SignedBeaconBlock(
message: await vc.client.get_v1_validator_block(slot, vc.graffitiBytes, randao_reveal) message: await vc.client.get_v1_validator_block(slot, vc.graffitiBytes, randao_reveal)
) )
newBlock.root = hash_tree_root(newBlock.message) newBlock.root = hash_tree_root(newBlock.message)

View File

@ -409,7 +409,7 @@ proc installBeaconApiHandlers*(rpcServer: RpcServer, node: BeaconNode) {.
# It was not integrated into the beacon node's database. # It was not integrated into the beacon node's database.
return 202 return 202
else: else:
let res = proposeSignedBlock(node, head, AttachedValidator(), blck) let res = await proposeSignedBlock(node, head, AttachedValidator(), blck)
if res == head: if res == head:
node.network.broadcast(getBeaconBlocksTopic(node.forkDigest), blck) node.network.broadcast(getBeaconBlocksTopic(node.forkDigest), blck)
# The block failed validation, but was successfully broadcast anyway. # The block failed validation, but was successfully broadcast anyway.

View File

@ -502,7 +502,7 @@ proc installBeaconApiHandlers*(router: var RestRouter, node: BeaconNode) =
else: else:
let idx = cindex.get() let idx = cindex.get()
if uint64(idx) < committees_per_slot: if uint64(idx) < committees_per_slot:
res.add(getCommittee(slot, CommitteeIndex(idx))) res.add(getCommittee(slot, idx))
var res: seq[RestBeaconStatesCommittees] var res: seq[RestBeaconStatesCommittees]
let qepoch = let qepoch =
@ -629,7 +629,7 @@ proc installBeaconApiHandlers*(router: var RestRouter, node: BeaconNode) =
node.network.broadcast(getBeaconBlocksTopic(node.forkDigest), blck) node.network.broadcast(getBeaconBlocksTopic(node.forkDigest), blck)
return RestApiResponse.jsonError(Http202, BlockValidationError) return RestApiResponse.jsonError(Http202, BlockValidationError)
else: else:
let res = proposeSignedBlock(node, head, AttachedValidator(), blck) let res = await proposeSignedBlock(node, head, AttachedValidator(), blck)
if res == head: if res == head:
node.network.broadcast(getBeaconBlocksTopic(node.forkDigest), blck) node.network.broadcast(getBeaconBlocksTopic(node.forkDigest), blck)
return RestApiResponse.jsonError(Http202, BlockValidationError) return RestApiResponse.jsonError(Http202, BlockValidationError)

View File

@ -336,9 +336,9 @@ proc decodeBody*[T](t: typedesc[T],
let data = let data =
try: try:
RestJson.decode(cast[string](body.data), T) RestJson.decode(cast[string](body.data), T)
except SerializationError as exc: except SerializationError:
return err("Unable to deserialize data") return err("Unable to deserialize data")
except CatchableError as exc: except CatchableError:
return err("Unexpected deserialization error") return err("Unexpected deserialization error")
ok(data) ok(data)

View File

@ -26,7 +26,7 @@ template withStateForStateId*(stateId: string, body: untyped): untyped =
if isState(node.dag.headState): if isState(node.dag.headState):
withStateVars(node.dag.headState): withStateVars(node.dag.headState):
var cache {.inject.}: StateCache var cache {.inject, used.}: StateCache
body body
else: else:
let rpcState = assignClone(node.dag.headState) let rpcState = assignClone(node.dag.headState)

View File

@ -56,7 +56,7 @@ proc installValidatorApiHandlers*(rpcServer: RpcServer, node: BeaconNode) {.
if head.slot >= body.message.slot: if head.slot >= body.message.slot:
raise newException(CatchableError, raise newException(CatchableError,
"Proposal is for a past slot: " & $body.message.slot) "Proposal is for a past slot: " & $body.message.slot)
if head == proposeSignedBlock(node, head, AttachedValidator(), body): if head == await proposeSignedBlock(node, head, AttachedValidator(), body):
raise newException(CatchableError, "Could not propose block") raise newException(CatchableError, "Could not propose block")
return true return true

View File

@ -104,14 +104,10 @@ func verifyStateRoot(state: phase0.BeaconState, blck: altair.TrustedBeaconBlock)
true true
type type
RollbackProc* = proc(v: var phase0.BeaconState) {.gcsafe, raises: [Defect].} RollbackProc* = proc() {.gcsafe, raises: [Defect].}
AltairRollbackProc* = proc(v: var altair.BeaconState) {.gcsafe, raises: [Defect].}
func noRollback*(state: var phase0.BeaconState) = func noRollback*() =
trace "Skipping rollback of broken phase 0 state" trace "Skipping rollback of broken 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].}

View File

@ -874,7 +874,7 @@ proc process_epoch*(
process_historical_roots_update(state) process_historical_roots_update(state)
process_participation_record_updates(state) process_participation_record_updates(state)
# https://github.com/ethereum/eth2.0-specs/blob/v1.1.0-alpha.6/specs/altair/beacon-chain.md#epoch-processing # https://github.com/ethereum/eth2.0-specs/blob/v1.1.0-alpha.7/specs/altair/beacon-chain.md#epoch-processing
proc process_epoch*( proc process_epoch*(
state: var altair.BeaconState, flags: UpdateFlags, cache: var StateCache, state: var altair.BeaconState, flags: UpdateFlags, cache: var StateCache,
rewards: var RewardInfo) {.nbench.} = rewards: var RewardInfo) {.nbench.} =

View File

@ -110,10 +110,10 @@ proc getAttachedValidator*(node: BeaconNode,
idx: ValidatorIndex): AttachedValidator = idx: ValidatorIndex): AttachedValidator =
if idx < state_validators.len.ValidatorIndex: if idx < state_validators.len.ValidatorIndex:
let validator = node.getAttachedValidator(state_validators[idx].pubkey) let validator = node.getAttachedValidator(state_validators[idx].pubkey)
if validator != nil and validator.index != some(idx.ValidatorIndex): if validator != nil and validator.index != some(idx):
# Update index, in case the validator was activated! # Update index, in case the validator was activated!
notice "Validator activated", pubkey = validator.pubkey, index = idx notice "Validator activated", pubkey = validator.pubkey, index = idx
validator.index = some(idx.ValidatorIndex) validator.index = some(idx)
validator validator
else: else:
warn "Validator index out of bounds", warn "Validator index out of bounds",
@ -340,7 +340,8 @@ proc makeBeaconBlockForHeadAndSlot*(node: BeaconNode,
proc proposeSignedBlock*(node: BeaconNode, proc proposeSignedBlock*(node: BeaconNode,
head: BlockRef, head: BlockRef,
validator: AttachedValidator, validator: AttachedValidator,
newBlock: SignedBeaconBlock): BlockRef = newBlock: SignedBeaconBlock):
Future[BlockRef] {.async.} =
let newBlockRef = node.dag.addRawBlock(node.quarantine, newBlock) do ( let newBlockRef = node.dag.addRawBlock(node.quarantine, newBlock) do (
blckRef: BlockRef, trustedBlock: TrustedSignedBeaconBlock, blckRef: BlockRef, trustedBlock: TrustedSignedBeaconBlock,
epochRef: EpochRef): epochRef: EpochRef):
@ -420,7 +421,7 @@ proc proposeBlock(node: BeaconNode,
newBlock.signature = await validator.signBlockProposal( newBlock.signature = await validator.signBlockProposal(
fork, genesis_validators_root, slot, newBlock.root) fork, genesis_validators_root, slot, newBlock.root)
return node.proposeSignedBlock(head, validator, newBlock) return await node.proposeSignedBlock(head, validator, newBlock)
proc handleAttestations(node: BeaconNode, head: BlockRef, slot: Slot) = proc handleAttestations(node: BeaconNode, head: BlockRef, slot: Slot) =
## Perform all attestations that the validators attached to this node should ## Perform all attestations that the validators attached to this node should

View File

@ -60,8 +60,7 @@ suite "Beacon chain DB" & preset():
db.getBlock(Eth2Digest()).isNone db.getBlock(Eth2Digest()).isNone
test "sanity check phase 0 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((phase0.TrustedBeaconBlock)()) signedBlock = withDigest((phase0.TrustedBeaconBlock)())
@ -90,8 +89,7 @@ suite "Beacon chain DB" & preset():
db.close() db.close()
test "sanity check Altair blocks" & preset(): test "sanity check Altair blocks" & preset():
var var db = BeaconChainDB.new(defaultRuntimePreset, "", inMemory = true)
db = BeaconChainDB.new(defaultRuntimePreset, "", inMemory = true)
let let
signedBlock = withDigest((altair.TrustedBeaconBlock)()) signedBlock = withDigest((altair.TrustedBeaconBlock)())
@ -225,6 +223,53 @@ suite "Beacon chain DB" & preset():
db.close() db.close()
test "sanity check phase 0 getState rollback" & preset():
var
db = makeTestDB(SLOTS_PER_EPOCH)
dag = init(ChainDAGRef, defaultRuntimePreset, db)
state = (ref ForkedHashedBeaconState)(
beaconStateFork: forkPhase0,
hbsPhase0: phase0.HashedBeaconState(data: phase0.BeaconState(
slot: 10.Slot)))
root = Eth2Digest()
db.putCorruptPhase0State(root)
let restoreAddr = addr dag.headState
func restore() =
assign(state[], restoreAddr[].data)
check:
state[].hbsPhase0.data.slot == 10.Slot
not db.getState(root, state[].hbsPhase0.data, restore)
state[].hbsPhase0.data.slot != 10.Slot
test "sanity check Altair and cross-fork getState rollback" & preset():
var
db = makeTestDB(SLOTS_PER_EPOCH)
dag = init(ChainDAGRef, defaultRuntimePreset, db)
state = (ref ForkedHashedBeaconState)(
beaconStateFork: forkAltair,
hbsAltair: altair.HashedBeaconState(data: altair.BeaconState(
slot: 10.Slot)))
root = Eth2Digest()
db.putCorruptAltairState(root)
let restoreAddr = addr dag.headState
func restore() =
assign(state[], restoreAddr[].data)
check:
state[].hbsAltair.data.slot == 10.Slot
not db.getAltairState(root, state[].hbsAltair.data, restore)
# assign() has switched the case object fork
state[].beaconStateFork == forkPhase0
state[].hbsPhase0.data.slot != 10.Slot
test "find ancestors" & preset(): test "find ancestors" & preset():
var var
db = BeaconChainDB.new(defaultRuntimePreset, "", inMemory = true) db = BeaconChainDB.new(defaultRuntimePreset, "", inMemory = true)