@lodestar/beacon-node
Version:
A Typescript implementation of the beacon chain
79 lines • 3.95 kB
JavaScript
import { MapDef, toRootHex } from "@lodestar/utils";
import { isSuperSetOrEqual } from "../../util/bitArray.js";
import { insertDesc } from "./seenAggregateAndProof.js";
/**
* SyncCommittee aggregates are only useful for the next block they have signed.
*/
const MAX_SLOTS_IN_CACHE = 8;
/**
* Cache SyncCommitteeContribution and seen ContributionAndProof.
* This is used for SignedContributionAndProof validation and block factory.
* This stays in-memory and should be pruned per slot.
*/
export class SeenContributionAndProof {
constructor(metrics) {
this.metrics = metrics;
this.seenAggregatorBySlot = new MapDef(() => new Set());
this.seenContributionBySlot = new MapDef(() => new MapDef(() => []));
}
/**
* _[IGNORE]_ A valid sync committee contribution with equal `slot`, `beacon_block_root` and `subcommittee_index` whose
* `aggregation_bits` is non-strict superset has _not_ already been seen.
*/
participantsKnown(contribution) {
const { aggregationBits, slot } = contribution;
const contributionMap = this.seenContributionBySlot.getOrDefault(slot);
const seenAggregationInfoArr = contributionMap.getOrDefault(toContributionDataKey(contribution));
this.metrics?.seenCache.committeeContributions.isKnownCalls.inc();
// seenAttestingIndicesArr is sorted by trueBitCount desc
for (let i = 0; i < seenAggregationInfoArr.length; i++) {
if (isSuperSetOrEqual(seenAggregationInfoArr[i].aggregationBits, aggregationBits)) {
this.metrics?.seenCache.committeeContributions.isKnownHits.inc();
this.metrics?.seenCache.committeeContributions.superSetCheckTotal.observe(i + 1);
return true;
}
}
this.metrics?.seenCache.committeeContributions.superSetCheckTotal.observe(seenAggregationInfoArr.length);
return false;
}
/**
* Gossip validation requires to check:
* The sync committee contribution is the first valid contribution received for the aggregator with index
* contribution_and_proof.aggregator_index for the slot contribution.slot and subcommittee index contribution.subcommittee_index.
*/
isAggregatorKnown(slot, subcommitteeIndex, aggregatorIndex) {
return this.seenAggregatorBySlot.get(slot)?.has(seenAggregatorKey(subcommitteeIndex, aggregatorIndex)) === true;
}
/** Register item as seen in the cache */
add(contributionAndProof, trueBitCount) {
const { contribution, aggregatorIndex } = contributionAndProof;
const { subcommitteeIndex, slot, aggregationBits } = contribution;
// add to seenAggregatorBySlot
this.seenAggregatorBySlot.getOrDefault(slot).add(seenAggregatorKey(subcommitteeIndex, aggregatorIndex));
// add to seenContributionBySlot
const contributionMap = this.seenContributionBySlot.getOrDefault(slot);
const seenAggregationInfoArr = contributionMap.getOrDefault(toContributionDataKey(contribution));
insertDesc(seenAggregationInfoArr, { aggregationBits, trueBitCount });
}
/** Prune per head slot */
prune(headSlot) {
for (const slot of this.seenAggregatorBySlot.keys()) {
if (slot < headSlot - MAX_SLOTS_IN_CACHE) {
this.seenAggregatorBySlot.delete(slot);
}
}
for (const slot of this.seenContributionBySlot.keys()) {
if (slot < headSlot - MAX_SLOTS_IN_CACHE) {
this.seenContributionBySlot.delete(slot);
}
}
}
}
function seenAggregatorKey(subcommitteeIndex, aggregatorIndex) {
return `${subcommitteeIndex}-${aggregatorIndex}`;
}
function toContributionDataKey(contribution) {
const { slot, beaconBlockRoot, subcommitteeIndex } = contribution;
return `${slot} - ${toRootHex(beaconBlockRoot)} - ${subcommitteeIndex}`;
}
//# sourceMappingURL=seenCommitteeContribution.js.map