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