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).
|
# * 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.
|
# 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
|
# The other part is arguably part of attestation pool -- the validation's
|
||||||
# something that should be happing on receipt, not aggregation per se. In
|
# something that should be happing on receipt, not aggregation per se. In
|
||||||
# that part, check that messages conform -- so, check for each type
|
# 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
|
# 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
|
# specifies. So by the time this calls attestation pool, all validation's
|
||||||
# already done.
|
# 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
|
import
|
||||||
options,
|
options,
|
||||||
|
@ -26,11 +18,7 @@ import
|
||||||
state_transition_block],
|
state_transition_block],
|
||||||
./attestation_pool, ./beacon_node_types, ./ssz
|
./attestation_pool, ./beacon_node_types, ./ssz
|
||||||
|
|
||||||
# TODO gossipsub validation lives somewhere, maybe here
|
# https://github.com/ethereum/eth2.0-specs/blob/v0.11.1/specs/phase0/validator.md#aggregation-selection
|
||||||
# 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
|
|
||||||
func is_aggregator(state: BeaconState, slot: Slot, index: uint64,
|
func is_aggregator(state: BeaconState, slot: Slot, index: uint64,
|
||||||
slot_signature: ValidatorSig): bool =
|
slot_signature: ValidatorSig): bool =
|
||||||
# TODO index is a CommitteeIndex, aka uint64
|
# TODO index is a CommitteeIndex, aka uint64
|
||||||
|
@ -43,34 +31,49 @@ func is_aggregator(state: BeaconState, slot: Slot, index: uint64,
|
||||||
|
|
||||||
proc aggregate_attestations*(
|
proc aggregate_attestations*(
|
||||||
pool: AttestationPool, state: BeaconState, index: uint64,
|
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
|
# TODO alias CommitteeIndex to actual type then convert various uint64's here
|
||||||
|
|
||||||
let
|
doAssert state.slot >= trailing_distance
|
||||||
slot = state.slot - 2
|
|
||||||
slot_signature = get_slot_signature(state.fork, slot, privkey)
|
# 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 slot + ATTESTATION_PROPAGATION_SLOT_RANGE >= state.slot
|
||||||
doAssert state.slot >= 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):
|
if not is_aggregator(state, slot, index, slot_signature):
|
||||||
return none(AggregateAndProof)
|
return none(AggregateAndProof)
|
||||||
|
|
||||||
let attestation_data =
|
# https://github.com/ethereum/eth2.0-specs/blob/v0.11.1/specs/phase0/validator.md#attestation-data
|
||||||
makeAttestationData(state, slot, index, get_block_root_at_slot(state, slot))
|
# 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
|
# https://github.com/ethereum/eth2.0-specs/blob/v0.11.1/specs/phase0/validator.md#construct-aggregate
|
||||||
for attestation in getAttestationsForBlock(pool, state, slot):
|
# 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:
|
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(
|
return some(AggregateAndProof(
|
||||||
aggregator_index: index,
|
aggregator_index: index,
|
||||||
aggregate: attestation,
|
aggregate: attestation,
|
||||||
selection_proof: slot_signature))
|
selection_proof: slot_signature))
|
||||||
|
|
||||||
# TODO in catch-up mode, we could get here, so probably shouldn't assert
|
|
||||||
doAssert false
|
|
||||||
none(AggregateAndProof)
|
none(AggregateAndProof)
|
||||||
|
|
|
@ -15,7 +15,8 @@ import
|
||||||
conf, time, state_transition, beacon_chain_db, validator_pool, extras,
|
conf, time, state_transition, beacon_chain_db, validator_pool, extras,
|
||||||
attestation_pool, block_pool, eth2_network, eth2_discovery,
|
attestation_pool, block_pool, eth2_network, eth2_discovery,
|
||||||
beacon_node_types, mainchain_monitor, version, ssz, ssz/dynamic_navigator,
|
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
|
const
|
||||||
genesisFile = "genesis.ssz"
|
genesisFile = "genesis.ssz"
|
||||||
|
@ -602,6 +603,45 @@ proc verifyFinalization(node: BeaconNode, slot: Slot) =
|
||||||
node.blockPool.finalizedHead.blck.slot.compute_epoch_at_slot()
|
node.blockPool.finalizedHead.blck.slot.compute_epoch_at_slot()
|
||||||
doAssert finalizedEpoch + 2 == epoch
|
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.} =
|
proc onSlotStart(node: BeaconNode, lastSlot, scheduledSlot: Slot) {.gcsafe, async.} =
|
||||||
## Called at the beginning of a slot - usually every slot, but sometimes might
|
## Called at the beginning of a slot - usually every slot, but sometimes might
|
||||||
## skip a few in case we're running late.
|
## 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()
|
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
|
# rationale in comment
|
||||||
beacon_head_slot.set slot.int64
|
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
|
# with any clock discrepancies once only, at the start of slot timer
|
||||||
# processing..
|
# processing..
|
||||||
|
|
||||||
# https://github.com/ethereum/eth2.0-specs/blob/v0.9.4/specs/validator/0_beacon-chain-validator.md#attesting
|
# 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
|
# A validator should create and broadcast the attestation to the associated
|
||||||
# associated attestation subnet one-third of the way through the slot
|
# attestation subnet when either (a) the validator has received a valid
|
||||||
# during which the validator is assigned―that is, SECONDS_PER_SLOT / 3
|
# block from the expected block proposer for the assigned slot or
|
||||||
# seconds after the start of slot.
|
# (b) one-third of the slot has transpired (`SECONDS_PER_SLOT / 3` seconds
|
||||||
|
# after the start of slot) -- whichever comes first.
|
||||||
let
|
let
|
||||||
attestationStart = node.beaconClock.fromNow(slot)
|
attestationStart = node.beaconClock.fromNow(slot)
|
||||||
thirdSlot = seconds(int64(SECONDS_PER_SLOT)) div 3
|
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)
|
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.
|
# TODO ... and beacon clock might jump here also. sigh.
|
||||||
let
|
let
|
||||||
nextSlotStart = saturate(node.beaconClock.fromNow(nextSlot))
|
nextSlotStart = saturate(node.beaconClock.fromNow(nextSlot))
|
||||||
|
|
|
@ -13,6 +13,7 @@ const
|
||||||
topicVoluntaryExits* = "/eth2/voluntary_exit/ssz"
|
topicVoluntaryExits* = "/eth2/voluntary_exit/ssz"
|
||||||
topicProposerSlashings* = "/eth2/proposer_slashing/ssz"
|
topicProposerSlashings* = "/eth2/proposer_slashing/ssz"
|
||||||
topicAttesterSlashings* = "/eth2/attester_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
|
# https://github.com/ethereum/eth2.0-specs/blob/v0.11.0/specs/phase0/p2p-interface.md#configuration
|
||||||
ATTESTATION_SUBNET_COUNT* = 64
|
ATTESTATION_SUBNET_COUNT* = 64
|
||||||
|
|
Loading…
Reference in New Issue