import { ATTESTATION_SUBNET_COUNT, ForkSeq, SYNC_COMMITTEE_SUBNET_COUNT, isForkPostAltair, isForkPostElectra, isForkPostFulu, } from "@lodestar/params";
import { ssz, sszTypesFor } from "@lodestar/types";
import { GossipAction, GossipActionError, GossipErrorCode } from "../../chain/errors/gossipValidation.js";
import { DEFAULT_ENCODING } from "./constants.js";
import { GossipEncoding, GossipType } from "./interface.js";
export class GossipTopicCache {
    forkDigestContext;
    topicsByTopicStr = new Map();
    constructor(forkDigestContext) {
        this.forkDigestContext = forkDigestContext;
    }
    /** Returns cached GossipTopic, otherwise attempts to parse it from the str */
    getTopic(topicStr) {
        let topic = this.topicsByTopicStr.get(topicStr);
        if (topic === undefined) {
            topic = parseGossipTopic(this.forkDigestContext, topicStr);
            // TODO: Consider just throwing here. We should only receive messages from known subscribed topics
            this.topicsByTopicStr.set(topicStr, topic);
        }
        return topic;
    }
    /** Returns cached GossipTopic, otherwise returns undefined */
    getKnownTopic(topicStr) {
        return this.topicsByTopicStr.get(topicStr);
    }
    setTopic(topicStr, topic) {
        if (!this.topicsByTopicStr.has(topicStr)) {
            this.topicsByTopicStr.set(topicStr, { encoding: DEFAULT_ENCODING, ...topic });
        }
    }
}
/**
 * Stringify a GossipTopic into a spec-ed formated topic string
 */
export function stringifyGossipTopic(forkDigestContext, topic) {
    const forkDigestHexNoPrefix = forkDigestContext.forkBoundary2ForkDigestHex(topic.boundary);
    const topicType = stringifyGossipTopicType(topic);
    const encoding = topic.encoding ?? DEFAULT_ENCODING;
    return `/eth2/${forkDigestHexNoPrefix}/${topicType}/${encoding}`;
}
/**
 * Stringify a GossipTopic into a spec-ed formated partial topic string
 */
function stringifyGossipTopicType(topic) {
    switch (topic.type) {
        case GossipType.beacon_block:
        case GossipType.beacon_aggregate_and_proof:
        case GossipType.voluntary_exit:
        case GossipType.proposer_slashing:
        case GossipType.attester_slashing:
        case GossipType.sync_committee_contribution_and_proof:
        case GossipType.light_client_finality_update:
        case GossipType.light_client_optimistic_update:
        case GossipType.bls_to_execution_change:
        case GossipType.execution_payload:
        case GossipType.payload_attestation_message:
        case GossipType.execution_payload_bid:
            return topic.type;
        case GossipType.beacon_attestation:
        case GossipType.sync_committee:
            return `${topic.type}_${topic.subnet}`;
        case GossipType.blob_sidecar:
            return `${topic.type}_${topic.subnet}`;
        case GossipType.data_column_sidecar:
            return `${topic.type}_${topic.subnet}`;
    }
}
export function getGossipSSZType(topic) {
    const { fork } = topic.boundary;
    switch (topic.type) {
        case GossipType.beacon_block:
            // beacon_block is updated in altair to support the updated SignedBeaconBlock type
            return ssz[fork].SignedBeaconBlock;
        case GossipType.blob_sidecar:
            return ssz.deneb.BlobSidecar;
        case GossipType.data_column_sidecar:
            return isForkPostFulu(fork) ? sszTypesFor(fork).DataColumnSidecar : ssz.fulu.DataColumnSidecar;
        case GossipType.beacon_aggregate_and_proof:
            return sszTypesFor(fork).SignedAggregateAndProof;
        case GossipType.beacon_attestation:
            return sszTypesFor(fork).SingleAttestation;
        case GossipType.proposer_slashing:
            return ssz.phase0.ProposerSlashing;
        case GossipType.attester_slashing:
            return sszTypesFor(fork).AttesterSlashing;
        case GossipType.voluntary_exit:
            return ssz.phase0.SignedVoluntaryExit;
        case GossipType.sync_committee_contribution_and_proof:
            return ssz.altair.SignedContributionAndProof;
        case GossipType.sync_committee:
            return ssz.altair.SyncCommitteeMessage;
        case GossipType.light_client_optimistic_update:
            return isForkPostAltair(fork)
                ? sszTypesFor(fork).LightClientOptimisticUpdate
                : ssz.altair.LightClientOptimisticUpdate;
        case GossipType.light_client_finality_update:
            return isForkPostAltair(fork)
                ? sszTypesFor(fork).LightClientFinalityUpdate
                : ssz.altair.LightClientFinalityUpdate;
        case GossipType.bls_to_execution_change:
            return ssz.capella.SignedBLSToExecutionChange;
        case GossipType.execution_payload:
            return ssz.gloas.SignedExecutionPayloadEnvelope;
        case GossipType.payload_attestation_message:
            return ssz.gloas.PayloadAttestationMessage;
        case GossipType.execution_payload_bid:
            return ssz.gloas.SignedExecutionPayloadBid;
    }
}
/**
 * Deserialize a gossip serialized data into an ssz object.
 */
