UNPKG

@lodestar/beacon-node

Version:

A Typescript implementation of the beacon chain

124 lines 5.43 kB
import { aggregateSignatures } from "@chainsafe/blst"; import { BitArray } from "@chainsafe/ssz"; import { SYNC_COMMITTEE_SIZE, SYNC_COMMITTEE_SUBNET_COUNT } from "@lodestar/params"; import { MapDef, toRootHex } from "@lodestar/utils"; import { InsertOutcome, OpPoolError, OpPoolErrorCode } from "./types.js"; import { pruneBySlot, signatureFromBytesNoCheck } from "./utils.js"; /** * SyncCommittee signatures are only useful during a single slot according to our peer's clocks */ const SLOTS_RETAINED = 3; /** * The maximum number of distinct `ContributionFast` that will be stored in each slot. * * This is a DoS protection measure. */ const MAX_ITEMS_PER_SLOT = 512; /** * Preaggregate SyncCommitteeMessage into SyncCommitteeContribution * and cache seen SyncCommitteeMessage by slot + validator index. * This stays in-memory and should be pruned per slot. */ export class SyncCommitteeMessagePool { constructor(clock, cutOffSecFromSlot, preaggregateSlotDistance = 0) { this.clock = clock; this.cutOffSecFromSlot = cutOffSecFromSlot; this.preaggregateSlotDistance = preaggregateSlotDistance; /** * Each array item is respective to a subcommitteeIndex. * Preaggregate into SyncCommitteeContribution. * */ this.contributionsByRootBySubnetBySlot = new MapDef(() => new MapDef(() => new Map())); this.lowestPermissibleSlot = 0; } /** Returns current count of unique ContributionFast by block root and subnet */ get size() { let count = 0; for (const contributionsByRootBySubnet of this.contributionsByRootBySubnetBySlot.values()) { for (const contributionsByRoot of contributionsByRootBySubnet.values()) { count += contributionsByRoot.size; } } return count; } // TODO: indexInSubcommittee: number should be indicesInSyncCommittee add(subnet, signature, indexInSubcommittee, priority) { const { slot, beaconBlockRoot } = signature; const rootHex = toRootHex(beaconBlockRoot); const lowestPermissibleSlot = this.lowestPermissibleSlot; // Reject if too old. if (slot < lowestPermissibleSlot) { return InsertOutcome.Old; } // validator gets SyncCommitteeContribution at 2/3 of slot, it's no use to preaggregate later than that time if (!priority && this.clock.secFromSlot(slot) > this.cutOffSecFromSlot) { return InsertOutcome.Late; } // Limit object per slot const contributionsByRoot = this.contributionsByRootBySubnetBySlot.getOrDefault(slot).getOrDefault(subnet); if (contributionsByRoot.size >= MAX_ITEMS_PER_SLOT) { throw new OpPoolError({ code: OpPoolErrorCode.REACHED_MAX_PER_SLOT }); } // Pre-aggregate the contribution with existing items const contribution = contributionsByRoot.get(rootHex); if (contribution) { // Aggregate mutating return aggregateSignatureInto(contribution, signature, indexInSubcommittee); } // Create new aggregate contributionsByRoot.set(rootHex, signatureToAggregate(subnet, signature, indexInSubcommittee)); return InsertOutcome.NewData; } /** * This is for the aggregator to produce ContributionAndProof. */ getContribution(subnet, slot, prevBlockRoot) { const contribution = this.contributionsByRootBySubnetBySlot.get(slot)?.get(subnet)?.get(toRootHex(prevBlockRoot)); if (!contribution) { return null; } return { ...contribution, aggregationBits: contribution.aggregationBits, signature: contribution.signature.toBytes(), }; } /** * Prune per clock slot. * SyncCommittee signatures are only useful during a single slot according to our peer's clocks */ prune(clockSlot) { pruneBySlot(this.contributionsByRootBySubnetBySlot, clockSlot, SLOTS_RETAINED); // by default preaggregateSlotDistance is 0, i.e only accept SyncCommitteeMessage in the same clock slot. this.lowestPermissibleSlot = Math.max(clockSlot - this.preaggregateSlotDistance, 0); } } /** * Aggregate a new signature into `contribution` mutating it */ function aggregateSignatureInto(contribution, signature, indexInSubcommittee) { if (contribution.aggregationBits.get(indexInSubcommittee) === true) { return InsertOutcome.AlreadyKnown; } contribution.aggregationBits.set(indexInSubcommittee, true); contribution.signature = aggregateSignatures([ contribution.signature, signatureFromBytesNoCheck(signature.signature), ]); return InsertOutcome.Aggregated; } /** * Format `signature` into an efficient `contribution` to add more signatures in with aggregateSignatureInto() */ function signatureToAggregate(subnet, signature, indexInSubcommittee) { const indexesPerSubnet = Math.floor(SYNC_COMMITTEE_SIZE / SYNC_COMMITTEE_SUBNET_COUNT); const aggregationBits = BitArray.fromSingleBit(indexesPerSubnet, indexInSubcommittee); return { slot: signature.slot, beaconBlockRoot: signature.beaconBlockRoot, subcommitteeIndex: subnet, aggregationBits, signature: signatureFromBytesNoCheck(signature.signature), }; } //# sourceMappingURL=syncCommitteeMessagePool.js.map