initial attestation aggregation (#769)
* initial attestation aggregation * fix usage of committee index, vs index in committee; uniformly set trailing/following distance; document how the only-broadcast-if mechanism works better and what aggregation already happens, not otherwise sufficiently clear; use correct BlockSlot across epoch boundaries * address inconsistent notion of which slot in past to target for aggregate broadcast; follow 0.11.x aggregate broadcast p2p interface topic * Fix get_slot_signature(...) call after get_domain(...) change required genesis_validators_root * mark all spec references which aren't dealt with in other PRs as v0.11.1 * update two more spec refs to v0.11.1
This commit is contained in:
parent
cd388bc9bb
commit
6eb4f1f39d
|
@ -5,20 +5,12 @@
|
|||
# * Apache v2 license (license terms in the root directory or at http://www.apache.org/licenses/LICENSE-2.0).
|
||||
# at your option. This file may not be copied, modified, or distributed except according to those terms.
|
||||
|
||||
# Have an an aggregated aggregation ready for broadcast at
|
||||
# SECONDS_PER_SLOT * 2 / 3, i.e. 2/3 through relevant slot
|
||||
# intervals.
|
||||
#
|
||||
# The other part is arguably part of attestation pool -- the validation's
|
||||
# something that should be happing on receipt, not aggregation per se. In
|
||||
# that part, check that messages conform -- so, check for each type
|
||||
# https://github.com/ethereum/eth2.0-specs/blob/v0.10.1/specs/phase0/p2p-interface.md#topics-and-messages
|
||||
# specifies. So by the time this calls attestation pool, all validation's
|
||||
# already done.
|
||||
#
|
||||
# Finally, some of the filtering's libp2p stuff. Consistency checks between
|
||||
# topic/message types and GOSSIP_MAX_SIZE -- mostly doesn't belong here, so
|
||||
# while TODO, isn't TODO for this module.
|
||||
|
||||
import
|
||||
options,
|
||||
|
@ -26,11 +18,7 @@ import
|
|||
state_transition_block],
|
||||
./attestation_pool, ./beacon_node_types, ./ssz
|
||||
|
||||
# TODO gossipsub validation lives somewhere, maybe here
|
||||
# TODO add tests, especially for validation
|
||||
# https://github.com/status-im/nim-beacon-chain/issues/122#issuecomment-562479965
|
||||
|
||||
# https://github.com/ethereum/eth2.0-specs/blob/v0.10.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: uint64,
|
||||
slot_signature: ValidatorSig): bool =
|
||||
# TODO index is a CommitteeIndex, aka uint64
|
||||
|
@ -43,34 +31,49 @@ func is_aggregator(state: BeaconState, slot: Slot, index: uint64,
|
|||
|
||||
proc aggregate_attestations*(
|
||||
pool: AttestationPool, state: BeaconState, index: uint64,
|
||||
privkey: ValidatorPrivKey): Option[AggregateAndProof] =
|
||||
privkey: ValidatorPrivKey, trailing_distance: uint64): Option[AggregateAndProof] =
|
||||
# TODO alias CommitteeIndex to actual type then convert various uint64's here
|
||||
|
||||
let
|
||||
slot = state.slot - 2
|
||||
slot_signature = get_slot_signature(state.fork, slot, privkey)
|
||||
doAssert state.slot >= trailing_distance
|
||||
|
||||
# https://github.com/ethereum/eth2.0-specs/blob/v0.11.1/specs/phase0/p2p-interface.md#configuration
|
||||
doAssert trailing_distance <= ATTESTATION_PROPAGATION_SLOT_RANGE
|
||||
|
||||
let
|
||||
slot = state.slot - trailing_distance
|
||||
slot_signature = get_slot_signature(
|
||||
state.fork, state.genesis_validators_root, slot, privkey)
|
||||
|
||||
if slot < 0:
|
||||
return none(AggregateAndProof)
|
||||
doAssert slot + ATTESTATION_PROPAGATION_SLOT_RANGE >= state.slot
|
||||
doAssert state.slot >= slot
|
||||
|
||||
# https://github.com/ethereum/eth2.0-specs/blob/v0.10.1/specs/phase0/validator.md#aggregation-selection
|
||||
# TODO performance issue for future, via get_active_validator_indices(...)
|
||||
doAssert index < get_committee_count_at_slot(state, slot)
|
||||
|
||||
# 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):
|
||||
return none(AggregateAndProof)
|
||||
|
||||
let attestation_data =
|
||||
makeAttestationData(state, slot, index, get_block_root_at_slot(state, slot))
|
||||
# https://github.com/ethereum/eth2.0-specs/blob/v0.11.1/specs/phase0/validator.md#attestation-data
|
||||
# describes how to construct an attestation, which applies for makeAttestationData(...)
|
||||
# TODO this won't actually match anything
|
||||
let attestation_data = AttestationData(
|
||||
slot: slot,
|
||||
index: index,
|
||||
beacon_block_root: get_block_root_at_slot(state, slot))
|
||||
|
||||
# https://github.com/ethereum/eth2.0-specs/blob/v0.10.1/specs/phase0/validator.md#construct-aggregate
|
||||
for attestation in getAttestationsForBlock(pool, state, slot):
|
||||
# https://github.com/ethereum/eth2.0-specs/blob/v0.11.1/specs/phase0/validator.md#construct-aggregate
|
||||
# TODO once EV goes in w/ refactoring of getAttestationsForBlock, pull out the getSlot version and use
|
||||
# it. This is incorrect.
|
||||
for attestation in getAttestationsForBlock(pool, state):
|
||||
# getAttestationsForBlock(...) already aggregates
|
||||
if attestation.data == attestation_data:
|
||||
# https://github.com/ethereum/eth2.0-specs/blob/v0.10.1/specs/phase0/validator.md#aggregateandproof
|
||||
# https://github.com/ethereum/eth2.0-specs/blob/v0.11.1/specs/phase0/validator.md#aggregateandproof
|
||||
return some(AggregateAndProof(
|
||||
aggregator_index: index,
|
||||
aggregate: attestation,
|
||||
selection_proof: slot_signature))
|
||||
|
||||
# TODO in catch-up mode, we could get here, so probably shouldn't assert
|
||||
doAssert false
|
||||
none(AggregateAndProof)
|
||||
|
|
|
@ -15,7 +15,8 @@ import
|
|||
conf, time, state_transition, beacon_chain_db, validator_pool, extras,
|
||||
attestation_pool, block_pool, eth2_network, eth2_discovery,
|
||||
beacon_node_types, mainchain_monitor, version, ssz, ssz/dynamic_navigator,
|
||||
sync_protocol, request_manager, validator_keygen, interop, statusbar
|
||||
sync_protocol, request_manager, validator_keygen, interop, statusbar,
|
||||
attestation_aggregation
|
||||
|
||||
const
|
||||
genesisFile = "genesis.ssz"
|
||||
|
@ -602,6 +603,45 @@ proc verifyFinalization(node: BeaconNode, slot: Slot) =
|
|||
node.blockPool.finalizedHead.blck.slot.compute_epoch_at_slot()
|
||||
doAssert finalizedEpoch + 2 == epoch
|
||||
|
||||
proc broadcastAggregatedAttestations(
|
||||
node: BeaconNode, state: auto, head: var auto, slot: Slot,
|
||||
trailing_distance: uint64) =
|
||||
# The index is via a
|
||||
# locally attested validator. Unlike in handleAttestations(...) there's a
|
||||
# single one at most per slot (because that's how aggregation attestation
|
||||
# works), so the machinery that has to handle looping across, basically a
|
||||
# set of locally attached validators is in principle not necessary, but a
|
||||
# way to organize this. Then the private key for that validator should be
|
||||
# the corresponding one -- whatver they are, they match.
|
||||
|
||||
let
|
||||
bs = BlockSlot(blck: head, slot: slot)
|
||||
committees_per_slot = get_committee_count_at_slot(state, slot)
|
||||
var cache = get_empty_per_epoch_cache()
|
||||
for committee_index in 0'u64..<committees_per_slot:
|
||||
let
|
||||
committee = get_beacon_committee(state, slot, committee_index, cache)
|
||||
|
||||
for index_in_committee, validatorIdx in committee:
|
||||
let validator = node.getAttachedValidator(state, validatorIdx)
|
||||
if validator != nil:
|
||||
# This is slightly strange/inverted control flow, since really it's
|
||||
# going to happen once per slot, but this is the best way to get at
|
||||
# the validator index and private key pair. TODO verify it only has
|
||||
# one isSome() with test.
|
||||
let option_aggregateandproof =
|
||||
aggregate_attestations(node.attestationPool, state,
|
||||
committee_index,
|
||||
# TODO https://github.com/status-im/nim-beacon-chain/issues/545
|
||||
# this assumes in-process private keys
|
||||
validator.privKey,
|
||||
trailing_distance)
|
||||
|
||||
# Don't broadcast when, e.g., this node isn't an aggregator
|
||||
if option_aggregateandproof.isSome:
|
||||
node.network.broadcast(
|
||||
topicAggregateAndProof, option_aggregateandproof.get)
|
||||
|
||||
proc onSlotStart(node: BeaconNode, lastSlot, scheduledSlot: Slot) {.gcsafe, async.} =
|
||||
## Called at the beginning of a slot - usually every slot, but sometimes might
|
||||
## skip a few in case we're running late.
|
||||
|
@ -697,7 +737,7 @@ proc onSlotStart(node: BeaconNode, lastSlot, scheduledSlot: Slot) {.gcsafe, asyn
|
|||
|
||||
var head = node.updateHead()
|
||||
|
||||
# TODO is the slot of the clock or the head block more interestion? provide
|
||||
# TODO is the slot of the clock or the head block more interesting? provide
|
||||
# rationale in comment
|
||||
beacon_head_slot.set slot.int64
|
||||
|
||||
|
@ -762,11 +802,12 @@ proc onSlotStart(node: BeaconNode, lastSlot, scheduledSlot: Slot) {.gcsafe, asyn
|
|||
# with any clock discrepancies once only, at the start of slot timer
|
||||
# processing..
|
||||
|
||||
# https://github.com/ethereum/eth2.0-specs/blob/v0.9.4/specs/validator/0_beacon-chain-validator.md#attesting
|
||||
# A validator should create and broadcast the attestation to the
|
||||
# associated attestation subnet one-third of the way through the slot
|
||||
# during which the validator is assigned―that is, SECONDS_PER_SLOT / 3
|
||||
# seconds after the start of slot.
|
||||
# https://github.com/ethereum/eth2.0-specs/blob/v0.11.1/specs/phase0/validator.md#attesting
|
||||
# A validator should create and broadcast the attestation to the associated
|
||||
# attestation subnet when either (a) the validator has received a valid
|
||||
# block from the expected block proposer for the assigned slot or
|
||||
# (b) one-third of the slot has transpired (`SECONDS_PER_SLOT / 3` seconds
|
||||
# after the start of slot) -- whichever comes first.
|
||||
let
|
||||
attestationStart = node.beaconClock.fromNow(slot)
|
||||
thirdSlot = seconds(int64(SECONDS_PER_SLOT)) div 3
|
||||
|
@ -788,6 +829,25 @@ proc onSlotStart(node: BeaconNode, lastSlot, scheduledSlot: Slot) {.gcsafe, asyn
|
|||
|
||||
handleAttestations(node, head, slot)
|
||||
|
||||
# https://github.com/ethereum/eth2.0-specs/blob/v0.11.1/specs/phase0/validator.md#broadcast-aggregate
|
||||
# If the validator is selected to aggregate (is_aggregator), then they
|
||||
# broadcast their best aggregate as a SignedAggregateAndProof to the global
|
||||
# aggregate channel (beacon_aggregate_and_proof) two-thirds of the way
|
||||
# through the slot-that is, SECONDS_PER_SLOT * 2 / 3 seconds after the start
|
||||
# of slot.
|
||||
if slot > 2:
|
||||
const TRAILING_DISTANCE = 1
|
||||
let aggregationSlot = slot - TRAILING_DISTANCE
|
||||
var aggregationHead = getAncestorAt(head, aggregationSlot)
|
||||
|
||||
let bs = BlockSlot(blck: aggregationHead, slot: aggregationSlot)
|
||||
node.blockPool.withState(node.blockPool.tmpState, bs):
|
||||
let twoThirdsSlot =
|
||||
toBeaconTime(slot, seconds(2*int64(SECONDS_PER_SLOT)) div 3)
|
||||
addTimer(saturate(node.beaconClock.fromNow(twoThirdsSlot))) do (p: pointer):
|
||||
broadcastAggregatedAttestations(
|
||||
node, state, aggregationHead, aggregationSlot, TRAILING_DISTANCE)
|
||||
|
||||
# TODO ... and beacon clock might jump here also. sigh.
|
||||
let
|
||||
nextSlotStart = saturate(node.beaconClock.fromNow(nextSlot))
|
||||
|
|
|
@ -13,6 +13,7 @@ const
|
|||
topicVoluntaryExits* = "/eth2/voluntary_exit/ssz"
|
||||
topicProposerSlashings* = "/eth2/proposer_slashing/ssz"
|
||||
topicAttesterSlashings* = "/eth2/attester_slashing/ssz"
|
||||
topicAggregateAndProof* = "/eth2/beacon_aggregate_and_proof/ssz"
|
||||
|
||||
# https://github.com/ethereum/eth2.0-specs/blob/v0.11.0/specs/phase0/p2p-interface.md#configuration
|
||||
ATTESTATION_SUBNET_COUNT* = 64
|
||||
|
|
Loading…
Reference in New Issue