update attestation and block validation to v0.12.2; clean up getAncestorAt()/get_ancestor() (#1417)

* update attestation validation to v0.12.2; clean up getAncestorAt()/get_ancestor()

* update beacon block validation to v0.12.2
This commit is contained in:
tersec 2020-08-03 19:47:42 +00:00 committed by GitHub
parent 93d7ddaf23
commit df80071bcf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 90 additions and 67 deletions

View File

@ -54,7 +54,7 @@ OK: 7/7 Fail: 0/7 Skip: 0/7
OK: 5/5 Fail: 0/5 Skip: 0/5
## BlockRef and helpers [Preset: mainnet]
```diff
+ getAncestorAt sanity [Preset: mainnet] OK
+ get_ancestor sanity [Preset: mainnet] OK
+ isAncestorOf sanity [Preset: mainnet] OK
```
OK: 2/2 Fail: 0/2 Skip: 0/2

View File

@ -99,11 +99,14 @@ proc isValidAttestationSlot(
true
func checkPropagationSlotRange(data: AttestationData, current_slot: Slot): bool =
# https://github.com/ethereum/eth2.0-specs/blob/v0.12.2/specs/phase0/p2p-interface.md#beacon_attestation_subnet_id
# TODO clock disparity
# attestation.data.slot + ATTESTATION_PROPAGATION_SLOT_RANGE >=
# current_slot >= attestation.data.slot
(data.slot + ATTESTATION_PROPAGATION_SLOT_RANGE >= current_slot) and
(current_slot >= data.slot)
# https://github.com/ethereum/eth2.0-specs/blob/v0.12.1/specs/phase0/p2p-interface.md#attestation-subnets
# https://github.com/ethereum/eth2.0-specs/blob/v0.12.2/specs/phase0/p2p-interface.md#beacon_attestation_subnet_id
proc isValidAttestation*(
pool: var AttestationPool, attestation: Attestation, current_slot: Slot,
topicCommitteeIndex: uint64): bool =
@ -154,11 +157,8 @@ proc isValidAttestation*(
# 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.
# TODO: consider a "slush pool" of attestations whose blocks have not yet
# propagated - i.e. imagine that attestations are smaller than blocks and
# therefore propagate faster, thus reordering their arrival in some nodes
# 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 "Block not found"
@ -173,16 +173,42 @@ proc isValidAttestation*(
let tgtBlck = pool.chainDag.getRef(attestation.data.target.root)
if tgtBlck.isNil:
debug "Target block not found"
pool.addUnresolved(attestation)
pool.quarantine.addMissing(attestation.data.beacon_block_root)
return
return false
# The current finalized_checkpoint is an ancestor of the block defined by
# attestation.data.beacon_block_root -- i.e. get_ancestor(store,
# attestation.data.beacon_block_root,
# compute_start_slot_at_epoch(store.finalized_checkpoint.epoch)) ==
# store.finalized_checkpoint.root
let
ancestor = get_ancestor(
attestationBlck,
compute_start_slot_at_epoch(
pool.chainDag.headState.data.data.finalized_checkpoint.epoch))
finalized_root =
pool.chainDag.headState.data.data.finalized_checkpoint.root
if ancestor.isNil:
debug "Can't find ancestor block"
return false
# TODO spec doesn't note the pre-finalization case, but maybe necessary
if not (finalized_root in [Eth2Digest(), ancestor.root]):
debug "Incorrect ancestor block",
ancestor_root = ancestor.root,
finalized_root = pool.chainDag.headState.data.data.finalized_checkpoint.root
return false
# TODO this could be any state in the target epoch
pool.chainDag.withState(
pool.chainDag.tmpState,
tgtBlck.atSlot(attestation.data.target.epoch.compute_start_slot_at_epoch)):
# https://github.com/ethereum/eth2.0-specs/blob/v0.12.1/specs/phase0/p2p-interface.md#attestation-subnets
# [REJECT] The attestation is for the correct subnet (i.e.
# compute_subnet_for_attestation(state, attestation) == subnet_id).
# 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.
# compute_subnet_for_attestation(committees_per_slot,
# attestation.data.slot, attestation.data.index) == subnet_id, where
# committees_per_slot = get_committee_count_per_slot(state,
# attestation.data.target.epoch), which may be pre-computed along with the
# committee information for the signature check.
let
epochInfo = blck.getEpochInfo(state)
requiredSubnetIndex =

View File

@ -94,9 +94,11 @@ func isAncestorOf*(a, b: BlockRef): bool =
doAssert b.slot > b.parent.slot
b = b.parent
func getAncestorAt*(blck: BlockRef, slot: Slot): BlockRef =
func get_ancestor*(blck: BlockRef, slot: Slot): BlockRef =
## https://github.com/ethereum/eth2.0-specs/blob/v0.12.2/specs/phase0/fork-choice.md#get_ancestor
## Return the most recent block as of the time at `slot` that not more recent
## than `blck` itself
doAssert not blck.isNil
var blck = blck
@ -115,29 +117,6 @@ 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.12.2/specs/phase0/fork-choice.md#get_ancestor
## Return ancestor at slot, or nil if queried block is older
var blck = blck
var depth = 0
const maxDepth = (100'i64 * 365 * 24 * 60 * 60 div SECONDS_PER_SLOT.int)
while true:
if blck.slot == slot:
return blck
if blck.slot < slot:
return nil
if blck.parent.isNil:
return nil
doAssert depth < maxDepth
depth += 1
blck = blck.parent
iterator get_ancestors_in_epoch(blockSlot: BlockSlot): BlockSlot =
let min_slot =
blockSlot.slot.compute_epoch_at_slot.compute_start_slot_at_epoch
@ -162,7 +141,7 @@ func atSlot*(blck: BlockRef, slot: Slot): BlockSlot =
## particular moment in time, or when imagining what it will look like in the
## near future if nothing happens (such as when looking ahead for the next
## block proposal)
BlockSlot(blck: blck.getAncestorAt(slot), slot: slot)
BlockSlot(blck: blck.get_ancestor(slot), slot: slot)
func atEpochStart*(blck: BlockRef, epoch: Epoch): BlockSlot =
## Return the BlockSlot corresponding to the first slot in the given epoch

View File

@ -253,7 +253,7 @@ proc addRawBlock*(
return err MissingParent
# https://github.com/ethereum/eth2.0-specs/blob/v0.11.1/specs/phase0/p2p-interface.md#global-topics
# https://github.com/ethereum/eth2.0-specs/blob/v0.12.2/specs/phase0/p2p-interface.md#beacon_block
proc isValidBeaconBlock*(
dag: ChainDAGRef, quarantine: var QuarantineRef,
signed_beacon_block: SignedBeaconBlock, current_slot: Slot,
@ -277,16 +277,15 @@ proc isValidBeaconBlock*(
current_slot
return err(Invalid)
# The block is from a slot greater than the latest finalized slot (with a
# MAXIMUM_GOSSIP_CLOCK_DISPARITY allowance) -- i.e. validate that
# signed_beacon_block.message.slot >
# [IGNORE] The block is from a slot greater than the latest finalized slot --
# i.e. validate that signed_beacon_block.message.slot >
# compute_start_slot_at_epoch(state.finalized_checkpoint.epoch)
if not (signed_beacon_block.message.slot > dag.finalizedHead.slot):
debug "block is not from a slot greater than the latest finalized slot"
return err(Invalid)
# The block is the first block with valid signature received for the proposer
# for the slot, signed_beacon_block.message.slot.
# [IGNORE] The block is the first block with valid signature received for the
# proposer for the slot, signed_beacon_block.message.slot.
#
# While this condition is similar to the proposer slashing condition at
# https://github.com/ethereum/eth2.0-specs/blob/v0.12.2/specs/phase0/validator.md#proposer-slashing
@ -325,19 +324,14 @@ proc isValidBeaconBlock*(
existing_block = shortLog(blck.message)
return err(Invalid)
# If this block doesn't have a parent we know about, we can't/don't really
# trace it back to a known-good state/checkpoint to verify its prevenance;
# while one could getOrResolve to queue up searching for missing parent it
# might not be the best place. As much as feasible, this function aims for
# answering yes/no, not queuing other action or otherwise altering state.
# [IGNORE] The block's parent (defined by block.parent_root) has been seen
# (via both gossip and non-gossip sources) (a client MAY queue blocks for
# processing once the parent block is retrieved).
#
# And implicitly:
# [REJECT] The block's parent (defined by block.parent_root) passes validation.
let parent_ref = dag.getRef(signed_beacon_block.message.parent_root)
if parent_ref.isNil:
# This doesn't mean a block is forever invalid, only that we haven't seen
# its ancestor blocks yet. While that means for now it should be blocked,
# at least, from libp2p propagation, it shouldn't be ignored. TODO, if in
# the future this block moves from pending to being resolved, consider if
# it's worth broadcasting it then.
# Pending dag gets checked via `ChainDAGRef.add(...)` later, and relevant
# checks are performed there. In usual paths beacon_node adds blocks via
# ChainDAGRef.add(...) directly, with no additional validity checks. TODO,
@ -347,8 +341,29 @@ proc isValidBeaconBlock*(
quarantine.add(dag, signed_beacon_block)
return err(MissingParent)
# The proposer signature, signed_beacon_block.signature, is valid with
# respect to the proposer_index pubkey.
# [REJECT] The current finalized_checkpoint is an ancestor of block -- i.e.
# get_ancestor(store, block.parent_root,
# compute_start_slot_at_epoch(store.finalized_checkpoint.epoch)) ==
# store.finalized_checkpoint.root
let
finalized_checkpoint = dag.headState.data.data.finalized_checkpoint
ancestor = get_ancestor(
parent_ref, compute_start_slot_at_epoch(finalized_checkpoint.epoch))
if ancestor.isNil:
debug "couldn't find ancestor block"
return err(Invalid)
if not (finalized_checkpoint.root in [ancestor.root, Eth2Digest()]):
debug "block not descendent of finalized block"
return err(Invalid)
# [REJECT] The block is proposed by the expected proposer_index for the
# block's slot in the context of the current shuffling (defined by
# parent_root/slot). If the proposer_index cannot immediately be verified
# against the expected shuffling, the block MAY be queued for later
# processing while proposers for the block's branch are calculated -- in such
# a case do not REJECT, instead IGNORE this message.
let
proposer = getProposer(dag, parent_ref, signed_beacon_block.message.slot)
@ -362,6 +377,8 @@ proc isValidBeaconBlock*(
expected_proposer = proposer.get()[0]
return err(Invalid)
# [REJECT] The proposer signature, signed_beacon_block.signature, is valid
# with respect to the proposer_index pubkey.
if not verify_block_signature(
dag.headState.data.data.fork,
dag.headState.data.data.genesis_validators_root,

View File

@ -508,6 +508,7 @@ func get_indexed_attestation*(state: BeaconState, attestation: TrustedAttestatio
# Attestation validation
# ------------------------------------------------------------------------------------------
# https://github.com/ethereum/eth2.0-specs/blob/v0.12.2/specs/phase0/beacon-chain.md#attestations
# https://github.com/ethereum/eth2.0-specs/blob/v0.12.2/specs/phase0/p2p-interface.md#beacon_attestation_subnet_id
func check_attestation_slot_target*(data: AttestationData): Result[void, cstring] =
if not (data.target.epoch == compute_epoch_at_slot(data.slot)):

View File

@ -498,7 +498,7 @@ proc handleValidatorDuties*(
const TRAILING_DISTANCE = 1
let
aggregationSlot = slot - TRAILING_DISTANCE
aggregationHead = getAncestorAt(head, aggregationSlot)
aggregationHead = get_ancestor(head, aggregationSlot)
broadcastAggregatedAttestations(
node, aggregationHead, aggregationSlot, TRAILING_DISTANCE)

View File

@ -35,7 +35,7 @@ suiteReport "BlockRef and helpers" & preset():
not s2.isAncestorOf(s1)
not s1.isAncestorOf(s0)
timedTest "getAncestorAt sanity" & preset():
timedTest "get_ancestor sanity" & preset():
let
s0 = BlockRef(slot: Slot(0))
s1 = BlockRef(slot: Slot(1), parent: s0)
@ -43,17 +43,17 @@ suiteReport "BlockRef and helpers" & preset():
s4 = BlockRef(slot: Slot(4), parent: s2)
check:
s0.getAncestorAt(Slot(0)) == s0
s0.getAncestorAt(Slot(1)) == s0
s0.get_ancestor(Slot(0)) == s0
s0.get_ancestor(Slot(1)) == s0
s1.getAncestorAt(Slot(0)) == s0
s1.getAncestorAt(Slot(1)) == s1
s1.get_ancestor(Slot(0)) == s0
s1.get_ancestor(Slot(1)) == s1
s4.getAncestorAt(Slot(0)) == s0
s4.getAncestorAt(Slot(1)) == s1
s4.getAncestorAt(Slot(2)) == s2
s4.getAncestorAt(Slot(3)) == s2
s4.getAncestorAt(Slot(4)) == s4
s4.get_ancestor(Slot(0)) == s0
s4.get_ancestor(Slot(1)) == s1
s4.get_ancestor(Slot(2)) == s2
s4.get_ancestor(Slot(3)) == s2
s4.get_ancestor(Slot(4)) == s4
suiteReport "BlockSlot and helpers" & preset():
timedTest "atSlot sanity" & preset():