UNPKG

@lodestar/beacon-node

Version:

A Typescript implementation of the beacon chain

131 lines 6 kB
import { aggregateSignatures } from "@chainsafe/blst"; import { BitArray } from "@chainsafe/ssz"; import { MAX_COMMITTEES_PER_SLOT, MAX_PAYLOAD_ATTESTATIONS, PTC_SIZE } from "@lodestar/params"; import { MapDef, toRootHex } from "@lodestar/utils"; import { InsertOutcome, OpPoolError, OpPoolErrorCode } from "./types.js"; import { pruneBySlot, signatureFromBytesNoCheck } from "./utils.js"; /** * TODO GLOAS: Revisit this value and add rational for choosing it */ const SLOTS_RETAINED = 2; /** * The maximum number of distinct `PayloadAttestationData` that will be stored in each slot. * * This is a DoS protection measure. */ // TODO GLOAS: Revisit this value. Educated guess would be MAX_ATTESTATIONS_PER_SLOT in AttestationPool divided by MAX_COMMITTEES_PER_SLOT const MAX_PAYLOAD_ATTESTATIONS_PER_SLOT = 16_384 / MAX_COMMITTEES_PER_SLOT; export class PayloadAttestationPool { config; clock; metrics; aggregateByDataRootByBlockRootBySlot = new MapDef(() => new Map()); lowestPermissibleSlot = 0; constructor(config, clock, metrics = null) { this.config = config; this.clock = clock; this.metrics = metrics; } get size() { let count = 0; for (const aggregateByDataRootByBlockRoot of this.aggregateByDataRootByBlockRootBySlot.values()) { for (const aggregateByDataRoot of aggregateByDataRootByBlockRoot.values()) { count += aggregateByDataRoot.size; } } return count; } add(message, payloadAttDataRootHex, validatorCommitteeIndex) { const slot = message.data.slot; const lowestPermissibleSlot = this.lowestPermissibleSlot; if (slot < lowestPermissibleSlot) { return InsertOutcome.Old; } if (slot < this.clock.slotWithPastTolerance(this.config.MAXIMUM_GOSSIP_CLOCK_DISPARITY / 1000)) { return InsertOutcome.Late; } const aggregateByDataRootByBlockRoot = this.aggregateByDataRootByBlockRootBySlot.getOrDefault(slot); let aggregateByDataRoot = aggregateByDataRootByBlockRoot.get(toRootHex(message.data.beaconBlockRoot)); if (!aggregateByDataRoot) { aggregateByDataRoot = new Map(); aggregateByDataRootByBlockRoot.set(toRootHex(message.data.beaconBlockRoot), aggregateByDataRoot); } if (aggregateByDataRoot.size >= MAX_PAYLOAD_ATTESTATIONS_PER_SLOT) { throw new OpPoolError({ code: OpPoolErrorCode.REACHED_MAX_PER_SLOT }); } const aggregate = aggregateByDataRoot.get(payloadAttDataRootHex); if (aggregate) { // Aggregate msg into aggregate return aggregateMessageInto(message, validatorCommitteeIndex, aggregate); } // Create a new aggregate with data aggregateByDataRoot.set(payloadAttDataRootHex, messageToAggregate(message, validatorCommitteeIndex)); return InsertOutcome.NewData; } /** * Get payload attestations to be included in a block. * Pick the top `MAX_PAYLOAD_ATTESTATIONS` aggregates with the most votes. */ getPayloadAttestationsForBlock(beaconBlockRoot, slot) { const aggregateByDataRootByBlockRoot = this.aggregateByDataRootByBlockRootBySlot.get(slot); if (!aggregateByDataRootByBlockRoot) { this.metrics?.opPool.payloadAttestationPool.getPayloadAttestationsCacheMisses.inc(); return []; } const aggregateByDataRoot = aggregateByDataRootByBlockRoot.get(beaconBlockRoot); if (!aggregateByDataRoot) { this.metrics?.opPool.payloadAttestationPool.getPayloadAttestationsCacheMisses.inc(); return []; } return Array.from(aggregateByDataRoot.values()) .slice() .sort((a, b) => b.aggregationBits.getTrueBitIndexes().length - a.aggregationBits.getTrueBitIndexes().length) .slice(0, MAX_PAYLOAD_ATTESTATIONS) .map(fastToPayloadAttestation); } getAll(slot) { const aggregates = []; const addAggregates = (aggregateByDataRootByBlockRoot) => { for (const aggregateByDataRoot of aggregateByDataRootByBlockRoot.values()) { aggregates.push(...aggregateByDataRoot.values()); } }; if (slot !== undefined) { const aggregateByDataRootByBlockRoot = this.aggregateByDataRootByBlockRootBySlot.get(slot); if (aggregateByDataRootByBlockRoot) { addAggregates(aggregateByDataRootByBlockRoot); } } else { for (const aggregateByDataRootByBlockRoot of this.aggregateByDataRootByBlockRootBySlot.values()) { addAggregates(aggregateByDataRootByBlockRoot); } } return aggregates .sort((a, b) => b.aggregationBits.getTrueBitIndexes().length - a.aggregationBits.getTrueBitIndexes().length) .map(fastToPayloadAttestation); } prune(clockSlot) { pruneBySlot(this.aggregateByDataRootByBlockRootBySlot, clockSlot, SLOTS_RETAINED); this.lowestPermissibleSlot = clockSlot; } } function messageToAggregate(message, validatorCommitteeIndex) { return { aggregationBits: BitArray.fromSingleBit(PTC_SIZE, validatorCommitteeIndex), data: message.data, signature: signatureFromBytesNoCheck(message.signature), }; } function aggregateMessageInto(message, validatorCommitteeIndex, aggregate) { if (aggregate.aggregationBits.get(validatorCommitteeIndex) === true) { return InsertOutcome.AlreadyKnown; } aggregate.aggregationBits.set(validatorCommitteeIndex, true); aggregate.signature = aggregateSignatures([aggregate.signature, signatureFromBytesNoCheck(message.signature)]); return InsertOutcome.Aggregated; } function fastToPayloadAttestation(aggFast) { return { ...aggFast, signature: aggFast.signature.toBytes() }; } //# sourceMappingURL=payloadAttestationPool.js.map