import { MapDef } from "@lodestar/utils";
import { isSuperSetOrEqual } from "../../util/bitArray.js";
/**
 * With this gossip validation condition:
 * pre-deneb:
 * - [IGNORE] aggregate.data.slot is within the last ATTESTATION_PROPAGATION_SLOT_RANGE slots (with a MAXIMUM_GOSSIP_CLOCK_DISPARITY allowance)
 * post-deneb:
 * - [IGNORE] the epoch of `aggregate.data.slot` is either the current or previous epoch
 * - [IGNORE] `aggregate.data.slot` is equal to or earlier than the `current_slot` (with a `MAXIMUM_GOSSIP_CLOCK_DISPARITY` allowance)
 *
 * We keep seen AggregateAndProof in the last 2 epochs pre and post deneb.
 */
const EPOCH_LOOKBACK_LIMIT = 2;
/**
 * Although there are up to TARGET_AGGREGATORS_PER_COMMITTEE (16 for mainnet) AggregateAndProof messages per slot,
 * they tend to have the same aggregate attestation, or one attestation is non-strict superset of another,
 * the gossipsub messages-ids are different because they are really different SignedAggregateAndProof object.
 * This is used to address the following spec in p2p-interface gossipsub:
 * _[IGNORE]_ A valid aggregate attestation defined by `hash_tree_root(aggregate.data)` whose `aggregation_bits` is a
 * non-strict superset has _not_ already been seen.
 *
 * We have AggregatedAttestationPool op pool, however aggregated attestations are not added to that place while this does.
 */
export class SeenAggregatedAttestations {
    metrics;
    /**
     * Array of AttestingIndices by same attestation data root by epoch.
     * Note that there are at most TARGET_AGGREGATORS_PER_COMMITTEE (16) per attestation data.
     * */
    aggregateRootsByEpoch = new MapDef(() => new MapDef(() => new MapDef(() => [])));
    lowestPermissibleEpoch = 0;
    constructor(metrics) {
        this.metrics = metrics;
    }
    isKnown(targetEpoch, committeeIndex, attDataRoot, aggregationBits) {
        const seenAggregationInfoArr = this.aggregateRootsByEpoch
            .getOrDefault(targetEpoch)
            .getOrDefault(committeeIndex)
            .getOrDefault(attDataRoot);
        this.metrics?.seenCache.aggregatedAttestations.isKnownCalls.inc();
        for (let i = 0; i < seenAggregationInfoArr.length; i++) {
            if (isSuperSetOrEqual(seenAggregationInfoArr[i].aggregationBits, aggregationBits)) {
                this.metrics?.seenCache.aggregatedAttestations.superSetCheckTotal.observe(i + 1);
                this.metrics?.seenCache.aggregatedAttestations.isKnownHits.inc();
                return true;
            }
        }
        this.metrics?.seenCache.aggregatedAttestations.superSetCheckTotal.observe(seenAggregationInfoArr.length);
        return false;
    }
    add(targetEpoch, committeeIndex, attDataRoot, newItem, checkIsKnown) {
        const { aggregationBits } = newItem;
        if (checkIsKnown && this.isKnown(targetEpoch, committeeIndex, attDataRoot, aggregationBits)) {
            return;
        }
        const seenAggregationInfoArr = this.aggregateRootsByEpoch
            .getOrDefault(targetEpoch)
            .getOrDefault(committeeIndex)
            .getOrDefault(attDataRoot);
        insertDesc(seenAggregationInfoArr, newItem);
    }
    prune(currentEpoch) {
        this.lowestPermissibleEpoch = Math.max(currentEpoch - EPOCH_LOOKBACK_LIMIT, 0);
        for (const epoch of this.aggregateRootsByEpoch.keys()) {
            if (epoch < this.lowestPermissibleEpoch) {
                this.aggregateRootsByEpoch.delete(epoch);
            }
        }
    }
}
/**
 * Make sure seenAggregationInfoArr is always in desc order based on trueBitCount so that isKnown can be faster
 */
export function insertDesc(seenAggregationInfoArr, newItem) {
    const { trueBitCount } = newItem;
    let found = false;
    for (let i = 0; i < seenAggregationInfoArr.length; i++) {
        if (trueBitCount >= seenAggregationInfoArr[i].trueBitCount) {
            seenAggregationInfoArr.splice(i, 0, newItem);
            found = true;
            break;
        }
    }
    if (!found)
        seenAggregationInfoArr.push(newItem);
}
//# sourceMappingURL=seenAggregateAndProof.js.map