@lodestar/beacon-node
Version:
A Typescript implementation of the beacon chain
131 lines • 6 kB
JavaScript
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