export function sszDeserialize(topic, serializedData) {
    const sszType = getGossipSSZType(topic);
    try {
        return sszType.deserialize(serializedData);
    }
    catch (_e) {
        throw new GossipActionError(GossipAction.REJECT, { code: GossipErrorCode.INVALID_SERIALIZED_BYTES_ERROR_CODE });
    }
}
/**
 * @deprecated
 * Deserialize a gossip serialized data into an Attestation object.
 * No longer used post-electra. Use `sszDeserializeSingleAttestation` instead
 */
export function sszDeserializeAttestation(fork, serializedData) {
    try {
        return sszTypesFor(fork).Attestation.deserialize(serializedData);
    }
    catch (_e) {
        throw new GossipActionError(GossipAction.REJECT, { code: GossipErrorCode.INVALID_SERIALIZED_BYTES_ERROR_CODE });
    }
}
/**
 * Deserialize a gossip seralized data into an SingleAttestation object.
 */
export function sszDeserializeSingleAttestation(fork, serializedData) {
    try {
        if (isForkPostElectra(fork)) {
            return sszTypesFor(fork).SingleAttestation.deserialize(serializedData);
        }
        return sszTypesFor(fork).Attestation.deserialize(serializedData);
    }
    catch (_e) {
        throw new GossipActionError(GossipAction.REJECT, { code: GossipErrorCode.INVALID_SERIALIZED_BYTES_ERROR_CODE });
    }
}
// Parsing
const gossipTopicRegex = /^\/eth2\/(\w+)\/(\w+)\/(\w+)/;
/**
 * Parse a `GossipTopic` object from its stringified form.
 * A gossip topic has the format
 * ```ts
 * /eth2/$FORK_DIGEST/$GOSSIP_TYPE/$ENCODING
 * ```
 */
export function parseGossipTopic(forkDigestContext, topicStr) {
    try {
        const matches = topicStr.match(gossipTopicRegex);
        if (matches === null) {
            throw Error(`Must match regex ${gossipTopicRegex}`);
        }
        const [, forkDigestHexNoPrefix, gossipTypeStr, encodingStr] = matches;
        const boundary = forkDigestContext.forkDigest2ForkBoundary(forkDigestHexNoPrefix);
        const encoding = parseEncodingStr(encodingStr);
        // Inline-d the parseGossipTopicType() function since spreading the resulting object x4 the time to parse a topicStr
        switch (gossipTypeStr) {
            case GossipType.beacon_block:
            case GossipType.beacon_aggregate_and_proof:
            case GossipType.voluntary_exit:
            case GossipType.proposer_slashing:
            case GossipType.attester_slashing:
            case GossipType.sync_committee_contribution_and_proof:
            case GossipType.light_client_finality_update:
            case GossipType.light_client_optimistic_update:
            case GossipType.bls_to_execution_change:
            case GossipType.execution_payload:
            case GossipType.payload_attestation_message:
            case GossipType.execution_payload_bid:
                return { type: gossipTypeStr, boundary, encoding };
        }
        for (const gossipType of [GossipType.beacon_attestation, GossipType.sync_committee]) {
            if (gossipTypeStr.startsWith(gossipType)) {
                const subnetStr = gossipTypeStr.slice(gossipType.length + 1); // +1 for '_' concatenating the topic name and the subnet
                const subnet = parseInt(subnetStr, 10);
                if (Number.isNaN(subnet))
                    throw Error(`Subnet ${subnetStr} is not a number`);
                return { type: gossipType, subnet, boundary, encoding };
            }
        }
        if (gossipTypeStr.startsWith(GossipType.blob_sidecar)) {
            const subnetStr = gossipTypeStr.slice(GossipType.blob_sidecar.length + 1); // +1 for '_' concatenating the topic name and the subnet
            const subnet = parseInt(subnetStr, 10);
            if (Number.isNaN(subnet))
                throw Error(`subnet ${subnetStr} is not a number`);
            return { type: GossipType.blob_sidecar, subnet, boundary, encoding };
        }
        if (gossipTypeStr.startsWith(GossipType.data_column_sidecar)) {
            const subnetStr = gossipTypeStr.slice(GossipType.data_column_sidecar.length + 1); // +1 for '_' concatenating the topic name and the subnet
            const subnet = parseInt(subnetStr, 10);
            if (Number.isNaN(subnet))
                throw Error(`subnet ${subnetStr} is not a number`);
            return { type: GossipType.data_column_sidecar, subnet, boundary, encoding };
        }
        throw Error(`Unknown gossip type ${gossipTypeStr}`);
    }
    catch (e) {
        e.message = `Invalid gossip topic ${topicStr}: ${e.message}`;
        throw e;
    }
}
/**
 * De-duplicate logic to pick fork topics between subscribeCoreTopicsAtFork and unsubscribeCoreTopicsAtFork
 */
