mirror of
https://github.com/status-im/nimbus-eth2.git
synced 2025-01-27 06:47:13 +00:00
Merge devel and resolve conflicts
This commit is contained in:
commit
3ec6a02b12
@ -9,8 +9,10 @@ AllTests-mainnet
|
||||
+ Can add and retrieve simple attestation [Preset: mainnet] OK
|
||||
+ Fork choice returns block with attestation OK
|
||||
+ Fork choice returns latest block with no attestations OK
|
||||
+ Trying to add a block twice tags the second as an error OK
|
||||
+ Trying to add a duplicate block from an old pruned epoch is tagged as an error OK
|
||||
```
|
||||
OK: 7/7 Fail: 0/7 Skip: 0/7
|
||||
OK: 9/9 Fail: 0/9 Skip: 0/9
|
||||
## Beacon chain DB [Preset: mainnet]
|
||||
```diff
|
||||
+ empty database [Preset: mainnet] OK
|
||||
@ -32,7 +34,7 @@ OK: 1/1 Fail: 0/1 Skip: 0/1
|
||||
OK: 1/1 Fail: 0/1 Skip: 0/1
|
||||
## Block pool processing [Preset: mainnet]
|
||||
```diff
|
||||
+ Can add same block twice [Preset: mainnet] OK
|
||||
+ Adding the same block twice returns a Duplicate error [Preset: mainnet] OK
|
||||
+ Reverse order block add & get [Preset: mainnet] OK
|
||||
+ Simple block add&get [Preset: mainnet] OK
|
||||
+ getRef returns nil for missing blocks OK
|
||||
@ -52,10 +54,9 @@ OK: 7/7 Fail: 0/7 Skip: 0/7
|
||||
OK: 5/5 Fail: 0/5 Skip: 0/5
|
||||
## BlockPool finalization tests [Preset: mainnet]
|
||||
```diff
|
||||
+ init with gaps [Preset: mainnet] OK
|
||||
+ prune heads on finalization [Preset: mainnet] OK
|
||||
```
|
||||
OK: 2/2 Fail: 0/2 Skip: 0/2
|
||||
OK: 1/1 Fail: 0/1 Skip: 0/1
|
||||
## BlockRef and helpers [Preset: mainnet]
|
||||
```diff
|
||||
+ getAncestorAt sanity [Preset: mainnet] OK
|
||||
@ -266,4 +267,4 @@ OK: 8/8 Fail: 0/8 Skip: 0/8
|
||||
OK: 1/1 Fail: 0/1 Skip: 0/1
|
||||
|
||||
---TOTAL---
|
||||
OK: 156/164 Fail: 0/164 Skip: 8/164
|
||||
OK: 157/165 Fail: 0/165 Skip: 8/165
|
||||
|
@ -18,7 +18,7 @@ import
|
||||
logScope:
|
||||
topics = "att_aggr"
|
||||
|
||||
# https://github.com/ethereum/eth2.0-specs/blob/v0.11.1/specs/phase0/validator.md#aggregation-selection
|
||||
# https://github.com/ethereum/eth2.0-specs/blob/v0.12.1/specs/phase0/validator.md#aggregation-selection
|
||||
func is_aggregator(state: BeaconState, slot: Slot, index: CommitteeIndex,
|
||||
slot_signature: ValidatorSig, cache: var StateCache): bool =
|
||||
let
|
||||
@ -52,7 +52,7 @@ proc aggregate_attestations*(
|
||||
if not is_aggregator(state, slot, index, slot_signature, cache):
|
||||
return none(AggregateAndProof)
|
||||
|
||||
# https://github.com/ethereum/eth2.0-specs/blob/v0.11.1/specs/phase0/validator.md#attestation-data
|
||||
# https://github.com/ethereum/eth2.0-specs/blob/v0.12.1/specs/phase0/validator.md#attestation-data
|
||||
# describes how to construct an attestation, which applies for makeAttestationData(...)
|
||||
# TODO this won't actually match anything
|
||||
let attestation_data = AttestationData(
|
||||
@ -60,7 +60,7 @@ proc aggregate_attestations*(
|
||||
index: index.uint64,
|
||||
beacon_block_root: get_block_root_at_slot(state, slot))
|
||||
|
||||
# https://github.com/ethereum/eth2.0-specs/blob/v0.11.1/specs/phase0/validator.md#construct-aggregate
|
||||
# https://github.com/ethereum/eth2.0-specs/blob/v0.12.1/specs/phase0/validator.md#construct-aggregate
|
||||
# TODO once EV goes in w/ refactoring of getAttestationsForBlock, pull out the getSlot version and use
|
||||
# it. This is incorrect.
|
||||
for attestation in getAttestationsForBlock(pool, state):
|
||||
|
@ -8,24 +8,87 @@
|
||||
{.push raises: [Defect].}
|
||||
|
||||
import
|
||||
# Standard libraries
|
||||
deques, sequtils, tables, options,
|
||||
# Status libraries
|
||||
chronicles, stew/[byteutils], json_serialization/std/sets,
|
||||
# Internal
|
||||
./spec/[beaconstate, datatypes, crypto, digest, helpers, validator],
|
||||
./extras, ./block_pool, ./block_pools/candidate_chains, ./beacon_node_types
|
||||
./extras, ./block_pool, ./block_pools/candidate_chains, ./beacon_node_types,
|
||||
./fork_choice/fork_choice
|
||||
|
||||
logScope: topics = "attpool"
|
||||
|
||||
func init*(T: type AttestationPool, blockPool: BlockPool): T =
|
||||
proc init*(T: type AttestationPool, blockPool: BlockPool): T =
|
||||
## Initialize an AttestationPool from the blockPool `headState`
|
||||
## The `finalized_root` works around the finalized_checkpoint of the genesis block
|
||||
## holding a zero_root.
|
||||
# TODO blockPool is only used when resolving orphaned attestations - it should
|
||||
# probably be removed as a dependency of AttestationPool (or some other
|
||||
# smart refactoring)
|
||||
|
||||
# TODO: Return Value Optimization
|
||||
|
||||
# TODO: In tests, on blockpool.init the finalized root
|
||||
# from the `headState` and `justifiedState` is zero
|
||||
var forkChoice = initForkChoice(
|
||||
finalized_block_slot = default(Slot), # This is unnecessary for fork choice but may help external components for example logging/debugging
|
||||
finalized_block_state_root = default(Eth2Digest), # This is unnecessary for fork choice but may help external components for example logging/debugging
|
||||
justified_epoch = blockPool.headState.data.data.current_justified_checkpoint.epoch,
|
||||
finalized_epoch = blockPool.headState.data.data.finalized_checkpoint.epoch,
|
||||
# We should use the checkpoint, but at genesis the headState finalized checkpoint is 0x0000...0000
|
||||
# finalized_root = blockPool.headState.data.data.finalized_checkpoint.root
|
||||
finalized_root = blockPool.finalizedHead.blck.root
|
||||
).get()
|
||||
|
||||
# Load all blocks since finalized head - TODO a proper test
|
||||
for blck in blockPool.dag.topoSortedSinceLastFinalization():
|
||||
if blck.root == blockPool.finalizedHead.blck.root:
|
||||
continue
|
||||
|
||||
# BlockRef
|
||||
# should ideally contain the justified_epoch and finalized_epoch
|
||||
# so that we can pass them directly to `process_block` without having to
|
||||
# redo "updateStateData"
|
||||
#
|
||||
# In any case, `updateStateData` should shortcut
|
||||
# to `getStateDataCached`
|
||||
|
||||
updateStateData(
|
||||
blockPool,
|
||||
blockPool.tmpState,
|
||||
BlockSlot(blck: blck, slot: blck.slot)
|
||||
)
|
||||
|
||||
debug "Preloading fork choice with block",
|
||||
block_root = shortlog(blck.root),
|
||||
parent_root = shortlog(blck.parent.root),
|
||||
justified_epoch = $blockPool.tmpState.data.data.current_justified_checkpoint.epoch,
|
||||
finalized_epoch = $blockPool.tmpState.data.data.finalized_checkpoint.epoch,
|
||||
slot = $blck.slot
|
||||
|
||||
let status = forkChoice.process_block(
|
||||
block_root = blck.root,
|
||||
parent_root = blck.parent.root,
|
||||
justified_epoch = blockPool.tmpState.data.data.current_justified_checkpoint.epoch,
|
||||
finalized_epoch = blockPool.tmpState.data.data.finalized_checkpoint.epoch,
|
||||
# Unused in fork choice - i.e. for logging or caching extra metadata
|
||||
slot = blck.slot,
|
||||
state_root = default(Eth2Digest)
|
||||
)
|
||||
|
||||
doAssert status.isOk(), "Error in preloading the fork choice: " & $status.error
|
||||
|
||||
info "Fork choice initialized",
|
||||
justified_epoch = $blockPool.headState.data.data.current_justified_checkpoint.epoch,
|
||||
finalized_epoch = $blockPool.headState.data.data.finalized_checkpoint.epoch,
|
||||
finalized_root = shortlog(blockPool.finalizedHead.blck.root)
|
||||
|
||||
T(
|
||||
mapSlotsToAttestations: initDeque[AttestationsSeen](),
|
||||
blockPool: blockPool,
|
||||
unresolved: initTable[Eth2Digest, UnresolvedAttestation](),
|
||||
forkChoice_v2: forkChoice
|
||||
)
|
||||
|
||||
proc combine*(tgt: var Attestation, src: Attestation, flags: UpdateFlags) =
|
||||
@ -107,13 +170,21 @@ proc slotIndex(
|
||||
func updateLatestVotes(
|
||||
pool: var AttestationPool, state: BeaconState, attestationSlot: Slot,
|
||||
participants: seq[ValidatorIndex], blck: BlockRef) =
|
||||
|
||||
# ForkChoice v2
|
||||
let target_epoch = compute_epoch_at_slot(attestationSlot)
|
||||
|
||||
for validator in participants:
|
||||
# ForkChoice v1
|
||||
let
|
||||
pubKey = state.validators[validator].pubkey
|
||||
current = pool.latestAttestations.getOrDefault(pubKey)
|
||||
if current.isNil or current.slot < attestationSlot:
|
||||
pool.latestAttestations[pubKey] = blck
|
||||
|
||||
# ForkChoice v2
|
||||
pool.forkChoice_v2.process_attestation(validator, blck.root, target_epoch)
|
||||
|
||||
func get_attesting_indices_seq(state: BeaconState,
|
||||
attestation_data: AttestationData,
|
||||
bits: CommitteeValidatorsBits,
|
||||
@ -254,7 +325,7 @@ proc addResolved(pool: var AttestationPool, blck: BlockRef, attestation: Attesta
|
||||
blockSlot = shortLog(blck.slot),
|
||||
cat = "filtering"
|
||||
|
||||
proc add*(pool: var AttestationPool, attestation: Attestation) =
|
||||
proc addAttestation*(pool: var AttestationPool, attestation: Attestation) =
|
||||
## Add a verified attestation to the fork choice context
|
||||
logScope: pcs = "atp_add_attestation"
|
||||
|
||||
@ -269,6 +340,68 @@ proc add*(pool: var AttestationPool, attestation: Attestation) =
|
||||
|
||||
pool.addResolved(blck, attestation)
|
||||
|
||||
proc addForkChoice_v2*(pool: var AttestationPool, blck: BlockRef) =
|
||||
## Add a verified block to the fork choice context
|
||||
## The current justifiedState of the block pool is used as reference
|
||||
|
||||
# TODO: add(BlockPool, blockRoot: Eth2Digest, SignedBeaconBlock): BlockRef
|
||||
# should ideally return the justified_epoch and finalized_epoch
|
||||
# so that we can pass them directly to this proc without having to
|
||||
# redo "updateStateData"
|
||||
#
|
||||
# In any case, `updateStateData` should shortcut
|
||||
# to `getStateDataCached`
|
||||
|
||||
var state: Result[void, string]
|
||||
# A stack of block to add in case recovery is needed
|
||||
var blockStack: seq[BlockSlot]
|
||||
var current = BlockSlot(blck: blck, slot: blck.slot)
|
||||
|
||||
while true: # The while loop should not be needed but it seems a block addition
|
||||
# scenario is unaccounted for
|
||||
updateStateData(
|
||||
pool.blockPool,
|
||||
pool.blockPool.tmpState,
|
||||
current
|
||||
)
|
||||
|
||||
let blockData = pool.blockPool.get(current.blck)
|
||||
state = pool.forkChoice_v2.process_block(
|
||||
slot = current.blck.slot,
|
||||
block_root = current.blck.root,
|
||||
parent_root = if not current.blck.parent.isNil: current.blck.parent.root else: default(Eth2Digest),
|
||||
state_root = default(Eth2Digest), # This is unnecessary for fork choice but may help external components
|
||||
justified_epoch = pool.blockPool.tmpState.data.data.current_justified_checkpoint.epoch,
|
||||
finalized_epoch = pool.blockPool.tmpState.data.data.finalized_checkpoint.epoch,
|
||||
)
|
||||
|
||||
# This should not happen and might lead to unresponsive networking while processing occurs
|
||||
if state.isErr:
|
||||
# TODO investigate, potential sources:
|
||||
# - Pruning
|
||||
# - Quarantine adding multiple blocks at once
|
||||
# - Own block proposal
|
||||
error "Desync between fork_choice and blockpool services, trying to recover.",
|
||||
msg = state.error,
|
||||
blck = shortlog(current.blck),
|
||||
parent = shortlog(current.blck.parent),
|
||||
finalizedHead = shortLog(pool.blockPool.finalizedHead),
|
||||
justifiedHead = shortLog(pool.blockPool.head.justified),
|
||||
head = shortLog(pool.blockPool.head.blck)
|
||||
blockStack.add(current)
|
||||
current = BlockSlot(blck: blck.parent, slot: blck.parent.slot)
|
||||
elif blockStack.len == 0:
|
||||
break
|
||||
else:
|
||||
info "Re-added missing or pruned block to fork choice",
|
||||
msg = state.error,
|
||||
blck = shortlog(current.blck),
|
||||
parent = shortlog(current.blck.parent),
|
||||
finalizedHead = shortLog(pool.blockPool.finalizedHead),
|
||||
justifiedHead = shortLog(pool.blockPool.head.justified),
|
||||
head = shortLog(pool.blockPool.head.blck)
|
||||
current = blockStack.pop()
|
||||
|
||||
proc getAttestationsForSlot*(pool: AttestationPool, newBlockSlot: Slot):
|
||||
Option[AttestationsSeen] =
|
||||
if newBlockSlot < (GENESIS_SLOT + MIN_ATTESTATION_INCLUSION_DELAY):
|
||||
@ -340,7 +473,7 @@ proc getAttestationsForBlock*(pool: AttestationPool,
|
||||
var cache = get_empty_per_epoch_cache()
|
||||
for a in attestations:
|
||||
var
|
||||
# https://github.com/ethereum/eth2.0-specs/blob/v0.11.1/specs/phase0/validator.md#construct-attestation
|
||||
# https://github.com/ethereum/eth2.0-specs/blob/v0.12.1/specs/phase0/validator.md#construct-attestation
|
||||
attestation = Attestation(
|
||||
aggregation_bits: a.validations[0].aggregation_bits,
|
||||
data: a.data,
|
||||
@ -403,7 +536,10 @@ proc resolve*(pool: var AttestationPool) =
|
||||
for a in resolved:
|
||||
pool.addResolved(a.blck, a.attestation)
|
||||
|
||||
func latestAttestation*(
|
||||
# Fork choice v1
|
||||
# ---------------------------------------------------------------
|
||||
|
||||
func latestAttestation(
|
||||
pool: AttestationPool, pubKey: ValidatorPubKey): BlockRef =
|
||||
pool.latestAttestations.getOrDefault(pubKey)
|
||||
|
||||
@ -411,7 +547,7 @@ func latestAttestation*(
|
||||
# The structure of this code differs from the spec since we use a different
|
||||
# strategy for storing states and justification points - it should nonetheless
|
||||
# be close in terms of functionality.
|
||||
func lmdGhost*(
|
||||
func lmdGhost(
|
||||
pool: AttestationPool, start_state: BeaconState,
|
||||
start_block: BlockRef): BlockRef =
|
||||
# TODO: a Fenwick Tree datastructure to keep track of cumulated votes
|
||||
@ -462,7 +598,7 @@ func lmdGhost*(
|
||||
winCount = candCount
|
||||
head = winner
|
||||
|
||||
proc selectHead*(pool: AttestationPool): BlockRef =
|
||||
proc selectHead_v1(pool: AttestationPool): BlockRef =
|
||||
let
|
||||
justifiedHead = pool.blockPool.latestJustifiedBlock()
|
||||
|
||||
@ -470,3 +606,47 @@ proc selectHead*(pool: AttestationPool): BlockRef =
|
||||
lmdGhost(pool, pool.blockPool.justifiedState.data.data, justifiedHead.blck)
|
||||
|
||||
newHead
|
||||
|
||||
# Fork choice v2
|
||||
# ---------------------------------------------------------------
|
||||
|
||||
func getAttesterBalances(state: StateData): seq[Gwei] {.noInit.}=
|
||||
## Get the balances from a state
|
||||
result.newSeq(state.data.data.validators.len) # zero-init
|
||||
|
||||
let epoch = state.data.data.slot.compute_epoch_at_slot()
|
||||
|
||||
for i in 0 ..< result.len:
|
||||
# All non-active validators have a 0 balance
|
||||
template validator: Validator = state.data.data.validators[i]
|
||||
if validator.is_active_validator(epoch):
|
||||
result[i] = validator.effective_balance
|
||||
|
||||
proc selectHead_v2(pool: var AttestationPool): BlockRef =
|
||||
let attesterBalances = pool.blockPool.justifiedState.getAttesterBalances()
|
||||
|
||||
let newHead = pool.forkChoice_v2.find_head(
|
||||
justified_epoch = pool.blockPool.justifiedState.data.data.slot.compute_epoch_at_slot(),
|
||||
justified_root = pool.blockPool.head.justified.blck.root,
|
||||
finalized_epoch = pool.blockPool.headState.data.data.finalized_checkpoint.epoch,
|
||||
justified_state_balances = attesterBalances
|
||||
).get()
|
||||
|
||||
pool.blockPool.getRef(newHead)
|
||||
|
||||
proc pruneBefore*(pool: var AttestationPool, finalizedhead: BlockSlot) =
|
||||
pool.forkChoice_v2.maybe_prune(finalizedHead.blck.root).get()
|
||||
|
||||
# Dual-Headed Fork choice
|
||||
# ---------------------------------------------------------------
|
||||
|
||||
proc selectHead*(pool: var AttestationPool): BlockRef =
|
||||
let head_v1 = pool.selectHead_v1()
|
||||
let head_v2 = pool.selectHead_v2()
|
||||
|
||||
if head_v1 != head_v2:
|
||||
error "Fork choice engines in disagreement, using block from v1.",
|
||||
v1_block = shortlog(head_v1),
|
||||
v2_block = shortlog(head_v2)
|
||||
|
||||
return head_v1
|
||||
|
@ -315,7 +315,7 @@ proc onAttestation(node: BeaconNode, attestation: Attestation) =
|
||||
attestationSlot = attestation.data.slot, headSlot = head.blck.slot
|
||||
return
|
||||
|
||||
node.attestationPool.add(attestation)
|
||||
node.attestationPool.addAttestation(attestation)
|
||||
|
||||
proc dumpBlock[T](
|
||||
node: BeaconNode, signedBlock: SignedBeaconBlock,
|
||||
@ -343,10 +343,17 @@ proc storeBlock(
|
||||
pcs = "receive_block"
|
||||
|
||||
beacon_blocks_received.inc()
|
||||
let blck = node.blockPool.add(blockRoot, signedBlock)
|
||||
|
||||
{.gcsafe.}: # TODO: fork choice and blockpool should sync via messages instead of callbacks
|
||||
let blck = node.blockPool.addRawBlock(blockRoot, signedBlock) do (validBlock: BlockRef):
|
||||
# Callback add to fork choice if valid
|
||||
node.attestationPool.addForkChoice_v2(validBlock)
|
||||
|
||||
node.dumpBlock(signedBlock, blck)
|
||||
|
||||
# There can be a scenario where we receive a block we already received.
|
||||
# However this block was before the last finalized epoch and so its parent
|
||||
# was pruned from the ForkChoice.
|
||||
if blck.isErr:
|
||||
return err(blck.error)
|
||||
|
||||
@ -357,7 +364,7 @@ proc storeBlock(
|
||||
attestation = shortLog(attestation),
|
||||
cat = "consensus" # Tag "consensus|attestation"?
|
||||
|
||||
node.attestationPool.add(attestation)
|
||||
node.attestationPool.addAttestation(attestation)
|
||||
ok()
|
||||
|
||||
proc onBeaconBlock(node: BeaconNode, signedBlock: SignedBeaconBlock) =
|
||||
@ -575,7 +582,10 @@ proc runForwardSyncLoop(node: BeaconNode) {.async.} =
|
||||
# We going to ignore `BlockError.Unviable` errors because we have working
|
||||
# backward sync and it can happens that we can perform overlapping
|
||||
# requests.
|
||||
if res.isErr and res.error != BlockError.Unviable:
|
||||
# For the same reason we ignore Duplicate blocks as if they are duplicate
|
||||
# from before the current finalized epoch, we can drop them
|
||||
# (and they may have no parents anymore in the fork choice if it was pruned)
|
||||
if res.isErr and res.error notin {BlockError.Unviable, BlockError.Old, BLockError.Duplicate}:
|
||||
return res
|
||||
discard node.updateHead()
|
||||
|
||||
|
@ -70,6 +70,9 @@ proc updateHead*(node: BeaconNode): BlockRef =
|
||||
node.blockPool.updateHead(newHead)
|
||||
beacon_head_root.set newHead.root.toGaugeValue
|
||||
|
||||
# Cleanup the fork choice v2 if we have a finalized head
|
||||
node.attestationPool.pruneBefore(node.blockPool.finalizedHead)
|
||||
|
||||
newHead
|
||||
|
||||
template findIt*(s: openarray, predicate: untyped): int64 =
|
||||
|
@ -5,7 +5,8 @@ import
|
||||
stew/endians2,
|
||||
spec/[datatypes, crypto, digest],
|
||||
block_pools/block_pools_types,
|
||||
block_pool # TODO: refactoring compat shim
|
||||
block_pool, # TODO: refactoring compat shim
|
||||
fork_choice/fork_choice_types
|
||||
|
||||
export block_pools_types
|
||||
|
||||
@ -74,6 +75,9 @@ type
|
||||
latestAttestations*: Table[ValidatorPubKey, BlockRef] ##\
|
||||
## Map that keeps track of the most recent vote of each attester - see
|
||||
## fork_choice
|
||||
forkChoice_v2*: ForkChoice ##\
|
||||
## The alternative fork choice "proto_array" that will ultimately
|
||||
## replace the original one
|
||||
|
||||
# #############################################
|
||||
#
|
||||
|
@ -26,7 +26,7 @@ type
|
||||
BlockPools* = object
|
||||
# TODO: Rename BlockPools
|
||||
quarantine: Quarantine
|
||||
dag: CandidateChains
|
||||
dag*: CandidateChains
|
||||
|
||||
BlockPool* = BlockPools
|
||||
|
||||
@ -53,9 +53,19 @@ template head*(pool: BlockPool): Head =
|
||||
template finalizedHead*(pool: BlockPool): BlockSlot =
|
||||
pool.dag.finalizedHead
|
||||
|
||||
proc add*(pool: var BlockPool, blockRoot: Eth2Digest,
|
||||
signedBlock: SignedBeaconBlock): Result[BlockRef, BlockError] {.gcsafe.} =
|
||||
add(pool.dag, pool.quarantine, blockRoot, signedBlock)
|
||||
proc addRawBlock*(pool: var BlockPool, blockRoot: Eth2Digest,
|
||||
signedBlock: SignedBeaconBlock,
|
||||
callback: proc(blck: BlockRef)
|
||||
): Result[BlockRef, BlockError] =
|
||||
## Add a raw block to the blockpool
|
||||
## Trigger "callback" on success
|
||||
## Adding a rawblock might unlock a consequent amount of blocks in quarantine
|
||||
# TODO: `addRawBlock` is accumulating significant cruft
|
||||
# and is in dire need of refactoring
|
||||
# - the ugly `inAdd` field
|
||||
# - the callback
|
||||
# - callback may be problematic as it's called in async validator duties
|
||||
result = addRawBlock(pool.dag, pool.quarantine, blockRoot, signedBlock, callback)
|
||||
|
||||
export parent # func parent*(bs: BlockSlot): BlockSlot
|
||||
export isAncestorOf # func isAncestorOf*(a, b: BlockRef): bool
|
||||
@ -70,7 +80,13 @@ proc init*(T: type BlockPools,
|
||||
updateFlags: UpdateFlags = {}): BlockPools =
|
||||
result.dag = init(CandidateChains, preset, db, updateFlags)
|
||||
|
||||
func addFlags*(pool: BlockPool, flags: UpdateFlags) =
|
||||
## Add a flag to the block processing
|
||||
## This is destined for testing to add skipBLSValidation flag
|
||||
pool.dag.updateFlags.incl flags
|
||||
|
||||
export init # func init*(T: type BlockRef, root: Eth2Digest, blck: BeaconBlock): BlockRef
|
||||
export addFlags
|
||||
|
||||
func getRef*(pool: BlockPool, root: Eth2Digest): BlockRef =
|
||||
## Retrieve a resolved block reference, if available
|
||||
|
@ -6,8 +6,11 @@
|
||||
# at your option. This file may not be copied, modified, or distributed except according to those terms.
|
||||
|
||||
import
|
||||
deques, tables,
|
||||
# Standard library
|
||||
deques, tables, hashes,
|
||||
# Status libraries
|
||||
stew/[endians2, byteutils], chronicles,
|
||||
# Internals
|
||||
../spec/[datatypes, crypto, digest],
|
||||
../beacon_chain_db, ../extras
|
||||
|
||||
@ -36,6 +39,8 @@ type
|
||||
Invalid ##\
|
||||
## Block is broken / doesn't apply cleanly - whoever sent it is fishy (or
|
||||
## we're buggy)
|
||||
Old
|
||||
Duplicate
|
||||
|
||||
Quarantine* = object
|
||||
## Keeps track of unsafe blocks coming from the network
|
||||
@ -190,3 +195,6 @@ proc shortLog*(v: BlockRef): string =
|
||||
|
||||
chronicles.formatIt BlockSlot: shortLog(it)
|
||||
chronicles.formatIt BlockRef: shortLog(it)
|
||||
|
||||
func hash*(blockRef: BlockRef): Hash =
|
||||
hash(blockRef.root)
|
||||
|
@ -8,8 +8,11 @@
|
||||
{.push raises: [Defect].}
|
||||
|
||||
import
|
||||
chronicles, options, sequtils, tables,
|
||||
# Standard libraries
|
||||
chronicles, options, sequtils, tables, sets,
|
||||
# Status libraries
|
||||
metrics,
|
||||
# Internals
|
||||
../ssz/merkleization, ../beacon_chain_db, ../extras,
|
||||
../spec/[crypto, datatypes, digest, helpers, validator, state_transition],
|
||||
block_pools_types
|
||||
@ -108,7 +111,7 @@ func getAncestorAt*(blck: BlockRef, slot: Slot): BlockRef =
|
||||
blck = blck.parent
|
||||
|
||||
func get_ancestor*(blck: BlockRef, slot: Slot): BlockRef =
|
||||
## https://github.com/ethereum/eth2.0-specs/blob/v0.11.1/specs/phase0/fork-choice.md#get_ancestor
|
||||
## https://github.com/ethereum/eth2.0-specs/blob/v0.12.1/specs/phase0/fork-choice.md#get_ancestor
|
||||
## Return ancestor at slot, or nil if queried block is older
|
||||
var blck = blck
|
||||
|
||||
@ -308,6 +311,25 @@ proc init*(T: type CandidateChains,
|
||||
|
||||
res
|
||||
|
||||
iterator topoSortedSinceLastFinalization*(dag: CandidateChains): BlockRef =
|
||||
## Iterate on the dag in topological order
|
||||
# TODO: this uses "children" for simplicity
|
||||
# but "children" should be deleted as it introduces cycles
|
||||
# that causes significant overhead at least and leaks at worst
|
||||
# for the GC.
|
||||
# This is not perf critical, it is only used to bootstrap the fork choice.
|
||||
var visited: HashSet[BlockRef]
|
||||
var stack: seq[BlockRef]
|
||||
|
||||
stack.add dag.finalizedHead.blck
|
||||
|
||||
while stack.len != 0:
|
||||
let node = stack.pop()
|
||||
if node notin visited:
|
||||
visited.incl node
|
||||
stack.add node.children
|
||||
yield node
|
||||
|
||||
proc getState(
|
||||
dag: CandidateChains, db: BeaconChainDB, stateRoot: Eth2Digest, blck: BlockRef,
|
||||
output: var StateData): bool =
|
||||
@ -902,7 +924,6 @@ proc getProposer*(
|
||||
dag.withState(dag.tmpState, head.atSlot(slot)):
|
||||
var cache = get_empty_per_epoch_cache()
|
||||
|
||||
# https://github.com/ethereum/eth2.0-specs/blob/v0.11.3/specs/phase0/validator.md#validator-assignments
|
||||
let proposerIdx = get_beacon_proposer_index(state, cache)
|
||||
if proposerIdx.isNone:
|
||||
warn "Missing proposer index",
|
||||
|
@ -34,15 +34,24 @@ func getOrResolve*(dag: CandidateChains, quarantine: var Quarantine, root: Eth2D
|
||||
if result.isNil:
|
||||
quarantine.missing[root] = MissingBlock()
|
||||
|
||||
proc add*(
|
||||
dag: var CandidateChains, quarantine: var Quarantine,
|
||||
blockRoot: Eth2Digest,
|
||||
signedBlock: SignedBeaconBlock): Result[BlockRef, BlockError] {.gcsafe.}
|
||||
proc addRawBlock*(
|
||||
dag: var CandidateChains, quarantine: var Quarantine,
|
||||
blockRoot: Eth2Digest,
|
||||
signedBlock: SignedBeaconBlock,
|
||||
callback: proc(blck: BlockRef)
|
||||
): Result[BlockRef, BlockError]
|
||||
|
||||
proc addResolvedBlock(
|
||||
dag: var CandidateChains, quarantine: var Quarantine,
|
||||
state: BeaconState, blockRoot: Eth2Digest,
|
||||
signedBlock: SignedBeaconBlock, parent: BlockRef): BlockRef =
|
||||
dag: var CandidateChains, quarantine: var Quarantine,
|
||||
state: BeaconState, blockRoot: Eth2Digest,
|
||||
signedBlock: SignedBeaconBlock, parent: BlockRef,
|
||||
callback: proc(blck: BlockRef)
|
||||
): BlockRef =
|
||||
# TODO: `addResolvedBlock` is accumulating significant cruft
|
||||
# and is in dire need of refactoring
|
||||
# - the ugly `quarantine.inAdd` field
|
||||
# - the callback
|
||||
# - callback may be problematic as it's called in async validator duties
|
||||
logScope: pcs = "block_resolution"
|
||||
doAssert state.slot == signedBlock.message.slot, "state must match block"
|
||||
|
||||
@ -86,6 +95,9 @@ proc addResolvedBlock(
|
||||
heads = dag.heads.len(),
|
||||
cat = "filtering"
|
||||
|
||||
# This MUST be added before the quarantine
|
||||
callback(blockRef)
|
||||
|
||||
# Now that we have the new block, we should see if any of the previously
|
||||
# unresolved blocks magically become resolved
|
||||
# TODO there are more efficient ways of doing this that don't risk
|
||||
@ -94,6 +106,7 @@ proc addResolvedBlock(
|
||||
# blocks being synced, there's a stack overflow as `add` gets called
|
||||
# for the whole chain of blocks. Instead we use this ugly field in `dag`
|
||||
# which could be avoided by refactoring the code
|
||||
# TODO unit test the logic, in particular interaction with fork choice block parents
|
||||
if not quarantine.inAdd:
|
||||
quarantine.inAdd = true
|
||||
defer: quarantine.inAdd = false
|
||||
@ -101,20 +114,26 @@ proc addResolvedBlock(
|
||||
while keepGoing:
|
||||
let retries = quarantine.orphans
|
||||
for k, v in retries:
|
||||
discard add(dag, quarantine, k, v)
|
||||
discard addRawBlock(dag, quarantine, k, v, callback)
|
||||
# Keep going for as long as the pending dag is shrinking
|
||||
# TODO inefficient! so what?
|
||||
keepGoing = quarantine.orphans.len < retries.len
|
||||
|
||||
blockRef
|
||||
|
||||
proc add*(
|
||||
dag: var CandidateChains, quarantine: var Quarantine,
|
||||
blockRoot: Eth2Digest,
|
||||
signedBlock: SignedBeaconBlock): Result[BlockRef, BlockError] {.gcsafe.} =
|
||||
proc addRawBlock*(
|
||||
dag: var CandidateChains, quarantine: var Quarantine,
|
||||
blockRoot: Eth2Digest,
|
||||
signedBlock: SignedBeaconBlock,
|
||||
callback: proc(blck: BlockRef)
|
||||
): Result[BlockRef, BlockError] =
|
||||
## return the block, if resolved...
|
||||
## the state parameter may be updated to include the given block, if
|
||||
## everything checks out
|
||||
# TODO reevaluate passing the state in like this
|
||||
|
||||
# TODO: `addRawBlock` is accumulating significant cruft
|
||||
# and is in dire need of refactoring
|
||||
# - the ugly `quarantine.inAdd` field
|
||||
# - the callback
|
||||
# - callback may be problematic as it's called in async validator duties
|
||||
|
||||
# TODO: to facilitate adding the block to the attestation pool
|
||||
# this should also return justified and finalized epoch corresponding
|
||||
@ -124,18 +143,22 @@ proc add*(
|
||||
|
||||
|
||||
let blck = signedBlock.message
|
||||
doAssert blockRoot == hash_tree_root(blck)
|
||||
doAssert blockRoot == hash_tree_root(blck), "blockRoot: 0x" & shortLog(blockRoot) & ", signedBlock: 0x" & shortLog(hash_tree_root(blck))
|
||||
|
||||
logScope: pcs = "block_addition"
|
||||
|
||||
# Already seen this block??
|
||||
dag.blocks.withValue(blockRoot, blockRef):
|
||||
if blockRoot in dag.blocks:
|
||||
debug "Block already exists",
|
||||
blck = shortLog(blck),
|
||||
blockRoot = shortLog(blockRoot),
|
||||
cat = "filtering"
|
||||
|
||||
return ok blockRef[]
|
||||
# There can be a scenario where we receive a block we already received.
|
||||
# However this block was before the last finalized epoch and so its parent
|
||||
# was pruned from the ForkChoice. Trying to add it again, even if the fork choice
|
||||
# supports duplicate will lead to a crash.
|
||||
return err Duplicate
|
||||
|
||||
quarantine.missing.del(blockRoot)
|
||||
|
||||
@ -220,9 +243,12 @@ proc add*(
|
||||
# the BlockRef first!
|
||||
dag.tmpState.blck = addResolvedBlock(
|
||||
dag, quarantine,
|
||||
dag.tmpState.data.data, blockRoot, signedBlock, parent)
|
||||
dag.tmpState.data.data, blockRoot, signedBlock, parent,
|
||||
callback
|
||||
)
|
||||
dag.putState(dag.tmpState.data, dag.tmpState.blck)
|
||||
|
||||
callback(dag.tmpState.blck)
|
||||
return ok dag.tmpState.blck
|
||||
|
||||
# TODO already checked hash though? main reason to keep this is because
|
||||
|
@ -189,7 +189,7 @@ const
|
||||
HandshakeTimeout = FaultOrError
|
||||
|
||||
# Spec constants
|
||||
# https://github.com/ethereum/eth2.0-specs/blob/dev/specs/networking/p2p-interface.md#eth-20-network-interaction-domains
|
||||
# https://github.com/ethereum/eth2.0-specs/blob/v0.12.1/specs/phase0/p2p-interface.md#eth2-network-interaction-domains
|
||||
MAX_CHUNK_SIZE* = 1 * 1024 * 1024 # bytes
|
||||
GOSSIP_MAX_SIZE* = 1 * 1024 * 1024 # bytes
|
||||
TTFB_TIMEOUT* = 5.seconds
|
||||
|
@ -117,7 +117,7 @@ func process_attestation*(
|
||||
vote.next_epoch = target_epoch
|
||||
|
||||
{.noSideEffect.}:
|
||||
info "Integrating vote in fork choice",
|
||||
trace "Integrating vote in fork choice",
|
||||
validator_index = $validator_index,
|
||||
new_vote = shortlog(vote)
|
||||
else:
|
||||
@ -129,7 +129,7 @@ func process_attestation*(
|
||||
ignored_block_root = shortlog(block_root),
|
||||
ignored_target_epoch = $target_epoch
|
||||
else:
|
||||
info "Ignoring double-vote for fork choice",
|
||||
trace "Ignoring double-vote for fork choice",
|
||||
validator_index = $validator_index,
|
||||
current_vote = shortlog(vote),
|
||||
ignored_block_root = shortlog(block_root),
|
||||
@ -159,7 +159,7 @@ func process_block*(
|
||||
return err("process_block_error: " & $err)
|
||||
|
||||
{.noSideEffect.}:
|
||||
info "Integrating block in fork choice",
|
||||
trace "Integrating block in fork choice",
|
||||
block_root = $shortlog(block_root),
|
||||
parent_root = $shortlog(parent_root),
|
||||
justified_epoch = $justified_epoch,
|
||||
@ -205,7 +205,7 @@ func find_head*(
|
||||
return err("find_head failed: " & $ghost_err)
|
||||
|
||||
{.noSideEffect.}:
|
||||
info "Fork choice requested",
|
||||
debug "Fork choice requested",
|
||||
justified_epoch = $justified_epoch,
|
||||
justified_root = shortlog(justified_root),
|
||||
finalized_epoch = $finalized_epoch,
|
||||
|
@ -10,11 +10,17 @@
|
||||
import
|
||||
# Standard library
|
||||
std/tables, std/options, std/typetraits,
|
||||
# Status libraries
|
||||
chronicles,
|
||||
# Internal
|
||||
../spec/[datatypes, digest],
|
||||
# Fork choice
|
||||
./fork_choice_types
|
||||
|
||||
logScope:
|
||||
topics = "fork_choice"
|
||||
cat = "fork_choice"
|
||||
|
||||
# https://github.com/ethereum/eth2.0-specs/blob/v0.11.1/specs/phase0/fork-choice.md
|
||||
# This is a port of https://github.com/sigp/lighthouse/pull/804
|
||||
# which is a port of "Proto-Array": https://github.com/protolambda/lmd-ghost
|
||||
@ -176,6 +182,14 @@ func on_block*(
|
||||
# Genesis (but Genesis might not be default(Eth2Digest))
|
||||
parent_index = none(int)
|
||||
elif parent notin self.indices:
|
||||
{.noSideEffect.}:
|
||||
error "Trying to add block with unknown parent",
|
||||
child_root = shortLog(root),
|
||||
parent_root = shortLog(parent),
|
||||
justified_epoch = $justified_epoch,
|
||||
finalized_epoch = $finalized_epoch,
|
||||
slot_optional = $slot
|
||||
|
||||
return ForkChoiceError(
|
||||
kind: fcErrUnknownParent,
|
||||
child_root: root,
|
||||
@ -297,6 +311,12 @@ func maybe_prune*(
|
||||
kind: fcErrInvalidNodeIndex,
|
||||
index: finalized_index
|
||||
)
|
||||
|
||||
{.noSideEffect.}:
|
||||
debug "Pruning blocks from fork choice",
|
||||
finalizedRoot = shortlog(finalized_root),
|
||||
pcs = "prune"
|
||||
|
||||
for node_index in 0 ..< finalized_index:
|
||||
self.indices.del(self.nodes[node_index].root)
|
||||
|
||||
|
@ -509,7 +509,7 @@ proc pubsubLogger(conf: InspectorConf, switch: Switch,
|
||||
try:
|
||||
if topic.endsWith(topicBeaconBlocksSuffix & "_snappy"):
|
||||
info "SignedBeaconBlock", msg = SSZ.decode(buffer, SignedBeaconBlock)
|
||||
elif topic.endsWith(topicMainnetAttestationsSuffix & "_snappy"):
|
||||
elif topic.endsWith("_snappy") and topic.contains("/beacon_attestation_"):
|
||||
info "Attestation", msg = SSZ.decode(buffer, Attestation)
|
||||
elif topic.endsWith(topicVoluntaryExitsSuffix & "_snappy"):
|
||||
info "SignedVoluntaryExit", msg = SSZ.decode(buffer,
|
||||
|
@ -444,7 +444,7 @@ func is_valid_indexed_attestation*(
|
||||
|
||||
true
|
||||
|
||||
# https://github.com/ethereum/eth2.0-specs/blob/v0.11.3/specs/phase0/beacon-chain.md#get_attesting_indices
|
||||
# https://github.com/ethereum/eth2.0-specs/blob/v0.12.1/specs/phase0/beacon-chain.md#get_attesting_indices
|
||||
func get_attesting_indices*(state: BeaconState,
|
||||
data: AttestationData,
|
||||
bits: CommitteeValidatorsBits,
|
||||
@ -458,7 +458,7 @@ func get_attesting_indices*(state: BeaconState,
|
||||
# This shouldn't happen if one begins with a valid BeaconState and applies
|
||||
# valid updates, but one can construct a BeaconState where it does. Do not
|
||||
# do anything here since the PendingAttestation wouldn't have made it past
|
||||
# https://github.com/ethereum/eth2.0-specs/blob/v0.11.3/specs/phase0/beacon-chain.md#attestations
|
||||
# https://github.com/ethereum/eth2.0-specs/blob/v0.12.1/specs/phase0/beacon-chain.md#attestations
|
||||
# which checks len(attestation.aggregation_bits) == len(committee) that in
|
||||
# nim-beacon-chain lives in check_attestation(...).
|
||||
# Addresses https://github.com/status-im/nim-beacon-chain/issues/922
|
||||
@ -573,7 +573,7 @@ proc isValidAttestationTargetEpoch*(
|
||||
|
||||
true
|
||||
|
||||
# https://github.com/ethereum/eth2.0-specs/blob/v0.11.2/specs/phase0/beacon-chain.md#attestations
|
||||
# https://github.com/ethereum/eth2.0-specs/blob/v0.12.1/specs/phase0/beacon-chain.md#attestations
|
||||
proc check_attestation*(
|
||||
state: BeaconState, attestation: SomeAttestation, flags: UpdateFlags,
|
||||
stateCache: var StateCache): bool =
|
||||
@ -694,9 +694,11 @@ func makeAttestationData*(
|
||||
if start_slot == state.slot: beacon_block_root
|
||||
else: get_block_root_at_slot(state, start_slot)
|
||||
|
||||
doAssert slot.compute_epoch_at_slot == current_epoch
|
||||
doAssert slot.compute_epoch_at_slot == current_epoch,
|
||||
"Computed epoch was " & $slot.compute_epoch_at_slot &
|
||||
" while the state current_epoch was " & $current_epoch
|
||||
|
||||
# https://github.com/ethereum/eth2.0-specs/blob/v0.11.2/specs/phase0/validator.md#attestation-data
|
||||
# https://github.com/ethereum/eth2.0-specs/blob/v0.12.1/specs/phase0/validator.md#attestation-data
|
||||
AttestationData(
|
||||
slot: slot,
|
||||
index: committee_index,
|
||||
|
@ -56,7 +56,7 @@ func is_active_validator*(validator: Validator, epoch: Epoch): bool =
|
||||
### Check if ``validator`` is active
|
||||
validator.activation_epoch <= epoch and epoch < validator.exit_epoch
|
||||
|
||||
# https://github.com/ethereum/eth2.0-specs/blob/v0.11.3/specs/phase0/beacon-chain.md#get_active_validator_indices
|
||||
# https://github.com/ethereum/eth2.0-specs/blob/v0.12.1/specs/phase0/beacon-chain.md#get_active_validator_indices
|
||||
func get_active_validator_indices*(state: BeaconState, epoch: Epoch):
|
||||
seq[ValidatorIndex] =
|
||||
# Return the sequence of active validator indices at ``epoch``.
|
||||
@ -85,13 +85,13 @@ func get_committee_count_at_slot*(state: BeaconState, slot: Slot): uint64 =
|
||||
# Otherwise, get_beacon_committee(...) cannot access some committees.
|
||||
doAssert (SLOTS_PER_EPOCH * MAX_COMMITTEES_PER_SLOT).uint64 >= result
|
||||
|
||||
# https://github.com/ethereum/eth2.0-specs/blob/v0.11.3/specs/phase0/beacon-chain.md#get_current_epoch
|
||||
# https://github.com/ethereum/eth2.0-specs/blob/v0.12.1/specs/phase0/beacon-chain.md#get_current_epoch
|
||||
func get_current_epoch*(state: BeaconState): Epoch =
|
||||
# Return the current epoch.
|
||||
doAssert state.slot >= GENESIS_SLOT, $state.slot
|
||||
compute_epoch_at_slot(state.slot)
|
||||
|
||||
# https://github.com/ethereum/eth2.0-specs/blob/v0.11.3/specs/phase0/beacon-chain.md#get_randao_mix
|
||||
# https://github.com/ethereum/eth2.0-specs/blob/v0.12.1/specs/phase0/beacon-chain.md#get_randao_mix
|
||||
func get_randao_mix*(state: BeaconState,
|
||||
epoch: Epoch): Eth2Digest =
|
||||
## Returns the randao mix at a recent ``epoch``.
|
||||
@ -132,7 +132,7 @@ func int_to_bytes4*(x: uint64): array[4, byte] =
|
||||
result[2] = ((x shr 16) and 0xff).byte
|
||||
result[3] = ((x shr 24) and 0xff).byte
|
||||
|
||||
# https://github.com/ethereum/eth2.0-specs/blob/v0.11.3/specs/phase0/beacon-chain.md#compute_fork_data_root
|
||||
# https://github.com/ethereum/eth2.0-specs/blob/v0.12.1/specs/phase0/beacon-chain.md#compute_fork_data_root
|
||||
func compute_fork_data_root(current_version: Version,
|
||||
genesis_validators_root: Eth2Digest): Eth2Digest =
|
||||
# Return the 32-byte fork data root for the ``current_version`` and
|
||||
@ -144,7 +144,7 @@ func compute_fork_data_root(current_version: Version,
|
||||
genesis_validators_root: genesis_validators_root
|
||||
))
|
||||
|
||||
# https://github.com/ethereum/eth2.0-specs/blob/v0.11.3/specs/phase0/beacon-chain.md#compute_fork_digest
|
||||
# https://github.com/ethereum/eth2.0-specs/blob/v0.12.1/specs/phase0/beacon-chain.md#compute_fork_digest
|
||||
func compute_fork_digest*(current_version: Version,
|
||||
genesis_validators_root: Eth2Digest): ForkDigest =
|
||||
# Return the 4-byte fork digest for the ``current_version`` and
|
||||
@ -194,7 +194,7 @@ func compute_signing_root*(ssz_object: auto, domain: Domain): Eth2Digest =
|
||||
)
|
||||
hash_tree_root(domain_wrapped_object)
|
||||
|
||||
# https://github.com/ethereum/eth2.0-specs/blob/v0.11.3/specs/phase0/beacon-chain.md#get_seed
|
||||
# https://github.com/ethereum/eth2.0-specs/blob/v0.12.1/specs/phase0/beacon-chain.md#get_seed
|
||||
func get_seed*(state: BeaconState, epoch: Epoch, domain_type: DomainType): Eth2Digest =
|
||||
# Return the seed at ``epoch``.
|
||||
|
||||
|
@ -19,9 +19,6 @@ const
|
||||
topicAttesterSlashingsSuffix* = "attester_slashing/ssz"
|
||||
topicAggregateAndProofsSuffix* = "beacon_aggregate_and_proof/ssz"
|
||||
|
||||
# https://github.com/ethereum/eth2.0-specs/blob/v0.11.3/specs/phase0/p2p-interface.md#topics-and-messages
|
||||
topicMainnetAttestationsSuffix* = "_beacon_attestation/ssz"
|
||||
|
||||
# https://github.com/ethereum/eth2.0-specs/blob/v0.12.1/specs/phase0/validator.md#misc
|
||||
ATTESTATION_SUBNET_COUNT* = 64
|
||||
|
||||
|
@ -16,7 +16,7 @@ import
|
||||
const
|
||||
# Misc
|
||||
# ---------------------------------------------------------------
|
||||
# https://github.com/ethereum/eth2.0-specs/blob/v0.11.3/configs/mainnet.yaml#L6
|
||||
# https://github.com/ethereum/eth2.0-specs/blob/v0.12.1/configs/mainnet.yaml#L6
|
||||
|
||||
MAX_COMMITTEES_PER_SLOT* {.intdefine.} = 64
|
||||
|
||||
@ -44,7 +44,7 @@ const
|
||||
|
||||
# Gwei values
|
||||
# ---------------------------------------------------------------
|
||||
# https://github.com/ethereum/eth2.0-specs/blob/v0.11.3/configs/mainnet.yaml#L58
|
||||
# https://github.com/ethereum/eth2.0-specs/blob/v0.12.1/configs/mainnet.yaml#L58
|
||||
|
||||
MIN_DEPOSIT_AMOUNT* = 2'u64^0 * 10'u64^9 ##\
|
||||
## Minimum amounth of ETH that can be deposited in one call - deposits can
|
||||
@ -61,13 +61,12 @@ const
|
||||
|
||||
# Initial values
|
||||
# ---------------------------------------------------------------
|
||||
# https://github.com/ethereum/eth2.0-specs/blob/v0.11.3/configs/mainnet.yaml#L70
|
||||
# https://github.com/ethereum/eth2.0-specs/blob/v0.12.1/configs/mainnet.yaml#L70
|
||||
BLS_WITHDRAWAL_PREFIX* = 0'u8
|
||||
|
||||
# Time parameters
|
||||
# ---------------------------------------------------------------
|
||||
# https://github.com/ethereum/eth2.0-specs/blob/v0.11.3/configs/mainnet.yaml#L77
|
||||
|
||||
# https://github.com/ethereum/eth2.0-specs/blob/v0.12.1/configs/mainnet.yaml#L77
|
||||
SECONDS_PER_SLOT* {.intdefine.} = 12'u64 # Compile with -d:SECONDS_PER_SLOT=1 for 12x faster slots
|
||||
## TODO consistent time unit across projects, similar to C++ chrono?
|
||||
|
||||
@ -92,8 +91,6 @@ const
|
||||
MIN_SEED_LOOKAHEAD* = 1 ##\
|
||||
## epochs (~6.4 minutes)
|
||||
|
||||
SHARD_COMMITTEE_PERIOD* = 256 # epochs (~27 hours)
|
||||
|
||||
MAX_SEED_LOOKAHEAD* = 4 ##\
|
||||
## epochs (~25.6 minutes)
|
||||
|
||||
@ -106,6 +103,8 @@ const
|
||||
MIN_VALIDATOR_WITHDRAWABILITY_DELAY* = 2'u64^8 ##\
|
||||
## epochs (~27 hours)
|
||||
|
||||
SHARD_COMMITTEE_PERIOD* = 256 # epochs (~27 hours)
|
||||
|
||||
MAX_EPOCHS_PER_CROSSLINK* = 2'u64^6 ##\
|
||||
## epochs (~7 hours)
|
||||
|
||||
@ -114,7 +113,7 @@ const
|
||||
|
||||
# State vector lengths
|
||||
# ---------------------------------------------------------------
|
||||
# https://github.com/ethereum/eth2.0-specs/blob/v0.11.3/configs/mainnet.yaml#L105
|
||||
# https://github.com/ethereum/eth2.0-specs/blob/v0.12.1/configs/mainnet.yaml#L105
|
||||
|
||||
EPOCHS_PER_HISTORICAL_VECTOR* = 65536 ##\
|
||||
## epochs (~0.8 years)
|
||||
@ -129,7 +128,7 @@ const
|
||||
|
||||
# Reward and penalty quotients
|
||||
# ---------------------------------------------------------------
|
||||
# https://github.com/ethereum/eth2.0-specs/blob/v0.11.3/configs/mainnet.yaml#L117
|
||||
# https://github.com/ethereum/eth2.0-specs/blob/v0.12.1/configs/mainnet.yaml#L117
|
||||
BASE_REWARD_FACTOR* = 2'u64^6
|
||||
WHISTLEBLOWER_REWARD_QUOTIENT* = 2'u64^9
|
||||
PROPOSER_REWARD_QUOTIENT* = 2'u64^3
|
||||
@ -138,7 +137,7 @@ const
|
||||
|
||||
# Max operations per block
|
||||
# ---------------------------------------------------------------
|
||||
# https://github.com/ethereum/eth2.0-specs/blob/v0.11.3/configs/mainnet.yaml#L131
|
||||
# https://github.com/ethereum/eth2.0-specs/blob/v0.12.1/configs/mainnet.yaml#L131
|
||||
MAX_PROPOSER_SLASHINGS* = 2^4
|
||||
MAX_ATTESTER_SLASHINGS* = 2^1
|
||||
MAX_ATTESTATIONS* = 2^7
|
||||
@ -147,12 +146,12 @@ const
|
||||
|
||||
# Fork choice
|
||||
# ---------------------------------------------------------------
|
||||
# https://github.com/ethereum/eth2.0-specs/blob/v0.11.3/configs/mainnet.yaml#L32
|
||||
# https://github.com/ethereum/eth2.0-specs/blob/v0.12.1/configs/mainnet.yaml#L32
|
||||
SAFE_SLOTS_TO_UPDATE_JUSTIFIED* = 8 # 96 seconds
|
||||
|
||||
# Validators
|
||||
# ---------------------------------------------------------------
|
||||
# https://github.com/ethereum/eth2.0-specs/blob/v0.11.3/configs/mainnet.yaml#L38
|
||||
# https://github.com/ethereum/eth2.0-specs/blob/v0.12.1/configs/mainnet.yaml#L38
|
||||
ETH1_FOLLOW_DISTANCE* {.intdefine.} = 1024 # blocks ~ 4 hours
|
||||
TARGET_AGGREGATORS_PER_COMMITTEE* = 16 # validators
|
||||
RANDOM_SUBNETS_PER_VALIDATOR* = 1 # subnet
|
||||
@ -160,14 +159,14 @@ const
|
||||
SECONDS_PER_ETH1_BLOCK* {.intdefine.} = 14 # (estimate from Eth1 mainnet)
|
||||
|
||||
# Phase 1: Upgrade from Phase 0
|
||||
# https://github.com/ethereum/eth2.0-specs/blob/v0.11.3/configs/mainnet.yaml#L161
|
||||
# https://github.com/ethereum/eth2.0-specs/blob/v0.12.1/configs/mainnet.yaml#L161
|
||||
PHASE_1_FORK_VERSION* = 1
|
||||
PHASE_1_GENESIS_SLOT* = 32 # [STUB]
|
||||
INITIAL_ACTIVE_SHARDS* = 64
|
||||
|
||||
# Phase 1: General
|
||||
# ---------------------------------------------------------------
|
||||
# https://github.com/ethereum/eth2.0-specs/blob/v0.11.3/configs/mainnet.yaml#L166
|
||||
# https://github.com/ethereum/eth2.0-specs/blob/v0.12.1/configs/mainnet.yaml#L168
|
||||
MAX_SHARDS* = 1024
|
||||
ONLINE_PERIOD* = 8 # epochs (~51 min)
|
||||
LIGHT_CLIENT_COMMITTEE_SIZE* = 128
|
||||
@ -183,18 +182,18 @@ const
|
||||
# Phase 1: Custody game
|
||||
# ---------------------------------------------------------------
|
||||
# Time parameters
|
||||
# https://github.com/ethereum/eth2.0-specs/blob/v0.11.3/configs/mainnet.yaml#L199
|
||||
# https://github.com/ethereum/eth2.0-specs/blob/v0.12.1/configs/mainnet.yaml#L199
|
||||
RANDAO_PENALTY_EPOCHS* = 2 # epochs (12.8 minutes)
|
||||
EPOCHS_PER_CUSTODY_PERIOD* = 2048 # epochs (~9 days)
|
||||
MAX_REVEAL_LATENESS_DECREMENT* = 128 # epochs (~14 hours)
|
||||
|
||||
# Max operations
|
||||
# https://github.com/ethereum/eth2.0-specs/blob/v0.11.3/configs/mainnet.yaml#L211
|
||||
# https://github.com/ethereum/eth2.0-specs/blob/v0.12.1/configs/mainnet.yaml#L211
|
||||
MAX_CUSTODY_KEY_REVEALS* = 256
|
||||
MAX_EARLY_DERIVED_SECRET_REVEALS* = 1
|
||||
MAX_CUSTODY_SLASHINGS* = 1
|
||||
|
||||
# Reward and penalty quotients
|
||||
# https://github.com/ethereum/eth2.0-specs/blob/v0.11.3/configs/mainnet.yaml#L217
|
||||
# https://github.com/ethereum/eth2.0-specs/blob/v0.12.1/configs/mainnet.yaml#L217
|
||||
EARLY_DERIVED_SECRET_REVEAL_SLOT_REWARD_MULTIPLE* = 2
|
||||
MINOR_REWARD_QUOTIENT* = 256
|
||||
|
@ -264,7 +264,7 @@ proc state_transition*(
|
||||
state.data, state.data.slot.compute_epoch_at_slot)
|
||||
state_transition(preset, state, signedBlock, cache, flags, rollback)
|
||||
|
||||
# https://github.com/ethereum/eth2.0-specs/blob/v0.11.1/specs/phase0/validator.md
|
||||
# https://github.com/ethereum/eth2.0-specs/blob/v0.12.1/specs/phase0/validator.md#preparing-for-a-beaconblock
|
||||
# TODO There's more to do here - the spec has helpers that deal set up some of
|
||||
# the fields in here!
|
||||
proc makeBeaconBlock*(
|
||||
|
@ -225,7 +225,7 @@ proc process_attester_slashing*(
|
||||
return err("Attester slashing: Trying to slash participant(s) twice")
|
||||
ok()
|
||||
|
||||
# https://github.com/ethereum/eth2.0-specs/blob/v0.9.4/specs/core/0_beacon-chain.md#voluntary-exits
|
||||
# https://github.com/ethereum/eth2.0-specs/blob/v0.12.1/specs/phase0/beacon-chain.md#voluntary-exits
|
||||
proc process_voluntary_exit*(
|
||||
state: var BeaconState,
|
||||
signed_voluntary_exit: SignedVoluntaryExit,
|
||||
@ -243,7 +243,7 @@ proc process_voluntary_exit*(
|
||||
if not is_active_validator(validator, get_current_epoch(state)):
|
||||
return err("Exit: validator not active")
|
||||
|
||||
# Verify the validator has not yet exited
|
||||
# Verify exit has not been initiated
|
||||
if validator.exit_epoch != FAR_FUTURE_EPOCH:
|
||||
return err("Exit: validator has exited")
|
||||
|
||||
|
@ -70,7 +70,7 @@ func get_total_active_balance*(state: BeaconState, cache: var StateCache): Gwei
|
||||
except KeyError:
|
||||
raiseAssert("get_total_active_balance(): cache always filled before usage")
|
||||
|
||||
# https://github.com/ethereum/eth2.0-specs/blob/v0.11.3/specs/phase0/beacon-chain.md#helper-functions-1
|
||||
# https://github.com/ethereum/eth2.0-specs/blob/v0.12.1/specs/phase0/beacon-chain.md#helper-functions-1
|
||||
func get_matching_source_attestations(state: BeaconState,
|
||||
epoch: Epoch): seq[PendingAttestation] =
|
||||
doAssert epoch in [get_current_epoch(state), get_previous_epoch(state)]
|
||||
@ -104,7 +104,7 @@ func get_attesting_balance(
|
||||
get_total_balance(state, get_unslashed_attesting_indices(
|
||||
state, attestations, stateCache))
|
||||
|
||||
# https://github.com/ethereum/eth2.0-specs/blob/v0.9.4/specs/core/0_beacon-chain.md#justification-and-finalization
|
||||
# https://github.com/ethereum/eth2.0-specs/blob/v0.12.1/specs/phase0/beacon-chain.md#justification-and-finalization
|
||||
proc process_justification_and_finalization*(state: var BeaconState,
|
||||
stateCache: var StateCache, updateFlags: UpdateFlags = {}) {.nbench.} =
|
||||
|
||||
@ -437,7 +437,7 @@ func process_rewards_and_penalties(
|
||||
increase_balance(state, i.ValidatorIndex, rewards[i])
|
||||
decrease_balance(state, i.ValidatorIndex, penalties[i])
|
||||
|
||||
# https://github.com/ethereum/eth2.0-specs/blob/v0.9.4/specs/core/0_beacon-chain.md#slashings
|
||||
# https://github.com/ethereum/eth2.0-specs/blob/v0.12.1/specs/phase0/beacon-chain.md#slashings
|
||||
func process_slashings*(state: var BeaconState, cache: var StateCache) {.nbench.}=
|
||||
let
|
||||
epoch = get_current_epoch(state)
|
||||
|
@ -24,7 +24,7 @@ func shortLog*(x: Checkpoint): string =
|
||||
# Helpers used in epoch transition and trace-level block transition
|
||||
# --------------------------------------------------------
|
||||
|
||||
# https://github.com/ethereum/eth2.0-specs/blob/v0.11.3/specs/phase0/beacon-chain.md#helper-functions-1
|
||||
# https://github.com/ethereum/eth2.0-specs/blob/v0.12.1/specs/phase0/beacon-chain.md#helper-functions-1
|
||||
func get_attesting_indices*(
|
||||
state: BeaconState, attestations: openarray[PendingAttestation],
|
||||
stateCache: var StateCache): HashSet[ValidatorIndex] =
|
||||
|
@ -224,7 +224,12 @@ proc proposeSignedBlock*(node: BeaconNode,
|
||||
validator: AttachedValidator,
|
||||
newBlock: SignedBeaconBlock,
|
||||
blockRoot: Eth2Digest): Future[BlockRef] {.async.} =
|
||||
let newBlockRef = node.blockPool.add(blockRoot, newBlock)
|
||||
|
||||
{.gcsafe.}: # TODO: fork choice and blockpool should sync via messages instead of callbacks
|
||||
let newBlockRef = node.blockPool.addRawBlock(blockRoot, newBlock) do (validBlock: BlockRef):
|
||||
# Callback Add to fork choice
|
||||
node.attestationPool.addForkChoice_v2(validBlock)
|
||||
|
||||
if newBlockRef.isErr:
|
||||
warn "Unable to add proposed block to block pool",
|
||||
newBlock = shortLog(newBlock.message),
|
||||
@ -324,7 +329,7 @@ proc handleAttestations(node: BeaconNode, head: BlockRef, slot: Slot) =
|
||||
# We need to run attestations exactly for the slot that we're attesting to.
|
||||
# In case blocks went missing, this means advancing past the latest block
|
||||
# using empty slots as fillers.
|
||||
# https://github.com/ethereum/eth2.0-specs/blob/v0.8.4/specs/validator/0_beacon-chain-validator.md#validator-assignments
|
||||
# https://github.com/ethereum/eth2.0-specs/blob/v0.12.1/specs/phase0/validator.md#validator-assignments
|
||||
# TODO we could cache the validator assignment since it's valid for the entire
|
||||
# epoch since it doesn't change, but that has to be weighed against
|
||||
# the complexity of handling forks correctly - instead, we use an adapted
|
||||
|
@ -24,7 +24,7 @@ func getValidator*(pool: ValidatorPool,
|
||||
validatorKey: ValidatorPubKey): AttachedValidator =
|
||||
pool.validators.getOrDefault(validatorKey)
|
||||
|
||||
# TODO: Honest validator - https://github.com/ethereum/eth2.0-specs/blob/v0.11.1/specs/phase0/validator.md
|
||||
# TODO: Honest validator - https://github.com/ethereum/eth2.0-specs/blob/v0.12.1/specs/phase0/validator.md
|
||||
proc signBlockProposal*(v: AttachedValidator, fork: Fork,
|
||||
genesis_validators_root: Eth2Digest, slot: Slot,
|
||||
blockRoot: Eth2Digest): Future[ValidatorSig] {.async.} =
|
||||
|
@ -85,7 +85,7 @@ cli do(slots = SLOTS_PER_EPOCH * 6,
|
||||
var aggregation_bits = CommitteeValidatorsBits.init(committee.len)
|
||||
aggregation_bits.setBit index_in_committee
|
||||
|
||||
attPool.add(
|
||||
attPool.addAttestation(
|
||||
Attestation(
|
||||
data: data,
|
||||
aggregation_bits: aggregation_bits,
|
||||
@ -134,9 +134,11 @@ cli do(slots = SLOTS_PER_EPOCH * 6,
|
||||
state.fork, state.genesis_validators_root, newBlock.message.slot,
|
||||
blockRoot, privKey)
|
||||
|
||||
let added = blockPool.add(blockRoot, newBlock).tryGet()
|
||||
blck() = added
|
||||
blockPool.updateHead(added)
|
||||
let added = blockPool.addRawBlock(blockRoot, newBlock) do (validBlock: BlockRef):
|
||||
# Callback Add to fork choice
|
||||
attPool.addForkChoice_v2(validBlock)
|
||||
blck() = added[]
|
||||
blockPool.updateHead(added[])
|
||||
|
||||
for i in 0..<slots:
|
||||
let
|
||||
|
@ -12,7 +12,7 @@ import
|
||||
# Specs
|
||||
../../beacon_chain/spec/[datatypes, beaconstate, presets],
|
||||
# Internals
|
||||
../../beacon_chain/[extras, interop],
|
||||
../../beacon_chain/interop,
|
||||
# Mocking procs
|
||||
./mock_deposits
|
||||
|
||||
|
@ -16,8 +16,18 @@ import
|
||||
chronicles,
|
||||
stew/byteutils,
|
||||
./testutil, ./testblockutil,
|
||||
../beacon_chain/spec/[digest, validator, state_transition, presets],
|
||||
../beacon_chain/[beacon_node_types, attestation_pool, block_pool]
|
||||
../beacon_chain/spec/[digest, validator, state_transition,
|
||||
helpers, beaconstate, presets],
|
||||
../beacon_chain/[beacon_node_types, attestation_pool, block_pool, extras],
|
||||
../beacon_chain/fork_choice/[fork_choice_types, fork_choice]
|
||||
|
||||
template wrappedTimedTest(name: string, body: untyped) =
|
||||
# `check` macro takes a copy of whatever it's checking, on the stack!
|
||||
block: # Symbol namespacing
|
||||
proc wrappedTest() =
|
||||
timedTest name:
|
||||
body
|
||||
wrappedTest()
|
||||
|
||||
suiteReport "Attestation pool processing" & preset():
|
||||
## For now just test that we can compile and execute block processing with
|
||||
@ -33,8 +43,6 @@ suiteReport "Attestation pool processing" & preset():
|
||||
check:
|
||||
process_slots(state.data, state.data.data.slot + 1)
|
||||
|
||||
# pool[].add(blockPool[].tail) # Make the tail known to fork choice
|
||||
|
||||
timedTest "Can add and retrieve simple attestation" & preset():
|
||||
var cache = get_empty_per_epoch_cache()
|
||||
let
|
||||
@ -44,7 +52,7 @@ suiteReport "Attestation pool processing" & preset():
|
||||
attestation = makeAttestation(
|
||||
state.data.data, state.blck.root, beacon_committee[0], cache)
|
||||
|
||||
pool[].add(attestation)
|
||||
pool[].addAttestation(attestation)
|
||||
|
||||
check:
|
||||
process_slots(state.data, MIN_ATTESTATION_INCLUSION_DELAY.Slot + 1)
|
||||
@ -73,8 +81,8 @@ suiteReport "Attestation pool processing" & preset():
|
||||
state.data.data, state.blck.root, bc1[0], cache)
|
||||
|
||||
# test reverse order
|
||||
pool[].add(attestation1)
|
||||
pool[].add(attestation0)
|
||||
pool[].addAttestation(attestation1)
|
||||
pool[].addAttestation(attestation0)
|
||||
|
||||
discard process_slots(state.data, MIN_ATTESTATION_INCLUSION_DELAY.Slot + 1)
|
||||
|
||||
@ -94,8 +102,8 @@ suiteReport "Attestation pool processing" & preset():
|
||||
attestation1 = makeAttestation(
|
||||
state.data.data, state.blck.root, bc0[1], cache)
|
||||
|
||||
pool[].add(attestation0)
|
||||
pool[].add(attestation1)
|
||||
pool[].addAttestation(attestation0)
|
||||
pool[].addAttestation(attestation1)
|
||||
|
||||
check:
|
||||
process_slots(state.data, MIN_ATTESTATION_INCLUSION_DELAY.Slot + 1)
|
||||
@ -119,8 +127,8 @@ suiteReport "Attestation pool processing" & preset():
|
||||
|
||||
attestation0.combine(attestation1, {})
|
||||
|
||||
pool[].add(attestation0)
|
||||
pool[].add(attestation1)
|
||||
pool[].addAttestation(attestation0)
|
||||
pool[].addAttestation(attestation1)
|
||||
|
||||
check:
|
||||
process_slots(state.data, MIN_ATTESTATION_INCLUSION_DELAY.Slot + 1)
|
||||
@ -143,8 +151,8 @@ suiteReport "Attestation pool processing" & preset():
|
||||
|
||||
attestation0.combine(attestation1, {})
|
||||
|
||||
pool[].add(attestation1)
|
||||
pool[].add(attestation0)
|
||||
pool[].addAttestation(attestation1)
|
||||
pool[].addAttestation(attestation0)
|
||||
|
||||
check:
|
||||
process_slots(state.data, MIN_ATTESTATION_INCLUSION_DELAY.Slot + 1)
|
||||
@ -159,62 +167,66 @@ suiteReport "Attestation pool processing" & preset():
|
||||
let
|
||||
b1 = addTestBlock(state.data, blockPool[].tail.root, cache)
|
||||
b1Root = hash_tree_root(b1.message)
|
||||
b1Add = blockpool[].add(b1Root, b1)[]
|
||||
b1Add = blockpool[].addRawBlock(b1Root, b1) do (validBlock: BlockRef):
|
||||
# Callback Add to fork choice
|
||||
pool[].addForkChoice_v2(validBlock)
|
||||
|
||||
# pool[].add(b1Add) - make a block known to the future fork choice
|
||||
let head = pool[].selectHead()
|
||||
|
||||
check:
|
||||
head == b1Add
|
||||
head == b1Add[]
|
||||
|
||||
let
|
||||
b2 = addTestBlock(state.data, b1Root, cache)
|
||||
b2Root = hash_tree_root(b2.message)
|
||||
b2Add = blockpool[].add(b2Root, b2)[]
|
||||
b2Add = blockpool[].addRawBlock(b2Root, b2) do (validBlock: BlockRef):
|
||||
# Callback Add to fork choice
|
||||
pool[].addForkChoice_v2(validBlock)
|
||||
|
||||
# pool[].add(b2Add) - make a block known to the future fork choice
|
||||
let head2 = pool[].selectHead()
|
||||
|
||||
check:
|
||||
head2 == b2Add
|
||||
head2 == b2Add[]
|
||||
|
||||
timedTest "Fork choice returns block with attestation":
|
||||
var cache = get_empty_per_epoch_cache()
|
||||
let
|
||||
b10 = makeTestBlock(state.data, blockPool[].tail.root, cache)
|
||||
b10Root = hash_tree_root(b10.message)
|
||||
b10Add = blockpool[].add(b10Root, b10)[]
|
||||
b10Add = blockpool[].addRawBlock(b10Root, b10) do (validBlock: BlockRef):
|
||||
# Callback Add to fork choice
|
||||
pool[].addForkChoice_v2(validBlock)
|
||||
|
||||
# pool[].add(b10Add) - make a block known to the future fork choice
|
||||
let head = pool[].selectHead()
|
||||
|
||||
check:
|
||||
head == b10Add
|
||||
head == b10Add[]
|
||||
|
||||
let
|
||||
b11 = makeTestBlock(state.data, blockPool[].tail.root, cache,
|
||||
graffiti = Eth2Digest(data: [1'u8, 0, 0, 0 ,0 ,0 ,0 ,0 ,0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0])
|
||||
)
|
||||
b11Root = hash_tree_root(b11.message)
|
||||
b11Add = blockpool[].add(b11Root, b11)[]
|
||||
b11Add = blockpool[].addRawBlock(b11Root, b11) do (validBlock: BlockRef):
|
||||
# Callback Add to fork choice
|
||||
pool[].addForkChoice_v2(validBlock)
|
||||
|
||||
bc1 = get_beacon_committee(
|
||||
state.data.data, state.data.data.slot, 1.CommitteeIndex, cache)
|
||||
attestation0 = makeAttestation(state.data.data, b10Root, bc1[0], cache)
|
||||
|
||||
# pool[].add(b11Add) - make a block known to the future fork choice
|
||||
pool[].add(attestation0)
|
||||
pool[].addAttestation(attestation0)
|
||||
|
||||
let head2 = pool[].selectHead()
|
||||
|
||||
check:
|
||||
# Single vote for b10 and no votes for b11
|
||||
head2 == b10Add
|
||||
head2 == b10Add[]
|
||||
|
||||
let
|
||||
attestation1 = makeAttestation(state.data.data, b11Root, bc1[1], cache)
|
||||
attestation2 = makeAttestation(state.data.data, b11Root, bc1[2], cache)
|
||||
pool[].add(attestation1)
|
||||
pool[].addAttestation(attestation1)
|
||||
|
||||
let head3 = pool[].selectHead()
|
||||
# Warning - the tiebreak are incorrect and guaranteed consensus fork, it should be bigger
|
||||
@ -225,12 +237,117 @@ suiteReport "Attestation pool processing" & preset():
|
||||
# all implementations favor the biggest root
|
||||
# TODO
|
||||
# currently using smaller as we have used for over a year
|
||||
head3 == smaller
|
||||
head3 == smaller[]
|
||||
|
||||
pool[].add(attestation2)
|
||||
pool[].addAttestation(attestation2)
|
||||
|
||||
let head4 = pool[].selectHead()
|
||||
|
||||
check:
|
||||
# Two votes for b11
|
||||
head4 == b11Add
|
||||
head4 == b11Add[]
|
||||
|
||||
timedTest "Trying to add a block twice tags the second as an error":
|
||||
var cache = get_empty_per_epoch_cache()
|
||||
let
|
||||
b10 = makeTestBlock(state.data, blockPool[].tail.root, cache)
|
||||
b10Root = hash_tree_root(b10.message)
|
||||
b10Add = blockpool[].addRawBlock(b10Root, b10) do (validBlock: BlockRef):
|
||||
# Callback Add to fork choice
|
||||
pool[].addForkChoice_v2(validBlock)
|
||||
|
||||
let head = pool[].selectHead()
|
||||
|
||||
check:
|
||||
head == b10Add[]
|
||||
|
||||
# -------------------------------------------------------------
|
||||
# Add back the old block to ensure we have a duplicate error
|
||||
let b10_clone = b10 # Assumes deep copy
|
||||
let b10Add_clone = blockpool[].addRawBlock(b10Root, b10_clone) do (validBlock: BlockRef):
|
||||
# Callback Add to fork choice
|
||||
pool[].addForkChoice_v2(validBlock)
|
||||
doAssert: b10Add_clone.error == Duplicate
|
||||
|
||||
wrappedTimedTest "Trying to add a duplicate block from an old pruned epoch is tagged as an error":
|
||||
var cache = get_empty_per_epoch_cache()
|
||||
|
||||
blockpool[].addFlags {skipBLSValidation}
|
||||
pool.forkChoice_v2.proto_array.prune_threshold = 1
|
||||
|
||||
let
|
||||
b10 = makeTestBlock(state.data, blockPool[].tail.root, cache)
|
||||
b10Root = hash_tree_root(b10.message)
|
||||
b10Add = blockpool[].addRawBlock(b10Root, b10) do (validBlock: BlockRef):
|
||||
# Callback Add to fork choice
|
||||
pool[].addForkChoice_v2(validBlock)
|
||||
|
||||
let head = pool[].selectHead()
|
||||
|
||||
doAssert: head == b10Add[]
|
||||
|
||||
let block_ok = state_transition(defaultRuntimePreset, state.data, b10, {}, noRollback)
|
||||
doAssert: block_ok
|
||||
|
||||
# -------------------------------------------------------------
|
||||
let b10_clone = b10 # Assumes deep copy
|
||||
|
||||
# -------------------------------------------------------------
|
||||
# Pass an epoch
|
||||
var block_root = b10Root
|
||||
|
||||
var attestations: seq[Attestation]
|
||||
|
||||
for epoch in 0 ..< 5:
|
||||
let start_slot = compute_start_slot_at_epoch(Epoch epoch)
|
||||
for slot in start_slot ..< start_slot + SLOTS_PER_EPOCH:
|
||||
|
||||
let new_block = makeTestBlock(state.data, block_root, cache, attestations = attestations)
|
||||
let block_ok = state_transition(defaultRuntimePreset, state.data, new_block, {skipBLSValidation}, noRollback)
|
||||
doAssert: block_ok
|
||||
|
||||
block_root = hash_tree_root(new_block.message)
|
||||
let blockRef = blockpool[].addRawBlock(block_root, new_block) do (validBlock: BlockRef):
|
||||
# Callback Add to fork choice
|
||||
pool[].addForkChoice_v2(validBlock)
|
||||
|
||||
let head = pool[].selectHead()
|
||||
doassert: head == blockRef[]
|
||||
blockPool[].updateHead(head)
|
||||
|
||||
attestations.setlen(0)
|
||||
for index in 0 ..< get_committee_count_at_slot(state.data.data, slot.Slot):
|
||||
let committee = get_beacon_committee(
|
||||
state.data.data, state.data.data.slot, index.CommitteeIndex, cache)
|
||||
|
||||
# Create a bitfield filled with the given count per attestation,
|
||||
# exactly on the right-most part of the committee field.
|
||||
var aggregation_bits = init(CommitteeValidatorsBits, committee.len)
|
||||
for v in 0 ..< committee.len * 2 div 3 + 1:
|
||||
aggregation_bits[v] = true
|
||||
|
||||
attestations.add Attestation(
|
||||
aggregation_bits: aggregation_bits,
|
||||
data: makeAttestationData(
|
||||
state.data.data, state.data.data.slot,
|
||||
index, blockroot
|
||||
)
|
||||
# signature: ValidatorSig()
|
||||
)
|
||||
|
||||
cache = get_empty_per_epoch_cache()
|
||||
|
||||
# -------------------------------------------------------------
|
||||
# Prune
|
||||
|
||||
echo "\nPruning all blocks before: ", shortlog(blockPool[].finalizedHead), '\n'
|
||||
doAssert: blockPool[].finalizedHead.slot != 0
|
||||
|
||||
pool[].pruneBefore(blockPool[].finalizedHead)
|
||||
doAssert: b10Root notin pool.forkChoice_v2
|
||||
|
||||
# Add back the old block to ensure we have a duplicate error
|
||||
let b10Add_clone = blockpool[].addRawBlock(b10Root, b10_clone) do (validBlock: BlockRef):
|
||||
# Callback Add to fork choice
|
||||
pool[].addForkChoice_v2(validBlock)
|
||||
doAssert: b10Add_clone.error == Duplicate
|
||||
|
@ -111,26 +111,28 @@ suiteReport "Block pool processing" & preset():
|
||||
|
||||
timedTest "Simple block add&get" & preset():
|
||||
let
|
||||
b1Add = pool.add(b1Root, b1)[]
|
||||
b1Add = pool.addRawBlock(b1Root, b1) do (validBlock: BlockRef):
|
||||
discard
|
||||
b1Get = pool.get(b1Root)
|
||||
|
||||
check:
|
||||
b1Get.isSome()
|
||||
b1Get.get().refs.root == b1Root
|
||||
b1Add.root == b1Get.get().refs.root
|
||||
b1Add[].root == b1Get.get().refs.root
|
||||
pool.heads.len == 1
|
||||
pool.heads[0].blck == b1Add
|
||||
pool.heads[0].blck == b1Add[]
|
||||
|
||||
let
|
||||
b2Add = pool.add(b2Root, b2)[]
|
||||
b2Add = pool.addRawBlock(b2Root, b2) do (validBlock: BlockRef):
|
||||
discard
|
||||
b2Get = pool.get(b2Root)
|
||||
|
||||
check:
|
||||
b2Get.isSome()
|
||||
b2Get.get().refs.root == b2Root
|
||||
b2Add.root == b2Get.get().refs.root
|
||||
b2Add[].root == b2Get.get().refs.root
|
||||
pool.heads.len == 1
|
||||
pool.heads[0].blck == b2Add
|
||||
pool.heads[0].blck == b2Add[]
|
||||
|
||||
# Skip one slot to get a gap
|
||||
check:
|
||||
@ -139,12 +141,13 @@ suiteReport "Block pool processing" & preset():
|
||||
let
|
||||
b4 = addTestBlock(stateData.data, b2Root, cache)
|
||||
b4Root = hash_tree_root(b4.message)
|
||||
b4Add = pool.add(b4Root, b4)[]
|
||||
b4Add = pool.addRawBlock(b4Root, b4) do (validBlock: BlockRef):
|
||||
discard
|
||||
|
||||
check:
|
||||
b4Add.parent == b2Add
|
||||
b4Add[].parent == b2Add[]
|
||||
|
||||
pool.updateHead(b4Add)
|
||||
pool.updateHead(b4Add[])
|
||||
|
||||
var blocks: array[3, BlockRef]
|
||||
|
||||
@ -153,16 +156,16 @@ suiteReport "Block pool processing" & preset():
|
||||
blocks[0..<1] == [pool.tail]
|
||||
|
||||
pool.getBlockRange(Slot(0), 1, blocks.toOpenArray(0, 1)) == 0
|
||||
blocks[0..<2] == [pool.tail, b1Add]
|
||||
blocks[0..<2] == [pool.tail, b1Add[]]
|
||||
|
||||
pool.getBlockRange(Slot(0), 2, blocks.toOpenArray(0, 1)) == 0
|
||||
blocks[0..<2] == [pool.tail, b2Add]
|
||||
blocks[0..<2] == [pool.tail, b2Add[]]
|
||||
|
||||
pool.getBlockRange(Slot(0), 3, blocks.toOpenArray(0, 1)) == 1
|
||||
blocks[0..<2] == [nil, pool.tail] # block 3 is missing!
|
||||
|
||||
pool.getBlockRange(Slot(2), 2, blocks.toOpenArray(0, 1)) == 0
|
||||
blocks[0..<2] == [b2Add, b4Add] # block 3 is missing!
|
||||
blocks[0..<2] == [b2Add[], b4Add[]] # block 3 is missing!
|
||||
|
||||
# empty length
|
||||
pool.getBlockRange(Slot(2), 2, blocks.toOpenArray(0, -1)) == 0
|
||||
@ -175,13 +178,18 @@ suiteReport "Block pool processing" & preset():
|
||||
blocks[0..<2] == [BlockRef nil, nil] # block 3 is missing!
|
||||
|
||||
timedTest "Reverse order block add & get" & preset():
|
||||
check: pool.add(b2Root, b2).error == MissingParent
|
||||
let missing = pool.addRawBlock(b2Root, b2) do (validBlock: BLockRef):
|
||||
discard
|
||||
check: missing.error == MissingParent
|
||||
|
||||
check:
|
||||
pool.get(b2Root).isNone() # Unresolved, shouldn't show up
|
||||
FetchRecord(root: b1Root) in pool.checkMissing()
|
||||
|
||||
check: pool.add(b1Root, b1).isOk
|
||||
let status = pool.addRawBlock(b1Root, b1) do (validBlock: BlockRef):
|
||||
discard
|
||||
|
||||
check: status.isOk
|
||||
|
||||
let
|
||||
b1Get = pool.get(b1Root)
|
||||
@ -214,32 +222,37 @@ suiteReport "Block pool processing" & preset():
|
||||
pool2.heads.len == 1
|
||||
pool2.heads[0].blck.root == b2Root
|
||||
|
||||
timedTest "Can add same block twice" & preset():
|
||||
timedTest "Adding the same block twice returns a Duplicate error" & preset():
|
||||
let
|
||||
b10 = pool.add(b1Root, b1)[]
|
||||
b11 = pool.add(b1Root, b1)[]
|
||||
b10 = pool.addRawBlock(b1Root, b1) do (validBlock: BlockRef):
|
||||
discard
|
||||
b11 = pool.addRawBlock(b1Root, b1) do (validBlock: BlockRef):
|
||||
discard
|
||||
|
||||
check:
|
||||
b10 == b11
|
||||
not b10.isNil
|
||||
b11.error == Duplicate
|
||||
not b10[].isNil
|
||||
|
||||
timedTest "updateHead updates head and headState" & preset():
|
||||
let
|
||||
b1Add = pool.add(b1Root, b1)[]
|
||||
b1Add = pool.addRawBlock(b1Root, b1) do (validBlock: BlockRef):
|
||||
discard
|
||||
|
||||
pool.updateHead(b1Add)
|
||||
pool.updateHead(b1Add[])
|
||||
|
||||
check:
|
||||
pool.head.blck == b1Add
|
||||
pool.headState.data.data.slot == b1Add.slot
|
||||
pool.head.blck == b1Add[]
|
||||
pool.headState.data.data.slot == b1Add[].slot
|
||||
|
||||
timedTest "updateStateData sanity" & preset():
|
||||
let
|
||||
b1Add = pool.add(b1Root, b1)[]
|
||||
b2Add = pool.add(b2Root, b2)[]
|
||||
bs1 = BlockSlot(blck: b1Add, slot: b1.message.slot)
|
||||
bs1_3 = b1Add.atSlot(3.Slot)
|
||||
bs2_3 = b2Add.atSlot(3.Slot)
|
||||
b1Add = pool.addRawBlock(b1Root, b1) do (validBlock: BlockRef):
|
||||
discard
|
||||
b2Add = pool.addRawBlock(b2Root, b2) do (validBlock: BlockRef):
|
||||
discard
|
||||
bs1 = BlockSlot(blck: b1Add[], slot: b1.message.slot)
|
||||
bs1_3 = b1Add[].atSlot(3.Slot)
|
||||
bs2_3 = b2Add[].atSlot(3.Slot)
|
||||
|
||||
var tmpState = assignClone(pool.headState)
|
||||
|
||||
@ -247,38 +260,38 @@ suiteReport "Block pool processing" & preset():
|
||||
pool.updateStateData(tmpState[], bs1)
|
||||
|
||||
check:
|
||||
tmpState.blck == b1Add
|
||||
tmpState.blck == b1Add[]
|
||||
tmpState.data.data.slot == bs1.slot
|
||||
|
||||
# Skip slots
|
||||
pool.updateStateData(tmpState[], bs1_3) # skip slots
|
||||
|
||||
check:
|
||||
tmpState.blck == b1Add
|
||||
tmpState.blck == b1Add[]
|
||||
tmpState.data.data.slot == bs1_3.slot
|
||||
|
||||
# Move back slots, but not blocks
|
||||
pool.updateStateData(tmpState[], bs1_3.parent())
|
||||
check:
|
||||
tmpState.blck == b1Add
|
||||
tmpState.blck == b1Add[]
|
||||
tmpState.data.data.slot == bs1_3.parent().slot
|
||||
|
||||
# Move to different block and slot
|
||||
pool.updateStateData(tmpState[], bs2_3)
|
||||
check:
|
||||
tmpState.blck == b2Add
|
||||
tmpState.blck == b2Add[]
|
||||
tmpState.data.data.slot == bs2_3.slot
|
||||
|
||||
# Move back slot and block
|
||||
pool.updateStateData(tmpState[], bs1)
|
||||
check:
|
||||
tmpState.blck == b1Add
|
||||
tmpState.blck == b1Add[]
|
||||
tmpState.data.data.slot == bs1.slot
|
||||
|
||||
# Move back to genesis
|
||||
pool.updateStateData(tmpState[], bs1.parent())
|
||||
check:
|
||||
tmpState.blck == b1Add.parent
|
||||
tmpState.blck == b1Add[].parent
|
||||
tmpState.data.data.slot == bs1.parent.slot
|
||||
|
||||
suiteReport "BlockPool finalization tests" & preset():
|
||||
@ -298,7 +311,11 @@ suiteReport "BlockPool finalization tests" & preset():
|
||||
tmpState[], tmpState.data.slot + (5 * SLOTS_PER_EPOCH).uint64)
|
||||
|
||||
let lateBlock = makeTestBlock(tmpState[], pool.head.blck.root, cache)
|
||||
check: pool.add(hash_tree_root(blck.message), blck).isOk
|
||||
block:
|
||||
let status = pool.addRawBlock(hash_tree_root(blck.message), blck) do (validBlock: BlockRef):
|
||||
discard
|
||||
check: status.isOk()
|
||||
|
||||
|
||||
for i in 0 ..< (SLOTS_PER_EPOCH * 6):
|
||||
if i == 1:
|
||||
@ -306,26 +323,32 @@ suiteReport "BlockPool finalization tests" & preset():
|
||||
check:
|
||||
pool.tail.children.len == 2
|
||||
pool.heads.len == 2
|
||||
var
|
||||
|
||||
if i mod SLOTS_PER_EPOCH == 0:
|
||||
# Reset cache at epoch boundaries
|
||||
cache = get_empty_per_epoch_cache()
|
||||
|
||||
blck = makeTestBlock(
|
||||
pool.headState.data, pool.head.blck.root, cache,
|
||||
attestations = makeFullAttestations(
|
||||
pool.headState.data.data, pool.head.blck.root,
|
||||
pool.headState.data.data.slot, cache, {}))
|
||||
let added = pool.add(hash_tree_root(blck.message), blck)[]
|
||||
pool.updateHead(added)
|
||||
blck = makeTestBlock(
|
||||
pool.headState.data, pool.head.blck.root, cache,
|
||||
attestations = makeFullAttestations(
|
||||
pool.headState.data.data, pool.head.blck.root,
|
||||
pool.headState.data.data.slot, cache, {}))
|
||||
let added = pool.addRawBlock(hash_tree_root(blck.message), blck) do (validBlock: BlockRef):
|
||||
discard
|
||||
check: added.isOk()
|
||||
pool.updateHead(added[])
|
||||
|
||||
check:
|
||||
pool.heads.len() == 1
|
||||
pool.head.justified.slot.compute_epoch_at_slot() == 5
|
||||
pool.tail.children.len == 1
|
||||
|
||||
check:
|
||||
block:
|
||||
# The late block is a block whose parent was finalized long ago and thus
|
||||
# is no longer a viable head candidate
|
||||
pool.add(hash_tree_root(lateBlock.message), lateBlock).error == Unviable
|
||||
let status = pool.addRawBlock(hash_tree_root(lateBlock.message), lateBlock) do (validBlock: BlockRef):
|
||||
discard
|
||||
check: status.error == Unviable
|
||||
|
||||
let
|
||||
pool2 = BlockPool.init(defaultRuntimePreset, db)
|
||||
@ -341,43 +364,47 @@ suiteReport "BlockPool finalization tests" & preset():
|
||||
hash_tree_root(pool2.justifiedState.data.data) ==
|
||||
hash_tree_root(pool.justifiedState.data.data)
|
||||
|
||||
timedTest "init with gaps" & preset():
|
||||
var cache = get_empty_per_epoch_cache()
|
||||
for i in 0 ..< (SLOTS_PER_EPOCH * 6 - 2):
|
||||
var
|
||||
blck = makeTestBlock(
|
||||
pool.headState.data, pool.head.blck.root, cache,
|
||||
attestations = makeFullAttestations(
|
||||
pool.headState.data.data, pool.head.blck.root,
|
||||
pool.headState.data.data.slot, cache, {}))
|
||||
let added = pool.add(hash_tree_root(blck.message), blck)[]
|
||||
pool.updateHead(added)
|
||||
# timedTest "init with gaps" & preset():
|
||||
# var cache = get_empty_per_epoch_cache()
|
||||
# for i in 0 ..< (SLOTS_PER_EPOCH * 6 - 2):
|
||||
# var
|
||||
# blck = makeTestBlock(
|
||||
# pool.headState.data, pool.head.blck.root, cache,
|
||||
# attestations = makeFullAttestations(
|
||||
# pool.headState.data.data, pool.head.blck.root,
|
||||
# pool.headState.data.data.slot, cache, {}))
|
||||
|
||||
# Advance past epoch so that the epoch transition is gapped
|
||||
check:
|
||||
process_slots(
|
||||
pool.headState.data, Slot(SLOTS_PER_EPOCH * 6 + 2) )
|
||||
# let added = pool.addRawBlock(hash_tree_root(blck.message), blck) do (validBlock: BlockRef):
|
||||
# discard
|
||||
# check: added.isOk()
|
||||
# pool.updateHead(added[])
|
||||
|
||||
var blck = makeTestBlock(
|
||||
pool.headState.data, pool.head.blck.root, cache,
|
||||
attestations = makeFullAttestations(
|
||||
pool.headState.data.data, pool.head.blck.root,
|
||||
pool.headState.data.data.slot, cache, {}))
|
||||
# # Advance past epoch so that the epoch transition is gapped
|
||||
# check:
|
||||
# process_slots(
|
||||
# pool.headState.data, Slot(SLOTS_PER_EPOCH * 6 + 2) )
|
||||
|
||||
let added = pool.add(hash_tree_root(blck.message), blck)[]
|
||||
pool.updateHead(added)
|
||||
# var blck = makeTestBlock(
|
||||
# pool.headState.data, pool.head.blck.root, cache,
|
||||
# attestations = makeFullAttestations(
|
||||
# pool.headState.data.data, pool.head.blck.root,
|
||||
# pool.headState.data.data.slot, cache, {}))
|
||||
|
||||
let
|
||||
pool2 = BlockPool.init(defaultRuntimePreset, db)
|
||||
# let added = pool.addRawBlock(hash_tree_root(blck.message), blck) do (validBlock: BlockRef):
|
||||
# discard
|
||||
# check: added.isOk()
|
||||
# pool.updateHead(added[])
|
||||
|
||||
# check that the state reloaded from database resembles what we had before
|
||||
check:
|
||||
pool2.tail.root == pool.tail.root
|
||||
pool2.head.blck.root == pool.head.blck.root
|
||||
pool2.finalizedHead.blck.root == pool.finalizedHead.blck.root
|
||||
pool2.finalizedHead.slot == pool.finalizedHead.slot
|
||||
hash_tree_root(pool2.headState.data.data) ==
|
||||
hash_tree_root(pool.headState.data.data)
|
||||
hash_tree_root(pool2.justifiedState.data.data) ==
|
||||
hash_tree_root(pool.justifiedState.data.data)
|
||||
# let
|
||||
# pool2 = BlockPool.init(db)
|
||||
|
||||
# # check that the state reloaded from database resembles what we had before
|
||||
# check:
|
||||
# pool2.tail.root == pool.tail.root
|
||||
# pool2.head.blck.root == pool.head.blck.root
|
||||
# pool2.finalizedHead.blck.root == pool.finalizedHead.blck.root
|
||||
# pool2.finalizedHead.slot == pool.finalizedHead.slot
|
||||
# hash_tree_root(pool2.headState.data.data) ==
|
||||
# hash_tree_root(pool.headState.data.data)
|
||||
# hash_tree_root(pool2.justifiedState.data.data) ==
|
||||
# hash_tree_root(pool.justifiedState.data.data)
|
||||
|
Loading…
x
Reference in New Issue
Block a user