implement clock disparity for attestation validation (#1568)
This implements disparity, resolving a part of https://github.com/status-im/nim-beacon-chain/issues/1367 * make BeaconTime a duration for fractional seconds * factor out attestation/aggregate validation * simplify recording of queued attestations * simplify attestation signature check * fix blocks_received metric * add some trivial validation tests * remove unresolved attestation table - attestations for unknown blocks are dropped instead (cannot verify their signature)
This commit is contained in:
parent
2081c4f505
commit
fa1621db46
|
@ -13,6 +13,11 @@ AllTests-mainnet
|
||||||
+ Trying to add a duplicate block from an old pruned epoch is tagged as an error OK
|
+ Trying to add a duplicate block from an old pruned epoch is tagged as an error OK
|
||||||
```
|
```
|
||||||
OK: 9/9 Fail: 0/9 Skip: 0/9
|
OK: 9/9 Fail: 0/9 Skip: 0/9
|
||||||
|
## Attestation validation [Preset: mainnet]
|
||||||
|
```diff
|
||||||
|
+ Validation sanity OK
|
||||||
|
```
|
||||||
|
OK: 1/1 Fail: 0/1 Skip: 0/1
|
||||||
## Beacon chain DB [Preset: mainnet]
|
## Beacon chain DB [Preset: mainnet]
|
||||||
```diff
|
```diff
|
||||||
+ empty database [Preset: mainnet] OK
|
+ empty database [Preset: mainnet] OK
|
||||||
|
@ -248,4 +253,4 @@ OK: 1/1 Fail: 0/1 Skip: 0/1
|
||||||
OK: 1/1 Fail: 0/1 Skip: 0/1
|
OK: 1/1 Fail: 0/1 Skip: 0/1
|
||||||
|
|
||||||
---TOTAL---
|
---TOTAL---
|
||||||
OK: 135/142 Fail: 0/142 Skip: 7/142
|
OK: 136/143 Fail: 0/143 Skip: 7/143
|
||||||
|
|
|
@ -13,11 +13,14 @@ import
|
||||||
beaconstate, datatypes, crypto, digest, helpers, network, validator,
|
beaconstate, datatypes, crypto, digest, helpers, network, validator,
|
||||||
signatures],
|
signatures],
|
||||||
./block_pools/[spec_cache, chain_dag, quarantine, spec_cache],
|
./block_pools/[spec_cache, chain_dag, quarantine, spec_cache],
|
||||||
./attestation_pool, ./beacon_node_types, ./ssz
|
./attestation_pool, ./beacon_node_types, ./ssz, ./time
|
||||||
|
|
||||||
logScope:
|
logScope:
|
||||||
topics = "att_aggr"
|
topics = "att_aggr"
|
||||||
|
|
||||||
|
const
|
||||||
|
MAXIMUM_GOSSIP_CLOCK_DISPARITY = 500.millis
|
||||||
|
|
||||||
# https://github.com/ethereum/eth2.0-specs/blob/v0.12.2/specs/phase0/validator.md#aggregation-selection
|
# https://github.com/ethereum/eth2.0-specs/blob/v0.12.2/specs/phase0/validator.md#aggregation-selection
|
||||||
func is_aggregator(state: BeaconState, slot: Slot, index: CommitteeIndex,
|
func is_aggregator(state: BeaconState, slot: Slot, index: CommitteeIndex,
|
||||||
slot_signature: ValidatorSig, cache: var StateCache): bool =
|
slot_signature: ValidatorSig, cache: var StateCache): bool =
|
||||||
|
@ -57,75 +60,118 @@ proc aggregate_attestations*(
|
||||||
aggregate: maybe_slot_attestation.get,
|
aggregate: maybe_slot_attestation.get,
|
||||||
selection_proof: slot_signature))
|
selection_proof: slot_signature))
|
||||||
|
|
||||||
proc isValidAttestationSlot(
|
func check_attestation_block_slot(
|
||||||
pool: AttestationPool, attestationSlot: Slot, attestationBlck: BlockRef): bool =
|
pool: AttestationPool, attestationSlot: Slot, attestationBlck: BlockRef): Result[void, cstring] =
|
||||||
# If we allow voting for very old blocks, the state transaction below will go
|
# If we allow voting for very old blocks, the state transaction below will go
|
||||||
# nuts and keep processing empty slots
|
# nuts and keep processing empty slots
|
||||||
logScope:
|
|
||||||
attestationSlot
|
|
||||||
attestationBlck = shortLog(attestationBlck)
|
|
||||||
|
|
||||||
if not (attestationBlck.slot > pool.chainDag.finalizedHead.slot):
|
if not (attestationBlck.slot > pool.chainDag.finalizedHead.slot):
|
||||||
debug "Voting for already-finalized block"
|
return err("Voting for already-finalized block")
|
||||||
return false
|
|
||||||
|
|
||||||
# we'll also cap it at 4 epochs which is somewhat arbitrary, but puts an
|
# we'll also cap it at 4 epochs which is somewhat arbitrary, but puts an
|
||||||
# upper bound on the processing done to validate the attestation
|
# upper bound on the processing done to validate the attestation
|
||||||
# TODO revisit with less arbitrary approach
|
# TODO revisit with less arbitrary approach
|
||||||
if not (attestationSlot >= attestationBlck.slot):
|
if not (attestationSlot >= attestationBlck.slot):
|
||||||
debug "Voting for block that didn't exist at the time"
|
return err("Voting for block that didn't exist at the time")
|
||||||
return false
|
|
||||||
|
|
||||||
if not ((attestationSlot - attestationBlck.slot) <= uint64(4 * SLOTS_PER_EPOCH)):
|
if not ((attestationSlot - attestationBlck.slot) <= uint64(4 * SLOTS_PER_EPOCH)):
|
||||||
debug "Voting for very old block"
|
return err("Voting for very old block")
|
||||||
return false
|
|
||||||
|
|
||||||
true
|
ok()
|
||||||
|
|
||||||
func checkPropagationSlotRange(data: AttestationData, current_slot: Slot): bool =
|
func check_propagation_slot_range(
|
||||||
# https://github.com/ethereum/eth2.0-specs/blob/v0.12.2/specs/phase0/p2p-interface.md#beacon_attestation_subnet_id
|
data: AttestationData, wallTime: BeaconTime): Result[void, cstring] =
|
||||||
# TODO clock disparity of 0.5s instead of whole slot
|
let
|
||||||
# attestation.data.slot + ATTESTATION_PROPAGATION_SLOT_RANGE >=
|
futureSlot = (wallTime + MAXIMUM_GOSSIP_CLOCK_DISPARITY).toSlot()
|
||||||
# current_slot >= attestation.data.slot
|
|
||||||
(data.slot + ATTESTATION_PROPAGATION_SLOT_RANGE + 1 >= current_slot) and
|
|
||||||
(current_slot >= data.slot)
|
|
||||||
|
|
||||||
# https://github.com/ethereum/eth2.0-specs/blob/v0.12.2/specs/phase0/p2p-interface.md#beacon_attestation_subnet_id
|
if not futureSlot.afterGenesis or data.slot > futureSlot.slot:
|
||||||
proc isValidAttestation*(
|
return err("Attestation slot in the future")
|
||||||
pool: var AttestationPool,
|
|
||||||
attestation: Attestation, current_slot: Slot,
|
|
||||||
topicCommitteeIndex: uint64): bool =
|
|
||||||
logScope:
|
|
||||||
topics = "att_aggr valid_att"
|
|
||||||
received_attestation = shortLog(attestation)
|
|
||||||
|
|
||||||
# TODO https://github.com/ethereum/eth2.0-specs/issues/1998
|
let
|
||||||
if (let v = check_attestation_slot_target(attestation.data); v.isErr):
|
pastSlot = (wallTime - MAXIMUM_GOSSIP_CLOCK_DISPARITY).toSlot()
|
||||||
debug "Invalid attestation", err = v.error
|
|
||||||
return false
|
|
||||||
|
|
||||||
if not checkPropagationSlotRange(attestation.data, current_slot):
|
if pastSlot.afterGenesis and
|
||||||
debug "Attestation slot not in propagation range",
|
data.slot + ATTESTATION_PROPAGATION_SLOT_RANGE < pastSlot.slot:
|
||||||
current_slot
|
return err("Attestation slot in the past")
|
||||||
return false
|
|
||||||
|
|
||||||
# The attestation is unaggregated -- that is, it has exactly one
|
ok()
|
||||||
# participating validator (len([bit for bit in attestation.aggregation_bits
|
|
||||||
# if bit == 0b1]) == 1).
|
func check_attestation_beacon_block(
|
||||||
|
pool: var AttestationPool, attestation: Attestation): Result[void, cstring] =
|
||||||
|
# The block being voted for (attestation.data.beacon_block_root) passes
|
||||||
|
# validation.
|
||||||
|
# We rely on the chain DAG to have been validated, so check for the existence
|
||||||
|
# of the block in the pool.
|
||||||
|
let attestationBlck = pool.chainDag.getRef(attestation.data.beacon_block_root)
|
||||||
|
if attestationBlck.isNil:
|
||||||
|
pool.quarantine.addMissing(attestation.data.beacon_block_root)
|
||||||
|
return err("Attestation block unknown")
|
||||||
|
|
||||||
|
# Not in spec - check that rewinding to the state is sane
|
||||||
|
? check_attestation_block_slot(pool, attestation.data.slot, attestationBlck)
|
||||||
|
|
||||||
|
ok()
|
||||||
|
|
||||||
|
func check_aggregation_count(
|
||||||
|
attestation: Attestation, singular: bool): Result[void, cstring] =
|
||||||
|
var onesCount = 0
|
||||||
# TODO a cleverer algorithm, along the lines of countOnes() in nim-stew
|
# TODO a cleverer algorithm, along the lines of countOnes() in nim-stew
|
||||||
# But that belongs in nim-stew, since it'd break abstraction layers, to
|
# But that belongs in nim-stew, since it'd break abstraction layers, to
|
||||||
# use details of its representation from nim-beacon-chain.
|
# use details of its representation from nim-beacon-chain.
|
||||||
var onesCount = 0
|
|
||||||
for aggregation_bit in attestation.aggregation_bits:
|
for aggregation_bit in attestation.aggregation_bits:
|
||||||
if not aggregation_bit:
|
if not aggregation_bit:
|
||||||
continue
|
continue
|
||||||
onesCount += 1
|
onesCount += 1
|
||||||
|
if singular: # More than one ok
|
||||||
if onesCount > 1:
|
if onesCount > 1:
|
||||||
debug "Attestation has too many aggregation bits"
|
return err("Attestation has too many aggregation bits")
|
||||||
return false
|
else:
|
||||||
if onesCount != 1:
|
break # Found the one we needed
|
||||||
debug "Attestation has too few aggregation bits"
|
|
||||||
return false
|
if onesCount < 1:
|
||||||
|
return err("Attestation has too few aggregation bits")
|
||||||
|
|
||||||
|
ok()
|
||||||
|
|
||||||
|
func check_attestation_subnet(
|
||||||
|
epochRef: EpochRef, attestation: Attestation,
|
||||||
|
topicCommitteeIndex: uint64): Result[void, cstring] =
|
||||||
|
let
|
||||||
|
expectedSubnet =
|
||||||
|
compute_subnet_for_attestation(
|
||||||
|
get_committee_count_per_slot(epochRef),
|
||||||
|
attestation.data.slot, attestation.data.index.CommitteeIndex)
|
||||||
|
|
||||||
|
if expectedSubnet != topicCommitteeIndex:
|
||||||
|
return err("Attestation's committee index not for the correct subnet")
|
||||||
|
|
||||||
|
ok()
|
||||||
|
|
||||||
|
# https://github.com/ethereum/eth2.0-specs/blob/v0.12.2/specs/phase0/p2p-interface.md#beacon_attestation_subnet_id
|
||||||
|
proc validateAttestation*(
|
||||||
|
pool: var AttestationPool,
|
||||||
|
attestation: Attestation, wallTime: BeaconTime,
|
||||||
|
topicCommitteeIndex: uint64): Result[HashSet[ValidatorIndex], cstring] =
|
||||||
|
? check_attestation_slot_target(attestation.data) # Not in spec - ignore
|
||||||
|
|
||||||
|
# attestation.data.slot is within the last ATTESTATION_PROPAGATION_SLOT_RANGE
|
||||||
|
# slots (within a MAXIMUM_GOSSIP_CLOCK_DISPARITY allowance) -- i.e.
|
||||||
|
# attestation.data.slot + ATTESTATION_PROPAGATION_SLOT_RANGE >= current_slot
|
||||||
|
# >= attestation.data.slot (a client MAY queue future attestations for\
|
||||||
|
# processing at the appropriate slot).
|
||||||
|
? check_propagation_slot_range(attestation.data, wallTime) # [IGNORE]
|
||||||
|
|
||||||
|
# The attestation is unaggregated -- that is, it has exactly one
|
||||||
|
# participating validator (len([bit for bit in attestation.aggregation_bits
|
||||||
|
# if bit == 0b1]) == 1).
|
||||||
|
? check_aggregation_count(attestation, singular = true) # [REJECT]
|
||||||
|
|
||||||
|
# The block being voted for (attestation.data.beacon_block_root) has been seen
|
||||||
|
# (via both gossip and non-gossip sources) (a client MAY queue aggregates for
|
||||||
|
# processing once block is retrieved).
|
||||||
|
# The block being voted for (attestation.data.beacon_block_root) passes
|
||||||
|
# validation.
|
||||||
|
? check_attestation_beacon_block(pool, attestation) # [IGNORE/REJECT]
|
||||||
|
|
||||||
# The attestation is the first valid attestation received for the
|
# The attestation is the first valid attestation received for the
|
||||||
# participating validator for the slot, attestation.data.slot.
|
# participating validator for the slot, attestation.data.slot.
|
||||||
|
@ -137,31 +183,12 @@ proc isValidAttestation*(
|
||||||
# Attestations might be aggregated eagerly or lazily; allow for both.
|
# Attestations might be aggregated eagerly or lazily; allow for both.
|
||||||
for validation in attestationEntry.validations:
|
for validation in attestationEntry.validations:
|
||||||
if attestation.aggregation_bits.isSubsetOf(validation.aggregation_bits):
|
if attestation.aggregation_bits.isSubsetOf(validation.aggregation_bits):
|
||||||
debug "Attestation already exists at slot",
|
return err("Attestation already exists at slot") # [IGNORE]
|
||||||
attestation_pool_validation = validation.aggregation_bits
|
|
||||||
return false
|
|
||||||
|
|
||||||
# The block being voted for (attestation.data.beacon_block_root) passes
|
|
||||||
# validation.
|
|
||||||
# We rely on the chain DAG to have been validated, so check for the existence
|
|
||||||
# of the block in the pool.
|
|
||||||
let attestationBlck = pool.chainDag.getRef(attestation.data.beacon_block_root)
|
|
||||||
if attestationBlck.isNil:
|
|
||||||
debug "Attestation block unknown"
|
|
||||||
pool.addUnresolved(attestation)
|
|
||||||
pool.quarantine.addMissing(attestation.data.beacon_block_root)
|
|
||||||
return false
|
|
||||||
|
|
||||||
if not isValidAttestationSlot(pool, attestation.data.slot, attestationBlck):
|
|
||||||
# Not in spec - check that rewinding to the state is sane
|
|
||||||
return false
|
|
||||||
|
|
||||||
let tgtBlck = pool.chainDag.getRef(attestation.data.target.root)
|
let tgtBlck = pool.chainDag.getRef(attestation.data.target.root)
|
||||||
if tgtBlck.isNil:
|
if tgtBlck.isNil:
|
||||||
debug "Attestation target block unknown"
|
|
||||||
pool.addUnresolved(attestation)
|
|
||||||
pool.quarantine.addMissing(attestation.data.target.root)
|
pool.quarantine.addMissing(attestation.data.target.root)
|
||||||
return false
|
return err("Attestation target block unknown")
|
||||||
|
|
||||||
# The following rule follows implicitly from that we clear out any
|
# The following rule follows implicitly from that we clear out any
|
||||||
# unviable blocks from the chain dag:
|
# unviable blocks from the chain dag:
|
||||||
|
@ -175,68 +202,43 @@ proc isValidAttestation*(
|
||||||
let epochRef = pool.chainDag.getEpochRef(
|
let epochRef = pool.chainDag.getEpochRef(
|
||||||
tgtBlck, attestation.data.target.epoch)
|
tgtBlck, attestation.data.target.epoch)
|
||||||
|
|
||||||
# https://github.com/ethereum/eth2.0-specs/blob/v0.12.2/specs/phase0/p2p-interface.md#beacon_attestation_subnet_id
|
|
||||||
# [REJECT] The attestation is for the correct subnet -- i.e.
|
# [REJECT] The attestation is for the correct subnet -- i.e.
|
||||||
# compute_subnet_for_attestation(committees_per_slot,
|
# compute_subnet_for_attestation(committees_per_slot,
|
||||||
# attestation.data.slot, attestation.data.index) == subnet_id, where
|
# attestation.data.slot, attestation.data.index) == subnet_id, where
|
||||||
# committees_per_slot = get_committee_count_per_slot(state,
|
# committees_per_slot = get_committee_count_per_slot(state,
|
||||||
# attestation.data.target.epoch), which may be pre-computed along with the
|
# attestation.data.target.epoch), which may be pre-computed along with the
|
||||||
# committee information for the signature check.
|
# committee information for the signature check.
|
||||||
let
|
? check_attestation_subnet(epochRef, attestation, topicCommitteeIndex)
|
||||||
requiredSubnetIndex =
|
|
||||||
compute_subnet_for_attestation(
|
|
||||||
get_committee_count_per_slot(epochRef),
|
|
||||||
attestation.data.slot, attestation.data.index.CommitteeIndex)
|
|
||||||
|
|
||||||
if requiredSubnetIndex != topicCommitteeIndex:
|
|
||||||
debug "Attestation's committee index not for the correct subnet",
|
|
||||||
topicCommitteeIndex = topicCommitteeIndex,
|
|
||||||
attestation_data_index = attestation.data.index,
|
|
||||||
requiredSubnetIndex = requiredSubnetIndex
|
|
||||||
return false
|
|
||||||
|
|
||||||
let
|
let
|
||||||
fork = pool.chainDag.headState.data.data.fork
|
fork = pool.chainDag.headState.data.data.fork
|
||||||
genesis_validators_root =
|
genesis_validators_root =
|
||||||
pool.chainDag.headState.data.data.genesis_validators_root
|
pool.chainDag.headState.data.data.genesis_validators_root
|
||||||
|
attesting_indices = get_attesting_indices(
|
||||||
|
epochRef, attestation.data, attestation.aggregation_bits)
|
||||||
|
|
||||||
# The signature of attestation is valid.
|
# The signature of attestation is valid.
|
||||||
if (let v = is_valid_indexed_attestation(
|
? is_valid_indexed_attestation(
|
||||||
fork, genesis_validators_root,
|
fork, genesis_validators_root, epochRef, attesting_indices, attestation, {})
|
||||||
epochRef, get_indexed_attestation(epochRef, attestation), {}); v.isErr):
|
|
||||||
debug "Attestation verification failed", err = v.error()
|
|
||||||
return false
|
|
||||||
|
|
||||||
true
|
ok(attesting_indices)
|
||||||
|
|
||||||
# https://github.com/ethereum/eth2.0-specs/blob/v0.12.2/specs/phase0/p2p-interface.md#beacon_aggregate_and_proof
|
# https://github.com/ethereum/eth2.0-specs/blob/v0.12.2/specs/phase0/p2p-interface.md#beacon_aggregate_and_proof
|
||||||
proc isValidAggregatedAttestation*(
|
proc validateAggregate*(
|
||||||
pool: var AttestationPool,
|
pool: var AttestationPool,
|
||||||
signedAggregateAndProof: SignedAggregateAndProof,
|
signedAggregateAndProof: SignedAggregateAndProof,
|
||||||
current_slot: Slot): bool =
|
wallTime: BeaconTime): Result[HashSet[ValidatorIndex], cstring] =
|
||||||
let
|
let
|
||||||
aggregate_and_proof = signedAggregateAndProof.message
|
aggregate_and_proof = signedAggregateAndProof.message
|
||||||
aggregate = aggregate_and_proof.aggregate
|
aggregate = aggregate_and_proof.aggregate
|
||||||
|
|
||||||
logScope:
|
? check_attestation_slot_target(aggregate.data) # Not in spec - ignore
|
||||||
aggregate = shortLog(aggregate)
|
|
||||||
|
|
||||||
# TODO https://github.com/ethereum/eth2.0-specs/issues/1998
|
|
||||||
if (let v = check_attestation_slot_target(aggregate.data); v.isErr):
|
|
||||||
debug "Invalid aggregate", err = v.error
|
|
||||||
return false
|
|
||||||
|
|
||||||
# There's some overlap between this and isValidAttestation(), but unclear if
|
|
||||||
# saving a few lines of code would balance well with losing straightforward,
|
|
||||||
# spec-based synchronization.
|
|
||||||
#
|
|
||||||
# [IGNORE] aggregate.data.slot is within the last
|
# [IGNORE] aggregate.data.slot is within the last
|
||||||
# ATTESTATION_PROPAGATION_SLOT_RANGE slots (with a
|
# ATTESTATION_PROPAGATION_SLOT_RANGE slots (with a
|
||||||
# MAXIMUM_GOSSIP_CLOCK_DISPARITY allowance) -- i.e. aggregate.data.slot +
|
# MAXIMUM_GOSSIP_CLOCK_DISPARITY allowance) -- i.e. aggregate.data.slot +
|
||||||
# ATTESTATION_PROPAGATION_SLOT_RANGE >= current_slot >= aggregate.data.slot
|
# ATTESTATION_PROPAGATION_SLOT_RANGE >= current_slot >= aggregate.data.slot
|
||||||
if not checkPropagationSlotRange(aggregate.data, current_slot):
|
? check_propagation_slot_range(aggregate.data, wallTime) # [IGNORE]
|
||||||
debug "Aggregate slot not in propagation range"
|
|
||||||
return false
|
|
||||||
|
|
||||||
# [IGNORE] The valid aggregate attestation defined by
|
# [IGNORE] The valid aggregate attestation defined by
|
||||||
# hash_tree_root(aggregate) has not already been seen (via aggregate gossip,
|
# hash_tree_root(aggregate) has not already been seen (via aggregate gossip,
|
||||||
|
@ -253,14 +255,6 @@ proc isValidAggregatedAttestation*(
|
||||||
# This is [IGNORE] and already effectively checked by attestation pool upon
|
# This is [IGNORE] and already effectively checked by attestation pool upon
|
||||||
# attempting to resolve attestations.
|
# attempting to resolve attestations.
|
||||||
|
|
||||||
# [REJECT] The block being voted for (aggregate.data.beacon_block_root)
|
|
||||||
# passes validation.
|
|
||||||
let attestationBlck = pool.chainDag.getRef(aggregate.data.beacon_block_root)
|
|
||||||
if attestationBlck.isNil:
|
|
||||||
debug "Aggregate block unknown"
|
|
||||||
pool.quarantine.addMissing(aggregate.data.beacon_block_root)
|
|
||||||
return false
|
|
||||||
|
|
||||||
# [REJECT] The attestation has participants -- that is,
|
# [REJECT] The attestation has participants -- that is,
|
||||||
# len(get_attesting_indices(state, aggregate.data, aggregate.aggregation_bits)) >= 1.
|
# len(get_attesting_indices(state, aggregate.data, aggregate.aggregation_bits)) >= 1.
|
||||||
#
|
#
|
||||||
|
@ -274,30 +268,26 @@ proc isValidAggregatedAttestation*(
|
||||||
# members, i.e. they counts don't match.
|
# members, i.e. they counts don't match.
|
||||||
# But (2) would reflect an invalid aggregation in other ways, so reject it
|
# But (2) would reflect an invalid aggregation in other ways, so reject it
|
||||||
# either way.
|
# either way.
|
||||||
if isZeros(aggregate.aggregation_bits):
|
? check_aggregation_count(aggregate, singular = false)
|
||||||
debug "Aggregate has no or invalid aggregation bits"
|
|
||||||
return false
|
|
||||||
|
|
||||||
if not isValidAttestationSlot(pool, aggregate.data.slot, attestationBlck):
|
# [REJECT] The block being voted for (aggregate.data.beacon_block_root)
|
||||||
# Not in spec - check that rewinding to the state is sane
|
# passes validation.
|
||||||
return false
|
? check_attestation_beacon_block(pool, aggregate)
|
||||||
|
|
||||||
# [REJECT] aggregate_and_proof.selection_proof selects the validator as an
|
# [REJECT] aggregate_and_proof.selection_proof selects the validator as an
|
||||||
# aggregator for the slot -- i.e. is_aggregator(state, aggregate.data.slot,
|
# aggregator for the slot -- i.e. is_aggregator(state, aggregate.data.slot,
|
||||||
# aggregate.data.index, aggregate_and_proof.selection_proof) returns True.
|
# aggregate.data.index, aggregate_and_proof.selection_proof) returns True.
|
||||||
let tgtBlck = pool.chainDag.getRef(aggregate.data.target.root)
|
let tgtBlck = pool.chainDag.getRef(aggregate.data.target.root)
|
||||||
if tgtBlck.isNil:
|
if tgtBlck.isNil:
|
||||||
debug "Aggregate target block unknown"
|
|
||||||
pool.quarantine.addMissing(aggregate.data.target.root)
|
pool.quarantine.addMissing(aggregate.data.target.root)
|
||||||
return
|
return err("Aggregate target block unknown")
|
||||||
|
|
||||||
let epochRef = pool.chainDag.getEpochRef(tgtBlck, aggregate.data.target.epoch)
|
let epochRef = pool.chainDag.getEpochRef(tgtBlck, aggregate.data.target.epoch)
|
||||||
|
|
||||||
if not is_aggregator(
|
if not is_aggregator(
|
||||||
epochRef, aggregate.data.slot, aggregate.data.index.CommitteeIndex,
|
epochRef, aggregate.data.slot, aggregate.data.index.CommitteeIndex,
|
||||||
aggregate_and_proof.selection_proof):
|
aggregate_and_proof.selection_proof):
|
||||||
debug "Incorrect aggregator"
|
return err("Incorrect aggregator")
|
||||||
return false
|
|
||||||
|
|
||||||
# [REJECT] The aggregator's validator index is within the committee -- i.e.
|
# [REJECT] The aggregator's validator index is within the committee -- i.e.
|
||||||
# aggregate_and_proof.aggregator_index in get_beacon_committee(state,
|
# aggregate_and_proof.aggregator_index in get_beacon_committee(state,
|
||||||
|
@ -305,16 +295,14 @@ proc isValidAggregatedAttestation*(
|
||||||
if aggregate_and_proof.aggregator_index.ValidatorIndex notin
|
if aggregate_and_proof.aggregator_index.ValidatorIndex notin
|
||||||
get_beacon_committee(
|
get_beacon_committee(
|
||||||
epochRef, aggregate.data.slot, aggregate.data.index.CommitteeIndex):
|
epochRef, aggregate.data.slot, aggregate.data.index.CommitteeIndex):
|
||||||
debug "Aggregator's validator index not in committee"
|
return err("Aggregator's validator index not in committee")
|
||||||
return false
|
|
||||||
|
|
||||||
# [REJECT] The aggregate_and_proof.selection_proof is a valid signature of the
|
# [REJECT] The aggregate_and_proof.selection_proof is a valid signature of the
|
||||||
# aggregate.data.slot by the validator with index
|
# aggregate.data.slot by the validator with index
|
||||||
# aggregate_and_proof.aggregator_index.
|
# aggregate_and_proof.aggregator_index.
|
||||||
# get_slot_signature(state, aggregate.data.slot, privkey)
|
# get_slot_signature(state, aggregate.data.slot, privkey)
|
||||||
if aggregate_and_proof.aggregator_index >= epochRef.validator_keys.lenu64:
|
if aggregate_and_proof.aggregator_index >= epochRef.validator_keys.lenu64:
|
||||||
debug "Invalid aggregator_index"
|
return err("Invalid aggregator_index")
|
||||||
return false
|
|
||||||
|
|
||||||
let
|
let
|
||||||
fork = pool.chainDag.headState.data.data.fork
|
fork = pool.chainDag.headState.data.data.fork
|
||||||
|
@ -324,23 +312,21 @@ proc isValidAggregatedAttestation*(
|
||||||
fork, genesis_validators_root, aggregate.data.slot,
|
fork, genesis_validators_root, aggregate.data.slot,
|
||||||
epochRef.validator_keys[aggregate_and_proof.aggregator_index],
|
epochRef.validator_keys[aggregate_and_proof.aggregator_index],
|
||||||
aggregate_and_proof.selection_proof):
|
aggregate_and_proof.selection_proof):
|
||||||
debug "Selection_proof signature verification failed"
|
return err("Selection_proof signature verification failed")
|
||||||
return false
|
|
||||||
|
|
||||||
# [REJECT] The aggregator signature, signed_aggregate_and_proof.signature, is valid.
|
# [REJECT] The aggregator signature, signed_aggregate_and_proof.signature, is valid.
|
||||||
if not verify_aggregate_and_proof_signature(
|
if not verify_aggregate_and_proof_signature(
|
||||||
fork, genesis_validators_root, aggregate_and_proof,
|
fork, genesis_validators_root, aggregate_and_proof,
|
||||||
epochRef.validator_keys[aggregate_and_proof.aggregator_index],
|
epochRef.validator_keys[aggregate_and_proof.aggregator_index],
|
||||||
signed_aggregate_and_proof.signature):
|
signed_aggregate_and_proof.signature):
|
||||||
debug "signed_aggregate_and_proof signature verification failed"
|
return err("signed_aggregate_and_proof signature verification failed")
|
||||||
return false
|
|
||||||
|
let attesting_indices = get_attesting_indices(
|
||||||
|
epochRef, aggregate.data, aggregate.aggregation_bits)
|
||||||
|
|
||||||
# [REJECT] The signature of aggregate is valid.
|
# [REJECT] The signature of aggregate is valid.
|
||||||
if (let v = is_valid_indexed_attestation(
|
? is_valid_indexed_attestation(
|
||||||
fork, genesis_validators_root,
|
fork, genesis_validators_root, epochRef, attesting_indices, aggregate, {})
|
||||||
epochRef, get_indexed_attestation(epochRef, aggregate), {}); v.isErr):
|
|
||||||
debug "Aggregate verification failed", err = v.error()
|
|
||||||
return false
|
|
||||||
|
|
||||||
# The following rule follows implicitly from that we clear out any
|
# The following rule follows implicitly from that we clear out any
|
||||||
# unviable blocks from the chain dag:
|
# unviable blocks from the chain dag:
|
||||||
|
@ -351,4 +337,4 @@ proc isValidAggregatedAttestation*(
|
||||||
# compute_start_slot_at_epoch(store.finalized_checkpoint.epoch)) ==
|
# compute_start_slot_at_epoch(store.finalized_checkpoint.epoch)) ==
|
||||||
# store.finalized_checkpoint.root
|
# store.finalized_checkpoint.root
|
||||||
|
|
||||||
true
|
ok(attesting_indices)
|
||||||
|
|
|
@ -25,9 +25,6 @@ proc init*(T: type AttestationPool, chainDag: ChainDAGRef, quarantine: Quarantin
|
||||||
## Initialize an AttestationPool from the chainDag `headState`
|
## Initialize an AttestationPool from the chainDag `headState`
|
||||||
## The `finalized_root` works around the finalized_checkpoint of the genesis block
|
## The `finalized_root` works around the finalized_checkpoint of the genesis block
|
||||||
## holding a zero_root.
|
## holding a zero_root.
|
||||||
# TODO chainDag/quarantine are only used when resolving orphaned attestations - they
|
|
||||||
# should probably be removed as a dependency of AttestationPool (or some other
|
|
||||||
# smart refactoring)
|
|
||||||
|
|
||||||
let
|
let
|
||||||
finalizedEpochRef = chainDag.getEpochRef(
|
finalizedEpochRef = chainDag.getEpochRef(
|
||||||
|
@ -65,25 +62,18 @@ proc init*(T: type AttestationPool, chainDag: ChainDAGRef, quarantine: Quarantin
|
||||||
T(
|
T(
|
||||||
chainDag: chainDag,
|
chainDag: chainDag,
|
||||||
quarantine: quarantine,
|
quarantine: quarantine,
|
||||||
unresolved: initTable[Eth2Digest, UnresolvedAttestation](),
|
|
||||||
forkChoice: forkChoice
|
forkChoice: forkChoice
|
||||||
)
|
)
|
||||||
|
|
||||||
proc processAttestation(
|
proc processAttestation(
|
||||||
pool: var AttestationPool, slot: Slot, participants: HashSet[ValidatorIndex],
|
pool: var AttestationPool, slot: Slot, participants: HashSet[ValidatorIndex],
|
||||||
block_root: Eth2Digest, target: Checkpoint, wallSlot: Slot) =
|
block_root: Eth2Digest, wallSlot: Slot) =
|
||||||
# Add attestation votes to fork choice
|
# Add attestation votes to fork choice
|
||||||
if (let v = pool.forkChoice.on_attestation(
|
if (let v = pool.forkChoice.on_attestation(
|
||||||
pool.chainDag, slot, block_root, toSeq(participants), target, wallSlot);
|
pool.chainDag, slot, block_root, participants, wallSlot);
|
||||||
v.isErr):
|
v.isErr):
|
||||||
warn "Couldn't process attestation", err = v.error()
|
warn "Couldn't process attestation", err = v.error()
|
||||||
|
|
||||||
func addUnresolved*(pool: var AttestationPool, attestation: Attestation) =
|
|
||||||
pool.unresolved[attestation.data.beacon_block_root] =
|
|
||||||
UnresolvedAttestation(
|
|
||||||
attestation: attestation,
|
|
||||||
)
|
|
||||||
|
|
||||||
func candidateIdx(pool: AttestationPool, slot: Slot): Option[uint64] =
|
func candidateIdx(pool: AttestationPool, slot: Slot): Option[uint64] =
|
||||||
if slot >= pool.startingSlot and
|
if slot >= pool.startingSlot and
|
||||||
slot < (pool.startingSlot + pool.candidates.lenu64):
|
slot < (pool.startingSlot + pool.candidates.lenu64):
|
||||||
|
@ -109,8 +99,9 @@ proc updateCurrent(pool: var AttestationPool, wallSlot: Slot) =
|
||||||
|
|
||||||
pool.startingSlot = newWallSlot
|
pool.startingSlot = newWallSlot
|
||||||
|
|
||||||
proc addResolved(
|
proc addAttestation*(pool: var AttestationPool,
|
||||||
pool: var AttestationPool, blck: BlockRef, attestation: Attestation,
|
attestation: Attestation,
|
||||||
|
participants: HashSet[ValidatorIndex],
|
||||||
wallSlot: Slot) =
|
wallSlot: Slot) =
|
||||||
# Add an attestation whose parent we know
|
# Add an attestation whose parent we know
|
||||||
logScope:
|
logScope:
|
||||||
|
@ -118,8 +109,6 @@ proc addResolved(
|
||||||
|
|
||||||
updateCurrent(pool, wallSlot)
|
updateCurrent(pool, wallSlot)
|
||||||
|
|
||||||
doAssert blck.root == attestation.data.beacon_block_root
|
|
||||||
|
|
||||||
let candidateIdx = pool.candidateIdx(attestation.data.slot)
|
let candidateIdx = pool.candidateIdx(attestation.data.slot)
|
||||||
if candidateIdx.isNone:
|
if candidateIdx.isNone:
|
||||||
debug "Attestation slot out of range",
|
debug "Attestation slot out of range",
|
||||||
|
@ -127,13 +116,10 @@ proc addResolved(
|
||||||
return
|
return
|
||||||
|
|
||||||
let
|
let
|
||||||
epochRef = pool.chainDag.getEpochRef(blck, attestation.data.target.epoch)
|
|
||||||
attestationsSeen = addr pool.candidates[candidateIdx.get]
|
attestationsSeen = addr pool.candidates[candidateIdx.get]
|
||||||
validation = Validation(
|
validation = Validation(
|
||||||
aggregation_bits: attestation.aggregation_bits,
|
aggregation_bits: attestation.aggregation_bits,
|
||||||
aggregate_signature: attestation.signature)
|
aggregate_signature: attestation.signature)
|
||||||
participants = get_attesting_indices(
|
|
||||||
epochRef, attestation.data, validation.aggregation_bits)
|
|
||||||
|
|
||||||
var found = false
|
var found = false
|
||||||
for a in attestationsSeen.attestations.mitems():
|
for a in attestationsSeen.attestations.mitems():
|
||||||
|
@ -164,12 +150,11 @@ proc addResolved(
|
||||||
a.validations.add(validation)
|
a.validations.add(validation)
|
||||||
pool.processAttestation(
|
pool.processAttestation(
|
||||||
attestation.data.slot, participants, attestation.data.beacon_block_root,
|
attestation.data.slot, participants, attestation.data.beacon_block_root,
|
||||||
attestation.data.target, wallSlot)
|
wallSlot)
|
||||||
|
|
||||||
info "Attestation resolved",
|
info "Attestation resolved",
|
||||||
attestation = shortLog(attestation),
|
attestation = shortLog(attestation),
|
||||||
validations = a.validations.len(),
|
validations = a.validations.len()
|
||||||
blockSlot = shortLog(blck.slot)
|
|
||||||
|
|
||||||
found = true
|
found = true
|
||||||
|
|
||||||
|
@ -178,36 +163,15 @@ proc addResolved(
|
||||||
if not found:
|
if not found:
|
||||||
attestationsSeen.attestations.add(AttestationEntry(
|
attestationsSeen.attestations.add(AttestationEntry(
|
||||||
data: attestation.data,
|
data: attestation.data,
|
||||||
blck: blck,
|
|
||||||
validations: @[validation]
|
validations: @[validation]
|
||||||
))
|
))
|
||||||
pool.processAttestation(
|
pool.processAttestation(
|
||||||
attestation.data.slot, participants, attestation.data.beacon_block_root,
|
attestation.data.slot, participants, attestation.data.beacon_block_root,
|
||||||
attestation.data.target, wallSlot)
|
wallSlot)
|
||||||
|
|
||||||
info "Attestation resolved",
|
info "Attestation resolved",
|
||||||
attestation = shortLog(attestation),
|
attestation = shortLog(attestation),
|
||||||
validations = 1,
|
validations = 1
|
||||||
blockSlot = shortLog(blck.slot)
|
|
||||||
|
|
||||||
proc addAttestation*(pool: var AttestationPool,
|
|
||||||
attestation: Attestation,
|
|
||||||
wallSlot: Slot) =
|
|
||||||
## Add a verified attestation to the fork choice context
|
|
||||||
logScope: pcs = "atp_add_attestation"
|
|
||||||
|
|
||||||
# Fetch the target block or notify the block pool that it's needed
|
|
||||||
let blck = pool.chainDag.getOrResolve(
|
|
||||||
pool.quarantine,
|
|
||||||
attestation.data.beacon_block_root)
|
|
||||||
|
|
||||||
# If the block exist, add it to the fork choice context
|
|
||||||
# Otherwise delay until it resolves
|
|
||||||
if blck.isNil:
|
|
||||||
pool.addUnresolved(attestation)
|
|
||||||
return
|
|
||||||
|
|
||||||
pool.addResolved(blck, attestation, wallSlot)
|
|
||||||
|
|
||||||
proc addForkChoice*(pool: var AttestationPool,
|
proc addForkChoice*(pool: var AttestationPool,
|
||||||
epochRef: EpochRef,
|
epochRef: EpochRef,
|
||||||
|
@ -261,7 +225,7 @@ proc getAttestationsForBlock*(pool: AttestationPool,
|
||||||
# intended thing -- sure, _blocks_ have to be popular (via attestation)
|
# intended thing -- sure, _blocks_ have to be popular (via attestation)
|
||||||
# but _attestations_ shouldn't have to be so frequently repeated, as an
|
# but _attestations_ shouldn't have to be so frequently repeated, as an
|
||||||
# artifact of this state-free, identical-across-clones choice basis. In
|
# artifact of this state-free, identical-across-clones choice basis. In
|
||||||
# addResolved, too, the new attestations get added to the end, while in
|
# addAttestation, too, the new attestations get added to the end, while in
|
||||||
# these functions, it's reading from the beginning, et cetera. This all
|
# these functions, it's reading from the beginning, et cetera. This all
|
||||||
# needs a single unified strategy.
|
# needs a single unified strategy.
|
||||||
for i in max(1, newBlockSlot.int64 - ATTESTATION_LOOKBACK.int64) .. newBlockSlot.int64:
|
for i in max(1, newBlockSlot.int64 - ATTESTATION_LOOKBACK.int64) .. newBlockSlot.int64:
|
||||||
|
@ -356,30 +320,6 @@ proc getAggregatedAttestation*(pool: AttestationPool,
|
||||||
|
|
||||||
none(Attestation)
|
none(Attestation)
|
||||||
|
|
||||||
proc resolve*(pool: var AttestationPool, wallSlot: Slot) =
|
|
||||||
## Check attestations in our unresolved deque
|
|
||||||
## if they can be integrated to the fork choice
|
|
||||||
logScope: pcs = "atp_resolve"
|
|
||||||
|
|
||||||
var
|
|
||||||
done: seq[Eth2Digest]
|
|
||||||
resolved: seq[tuple[blck: BlockRef, attestation: Attestation]]
|
|
||||||
|
|
||||||
for k, v in pool.unresolved.mpairs():
|
|
||||||
if (let blck = pool.chainDag.getRef(k); not blck.isNil()):
|
|
||||||
resolved.add((blck, v.attestation))
|
|
||||||
done.add(k)
|
|
||||||
elif v.tries > 8:
|
|
||||||
done.add(k)
|
|
||||||
else:
|
|
||||||
inc v.tries
|
|
||||||
|
|
||||||
for k in done:
|
|
||||||
pool.unresolved.del(k)
|
|
||||||
|
|
||||||
for a in resolved:
|
|
||||||
pool.addResolved(a.blck, a.attestation, wallSlot)
|
|
||||||
|
|
||||||
proc selectHead*(pool: var AttestationPool, wallSlot: Slot): BlockRef =
|
proc selectHead*(pool: var AttestationPool, wallSlot: Slot): BlockRef =
|
||||||
## Trigger fork choice and returns the new head block.
|
## Trigger fork choice and returns the new head block.
|
||||||
## Can return `nil`
|
## Can return `nil`
|
||||||
|
|
|
@ -867,8 +867,8 @@ proc start(node: BeaconNode) =
|
||||||
version = fullVersionStr,
|
version = fullVersionStr,
|
||||||
nim = shortNimBanner(),
|
nim = shortNimBanner(),
|
||||||
timeSinceFinalization =
|
timeSinceFinalization =
|
||||||
int64(finalizedHead.slot.toBeaconTime()) -
|
finalizedHead.slot.toBeaconTime() -
|
||||||
int64(node.beaconClock.now()),
|
node.beaconClock.now(),
|
||||||
head = shortLog(head),
|
head = shortLog(head),
|
||||||
finalizedHead = shortLog(finalizedHead),
|
finalizedHead = shortLog(finalizedHead),
|
||||||
SLOTS_PER_EPOCH,
|
SLOTS_PER_EPOCH,
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
import
|
import
|
||||||
deques, tables,
|
deques, tables,
|
||||||
stew/endians2,
|
stew/endians2,
|
||||||
spec/[datatypes, crypto, digest],
|
spec/[datatypes, crypto],
|
||||||
block_pools/block_pools_types,
|
block_pools/block_pools_types,
|
||||||
fork_choice/fork_choice_types
|
fork_choice/fork_choice_types
|
||||||
|
|
||||||
|
@ -44,10 +44,6 @@ type
|
||||||
## TODO this could be a Table[AttestationData, seq[Validation] or something
|
## TODO this could be a Table[AttestationData, seq[Validation] or something
|
||||||
## less naive
|
## less naive
|
||||||
|
|
||||||
UnresolvedAttestation* = object
|
|
||||||
attestation*: Attestation
|
|
||||||
tries*: int
|
|
||||||
|
|
||||||
AttestationPool* = object
|
AttestationPool* = object
|
||||||
## The attestation pool keeps track of all attestations that potentially
|
## The attestation pool keeps track of all attestations that potentially
|
||||||
## could be added to a block during block production.
|
## could be added to a block during block production.
|
||||||
|
@ -66,8 +62,6 @@ type
|
||||||
chainDag*: ChainDAGRef
|
chainDag*: ChainDAGRef
|
||||||
quarantine*: QuarantineRef
|
quarantine*: QuarantineRef
|
||||||
|
|
||||||
unresolved*: Table[Eth2Digest, UnresolvedAttestation]
|
|
||||||
|
|
||||||
forkChoice*: ForkChoice
|
forkChoice*: ForkChoice
|
||||||
|
|
||||||
# #############################################
|
# #############################################
|
||||||
|
|
|
@ -122,6 +122,31 @@ proc is_valid_indexed_attestation*(
|
||||||
|
|
||||||
ok()
|
ok()
|
||||||
|
|
||||||
|
# https://github.com/ethereum/eth2.0-specs/blob/v0.12.2/specs/phase0/beacon-chain.md#is_valid_indexed_attestation
|
||||||
|
proc is_valid_indexed_attestation*(
|
||||||
|
fork: Fork, genesis_validators_root: Eth2Digest,
|
||||||
|
epochRef: EpochRef, attesting_indices: HashSet[ValidatorIndex],
|
||||||
|
attestation: SomeAttestation, flags: UpdateFlags): Result[void, cstring] =
|
||||||
|
# This is a variation on `is_valid_indexed_attestation` that works directly
|
||||||
|
# with an attestation instead of first constructing an `IndexedAttestation`
|
||||||
|
# and then validating it - for the purpose of validating the signature, the
|
||||||
|
# order doesn't matter and we can proceed straight to validating the
|
||||||
|
# signature instead
|
||||||
|
if attesting_indices.len == 0:
|
||||||
|
return err("indexed_attestation: no attesting indices")
|
||||||
|
|
||||||
|
# Verify aggregate signature
|
||||||
|
if skipBLSValidation notin flags:
|
||||||
|
# TODO: fuse loops with blsFastAggregateVerify
|
||||||
|
let pubkeys = mapIt(
|
||||||
|
attesting_indices, epochRef.validator_keys[it])
|
||||||
|
if not verify_attestation_signature(
|
||||||
|
fork, genesis_validators_root, attestation.data,
|
||||||
|
pubkeys, attestation.signature):
|
||||||
|
return err("indexed attestation: signature verification failure")
|
||||||
|
|
||||||
|
ok()
|
||||||
|
|
||||||
func makeAttestationData*(
|
func makeAttestationData*(
|
||||||
epochRef: EpochRef, bs: BlockSlot,
|
epochRef: EpochRef, bs: BlockSlot,
|
||||||
committee_index: uint64): AttestationData =
|
committee_index: uint64): AttestationData =
|
||||||
|
|
|
@ -36,16 +36,18 @@ declareGauge beacon_head_root,
|
||||||
type
|
type
|
||||||
GetWallTimeFn* = proc(): BeaconTime {.gcsafe, raises: [Defect].}
|
GetWallTimeFn* = proc(): BeaconTime {.gcsafe, raises: [Defect].}
|
||||||
|
|
||||||
Entry[T] = object
|
|
||||||
v*: T
|
|
||||||
|
|
||||||
SyncBlock* = object
|
SyncBlock* = object
|
||||||
blk*: SignedBeaconBlock
|
blk*: SignedBeaconBlock
|
||||||
resfut*: Future[Result[void, BlockError]]
|
resfut*: Future[Result[void, BlockError]]
|
||||||
|
|
||||||
BlockEntry* = Entry[SyncBlock]
|
BlockEntry* = object
|
||||||
AttestationEntry* = Entry[Attestation]
|
v*: SyncBlock
|
||||||
AggregateEntry* = Entry[Attestation]
|
|
||||||
|
AttestationEntry* = object
|
||||||
|
v*: Attestation
|
||||||
|
attesting_indices*: HashSet[ValidatorIndex]
|
||||||
|
|
||||||
|
AggregateEntry* = AttestationEntry
|
||||||
|
|
||||||
Eth2Processor* = object
|
Eth2Processor* = object
|
||||||
config*: BeaconNodeConf
|
config*: BeaconNodeConf
|
||||||
|
@ -61,9 +63,6 @@ type
|
||||||
proc updateHead*(self: var Eth2Processor, wallSlot: Slot): BlockRef =
|
proc updateHead*(self: var Eth2Processor, wallSlot: Slot): BlockRef =
|
||||||
## Trigger fork choice and returns the new head block.
|
## Trigger fork choice and returns the new head block.
|
||||||
## Can return `nil`
|
## Can return `nil`
|
||||||
# Check pending attestations - maybe we found some blocks for them
|
|
||||||
self.attestationPool[].resolve(wallSlot)
|
|
||||||
|
|
||||||
# Grab the new head according to our latest attestation data
|
# Grab the new head according to our latest attestation data
|
||||||
let newHead = self.attestationPool[].selectHead(wallSlot)
|
let newHead = self.attestationPool[].selectHead(wallSlot)
|
||||||
if newHead.isNil():
|
if newHead.isNil():
|
||||||
|
@ -153,8 +152,9 @@ proc processAttestation(
|
||||||
error "Processing attestation before genesis, clock turned back?"
|
error "Processing attestation before genesis, clock turned back?"
|
||||||
quit 1
|
quit 1
|
||||||
|
|
||||||
debug "Processing attestation"
|
trace "Processing attestation"
|
||||||
self.attestationPool[].addAttestation(entry.v, wallSlot)
|
self.attestationPool[].addAttestation(
|
||||||
|
entry.v, entry.attesting_indices, wallSlot)
|
||||||
|
|
||||||
proc processAggregate(
|
proc processAggregate(
|
||||||
self: var Eth2Processor, entry: AggregateEntry) =
|
self: var Eth2Processor, entry: AggregateEntry) =
|
||||||
|
@ -169,8 +169,9 @@ proc processAggregate(
|
||||||
error "Processing aggregate before genesis, clock turned back?"
|
error "Processing aggregate before genesis, clock turned back?"
|
||||||
quit 1
|
quit 1
|
||||||
|
|
||||||
debug "Processing aggregate"
|
trace "Processing aggregate"
|
||||||
self.attestationPool[].addAttestation(entry.v, wallSlot)
|
self.attestationPool[].addAttestation(
|
||||||
|
entry.v, entry.attesting_indices, wallSlot)
|
||||||
|
|
||||||
proc processBlock(self: var Eth2Processor, entry: BlockEntry) =
|
proc processBlock(self: var Eth2Processor, entry: BlockEntry) =
|
||||||
logScope:
|
logScope:
|
||||||
|
@ -266,17 +267,18 @@ proc blockValidator*(
|
||||||
if not blck.isOk:
|
if not blck.isOk:
|
||||||
return false
|
return false
|
||||||
|
|
||||||
|
beacon_blocks_received.inc()
|
||||||
|
beacon_block_delay.observe(float(milliseconds(delay)) / 1000.0)
|
||||||
|
|
||||||
# Block passed validation - enqueue it for processing. The block processing
|
# Block passed validation - enqueue it for processing. The block processing
|
||||||
# queue is effectively unbounded as we use a freestanding task to enqueue
|
# queue is effectively unbounded as we use a freestanding task to enqueue
|
||||||
# the block - this is done so that when blocks arrive concurrently with
|
# the block - this is done so that when blocks arrive concurrently with
|
||||||
# sync, we don't lose the gossip blocks, but also don't block the gossip
|
# sync, we don't lose the gossip blocks, but also don't block the gossip
|
||||||
# propagation of seemingly good blocks
|
# propagation of seemingly good blocks
|
||||||
debug "Block validated"
|
trace "Block validated"
|
||||||
traceAsyncErrors self.blocksQueue.addLast(
|
traceAsyncErrors self.blocksQueue.addLast(
|
||||||
BlockEntry(v: SyncBlock(blk: signedBlock)))
|
BlockEntry(v: SyncBlock(blk: signedBlock)))
|
||||||
|
|
||||||
beacon_block_delay.observe(float(milliseconds(delay)) / 1000.0)
|
|
||||||
|
|
||||||
true
|
true
|
||||||
|
|
||||||
proc attestationValidator*(
|
proc attestationValidator*(
|
||||||
|
@ -299,9 +301,11 @@ proc attestationValidator*(
|
||||||
|
|
||||||
let delay = wallTime - attestation.data.slot.toBeaconTime
|
let delay = wallTime - attestation.data.slot.toBeaconTime
|
||||||
debug "Attestation received", delay
|
debug "Attestation received", delay
|
||||||
if not self.attestationPool[].isValidAttestation(
|
let v = self.attestationPool[].validateAttestation(
|
||||||
attestation, wallSlot, committeeIndex):
|
attestation, wallTime, committeeIndex)
|
||||||
return false # logged in validation
|
if v.isErr():
|
||||||
|
debug "Dropping attestation", err = v.error()
|
||||||
|
return false
|
||||||
|
|
||||||
beacon_attestations_received.inc()
|
beacon_attestations_received.inc()
|
||||||
beacon_attestation_delay.observe(float(milliseconds(delay)) / 1000.0)
|
beacon_attestation_delay.observe(float(milliseconds(delay)) / 1000.0)
|
||||||
|
@ -312,9 +316,9 @@ proc attestationValidator*(
|
||||||
notice "Queue full, dropping attestation",
|
notice "Queue full, dropping attestation",
|
||||||
dropped = shortLog(dropped.read().v)
|
dropped = shortLog(dropped.read().v)
|
||||||
|
|
||||||
debug "Attestation validated"
|
trace "Attestation validated"
|
||||||
traceAsyncErrors self.attestationsQueue.addLast(
|
traceAsyncErrors self.attestationsQueue.addLast(
|
||||||
AttestationEntry(v: attestation))
|
AttestationEntry(v: attestation, attesting_indices: v.get()))
|
||||||
|
|
||||||
true
|
true
|
||||||
|
|
||||||
|
@ -339,8 +343,10 @@ proc aggregateValidator*(
|
||||||
wallTime - signedAggregateAndProof.message.aggregate.data.slot.toBeaconTime
|
wallTime - signedAggregateAndProof.message.aggregate.data.slot.toBeaconTime
|
||||||
debug "Aggregate received", delay
|
debug "Aggregate received", delay
|
||||||
|
|
||||||
if not self.attestationPool[].isValidAggregatedAttestation(
|
let v = self.attestationPool[].validateAggregate(
|
||||||
signedAggregateAndProof, wallSlot):
|
signedAggregateAndProof, wallTime)
|
||||||
|
if v.isErr:
|
||||||
|
debug "Dropping aggregate", err = v.error
|
||||||
return false
|
return false
|
||||||
|
|
||||||
beacon_aggregates_received.inc()
|
beacon_aggregates_received.inc()
|
||||||
|
@ -352,9 +358,10 @@ proc aggregateValidator*(
|
||||||
notice "Queue full, dropping aggregate",
|
notice "Queue full, dropping aggregate",
|
||||||
dropped = shortLog(dropped.read().v)
|
dropped = shortLog(dropped.read().v)
|
||||||
|
|
||||||
debug "Aggregate validated"
|
trace "Aggregate validated"
|
||||||
traceAsyncErrors self.aggregatesQueue.addLast(AggregateEntry(
|
traceAsyncErrors self.aggregatesQueue.addLast(AggregateEntry(
|
||||||
v: signedAggregateAndProof.message.aggregate))
|
v: signedAggregateAndProof.message.aggregate,
|
||||||
|
attesting_indices: v.get()))
|
||||||
|
|
||||||
true
|
true
|
||||||
|
|
||||||
|
|
|
@ -9,7 +9,7 @@
|
||||||
|
|
||||||
import
|
import
|
||||||
# Standard library
|
# Standard library
|
||||||
std/[sets, tables, typetraits],
|
std/[sequtils, sets, tables, typetraits],
|
||||||
# Status libraries
|
# Status libraries
|
||||||
stew/results, chronicles,
|
stew/results, chronicles,
|
||||||
# Internal
|
# Internal
|
||||||
|
@ -157,7 +157,7 @@ proc process_attestation_queue(self: var ForkChoice) =
|
||||||
for validator_index in attestation.attesting_indices:
|
for validator_index in attestation.attesting_indices:
|
||||||
self.backend.process_attestation(
|
self.backend.process_attestation(
|
||||||
validator_index, attestation.block_root,
|
validator_index, attestation.block_root,
|
||||||
attestation.target_epoch)
|
attestation.slot.epoch())
|
||||||
else:
|
else:
|
||||||
keep.add attestation
|
keep.add attestation
|
||||||
|
|
||||||
|
@ -174,10 +174,9 @@ func contains*(self: ForkChoiceBackend, block_root: Eth2Digest): bool =
|
||||||
proc on_attestation*(
|
proc on_attestation*(
|
||||||
self: var ForkChoice,
|
self: var ForkChoice,
|
||||||
dag: ChainDAGRef,
|
dag: ChainDAGRef,
|
||||||
slot: Slot,
|
attestation_slot: Slot,
|
||||||
beacon_block_root: Eth2Digest,
|
beacon_block_root: Eth2Digest,
|
||||||
attesting_indices: openArray[ValidatorIndex],
|
attesting_indices: HashSet[ValidatorIndex],
|
||||||
target: Checkpoint,
|
|
||||||
wallSlot: Slot
|
wallSlot: Slot
|
||||||
): FcResult[void] =
|
): FcResult[void] =
|
||||||
? self.update_time(dag, wallSlot)
|
? self.update_time(dag, wallSlot)
|
||||||
|
@ -185,16 +184,16 @@ proc on_attestation*(
|
||||||
if beacon_block_root == Eth2Digest():
|
if beacon_block_root == Eth2Digest():
|
||||||
return ok()
|
return ok()
|
||||||
|
|
||||||
if slot < self.checkpoints.time:
|
if attestation_slot < self.checkpoints.time:
|
||||||
for validator_index in attesting_indices:
|
for validator_index in attesting_indices:
|
||||||
|
# attestation_slot and target epoch must match, per attestation rules
|
||||||
self.backend.process_attestation(
|
self.backend.process_attestation(
|
||||||
validator_index, beacon_block_root, target.epoch)
|
validator_index, beacon_block_root, attestation_slot.epoch)
|
||||||
else:
|
else:
|
||||||
self.queued_attestations.add(QueuedAttestation(
|
self.queuedAttestations.add(QueuedAttestation(
|
||||||
slot: slot,
|
slot: attestation_slot,
|
||||||
attesting_indices: @attesting_indices,
|
attesting_indices: toSeq(attesting_indices),
|
||||||
block_root: beacon_block_root,
|
block_root: beacon_block_root))
|
||||||
target_epoch: target.epoch))
|
|
||||||
ok()
|
ok()
|
||||||
|
|
||||||
# https://github.com/ethereum/eth2.0-specs/blob/v0.12.1/specs/phase0/fork-choice.md#should_update_justified_checkpoint
|
# https://github.com/ethereum/eth2.0-specs/blob/v0.12.1/specs/phase0/fork-choice.md#should_update_justified_checkpoint
|
||||||
|
|
|
@ -9,7 +9,7 @@
|
||||||
|
|
||||||
import
|
import
|
||||||
# Standard library
|
# Standard library
|
||||||
std/[tables, options],
|
std/[options, tables],
|
||||||
# Status
|
# Status
|
||||||
stew/results,
|
stew/results,
|
||||||
|
|
||||||
|
|
|
@ -190,44 +190,38 @@ func get_previous_epoch*(state: BeaconState): Epoch =
|
||||||
get_previous_epoch(get_current_epoch(state))
|
get_previous_epoch(get_current_epoch(state))
|
||||||
|
|
||||||
# https://github.com/ethereum/eth2.0-specs/blob/v0.12.2/specs/phase0/beacon-chain.md#compute_committee
|
# https://github.com/ethereum/eth2.0-specs/blob/v0.12.2/specs/phase0/beacon-chain.md#compute_committee
|
||||||
|
func compute_committee_slice*(
|
||||||
|
active_validators, index, count: uint64): Slice[int] =
|
||||||
|
doAssert active_validators <= ValidatorIndex.high.uint64
|
||||||
|
|
||||||
|
let
|
||||||
|
start = (active_validators * index) div count
|
||||||
|
endIdx = (active_validators * (index + 1)) div count
|
||||||
|
|
||||||
|
start.int..(endIdx.int - 1)
|
||||||
|
|
||||||
func compute_committee*(shuffled_indices: seq[ValidatorIndex],
|
func compute_committee*(shuffled_indices: seq[ValidatorIndex],
|
||||||
index: uint64, count: uint64): seq[ValidatorIndex] =
|
index: uint64, count: uint64): seq[ValidatorIndex] =
|
||||||
## Return the committee corresponding to ``indices``, ``seed``, ``index``,
|
## Return the committee corresponding to ``indices``, ``seed``, ``index``,
|
||||||
## and committee ``count``.
|
## and committee ``count``.
|
||||||
## In this version, we pass in the shuffled indices meaning we no longer need
|
## In this version, we pass in the shuffled indices meaning we no longer need
|
||||||
## the seed.
|
## the seed.
|
||||||
|
|
||||||
let
|
let
|
||||||
active_validators = shuffled_indices.len.uint64
|
slice = compute_committee_slice(shuffled_indices.lenu64, index, count)
|
||||||
start = (active_validators * index) div count
|
|
||||||
endIdx = (active_validators * (index + 1)) div count
|
|
||||||
|
|
||||||
# These assertions from compute_shuffled_index(...)
|
|
||||||
doAssert endIdx <= active_validators
|
|
||||||
doAssert active_validators <= 2'u64^40
|
|
||||||
|
|
||||||
# In spec, this calls get_shuffled_index() every time, but that's wasteful
|
# In spec, this calls get_shuffled_index() every time, but that's wasteful
|
||||||
# Here, get_beacon_committee() gets the shuffled version.
|
# Here, get_beacon_committee() gets the shuffled version.
|
||||||
shuffled_indices[start.int .. (endIdx.int-1)]
|
shuffled_indices[slice]
|
||||||
|
|
||||||
func compute_committee_len*(active_validators: uint64,
|
func compute_committee_len*(
|
||||||
index: uint64, count: uint64): uint64 =
|
active_validators, index, count: uint64): uint64 =
|
||||||
## Return the committee corresponding to ``indices``, ``seed``, ``index``,
|
## Return the committee corresponding to ``indices``, ``seed``, ``index``,
|
||||||
## and committee ``count``.
|
## and committee ``count``.
|
||||||
|
|
||||||
# indices only used here for its length, or for the shuffled version,
|
|
||||||
# so unlike spec, pass the shuffled version in directly.
|
|
||||||
let
|
let
|
||||||
start = (active_validators * index) div count
|
slice = compute_committee_slice(active_validators, index, count)
|
||||||
endIdx = (active_validators * (index + 1)) div count
|
|
||||||
|
|
||||||
# These assertions from compute_shuffled_index(...)
|
(slice.b - slice.a + 1).uint64
|
||||||
doAssert endIdx <= active_validators
|
|
||||||
doAssert active_validators <= 2'u64^40
|
|
||||||
|
|
||||||
# In spec, this calls get_shuffled_index() every time, but that's wasteful
|
|
||||||
# Here, get_beacon_committee() gets the shuffled version.
|
|
||||||
endIdx - start
|
|
||||||
|
|
||||||
# https://github.com/ethereum/eth2.0-specs/blob/v0.12.2/specs/phase0/beacon-chain.md#get_beacon_committee
|
# https://github.com/ethereum/eth2.0-specs/blob/v0.12.2/specs/phase0/beacon-chain.md#get_beacon_committee
|
||||||
func get_beacon_committee*(
|
func get_beacon_committee*(
|
||||||
|
|
|
@ -4,7 +4,7 @@ import
|
||||||
chronos,
|
chronos,
|
||||||
spec/datatypes
|
spec/datatypes
|
||||||
|
|
||||||
from times import Time, getTime, fromUnix, `<`, `-`
|
from times import Time, getTime, fromUnix, `<`, `-`, inNanoseconds
|
||||||
|
|
||||||
type
|
type
|
||||||
BeaconClock* = object
|
BeaconClock* = object
|
||||||
|
@ -25,7 +25,7 @@ type
|
||||||
# https://ethresear.ch/t/network-adjusted-timestamps/4187
|
# https://ethresear.ch/t/network-adjusted-timestamps/4187
|
||||||
genesis: Time
|
genesis: Time
|
||||||
|
|
||||||
BeaconTime* = distinct int64 ## Seconds from beacon genesis time
|
BeaconTime* = distinct Duration ## Nanoseconds from beacon genesis time
|
||||||
|
|
||||||
proc init*(T: type BeaconClock, genesis_time: uint64): T =
|
proc init*(T: type BeaconClock, genesis_time: uint64): T =
|
||||||
let
|
let
|
||||||
|
@ -43,13 +43,22 @@ proc init*(T: type BeaconClock, state: BeaconState): T =
|
||||||
BeaconClock.init(state.genesis_time)
|
BeaconClock.init(state.genesis_time)
|
||||||
|
|
||||||
template `<`*(a, b: BeaconTime): bool =
|
template `<`*(a, b: BeaconTime): bool =
|
||||||
int64(a) < int64(b)
|
Duration(a) < Duration(b)
|
||||||
|
|
||||||
template `<=`*(a, b: BeaconTime): bool =
|
template `<=`*(a, b: BeaconTime): bool =
|
||||||
int64(a) <= int64(b)
|
Duration(a) <= Duration(b)
|
||||||
|
|
||||||
|
template `+`*(t: BeaconTime, offset: Duration): BeaconTime =
|
||||||
|
BeaconTime(Duration(t) + offset)
|
||||||
|
|
||||||
|
template `-`*(t: BeaconTime, offset: Duration): BeaconTime =
|
||||||
|
BeaconTime(Duration(t) - offset)
|
||||||
|
|
||||||
|
template `-`*(a, b: BeaconTime): Duration =
|
||||||
|
Duration(a) - Duration(b)
|
||||||
|
|
||||||
func toSlot*(t: BeaconTime): tuple[afterGenesis: bool, slot: Slot] =
|
func toSlot*(t: BeaconTime): tuple[afterGenesis: bool, slot: Slot] =
|
||||||
let ti = t.int64
|
let ti = seconds(Duration(t))
|
||||||
if ti >= 0:
|
if ti >= 0:
|
||||||
(true, Slot(uint64(ti) div SECONDS_PER_SLOT))
|
(true, Slot(uint64(ti) div SECONDS_PER_SLOT))
|
||||||
else:
|
else:
|
||||||
|
@ -61,13 +70,13 @@ func slotOrZero*(time: BeaconTime): Slot =
|
||||||
else: Slot(0)
|
else: Slot(0)
|
||||||
|
|
||||||
func toBeaconTime*(c: BeaconClock, t: Time): BeaconTime =
|
func toBeaconTime*(c: BeaconClock, t: Time): BeaconTime =
|
||||||
BeaconTime(times.inSeconds(t - c.genesis))
|
BeaconTime(nanoseconds(inNanoseconds(t - c.genesis)))
|
||||||
|
|
||||||
func toSlot*(c: BeaconClock, t: Time): tuple[afterGenesis: bool, slot: Slot] =
|
func toSlot*(c: BeaconClock, t: Time): tuple[afterGenesis: bool, slot: Slot] =
|
||||||
c.toBeaconTime(t).toSlot()
|
c.toBeaconTime(t).toSlot()
|
||||||
|
|
||||||
func toBeaconTime*(s: Slot, offset = chronos.seconds(0)): BeaconTime =
|
func toBeaconTime*(s: Slot, offset = Duration()): BeaconTime =
|
||||||
BeaconTime(int64(uint64(s) * SECONDS_PER_SLOT) + seconds(offset))
|
BeaconTime(seconds(int64(uint64(s) * SECONDS_PER_SLOT)) + offset)
|
||||||
|
|
||||||
proc now*(c: BeaconClock): BeaconTime =
|
proc now*(c: BeaconClock): BeaconTime =
|
||||||
## Current time, in slots - this may end up being less than GENESIS_SLOT(!)
|
## Current time, in slots - this may end up being less than GENESIS_SLOT(!)
|
||||||
|
@ -76,10 +85,10 @@ proc now*(c: BeaconClock): BeaconTime =
|
||||||
proc fromNow*(c: BeaconClock, t: BeaconTime): tuple[inFuture: bool, offset: Duration] =
|
proc fromNow*(c: BeaconClock, t: BeaconTime): tuple[inFuture: bool, offset: Duration] =
|
||||||
let now = c.now()
|
let now = c.now()
|
||||||
|
|
||||||
if int64(t) > int64(now):
|
if t > now:
|
||||||
(true, seconds(int64(t) - int64(now)))
|
(true, t - now)
|
||||||
else:
|
else:
|
||||||
(false, seconds(int64(now) - int64(t)))
|
(false, now - t)
|
||||||
|
|
||||||
proc fromNow*(c: BeaconClock, slot: Slot): tuple[inFuture: bool, offset: Duration] =
|
proc fromNow*(c: BeaconClock, slot: Slot): tuple[inFuture: bool, offset: Duration] =
|
||||||
c.fromNow(slot.toBeaconTime())
|
c.fromNow(slot.toBeaconTime())
|
||||||
|
@ -115,8 +124,5 @@ func shortLog*(d: Duration): string =
|
||||||
tmp &= $frac & "m"
|
tmp &= $frac & "m"
|
||||||
tmp
|
tmp
|
||||||
|
|
||||||
func `$`*(v: BeaconTime): string = $(int64(v))
|
func `$`*(v: BeaconTime): string = $Duration(v)
|
||||||
func shortLog*(v: BeaconTime): int64 = v.int64
|
func shortLog*(v: BeaconTime): Duration = Duration(v)
|
||||||
|
|
||||||
func `-`*(a, b: BeaconTime): Duration =
|
|
||||||
seconds(int64(a)) - seconds(int64(b))
|
|
||||||
|
|
|
@ -93,7 +93,7 @@ cli do(slots = SLOTS_PER_EPOCH * 6,
|
||||||
data: data,
|
data: data,
|
||||||
aggregation_bits: aggregation_bits,
|
aggregation_bits: aggregation_bits,
|
||||||
signature: sig
|
signature: sig
|
||||||
), data.slot)
|
), [validatorIdx].toHashSet(), data.slot)
|
||||||
|
|
||||||
proc proposeBlock(slot: Slot) =
|
proc proposeBlock(slot: Slot) =
|
||||||
if rand(r, 1.0) > blockRatio:
|
if rand(r, 1.0) > blockRatio:
|
||||||
|
|
|
@ -8,13 +8,14 @@
|
||||||
{.used.}
|
{.used.}
|
||||||
|
|
||||||
import
|
import
|
||||||
std/[unittest, options],
|
std/unittest,
|
||||||
chronicles,
|
chronicles, chronos,
|
||||||
stew/byteutils,
|
stew/byteutils,
|
||||||
./testutil, ./testblockutil,
|
./testutil, ./testblockutil,
|
||||||
../beacon_chain/spec/[crypto, datatypes, digest, validator, state_transition,
|
../beacon_chain/spec/[crypto, datatypes, digest, validator, state_transition,
|
||||||
helpers, beaconstate, presets],
|
helpers, beaconstate, presets, network],
|
||||||
../beacon_chain/[beacon_node_types, attestation_pool, extras],
|
../beacon_chain/[
|
||||||
|
beacon_node_types, attestation_pool, attestation_aggregation, extras, time],
|
||||||
../beacon_chain/fork_choice/[fork_choice_types, fork_choice],
|
../beacon_chain/fork_choice/[fork_choice_types, fork_choice],
|
||||||
../beacon_chain/block_pools/[chain_dag, clearance]
|
../beacon_chain/block_pools/[chain_dag, clearance]
|
||||||
|
|
||||||
|
@ -54,10 +55,10 @@ suiteReport "Attestation pool processing" & preset():
|
||||||
setup:
|
setup:
|
||||||
# Genesis state that results in 3 members per committee
|
# Genesis state that results in 3 members per committee
|
||||||
var
|
var
|
||||||
chainDag = newClone(init(ChainDAGRef, defaultRuntimePreset, makeTestDB(SLOTS_PER_EPOCH * 3)))
|
chainDag = init(ChainDAGRef, defaultRuntimePreset, makeTestDB(SLOTS_PER_EPOCH * 3))
|
||||||
quarantine = newClone(QuarantineRef())
|
quarantine = QuarantineRef()
|
||||||
pool = newClone(AttestationPool.init(chainDag, quarantine))
|
pool = newClone(AttestationPool.init(chainDag, quarantine))
|
||||||
state = newClone(loadTailState(chainDag))
|
state = newClone(chainDag.headState)
|
||||||
# Slot 0 is a finalized slot - won't be making attestations for it..
|
# Slot 0 is a finalized slot - won't be making attestations for it..
|
||||||
check:
|
check:
|
||||||
process_slots(state.data, state.data.data.slot + 1)
|
process_slots(state.data, state.data.data.slot + 1)
|
||||||
|
@ -71,7 +72,8 @@ suiteReport "Attestation pool processing" & preset():
|
||||||
attestation = makeAttestation(
|
attestation = makeAttestation(
|
||||||
state.data.data, state.blck.root, beacon_committee[0], cache)
|
state.data.data, state.blck.root, beacon_committee[0], cache)
|
||||||
|
|
||||||
pool[].addAttestation(attestation, attestation.data.slot)
|
pool[].addAttestation(
|
||||||
|
attestation, [beacon_committee[0]].toHashSet(), attestation.data.slot)
|
||||||
|
|
||||||
check:
|
check:
|
||||||
process_slots(state.data, MIN_ATTESTATION_INCLUSION_DELAY.Slot + 1)
|
process_slots(state.data, MIN_ATTESTATION_INCLUSION_DELAY.Slot + 1)
|
||||||
|
@ -100,8 +102,10 @@ suiteReport "Attestation pool processing" & preset():
|
||||||
state.data.data, state.blck.root, bc1[0], cache)
|
state.data.data, state.blck.root, bc1[0], cache)
|
||||||
|
|
||||||
# test reverse order
|
# test reverse order
|
||||||
pool[].addAttestation(attestation1, attestation1.data.slot)
|
pool[].addAttestation(
|
||||||
pool[].addAttestation(attestation0, attestation1.data.slot)
|
attestation1, [bc1[0]].toHashSet, attestation1.data.slot)
|
||||||
|
pool[].addAttestation(
|
||||||
|
attestation0, [bc0[0]].toHashSet, attestation1.data.slot)
|
||||||
|
|
||||||
discard process_slots(state.data, MIN_ATTESTATION_INCLUSION_DELAY.Slot + 1)
|
discard process_slots(state.data, MIN_ATTESTATION_INCLUSION_DELAY.Slot + 1)
|
||||||
|
|
||||||
|
@ -121,8 +125,10 @@ suiteReport "Attestation pool processing" & preset():
|
||||||
attestation1 = makeAttestation(
|
attestation1 = makeAttestation(
|
||||||
state.data.data, state.blck.root, bc0[1], cache)
|
state.data.data, state.blck.root, bc0[1], cache)
|
||||||
|
|
||||||
pool[].addAttestation(attestation0, attestation0.data.slot)
|
pool[].addAttestation(
|
||||||
pool[].addAttestation(attestation1, attestation1.data.slot)
|
attestation0, [bc0[0]].toHashSet, attestation0.data.slot)
|
||||||
|
pool[].addAttestation(
|
||||||
|
attestation1, [bc0[1]].toHashSet, attestation1.data.slot)
|
||||||
|
|
||||||
check:
|
check:
|
||||||
process_slots(state.data, MIN_ATTESTATION_INCLUSION_DELAY.Slot + 1)
|
process_slots(state.data, MIN_ATTESTATION_INCLUSION_DELAY.Slot + 1)
|
||||||
|
@ -146,8 +152,10 @@ suiteReport "Attestation pool processing" & preset():
|
||||||
|
|
||||||
attestation0.combine(attestation1, {})
|
attestation0.combine(attestation1, {})
|
||||||
|
|
||||||
pool[].addAttestation(attestation0, attestation0.data.slot)
|
pool[].addAttestation(
|
||||||
pool[].addAttestation(attestation1, attestation1.data.slot)
|
attestation0, [bc0[0]].toHashSet, attestation0.data.slot)
|
||||||
|
pool[].addAttestation(
|
||||||
|
attestation1, [bc0[1]].toHashSet, attestation1.data.slot)
|
||||||
|
|
||||||
check:
|
check:
|
||||||
process_slots(state.data, MIN_ATTESTATION_INCLUSION_DELAY.Slot + 1)
|
process_slots(state.data, MIN_ATTESTATION_INCLUSION_DELAY.Slot + 1)
|
||||||
|
@ -170,8 +178,10 @@ suiteReport "Attestation pool processing" & preset():
|
||||||
|
|
||||||
attestation0.combine(attestation1, {})
|
attestation0.combine(attestation1, {})
|
||||||
|
|
||||||
pool[].addAttestation(attestation1, attestation1.data.slot)
|
pool[].addAttestation(
|
||||||
pool[].addAttestation(attestation0, attestation0.data.slot)
|
attestation1, [bc0[1]].toHashSet, attestation1.data.slot)
|
||||||
|
pool[].addAttestation(
|
||||||
|
attestation0, [bc0[0]].toHashSet, attestation0.data.slot)
|
||||||
|
|
||||||
check:
|
check:
|
||||||
process_slots(state.data, MIN_ATTESTATION_INCLUSION_DELAY.Slot + 1)
|
process_slots(state.data, MIN_ATTESTATION_INCLUSION_DELAY.Slot + 1)
|
||||||
|
@ -238,7 +248,8 @@ suiteReport "Attestation pool processing" & preset():
|
||||||
state.data.data, state.data.data.slot - 1, 1.CommitteeIndex, cache)
|
state.data.data, state.data.data.slot - 1, 1.CommitteeIndex, cache)
|
||||||
attestation0 = makeAttestation(state.data.data, b10.root, bc1[0], cache)
|
attestation0 = makeAttestation(state.data.data, b10.root, bc1[0], cache)
|
||||||
|
|
||||||
pool[].addAttestation(attestation0, attestation0.data.slot)
|
pool[].addAttestation(
|
||||||
|
attestation0, [bc1[0]].toHashSet, attestation0.data.slot)
|
||||||
|
|
||||||
let head2 = pool[].selectHead(b10Add[].slot)
|
let head2 = pool[].selectHead(b10Add[].slot)
|
||||||
|
|
||||||
|
@ -249,7 +260,8 @@ suiteReport "Attestation pool processing" & preset():
|
||||||
let
|
let
|
||||||
attestation1 = makeAttestation(state.data.data, b11.root, bc1[1], cache)
|
attestation1 = makeAttestation(state.data.data, b11.root, bc1[1], cache)
|
||||||
attestation2 = makeAttestation(state.data.data, b11.root, bc1[2], cache)
|
attestation2 = makeAttestation(state.data.data, b11.root, bc1[2], cache)
|
||||||
pool[].addAttestation(attestation1, attestation1.data.slot)
|
pool[].addAttestation(
|
||||||
|
attestation1, [bc1[1]].toHashSet, attestation1.data.slot)
|
||||||
|
|
||||||
let head3 = pool[].selectHead(b10Add[].slot)
|
let head3 = pool[].selectHead(b10Add[].slot)
|
||||||
let bigger = if b11.root.data < b10.root.data: b10Add else: b11Add
|
let bigger = if b11.root.data < b10.root.data: b10Add else: b11Add
|
||||||
|
@ -258,7 +270,8 @@ suiteReport "Attestation pool processing" & preset():
|
||||||
# Ties broken lexicographically in spec -> ?
|
# Ties broken lexicographically in spec -> ?
|
||||||
head3 == bigger[]
|
head3 == bigger[]
|
||||||
|
|
||||||
pool[].addAttestation(attestation2, attestation2.data.slot)
|
pool[].addAttestation(
|
||||||
|
attestation2, [bc1[2]].toHashSet, attestation2.data.slot)
|
||||||
|
|
||||||
let head4 = pool[].selectHead(b11Add[].slot)
|
let head4 = pool[].selectHead(b11Add[].slot)
|
||||||
|
|
||||||
|
@ -298,8 +311,8 @@ suiteReport "Attestation pool processing" & preset():
|
||||||
chainDag.updateFlags.incl {skipBLSValidation}
|
chainDag.updateFlags.incl {skipBLSValidation}
|
||||||
var cache = StateCache()
|
var cache = StateCache()
|
||||||
let
|
let
|
||||||
b10 = newClone(makeTestBlock(state.data, chainDag.tail.root, cache))
|
b10 = addTestBlock(state.data, chainDag.tail.root, cache)
|
||||||
b10Add = chainDag.addRawBlock(quarantine, b10[]) do (
|
b10Add = chainDag.addRawBlock(quarantine, b10) do (
|
||||||
blckRef: BlockRef, signedBlock: SignedBeaconBlock,
|
blckRef: BlockRef, signedBlock: SignedBeaconBlock,
|
||||||
epochRef: EpochRef, state: HashedBeaconState):
|
epochRef: EpochRef, state: HashedBeaconState):
|
||||||
# Callback add to fork choice if valid
|
# Callback add to fork choice if valid
|
||||||
|
@ -309,9 +322,6 @@ suiteReport "Attestation pool processing" & preset():
|
||||||
|
|
||||||
doAssert: head == b10Add[]
|
doAssert: head == b10Add[]
|
||||||
|
|
||||||
let block_ok = state_transition(defaultRuntimePreset, state.data, b10[], {}, noRollback)
|
|
||||||
doAssert: block_ok
|
|
||||||
|
|
||||||
# -------------------------------------------------------------
|
# -------------------------------------------------------------
|
||||||
let b10_clone = b10 # Assumes deep copy
|
let b10_clone = b10 # Assumes deep copy
|
||||||
|
|
||||||
|
@ -326,14 +336,11 @@ suiteReport "Attestation pool processing" & preset():
|
||||||
let committees_per_slot =
|
let committees_per_slot =
|
||||||
get_committee_count_per_slot(state.data.data, Epoch epoch, cache)
|
get_committee_count_per_slot(state.data.data, Epoch epoch, cache)
|
||||||
for slot in start_slot ..< start_slot + SLOTS_PER_EPOCH:
|
for slot in start_slot ..< start_slot + SLOTS_PER_EPOCH:
|
||||||
let new_block = newClone(makeTestBlock(
|
let new_block = addTestBlock(
|
||||||
state.data, block_root, cache, attestations = attestations))
|
state.data, block_root, cache, attestations = attestations)
|
||||||
let block_ok = state_transition(
|
|
||||||
defaultRuntimePreset, state.data, new_block[], {skipBLSValidation}, noRollback)
|
|
||||||
doAssert: block_ok
|
|
||||||
|
|
||||||
block_root = new_block.root
|
block_root = new_block.root
|
||||||
let blockRef = chainDag.addRawBlock(quarantine, new_block[]) do (
|
let blockRef = chainDag.addRawBlock(quarantine, new_block) do (
|
||||||
blckRef: BlockRef, signedBlock: SignedBeaconBlock,
|
blckRef: BlockRef, signedBlock: SignedBeaconBlock,
|
||||||
epochRef: EpochRef, state: HashedBeaconState):
|
epochRef: EpochRef, state: HashedBeaconState):
|
||||||
# Callback add to fork choice if valid
|
# Callback add to fork choice if valid
|
||||||
|
@ -371,13 +378,76 @@ suiteReport "Attestation pool processing" & preset():
|
||||||
doAssert: chainDag.finalizedHead.slot != 0
|
doAssert: chainDag.finalizedHead.slot != 0
|
||||||
|
|
||||||
pool[].prune()
|
pool[].prune()
|
||||||
doAssert: b10[].root notin pool.forkChoice.backend
|
doAssert: b10.root notin pool.forkChoice.backend
|
||||||
|
|
||||||
# Add back the old block to ensure we have a duplicate error
|
# Add back the old block to ensure we have a duplicate error
|
||||||
let b10Add_clone = chainDag.addRawBlock(quarantine, b10_clone[]) do (
|
let b10Add_clone = chainDag.addRawBlock(quarantine, b10_clone) do (
|
||||||
blckRef: BlockRef, signedBlock: SignedBeaconBlock,
|
blckRef: BlockRef, signedBlock: SignedBeaconBlock,
|
||||||
epochRef: EpochRef, state: HashedBeaconState):
|
epochRef: EpochRef, state: HashedBeaconState):
|
||||||
# Callback add to fork choice if valid
|
# Callback add to fork choice if valid
|
||||||
pool[].addForkChoice(epochRef, blckRef, signedBlock.message, blckRef.slot)
|
pool[].addForkChoice(epochRef, blckRef, signedBlock.message, blckRef.slot)
|
||||||
|
|
||||||
doAssert: b10Add_clone.error == Duplicate
|
doAssert: b10Add_clone.error == Duplicate
|
||||||
|
|
||||||
|
|
||||||
|
suiteReport "Attestation validation " & preset():
|
||||||
|
setup:
|
||||||
|
# Genesis state that results in 3 members per committee
|
||||||
|
var
|
||||||
|
chainDag = init(ChainDAGRef, defaultRuntimePreset, makeTestDB(SLOTS_PER_EPOCH * 3))
|
||||||
|
quarantine = QuarantineRef()
|
||||||
|
pool = newClone(AttestationPool.init(chainDag, quarantine))
|
||||||
|
state = newClone(loadTailState(chainDag))
|
||||||
|
# Slot 0 is a finalized slot - won't be making attestations for it..
|
||||||
|
check:
|
||||||
|
process_slots(state.data, state.data.data.slot + 1)
|
||||||
|
|
||||||
|
wrappedTimedTest "Validation sanity":
|
||||||
|
chainDag.updateFlags.incl {skipBLSValidation}
|
||||||
|
|
||||||
|
var
|
||||||
|
cache: StateCache
|
||||||
|
for blck in makeTestBlocks(
|
||||||
|
chainDag.headState.data, chainDag.head.root, cache,
|
||||||
|
int(SLOTS_PER_EPOCH * 5), false):
|
||||||
|
let added = chainDag.addRawBlock(quarantine, blck) do (
|
||||||
|
blckRef: BlockRef, signedBlock: SignedBeaconBlock,
|
||||||
|
epochRef: EpochRef, state: HashedBeaconState):
|
||||||
|
# Callback add to fork choice if valid
|
||||||
|
pool[].addForkChoice(epochRef, blckRef, signedBlock.message, blckRef.slot)
|
||||||
|
|
||||||
|
check: added.isOk()
|
||||||
|
chainDag.updateHead(added[])
|
||||||
|
|
||||||
|
var
|
||||||
|
# Create an attestation for slot 1!
|
||||||
|
beacon_committee = get_beacon_committee(
|
||||||
|
chainDag.headState.data.data, chainDag.head.slot, 0.CommitteeIndex, cache)
|
||||||
|
attestation = makeAttestation(
|
||||||
|
chainDag.headState.data.data, chainDag.head.root, beacon_committee[0], cache)
|
||||||
|
|
||||||
|
committees_per_slot =
|
||||||
|
get_committee_count_per_slot(chainDag.headState.data.data,
|
||||||
|
attestation.data.slot.epoch, cache)
|
||||||
|
|
||||||
|
subnet = compute_subnet_for_attestation(
|
||||||
|
committees_per_slot,
|
||||||
|
attestation.data.slot, attestation.data.index.CommitteeIndex)
|
||||||
|
|
||||||
|
beaconTime = attestation.data.slot.toBeaconTime()
|
||||||
|
|
||||||
|
check:
|
||||||
|
validateAttestation(pool[], attestation, beaconTime, subnet).isOk
|
||||||
|
|
||||||
|
# Wrong subnet
|
||||||
|
validateAttestation(pool[], attestation, beaconTime, subnet + 1).isErr
|
||||||
|
|
||||||
|
# Too far in the future
|
||||||
|
validateAttestation(
|
||||||
|
pool[], attestation, beaconTime - 1.seconds, subnet + 1).isErr
|
||||||
|
|
||||||
|
# Too far in the past
|
||||||
|
validateAttestation(
|
||||||
|
pool[], attestation,
|
||||||
|
beaconTime - (SECONDS_PER_SLOT * SLOTS_PER_EPOCH - 1).int.seconds,
|
||||||
|
subnet + 1).isErr
|
||||||
|
|
|
@ -118,7 +118,7 @@ suiteReport "Block pool processing" & preset():
|
||||||
db = makeTestDB(SLOTS_PER_EPOCH)
|
db = makeTestDB(SLOTS_PER_EPOCH)
|
||||||
dag = init(ChainDAGRef, defaultRuntimePreset, db)
|
dag = init(ChainDAGRef, defaultRuntimePreset, db)
|
||||||
quarantine = QuarantineRef()
|
quarantine = QuarantineRef()
|
||||||
stateData = newClone(dag.loadTailState())
|
stateData = newClone(dag.headState)
|
||||||
cache = StateCache()
|
cache = StateCache()
|
||||||
b1 = addTestBlock(stateData.data, dag.tail.root, cache)
|
b1 = addTestBlock(stateData.data, dag.tail.root, cache)
|
||||||
b1Root = hash_tree_root(b1.message)
|
b1Root = hash_tree_root(b1.message)
|
||||||
|
@ -330,22 +330,23 @@ suiteReport "chain DAG finalization tests" & preset():
|
||||||
process_slots(
|
process_slots(
|
||||||
tmpState[], tmpState.data.slot + (5 * SLOTS_PER_EPOCH).uint64)
|
tmpState[], tmpState.data.slot + (5 * SLOTS_PER_EPOCH).uint64)
|
||||||
|
|
||||||
let lateBlock = makeTestBlock(tmpState[], dag.head.root, cache)
|
let lateBlock = addTestBlock(tmpState[], dag.head.root, cache)
|
||||||
block:
|
block:
|
||||||
let status = dag.addRawBlock(quarantine, blck, nil)
|
let status = dag.addRawBlock(quarantine, blck, nil)
|
||||||
check: status.isOk()
|
check: status.isOk()
|
||||||
|
|
||||||
|
assign(tmpState[], dag.headState.data)
|
||||||
|
|
||||||
for i in 0 ..< (SLOTS_PER_EPOCH * 6):
|
for i in 0 ..< (SLOTS_PER_EPOCH * 6):
|
||||||
if i == 1:
|
if i == 1:
|
||||||
# There are 2 heads now because of the fork at slot 1
|
# There are 2 heads now because of the fork at slot 1
|
||||||
check:
|
check:
|
||||||
dag.heads.len == 2
|
dag.heads.len == 2
|
||||||
|
|
||||||
blck = makeTestBlock(
|
blck = addTestBlock(
|
||||||
dag.headState.data, dag.head.root, cache,
|
tmpState[], dag.head.root, cache,
|
||||||
attestations = makeFullAttestations(
|
attestations = makeFullAttestations(
|
||||||
dag.headState.data.data, dag.head.root,
|
tmpState[].data, dag.head.root, tmpState[].data.slot, cache, {}))
|
||||||
dag.headState.data.data.slot, cache, {}))
|
|
||||||
let added = dag.addRawBlock(quarantine, blck, nil)
|
let added = dag.addRawBlock(quarantine, blck, nil)
|
||||||
check: added.isOk()
|
check: added.isOk()
|
||||||
dag.updateHead(added[])
|
dag.updateHead(added[])
|
||||||
|
@ -436,15 +437,10 @@ suiteReport "chain DAG finalization tests" & preset():
|
||||||
quarantine = QuarantineRef()
|
quarantine = QuarantineRef()
|
||||||
cache = StateCache()
|
cache = StateCache()
|
||||||
|
|
||||||
wrappedTimedTest "init with gaps" & preset():
|
timedTest "init with gaps" & preset():
|
||||||
for i in 0 ..< (SLOTS_PER_EPOCH * 6 - 2):
|
for blck in makeTestBlocks(
|
||||||
var
|
dag.headState.data, dag.head.root, cache, int(SLOTS_PER_EPOCH * 6 - 2),
|
||||||
blck = makeTestBlock(
|
true):
|
||||||
dag.headState.data, dag.head.root, cache,
|
|
||||||
attestations = makeFullAttestations(
|
|
||||||
dag.headState.data.data, dag.head.root,
|
|
||||||
dag.headState.data.data.slot, cache, {}))
|
|
||||||
|
|
||||||
let added = dag.addRawBlock(quarantine, blck, nil)
|
let added = dag.addRawBlock(quarantine, blck, nil)
|
||||||
check: added.isOk()
|
check: added.isOk()
|
||||||
dag.updateHead(added[])
|
dag.updateHead(added[])
|
||||||
|
|
|
@ -248,3 +248,27 @@ proc makeFullAttestations*(
|
||||||
|
|
||||||
attestation.signature = agg.finish()
|
attestation.signature = agg.finish()
|
||||||
result.add attestation
|
result.add attestation
|
||||||
|
|
||||||
|
iterator makeTestBlocks*(
|
||||||
|
state: HashedBeaconState,
|
||||||
|
parent_root: Eth2Digest,
|
||||||
|
cache: var StateCache,
|
||||||
|
blocks: int,
|
||||||
|
attested: bool,
|
||||||
|
flags: set[UpdateFlag] = {}
|
||||||
|
): SignedBeaconBlock =
|
||||||
|
var
|
||||||
|
state = assignClone(state)
|
||||||
|
parent_root = parent_root
|
||||||
|
for _ in 0..<blocks:
|
||||||
|
let attestations = if attested:
|
||||||
|
makeFullAttestations(
|
||||||
|
state[].data, parent_root,
|
||||||
|
state[].data.slot, cache, flags)
|
||||||
|
else:
|
||||||
|
@[]
|
||||||
|
|
||||||
|
let blck = addTestBlock(
|
||||||
|
state[], parent_root, cache, attestations = attestations, flags = flags)
|
||||||
|
yield blck
|
||||||
|
parent_root = blck.root
|
||||||
|
|
Loading…
Reference in New Issue