implement aggregated attestation receiving/validating (#1272)
* implement aggregated attestation receiving/validating * document the conditions without explicit implementations in isValidAggregatedAttestation()
This commit is contained in:
parent
27ba123d8a
commit
c64737e7f2
|
@ -20,9 +20,7 @@ logScope:
|
|||
|
||||
# https://github.com/ethereum/eth2.0-specs/blob/v0.11.1/specs/phase0/validator.md#aggregation-selection
|
||||
func is_aggregator(state: BeaconState, slot: Slot, index: CommitteeIndex,
|
||||
slot_signature: ValidatorSig): bool =
|
||||
var cache = get_empty_per_epoch_cache()
|
||||
|
||||
slot_signature: ValidatorSig, cache: var StateCache): bool =
|
||||
let
|
||||
committee = get_beacon_committee(state, slot, index, cache)
|
||||
modulo = max(1, len(committee) div TARGET_AGGREGATORS_PER_COMMITTEE).uint64
|
||||
|
@ -33,7 +31,7 @@ proc aggregate_attestations*(
|
|||
privkey: ValidatorPrivKey, trailing_distance: uint64): Option[AggregateAndProof] =
|
||||
doAssert state.slot >= trailing_distance
|
||||
|
||||
# https://github.com/ethereum/eth2.0-specs/blob/v0.11.1/specs/phase0/p2p-interface.md#configuration
|
||||
# https://github.com/ethereum/eth2.0-specs/blob/v0.12.1/specs/phase0/p2p-interface.md#configuration
|
||||
doAssert trailing_distance <= ATTESTATION_PROPAGATION_SLOT_RANGE
|
||||
|
||||
let
|
||||
|
@ -49,8 +47,9 @@ proc aggregate_attestations*(
|
|||
|
||||
# TODO for testing purposes, refactor this into the condition check
|
||||
# and just calculation
|
||||
# https://github.com/ethereum/eth2.0-specs/blob/v0.11.1/specs/phase0/validator.md#aggregation-selection
|
||||
if not is_aggregator(state, slot, index, slot_signature):
|
||||
# https://github.com/ethereum/eth2.0-specs/blob/v0.12.1/specs/phase0/validator.md#aggregation-selection
|
||||
var cache = get_empty_per_epoch_cache()
|
||||
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
|
||||
|
@ -67,7 +66,7 @@ proc aggregate_attestations*(
|
|||
for attestation in getAttestationsForBlock(pool, state):
|
||||
# getAttestationsForBlock(...) already aggregates
|
||||
if attestation.data == attestation_data:
|
||||
# https://github.com/ethereum/eth2.0-specs/blob/v0.11.1/specs/phase0/validator.md#aggregateandproof
|
||||
# https://github.com/ethereum/eth2.0-specs/blob/v0.12.1/specs/phase0/validator.md#aggregateandproof
|
||||
return some(AggregateAndProof(
|
||||
aggregator_index: index.uint64,
|
||||
aggregate: attestation,
|
||||
|
@ -160,3 +159,120 @@ proc isValidAttestation*(
|
|||
return false
|
||||
|
||||
true
|
||||
|
||||
# https://github.com/ethereum/eth2.0-specs/blob/v0.12.1/specs/phase0/p2p-interface.md#global-topics
|
||||
proc isValidAggregatedAttestation*(
|
||||
pool: var AttestationPool,
|
||||
signedAggregateAndProof: SignedAggregateAndProof,
|
||||
current_slot: Slot): bool =
|
||||
let
|
||||
aggregate_and_proof = signedAggregateAndProof.message
|
||||
aggregate = aggregate_and_proof.aggregate
|
||||
|
||||
# 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
|
||||
# ATTESTATION_PROPAGATION_SLOT_RANGE slots (with a
|
||||
# MAXIMUM_GOSSIP_CLOCK_DISPARITY allowance) -- i.e. aggregate.data.slot +
|
||||
# ATTESTATION_PROPAGATION_SLOT_RANGE >= current_slot >= aggregate.data.slot
|
||||
if not (aggregate.data.slot + ATTESTATION_PROPAGATION_SLOT_RANGE >=
|
||||
current_slot and current_slot >= aggregate.data.slot):
|
||||
debug "isValidAggregatedAttestation: aggregation.data.slot not within ATTESTATION_PROPAGATION_SLOT_RANGE"
|
||||
return false
|
||||
|
||||
# [IGNORE] The valid aggregate attestation defined by
|
||||
# hash_tree_root(aggregate) has not already been seen (via aggregate gossip,
|
||||
# within a verified block, or through the creation of an equivalent aggregate
|
||||
# locally).
|
||||
#
|
||||
# This is [IGNORE] and already checked by attestation pool when aggregate is
|
||||
# added.
|
||||
|
||||
# [IGNORE] The aggregate is the first valid aggregate received for the
|
||||
# aggregator with index aggregate_and_proof.aggregator_index for the epoch
|
||||
# aggregate.data.target.epoch.
|
||||
#
|
||||
# This is [IGNORE] and already effectively checked by attestation pool upon
|
||||
# attempting to resolve attestations.
|
||||
|
||||
# [REJECT] The block being voted for (aggregate.data.beacon_block_root)
|
||||
# passes validation.
|
||||
let attestationBlck = pool.blockPool.getRef(aggregate.data.beacon_block_root)
|
||||
if attestationBlck.isNil:
|
||||
debug "isValidAggregatedAttestation: block doesn't exist in block pool"
|
||||
pool.blockPool.addMissing(aggregate.data.beacon_block_root)
|
||||
return false
|
||||
|
||||
# [REJECT] The attestation has participants -- that is,
|
||||
# len(get_attesting_indices(state, aggregate.data, aggregate.aggregation_bits)) >= 1.
|
||||
#
|
||||
# get_attesting_indices() is:
|
||||
# committee = get_beacon_committee(state, data.slot, data.index)
|
||||
# return set(index for i, index in enumerate(committee) if bits[i])
|
||||
#
|
||||
# the attestation doesn't have participants is iff either:
|
||||
# (1) the aggregation bits are all 0; or
|
||||
# (2) the non-zero aggregation bits don't overlap with extant committee
|
||||
# members, i.e. they counts don't match.
|
||||
# But (2) would reflect an invalid aggregation in other ways, so reject it
|
||||
# either way.
|
||||
if isZeros(aggregate.aggregation_bits):
|
||||
debug "isValidAggregatedAttestation: attestation has no or invalid aggregation bits"
|
||||
return false
|
||||
|
||||
# [REJECT] aggregate_and_proof.selection_proof selects the validator as an
|
||||
# aggregator for the slot -- i.e. is_aggregator(state, aggregate.data.slot,
|
||||
# aggregate.data.index, aggregate_and_proof.selection_proof) returns True.
|
||||
# TODO use withEpochState when it works more reliably
|
||||
pool.blockPool.withState(
|
||||
pool.blockPool.tmpState,
|
||||
BlockSlot(blck: attestationBlck, slot: aggregate.data.slot)):
|
||||
var cache = getEpochCache(blck, state)
|
||||
if not is_aggregator(
|
||||
state, aggregate.data.slot, aggregate.data.index.CommitteeIndex,
|
||||
aggregate_and_proof.selection_proof, cache):
|
||||
debug "isValidAggregatedAttestation: incorrect aggregator"
|
||||
return false
|
||||
|
||||
# [REJECT] The aggregator's validator index is within the committee -- i.e.
|
||||
# aggregate_and_proof.aggregator_index in get_beacon_committee(state,
|
||||
# aggregate.data.slot, aggregate.data.index).
|
||||
if aggregate_and_proof.aggregator_index.ValidatorIndex notin
|
||||
get_beacon_committee(
|
||||
state, aggregate.data.slot, aggregate.data.index.CommitteeIndex, cache):
|
||||
debug "isValidAggregatedAttestation: aggregator's validator index not in committee"
|
||||
return false
|
||||
|
||||
# [REJECT] The aggregate_and_proof.selection_proof is a valid signature of the
|
||||
# aggregate.data.slot by the validator with index
|
||||
# aggregate_and_proof.aggregator_index.
|
||||
# get_slot_signature(state, aggregate.data.slot, privkey)
|
||||
if aggregate_and_proof.aggregator_index >= state.validators.len.uint64:
|
||||
debug "isValidAggregatedAttestation: invalid aggregator_index"
|
||||
return false
|
||||
|
||||
if not verify_slot_signature(
|
||||
state.fork, state.genesis_validators_root, aggregate.data.slot,
|
||||
state.validators[aggregate_and_proof.aggregator_index].pubkey,
|
||||
aggregate_and_proof.selection_proof):
|
||||
debug "isValidAggregatedAttestation: selection_proof signature verification failed"
|
||||
return false
|
||||
|
||||
# [REJECT] The aggregator signature, signed_aggregate_and_proof.signature, is valid.
|
||||
if not verify_aggregate_and_proof_signature(
|
||||
state.fork, state.genesis_validators_root, aggregate_and_proof,
|
||||
state.validators[aggregate_and_proof.aggregator_index].pubkey,
|
||||
signed_aggregate_and_proof.signature):
|
||||
debug "isValidAggregatedAttestation: signed_aggregate_and_proof signature verification failed"
|
||||
return false
|
||||
|
||||
# [REJECT] The signature of aggregate is valid.
|
||||
if not is_valid_indexed_attestation(
|
||||
state, get_indexed_attestation(state, aggregate, cache), {}):
|
||||
debug "isValidAggregatedAttestation: aggregate signature verification failed"
|
||||
return false
|
||||
|
||||
debug "isValidAggregatedAttestation: succeeded"
|
||||
true
|
||||
|
|
|
@ -751,6 +751,13 @@ proc installAttestationHandlers(node: BeaconNode) =
|
|||
return false
|
||||
node.attestationPool.isValidAttestation(attestation, slot, committeeIndex)
|
||||
|
||||
proc aggregatedAttestationValidator(
|
||||
signedAggregateAndProof: SignedAggregateAndProof): bool =
|
||||
let (afterGenesis, slot) = node.beaconClock.now().toSlot()
|
||||
if not afterGenesis:
|
||||
return false
|
||||
node.attestationPool.isValidAggregatedAttestation(signedAggregateAndProof, slot)
|
||||
|
||||
var attestationSubscriptions: seq[Future[void]] = @[]
|
||||
|
||||
# https://github.com/ethereum/eth2.0-specs/blob/v0.12.1/specs/phase0/p2p-interface.md#attestations-and-aggregation
|
||||
|
@ -764,6 +771,14 @@ proc installAttestationHandlers(node: BeaconNode) =
|
|||
attestationValidator(attestation, ci)
|
||||
))
|
||||
|
||||
attestationSubscriptions.add(node.network.subscribe(
|
||||
getAggregateAndProofsTopic(node.forkDigest),
|
||||
proc(signedAggregateAndProof: SignedAggregateAndProof) =
|
||||
attestationHandler(signedAggregateAndProof.message.aggregate),
|
||||
proc(signedAggregateAndProof: SignedAggregateAndProof): bool =
|
||||
aggregatedAttestationValidator(signedAggregateAndProof)
|
||||
))
|
||||
|
||||
waitFor allFutures(attestationSubscriptions)
|
||||
|
||||
proc stop*(node: BeaconNode) =
|
||||
|
|
|
@ -640,16 +640,9 @@ template withEpochState*(
|
|||
## cache is unsafe outside of block.
|
||||
## TODO async transformations will lead to a race where cache gets updated
|
||||
## while waiting for future to complete - catch this here somehow?
|
||||
|
||||
for ancestor in get_ancestors_in_epoch(blockSlot):
|
||||
if getStateDataCached(dag, cache, ancestor):
|
||||
break
|
||||
|
||||
template hashedState(): HashedBeaconState {.inject, used.} = cache.data
|
||||
template state(): BeaconState {.inject, used.} = cache.data.data
|
||||
template blck(): BlockRef {.inject, used.} = cache.blck
|
||||
template root(): Eth2Digest {.inject, used.} = cache.data.root
|
||||
|
||||
# TODO implement the looser constraints allowed by epoch, not precise slot target
|
||||
# allow expressing preference to opt-in to looser constraints regardless
|
||||
dag.withState(cache, blockSlot):
|
||||
body
|
||||
|
||||
proc updateStateData*(dag: CandidateChains, state: var StateData, bs: BlockSlot) =
|
||||
|
|
|
@ -28,6 +28,18 @@ func get_slot_signature*(
|
|||
|
||||
blsSign(privKey, signing_root.data)
|
||||
|
||||
func verify_slot_signature*(
|
||||
fork: Fork, genesis_validators_root: Eth2Digest, slot: Slot,
|
||||
pubkey: ValidatorPubKey, signature: SomeSig): bool =
|
||||
withTrust(signature):
|
||||
let
|
||||
epoch = compute_epoch_at_slot(slot)
|
||||
domain = get_domain(
|
||||
fork, DOMAIN_SELECTION_PROOF, epoch, genesis_validators_root)
|
||||
signing_root = compute_signing_root(slot, domain)
|
||||
|
||||
blsVerify(pubkey, signing_root.data, signature)
|
||||
|
||||
# https://github.com/ethereum/eth2.0-specs/blob/v0.12.1/specs/phase0/validator.md#randao-reveal
|
||||
func get_epoch_signature*(
|
||||
fork: Fork, genesis_validators_root: Eth2Digest, epoch: Epoch,
|
||||
|
@ -86,6 +98,18 @@ func get_aggregate_and_proof_signature*(fork: Fork, genesis_validators_root: Eth
|
|||
|
||||
blsSign(privKey, signing_root.data)
|
||||
|
||||
func verify_aggregate_and_proof_signature*(fork: Fork, genesis_validators_root: Eth2Digest,
|
||||
aggregate_and_proof: AggregateAndProof,
|
||||
pubkey: ValidatorPubKey, signature: SomeSig): bool =
|
||||
withTrust(signature):
|
||||
let
|
||||
epoch = compute_epoch_at_slot(aggregate_and_proof.aggregate.data.slot)
|
||||
domain = get_domain(
|
||||
fork, DOMAIN_AGGREGATE_AND_PROOF, epoch, genesis_validators_root)
|
||||
signing_root = compute_signing_root(aggregate_and_proof, domain)
|
||||
|
||||
blsVerify(pubKey, signing_root.data, signature)
|
||||
|
||||
# https://github.com/ethereum/eth2.0-specs/blob/v0.12.1/specs/phase0/validator.md#aggregate-signature
|
||||
func get_attestation_signature*(
|
||||
fork: Fork, genesis_validators_root: Eth2Digest,
|
||||
|
|
|
@ -136,6 +136,7 @@ template clearBit*(x: var BitList, idx: int) = clearBit(BitSeq(x), idx)
|
|||
template overlaps*(a, b: BitList): bool = overlaps(BitSeq(a), BitSeq(b))
|
||||
template combine*(a: var BitList, b: BitList) = combine(BitSeq(a), BitSeq(b))
|
||||
template isSubsetOf*(a, b: BitList): bool = isSubsetOf(BitSeq(a), BitSeq(b))
|
||||
template isZeros*(x: BitList): bool = isZeros(BitSeq(x))
|
||||
template `$`*(a: BitList): string = $(BitSeq(a))
|
||||
|
||||
iterator items*(x: BitList): bool =
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
# beacon_chain
|
||||
# Copyright (c) 2018-2019 Status Research & Development GmbH
|
||||
# Copyright (c) 2018-2020 Status Research & Development GmbH
|
||||
# Licensed and distributed under either of
|
||||
# * MIT license (license terms in the root directory or at https://opensource.org/licenses/MIT).
|
||||
# * Apache v2 license (license terms in the root directory or at https://www.apache.org/licenses/LICENSE-2.0).
|
||||
|
@ -7,8 +7,4 @@
|
|||
|
||||
func round_multiple_down*(x: uint64, n: uint64): uint64 {.inline.} =
|
||||
## Round the input to the previous multiple of "n"
|
||||
result = x - x mod n
|
||||
|
||||
func round_multiple_up*(x: uint64, n: uint64): uint64 {.inline.} =
|
||||
## Round the input to the next multiple of "n"
|
||||
result = ((x + n - 1) div n) * n
|
||||
x - x mod n
|
||||
|
|
|
@ -112,7 +112,7 @@ proc mockAttestation*(
|
|||
flags: UpdateFlags = {}): Attestation {.inline.}=
|
||||
mockAttestationImpl(state, slot, flags)
|
||||
|
||||
proc fillAggregateAttestation*(state: BeaconState, attestation: var Attestation) =
|
||||
func fillAggregateAttestation*(state: BeaconState, attestation: var Attestation) =
|
||||
var cache = get_empty_per_epoch_cache()
|
||||
let beacon_committee = get_beacon_committee(
|
||||
state,
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
# beacon_chain
|
||||
# Copyright (c) 2018-2019 Status Research & Development GmbH
|
||||
# Copyright (c) 2018-2020 Status Research & Development GmbH
|
||||
# Licensed and distributed under either of
|
||||
# * MIT license (license terms in the root directory or at https://opensource.org/licenses/MIT).
|
||||
# * Apache v2 license (license terms in the root directory or at https://www.apache.org/licenses/LICENSE-2.0).
|
||||
|
@ -19,7 +19,7 @@ proc genMockPrivKeys(privkeys: var array[MIN_GENESIS_ACTIVE_VALIDATOR_COUNT, Val
|
|||
let pair = newKeyPair()[]
|
||||
privkeys[i] = pair.priv
|
||||
|
||||
proc genMockPubKeys(
|
||||
func genMockPubKeys(
|
||||
pubkeys: var array[MIN_GENESIS_ACTIVE_VALIDATOR_COUNT, ValidatorPubKey],
|
||||
privkeys: array[MIN_GENESIS_ACTIVE_VALIDATOR_COUNT, ValidatorPrivKey]
|
||||
) =
|
||||
|
|
Loading…
Reference in New Issue