3.4 Code Lodestar Attestation
Receive Gossip Topic beacon_attestation_{subnet_id}
#
export function getGossipHandlers (modules: ValidatorFnsModules, options: GossipHandlerOpts): GossipHandlers {
const {chain, config, metrics, network, logger} = modules;
return {
[GossipType.beacon_attestation]: async (attestation, {subnet}, _peer, seenTimestampSec) => {
let validationResult: {indexedAttestation: phase0.IndexedAttestation; subnet: number};
try {
/* calls validateGossipAttestation. will go into details later. */
validationResult = await validateGossipAttestationRetryUnknownRoot(chain, attestation, subnet);
} catch (e) {
if (e instanceof AttestationError && e.action === GossipAction.REJECT) {
chain.persistInvalidSszValue(ssz.phase0.Attestation, attestation, "gossip_reject");
}
throw e;
}
// Handler
const {indexedAttestation} = validationResult;
metrics?.registerGossipUnaggregatedAttestation(seenTimestampSec, indexedAttestation);
// Node may be subscribe to extra subnets (long-lived random subnets). For those, validate the messages
// but don't import them, to save CPU and RAM
if (!network.attnetsService.shouldProcess(subnet, attestation.data.slot)) {
return;
}
try {
/* add to attestationPool, which later produces aggregated attestation */
const insertOutcome = chain.attestationPool.add(attestation);
metrics?.opPool.attestationPoolInsertOutcome.inc({insertOutcome});
} catch (e) {
logger.error("Error adding unaggregated attestation to pool", {subnet}, e as Error);
}
if (!options.dontSendGossipAttestationsToForkchoice) {
try {
chain.forkChoice.onAttestation(indexedAttestation);
} catch (e) {
logger.debug("Error adding gossip unaggregated attestation to forkchoice", {subnet}, e as Error);
}
}
},
}
}
publishBeaconAttestation
packages/beacon-node/src/network/gossip/gossipsub.ts ->
submitPoolAttestations
packages/beacon-node/src/api/impl/beacon/pool/index.ts ->
signAndPublishAttestations
packages/validator/src/services/attestation.ts
Send Gossip Topic beacon_attestation_{subnet_id}
#
produceAttestation
is called every slot by AttestationService
if the validator is 'on duty' to be an attester of the committee.
const attestationNoCommittee = await this.produceAttestation(duties[0].duty.committeeIndex, slot);
It further calls
api.validator.produceAttestationData()
, which depends on the data from the chain.forkChoice
and chain.getHeadState
. (refer to 3.5 Code - Lodestar - Block Building#importBlock
)
await this.signAndPublishAttestations(slot, attestationNoCommittee, duties);
It calls
api.beacon.submitPoolAttestations(signedAttestations)
to send gossip topic beacon_attestation_{subnet_id}
Note that it happens latest in ⅓ of the slot or as soon as the local beacon chain head changes:
await Promise.race([sleep(this.clock.msToSlot(slot + 1 / 3), signal), this.emitter.waitForBlockSlot(slot)]);
Send Gossip Topic beacon_aggregate_and_proof
#
produceAndPublishAggregates
is called every slot by AttestationService
if the validator is 'on duty' to be an aggregator.
It produces aggregated attestation and signing them, and eventually calls the api.validator.publishAggregateAndProofs
. It then sends to the global gossip topic GossipType.beacon_aggregate_and_proof
.
In https://github.com/ChainSafe/lodestar/blob/v1.5.1/packages/beacon-node/src/chain/opPools/attestationPool.ts function aggregateAttestationInto
:
aggregate.aggregationBits.set(bitIndex, true);
aggregate.signature = bls.Signature.aggregate([
aggregate.signature,
signatureFromBytesNoCheck(attestation.signature),
]);
It aggregates single attestations and their signatures using the BLS curve. For a great explanation of attestation and BLS curve, refer to BLS Signatures from eth2book
Attestation Committee and Aggregator Selection#
(The same principle actually also applies to other committees, like sync committee.)
It is calculated per epoch by a calculation based on the locally known validators and their BLS signature. The selection of aggregator is based on RANDAO and BLS signature. For detailed explanation, refer to:
- https://eth2book.info/capella/part2/building_blocks/shuffling/#shuffling for assigning attestation committees.
- https://eth2book.info/capella/part2/building_blocks/aggregator/ for selecting aggregators.
Some highlights:
- lodestar checks for each local validator which committee they belong in
api.validator.getAttesterDuties
. The algorithm (shuffling) is a pseudo-random functiongetCommitteeAssignments(epoch, requestedValidatorIndices)
- For each local validator, lodestar is able to check whether it is an aggregator by pseudo-random function
isAggregatorFromCommitteeLength(committee.length, slotSignature)