import { ForkSeq } from "@lodestar/params";
import { computeStartSlotAtEpoch } from "@lodestar/state-transition";
import { ssz } from "@lodestar/types";
import { toHex } from "@lodestar/utils";
import { FAR_FUTURE_EPOCH } from "../constants/index.js";
import { serializeCgc } from "../util/metadata.js";
import { getCurrentAndNextForkBoundary } from "./forks.js";
export { ENRKey };
var ENRKey;
(function (ENRKey) {
    ENRKey["tcp"] = "tcp";
    ENRKey["quic"] = "quic";
    ENRKey["eth2"] = "eth2";
    ENRKey["attnets"] = "attnets";
    ENRKey["syncnets"] = "syncnets";
    ENRKey["cgc"] = "cgc";
    ENRKey["nfd"] = "nfd";
})(ENRKey || (ENRKey = {}));
export { SubnetType };
var SubnetType;
(function (SubnetType) {
    SubnetType["attnets"] = "attnets";
    SubnetType["syncnets"] = "syncnets";
})(SubnetType || (SubnetType = {}));
/**
 * Implementation of Ethereum Consensus p2p MetaData.
 * For the spec that this code is based on, see:
 * https://github.com/ethereum/consensus-specs/blob/v1.1.10/specs/phase0/p2p-interface.md#metadata
 */
export class MetadataController {
    onSetValue;
    networkConfig;
    logger;
    _metadata;
    constructor(opts, modules) {
        this.networkConfig = modules.networkConfig;
        this.logger = modules.logger;
        this.onSetValue = modules.onSetValue;
        this._metadata = opts.metadata ?? {
            ...ssz.fulu.Metadata.defaultValue(),
            custodyGroupCount: modules.networkConfig.custodyConfig.targetCustodyGroupCount,
        };
    }
    upstreamValues(currentEpoch) {
        // updateEth2Field() MUST be called with clock epoch
        this.updateEth2Field(currentEpoch);
        this.onSetValue(ENRKey.attnets, ssz.phase0.AttestationSubnets.serialize(this._metadata.attnets));
        const config = this.networkConfig.config;
        if (config.getForkSeq(computeStartSlotAtEpoch(currentEpoch)) >= ForkSeq.altair) {
            // Only persist syncnets if altair fork is already activated. If currentFork is altair but head is phase0
            // adding syncnets to the ENR is not a problem, we will just have a useless field for a few hours.
            this.onSetValue(ENRKey.syncnets, ssz.altair.SyncSubnets.serialize(this._metadata.syncnets));
        }
        // Set CGC regardless of fork. It may be useful to clients before Fulu, and will be ignored otherwise.
        this.onSetValue(ENRKey.cgc, serializeCgc(this._metadata.custodyGroupCount));
    }
    get seqNumber() {
        return this._metadata.seqNumber;
    }
    get syncnets() {
        return this._metadata.syncnets;
    }
    set syncnets(syncnets) {
        this.onSetValue(ENRKey.syncnets, ssz.altair.SyncSubnets.serialize(syncnets));
        this._metadata.seqNumber++;
        this._metadata.syncnets = syncnets;
    }
    get attnets() {
        return this._metadata.attnets;
    }
    set attnets(attnets) {
        this.onSetValue(ENRKey.attnets, ssz.phase0.AttestationSubnets.serialize(attnets));
        this._metadata.seqNumber++;
        this._metadata.attnets = attnets;
    }
    get custodyGroupCount() {
        return this._metadata.custodyGroupCount;
    }
    set custodyGroupCount(custodyGroupCount) {
        if (custodyGroupCount === this._metadata.custodyGroupCount) {
            return;
        }
        this.onSetValue(ENRKey.cgc, serializeCgc(custodyGroupCount));
        this.logger.debug("Updated cgc field in ENR", { custodyGroupCount });
        this._metadata.seqNumber++;
        this._metadata.custodyGroupCount = custodyGroupCount;
    }
    /** Consumers that need the phase0.Metadata type can just ignore the .syncnets property */
    get json() {
        return this._metadata;
    }
    /**
     * From spec:
     *   fork_digest is compute_fork_digest(current_fork_version, genesis_validators_root) where
     *   - current_fork_version is the fork version at the node's current epoch defined by the wall-clock time (not
     *     necessarily the epoch to which the node is sync)
     *   - genesis_validators_root is the static Root found in state.genesis_validators_root
     *   - epoch of fork boundary is used to get blob parameters of current Blob Parameter Only (BPO) fork
     *
     * 1. MUST be called on start to populate ENR
     * 2. Network MUST call this method on fork transition.
     *    Current Clock implementation ensures no race conditions, epoch is correct if re-fetched
     */
    updateEth2Field(epoch) {
        const config = this.networkConfig.config;
        const enrForkId = getENRForkID(config, epoch);
        const { forkDigest, nextForkVersion, nextForkEpoch } = enrForkId;
        this.onSetValue(ENRKey.eth2, ssz.phase0.ENRForkID.serialize(enrForkId));
        this.logger.debug("Updated eth2 field in ENR", {
            forkDigest: toHex(forkDigest),
            nextForkVersion: toHex(nextForkVersion),
            nextForkEpoch,
        });
        const nextForkDigest = nextForkEpoch !== FAR_FUTURE_EPOCH
            ? config.forkBoundary2ForkDigest(config.getForkBoundaryAtEpoch(nextForkEpoch))
            : ssz.ForkDigest.defaultValue();
        this.onSetValue(ENRKey.nfd, nextForkDigest);
        this.logger.debug("Updated nfd field in ENR", { nextForkDigest: toHex(nextForkDigest) });
        return enrForkId;
    }
}
export function getENRForkID(config, clockEpoch) {
    const { currentBoundary, nextBoundary } = getCurrentAndNextForkBoundary(config, clockEpoch);
    return {
        // Current fork digest
        forkDigest: config.forkBoundary2ForkDigest(currentBoundary),
        // Next planned fork version
        nextForkVersion: nextBoundary
            ? config.forks[nextBoundary.fork].version
            : config.forks[currentBoundary.fork].version,
        // Next fork epoch
        nextForkEpoch: nextBoundary ? nextBoundary.epoch : FAR_FUTURE_EPOCH,
    };
}
//# sourceMappingURL=metadata.js.map