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
|
# 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,
|
func is_aggregator(state: BeaconState, slot: Slot, index: CommitteeIndex,
|
||||||
slot_signature: ValidatorSig): bool =
|
slot_signature: ValidatorSig, cache: var StateCache): bool =
|
||||||
var cache = get_empty_per_epoch_cache()
|
|
||||||
|
|
||||||
let
|
let
|
||||||
committee = get_beacon_committee(state, slot, index, cache)
|
committee = get_beacon_committee(state, slot, index, cache)
|
||||||
modulo = max(1, len(committee) div TARGET_AGGREGATORS_PER_COMMITTEE).uint64
|
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] =
|
privkey: ValidatorPrivKey, trailing_distance: uint64): Option[AggregateAndProof] =
|
||||||
doAssert state.slot >= trailing_distance
|
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
|
doAssert trailing_distance <= ATTESTATION_PROPAGATION_SLOT_RANGE
|
||||||
|
|
||||||
let
|
let
|
||||||
|
@ -49,8 +47,9 @@ proc aggregate_attestations*(
|
||||||
|
|
||||||
# TODO for testing purposes, refactor this into the condition check
|
# TODO for testing purposes, refactor this into the condition check
|
||||||
# and just calculation
|
# and just calculation
|
||||||
# https://github.com/ethereum/eth2.0-specs/blob/v0.11.1/specs/phase0/validator.md#aggregation-selection
|
# https://github.com/ethereum/eth2.0-specs/blob/v0.12.1/specs/phase0/validator.md#aggregation-selection
|
||||||
if not is_aggregator(state, slot, index, slot_signature):
|
var cache = get_empty_per_epoch_cache()
|
||||||
|
if not is_aggregator(state, slot, index, slot_signature, cache):
|
||||||
return none(AggregateAndProof)
|
return none(AggregateAndProof)
|
||||||
|
|
||||||
# https://github.com/ethereum/eth2.0-specs/blob/v0.11.1/specs/phase0/validator.md#attestation-data
|
# https://github.com/ethereum/eth2.0-specs/blob/v0.11.1/specs/phase0/validator.md#attestation-data
|
||||||
|
@ -67,7 +66,7 @@ proc aggregate_attestations*(
|
||||||
for attestation in getAttestationsForBlock(pool, state):
|
for attestation in getAttestationsForBlock(pool, state):
|
||||||
# getAttestationsForBlock(...) already aggregates
|
# getAttestationsForBlock(...) already aggregates
|
||||||
if attestation.data == attestation_data:
|
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(
|
return some(AggregateAndProof(
|
||||||
aggregator_index: index.uint64,
|
aggregator_index: index.uint64,
|
||||||
aggregate: attestation,
|
aggregate: attestation,
|
||||||
|
@ -160,3 +159,120 @@ proc isValidAttestation*(
|
||||||
return false
|
return false
|
||||||
|
|
||||||
true
|
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
|
return false
|
||||||
node.attestationPool.isValidAttestation(attestation, slot, committeeIndex)
|
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]] = @[]
|
var attestationSubscriptions: seq[Future[void]] = @[]
|
||||||
|
|
||||||
# https://github.com/ethereum/eth2.0-specs/blob/v0.12.1/specs/phase0/p2p-interface.md#attestations-and-aggregation
|
# 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)
|
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)
|
waitFor allFutures(attestationSubscriptions)
|
||||||
|
|
||||||
proc stop*(node: BeaconNode) =
|
proc stop*(node: BeaconNode) =
|
||||||
|
|
|
@ -640,17 +640,10 @@ template withEpochState*(
|
||||||
## cache is unsafe outside of block.
|
## cache is unsafe outside of block.
|
||||||
## TODO async transformations will lead to a race where cache gets updated
|
## TODO async transformations will lead to a race where cache gets updated
|
||||||
## while waiting for future to complete - catch this here somehow?
|
## while waiting for future to complete - catch this here somehow?
|
||||||
|
# TODO implement the looser constraints allowed by epoch, not precise slot target
|
||||||
for ancestor in get_ancestors_in_epoch(blockSlot):
|
# allow expressing preference to opt-in to looser constraints regardless
|
||||||
if getStateDataCached(dag, cache, ancestor):
|
dag.withState(cache, blockSlot):
|
||||||
break
|
body
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
body
|
|
||||||
|
|
||||||
proc updateStateData*(dag: CandidateChains, state: var StateData, bs: BlockSlot) =
|
proc updateStateData*(dag: CandidateChains, state: var StateData, bs: BlockSlot) =
|
||||||
## Rewind or advance state such that it matches the given block and slot -
|
## Rewind or advance state such that it matches the given block and slot -
|
||||||
|
|
|
@ -28,6 +28,18 @@ func get_slot_signature*(
|
||||||
|
|
||||||
blsSign(privKey, signing_root.data)
|
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
|
# https://github.com/ethereum/eth2.0-specs/blob/v0.12.1/specs/phase0/validator.md#randao-reveal
|
||||||
func get_epoch_signature*(
|
func get_epoch_signature*(
|
||||||
fork: Fork, genesis_validators_root: Eth2Digest, epoch: Epoch,
|
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)
|
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
|
# https://github.com/ethereum/eth2.0-specs/blob/v0.12.1/specs/phase0/validator.md#aggregate-signature
|
||||||
func get_attestation_signature*(
|
func get_attestation_signature*(
|
||||||
fork: Fork, genesis_validators_root: Eth2Digest,
|
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 overlaps*(a, b: BitList): bool = overlaps(BitSeq(a), BitSeq(b))
|
||||||
template combine*(a: var BitList, b: BitList) = combine(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 isSubsetOf*(a, b: BitList): bool = isSubsetOf(BitSeq(a), BitSeq(b))
|
||||||
|
template isZeros*(x: BitList): bool = isZeros(BitSeq(x))
|
||||||
template `$`*(a: BitList): string = $(BitSeq(a))
|
template `$`*(a: BitList): string = $(BitSeq(a))
|
||||||
|
|
||||||
iterator items*(x: BitList): bool =
|
iterator items*(x: BitList): bool =
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
# beacon_chain
|
# beacon_chain
|
||||||
# Copyright (c) 2018-2019 Status Research & Development GmbH
|
# Copyright (c) 2018-2020 Status Research & Development GmbH
|
||||||
# Licensed and distributed under either of
|
# Licensed and distributed under either of
|
||||||
# * MIT license (license terms in the root directory or at https://opensource.org/licenses/MIT).
|
# * 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).
|
# * 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.} =
|
func round_multiple_down*(x: uint64, n: uint64): uint64 {.inline.} =
|
||||||
## Round the input to the previous multiple of "n"
|
## Round the input to the previous multiple of "n"
|
||||||
result = x - x mod n
|
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
|
|
||||||
|
|
|
@ -112,7 +112,7 @@ proc mockAttestation*(
|
||||||
flags: UpdateFlags = {}): Attestation {.inline.}=
|
flags: UpdateFlags = {}): Attestation {.inline.}=
|
||||||
mockAttestationImpl(state, slot, flags)
|
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()
|
var cache = get_empty_per_epoch_cache()
|
||||||
let beacon_committee = get_beacon_committee(
|
let beacon_committee = get_beacon_committee(
|
||||||
state,
|
state,
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
# beacon_chain
|
# beacon_chain
|
||||||
# Copyright (c) 2018-2019 Status Research & Development GmbH
|
# Copyright (c) 2018-2020 Status Research & Development GmbH
|
||||||
# Licensed and distributed under either of
|
# Licensed and distributed under either of
|
||||||
# * MIT license (license terms in the root directory or at https://opensource.org/licenses/MIT).
|
# * 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).
|
# * 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()[]
|
let pair = newKeyPair()[]
|
||||||
privkeys[i] = pair.priv
|
privkeys[i] = pair.priv
|
||||||
|
|
||||||
proc genMockPubKeys(
|
func genMockPubKeys(
|
||||||
pubkeys: var array[MIN_GENESIS_ACTIVE_VALIDATOR_COUNT, ValidatorPubKey],
|
pubkeys: var array[MIN_GENESIS_ACTIVE_VALIDATOR_COUNT, ValidatorPubKey],
|
||||||
privkeys: array[MIN_GENESIS_ACTIVE_VALIDATOR_COUNT, ValidatorPrivKey]
|
privkeys: array[MIN_GENESIS_ACTIVE_VALIDATOR_COUNT, ValidatorPrivKey]
|
||||||
) =
|
) =
|
||||||
|
|
Loading…
Reference in New Issue