export function getCoreTopicsAtFork(networkConfig, fork, opts) {
    // Common topics for all forks
    const topics = [
        { type: GossipType.beacon_block },
        { type: GossipType.beacon_aggregate_and_proof },
        { type: GossipType.voluntary_exit },
        { type: GossipType.proposer_slashing },
        { type: GossipType.attester_slashing },
    ];
    if (ForkSeq[fork] >= ForkSeq.gloas) {
        topics.push({ type: GossipType.execution_payload });
        topics.push({ type: GossipType.payload_attestation_message });
        topics.push({ type: GossipType.execution_payload_bid });
    }
    // After fulu also track data_column_sidecar_{index}
    if (ForkSeq[fork] >= ForkSeq.fulu) {
        topics.push(...getDataColumnSidecarTopics(networkConfig));
    }
    // After Deneb and before Fulu also track blob_sidecar_{subnet_id}
    if (ForkSeq[fork] >= ForkSeq.deneb && ForkSeq[fork] < ForkSeq.fulu) {
        const { config } = networkConfig;
        const subnetCount = isForkPostElectra(fork)
            ? config.BLOB_SIDECAR_SUBNET_COUNT_ELECTRA
            : config.BLOB_SIDECAR_SUBNET_COUNT;
        for (let subnet = 0; subnet < subnetCount; subnet++) {
            topics.push({ type: GossipType.blob_sidecar, subnet });
        }
    }
    // capella
    if (ForkSeq[fork] >= ForkSeq.capella) {
        topics.push({ type: GossipType.bls_to_execution_change });
    }
    // Any fork after altair included
    if (ForkSeq[fork] >= ForkSeq.altair) {
        topics.push({ type: GossipType.sync_committee_contribution_and_proof });
        if (!opts.disableLightClientServer) {
            topics.push({ type: GossipType.light_client_optimistic_update });
            topics.push({ type: GossipType.light_client_finality_update });
        }
    }
    if (opts.subscribeAllSubnets) {
        for (let subnet = 0; subnet < ATTESTATION_SUBNET_COUNT; subnet++) {
            topics.push({ type: GossipType.beacon_attestation, subnet });
        }
        if (ForkSeq[fork] >= ForkSeq.altair) {
            for (let subnet = 0; subnet < SYNC_COMMITTEE_SUBNET_COUNT; subnet++) {
                topics.push({ type: GossipType.sync_committee, subnet });
            }
        }
    }
    return topics;
}
/**
 * Pick data column subnets to subscribe to post-fulu.
 */
export function getDataColumnSidecarTopics(networkConfig) {
    const topics = [];
    const subnets = networkConfig.custodyConfig.sampledSubnets;
    for (const subnet of subnets) {
        topics.push({ type: GossipType.data_column_sidecar, subnet });
    }
    return topics;
}
/**
 * Validate that a `encodingStr` is a known `GossipEncoding`
 */
function parseEncodingStr(encodingStr) {
    switch (encodingStr) {
        case GossipEncoding.ssz_snappy:
            return encodingStr;
        default:
            throw Error(`Unknown encoding ${encodingStr}`);
    }
}
// TODO: Review which yes, and which not
export const gossipTopicIgnoreDuplicatePublishError = {
    [GossipType.beacon_block]: true,
    [GossipType.blob_sidecar]: true,
    [GossipType.data_column_sidecar]: true,
    [GossipType.beacon_aggregate_and_proof]: true,
    [GossipType.beacon_attestation]: true,
    [GossipType.voluntary_exit]: true,
    [GossipType.proposer_slashing]: false, // Why not this ones?
    [GossipType.attester_slashing]: false,
    [GossipType.sync_committee_contribution_and_proof]: true,
    [GossipType.sync_committee]: true,
    [GossipType.light_client_finality_update]: false,
    [GossipType.light_client_optimistic_update]: false,
    [GossipType.bls_to_execution_change]: true,
    [GossipType.execution_payload]: true,
    [GossipType.payload_attestation_message]: true,
    [GossipType.execution_payload_bid]: true,
};
//# sourceMappingURL=topic.js.map