import { MapDef } from "@lodestar/utils";
import { InsertOutcome } from "../opPools/types.js";
export { RejectReason };
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 {
    metrics;
    cacheSlotDistance;
    maxCacheSizePerSlot;
    cacheEntryByAttDataByIndexBySlot = new MapDef(() => new MapDef(() => new Map()));
    lowestPermissibleSlot = 0;
    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;
        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