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 {
    metrics;
    seenAggregatorBySlot = new MapDef(() => new Set());
    seenContributionBySlot = new MapDef(() => new MapDef(() => []));
    constructor(metrics) {
        this.metrics = metrics;
    }
    /**
     * _[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