@lodestar/beacon-node
Version:
A Typescript implementation of the beacon chain
102 lines • 5.22 kB
JavaScript
import { MapDef } from "@lodestar/utils";
import { InsertOutcome } from "../opPools/types.js";
export var RejectReason;
(function (RejectReason) {
// attestation data reaches MAX_CACHE_SIZE_PER_SLOT
RejectReason["reached_limit"] = "reached_limit";
// attestation data is too old
RejectReason["too_old"] = "too_old";
// attestation data is already known
RejectReason["already_known"] = "already_known";
})(RejectReason || (RejectReason = {}));
// For pre-electra, there is no committeeIndex in SingleAttestation, so we hard code it to 0
// AttDataBase64 has committeeIndex instead
export const PRE_ELECTRA_SINGLE_ATTESTATION_COMMITTEE_INDEX = 0;
/**
* There are maximum 64 committees per slot, assuming 1 committee may have up to 3 different data due to some nodes
* are not up to date, we can have up to 192 different attestation data per slot.
*/
const DEFAULT_MAX_CACHE_SIZE_PER_SLOT = 200;
/**
* It takes less than 300kb to cache 200 attestation data per slot, so we can cache 3 slots worth of attestation data.
*/
const DEFAULT_CACHE_SLOT_DISTANCE = 2;
/**
* Cached seen AttestationData to improve gossip validation. For Electra, this still take into account attestationIndex
* even through it is moved outside of AttestationData.
* As of April 2023, validating gossip attestation takes ~12% of cpu time for a node subscribing to all subnets on mainnet.
* Having this cache help saves a lot of cpu time since most of the gossip attestations are on the same slot.
*/
export class SeenAttestationDatas {
constructor(metrics, cacheSlotDistance = DEFAULT_CACHE_SLOT_DISTANCE,
// mainly for unit test
maxCacheSizePerSlot = DEFAULT_MAX_CACHE_SIZE_PER_SLOT) {
this.metrics = metrics;
this.cacheSlotDistance = cacheSlotDistance;
this.maxCacheSizePerSlot = maxCacheSizePerSlot;
this.cacheEntryByAttDataByIndexBySlot = new MapDef(() => new MapDef(() => new Map()));
this.lowestPermissibleSlot = 0;
metrics?.seenCache.attestationData.totalSlot.addCollect(() => this.onScrapeLodestarMetrics(metrics));
}
/**
* Add an AttestationDataCacheEntry to the cache.
* - preElectra: add(slot, PRE_ELECTRA_SINGLE_ATTESTATION_COMMITTEE_INDEX, attDataBase64, cacheEntry)
* - electra: add(slot, committeeIndex, attDataBase64, cacheEntry)
*/
add(slot, committeeIndex, attDataBase64, cacheEntry) {
if (slot < this.lowestPermissibleSlot) {
this.metrics?.seenCache.attestationData.reject.inc({ reason: RejectReason.too_old });
return InsertOutcome.Old;
}
const cacheEntryByAttDataByIndex = this.cacheEntryByAttDataByIndexBySlot.getOrDefault(slot);
const cacheEntryByAttData = cacheEntryByAttDataByIndex.getOrDefault(committeeIndex);
if (cacheEntryByAttData.has(attDataBase64)) {
this.metrics?.seenCache.attestationData.reject.inc({ reason: RejectReason.already_known });
return InsertOutcome.AlreadyKnown;
}
if (cacheEntryByAttData.size >= this.maxCacheSizePerSlot) {
this.metrics?.seenCache.attestationData.reject.inc({ reason: RejectReason.reached_limit });
return InsertOutcome.ReachLimit;
}
cacheEntryByAttData.set(attDataBase64, cacheEntry);
return InsertOutcome.NewData;
}
/**
* Get an AttestationDataCacheEntry from the cache.
* - preElectra: get(slot, PRE_ELECTRA_SINGLE_ATTESTATION_COMMITTEE_INDEX, attDataBase64)
* - electra: get(slot, committeeIndex, attDataBase64)
*/
get(slot, committeeIndex, attDataBase64) {
const cacheEntryByAttDataByIndex = this.cacheEntryByAttDataByIndexBySlot.get(slot);
const cacheEntryByAttData = cacheEntryByAttDataByIndex?.get(committeeIndex);
const cacheEntry = cacheEntryByAttData?.get(attDataBase64);
if (cacheEntry) {
this.metrics?.seenCache.attestationData.hit.inc();
}
else {
this.metrics?.seenCache.attestationData.miss.inc();
}
return cacheEntry ?? null;
}
onSlot(clockSlot) {
this.lowestPermissibleSlot = Math.max(clockSlot - this.cacheSlotDistance, 0);
for (const slot of this.cacheEntryByAttDataByIndexBySlot.keys()) {
if (slot < this.lowestPermissibleSlot) {
this.cacheEntryByAttDataByIndexBySlot.delete(slot);
}
}
}
onScrapeLodestarMetrics(metrics) {
metrics?.seenCache.attestationData.totalSlot.set(this.cacheEntryByAttDataByIndexBySlot.size);
// tracking number of attestation data at current slot may not be correct if scrape time is not at the end of slot
// so we track it at the previous slot
const previousSlot = this.lowestPermissibleSlot + this.cacheSlotDistance - 1;
const cacheEntryByAttDataByIndex = this.cacheEntryByAttDataByIndexBySlot.get(previousSlot);
let count = 0;
for (const cacheEntryByAttDataBase64 of cacheEntryByAttDataByIndex?.values() ?? []) {
count += cacheEntryByAttDataBase64.size;
}
metrics?.seenCache.attestationData.countPerSlot.set(count);
}
}
//# sourceMappingURL=seenAttestationData.js.map