import { computeEpochAtSlot, computeStartSlotAtEpoch } from "@lodestar/state-transition";
export { PeerSyncType };
/** The type of peer relative to our current state */
var PeerSyncType;
(function (PeerSyncType) {
    /** The peer is on our chain and is fully synced with respect to our chain */
    PeerSyncType["FullySynced"] = "FullySynced";
    /** The peer has a greater knowledge of the chain than us that warrants a full sync */
    PeerSyncType["Advanced"] = "Advanced";
    /** A peer is behind in the sync and not useful to us for downloading blocks */
    PeerSyncType["Behind"] = "Behind";
})(PeerSyncType || (PeerSyncType = {}));
// Cache Object.keys iteration for faster loops in metrics
export const peerSyncTypes = Object.keys(PeerSyncType);
function withinRangeOf(value, target, range) {
    return value >= target - range && value <= target + range;
}
export function getPeerSyncType(local, remote, forkChoice, slotImportTolerance) {
    // Aux vars: Inclusive boundaries of the range to consider a peer's head synced to ours.
    const nearRangeStart = local.headSlot - slotImportTolerance;
    const nearRangeEnd = local.headSlot + slotImportTolerance;
    if (remote.finalizedEpoch < local.finalizedEpoch) {
        // The node has a lower finalized epoch, their chain is not useful to us. There are two
        // cases where a node can have a lower finalized epoch:
        //
        // ## The node is on the same chain
        //
        // If a node is on the same chain but has a lower finalized epoch, their head must be
        // lower than ours. Therefore, we have nothing to request from them.
        //
        // ## The node is on a fork
        //
        // If a node is on a fork that has a lower finalized epoch, switching to that fork would
        // cause us to revert a finalized block. This is not permitted, therefore we have no
        // interest in their blocks.
        //
        // We keep these peers to allow them to sync from us.
        return PeerSyncType.Behind;
    }
    if (remote.finalizedEpoch > local.finalizedEpoch) {
        if (
        // Peer is in next epoch, and head is within range => SYNCED
        (local.finalizedEpoch + 1 === remote.finalizedEpoch &&
            withinRangeOf(remote.headSlot, local.headSlot, slotImportTolerance)) ||
            // Peer's head is known => SYNCED
            forkChoice.hasBlock(remote.headRoot)) {
            return PeerSyncType.FullySynced;
        }
        return PeerSyncType.Advanced;
    }
    // remote.finalizedEpoch == local.finalizedEpoch
    // NOTE: if a peer has our same `finalizedEpoch` with a different `finalized_root`
    // they are not considered relevant and won't be propagated to sync.
    // Check if the peer is the peer is inside the tolerance range to be considered synced.
    if (remote.headSlot < nearRangeStart) {
        return PeerSyncType.Behind;
    }
    if (remote.headSlot > nearRangeEnd && !forkChoice.hasBlock(remote.headRoot)) {
        // This peer has a head ahead enough of ours and we have no knowledge of their best block.
        return PeerSyncType.Advanced;
    }
    // This peer is either in the tolerance range, or ahead us with an already rejected block.
    return PeerSyncType.FullySynced;
}
export { RangeSyncType };
var RangeSyncType;
(function (RangeSyncType) {
    /** A finalized chain sync should be started with this peer */
    RangeSyncType["Finalized"] = "Finalized";
    /** A head chain sync should be started with this peer */
    RangeSyncType["Head"] = "Head";
})(RangeSyncType || (RangeSyncType = {}));
// Cache Object.keys iteration for faster loops in metrics
export const rangeSyncTypes = Object.keys(RangeSyncType);
/**
 * Check if a peer requires a finalized chain sync. Only if:
 * - The remotes finalized epoch is greater than our current finalized epoch and we have
 *   not seen the finalized hash before
 */
export function getRangeSyncType(local, remote, forkChoice) {
    if (remote.finalizedEpoch > local.finalizedEpoch && !forkChoice.hasBlock(remote.finalizedRoot)) {
        return RangeSyncType.Finalized;
    }
    return RangeSyncType.Head;
}
export function getRangeSyncTarget(local, remote, chain) {
    const forkChoice = chain.forkChoice;
    // finalized sync
    if (remote.finalizedEpoch > local.finalizedEpoch && !forkChoice.hasBlock(remote.finalizedRoot)) {
        return {
            // If  RangeSyncType.Finalized, the range of blocks fetchable from startEpoch and target must allow to switch
            // to RangeSyncType.Head
            //
            // finalizedRoot is a block with slot <= computeStartSlotAtEpoch(finalizedEpoch).
            // If finalizedEpoch does not start with a skipped slot, the SyncChain with this target MUST process the
            // first block of the next epoch in order to trigger the condition above `forkChoice.hasBlock(remote.finalizedRoot)`
            // and do a Head sync.
            //
            // When doing a finalized sync, we'll process blocks up to the finalized checkpoint, which does not allow to
            // finalize that checkpoint. Instead, our head will be the finalized checkpoint and our finalized checkpoint will
            // be some older checkpoint. After completing a finalized SyncChain:
            //
            // (== finalized, -- non-finalized)
            // Remote  ====================================================|----------------|
            // Local   =====================================|--------------|
            syncType: RangeSyncType.Finalized,
            startEpoch: local.finalizedEpoch,
            target: {
                slot: computeStartSlotAtEpoch(remote.finalizedEpoch),
                root: remote.finalizedRoot,
            },
        };
    }
    // we don't want to sync from epoch < minEpoch
    // if we boot from an unfinalized checkpoint state, we don't want to sync before anchorStateLatestBlockSlot
    // if we boot from a finalized checkpoint state, anchorStateLatestBlockSlot is trusted and we also don't want to sync before it
    const minEpoch = Math.max(remote.finalizedEpoch, computeEpochAtSlot(chain.anchorStateLatestBlockSlot));
    // head sync
    return {
        syncType: RangeSyncType.Head,
        // The new peer has the same finalized `remote.finalizedEpoch == local.finalizedEpoch` since
        // previous filters should prevent a peer with an earlier finalized chain from reaching here.
        //
        // By default and during stable network conditions, the head sync always starts from
        // the finalized epoch (even though it's the head sync) because finalized epoch is < local head.
        // This is to prevent the issue noted here https://github.com/ChainSafe/lodestar/pull/7509#discussion_r1984353063.
        //
        // During non-finality of the network, when starting from an unfinalized checkpoint state, we don't want
        // to sync before anchorStateLatestBlockSlot as finalized epoch is too far away. Local head will also be
        // the same to that value at startup, the head sync always starts from anchorStateLatestBlockSlot in this case.
        startEpoch: Math.min(computeEpochAtSlot(local.headSlot), minEpoch),
        target: {
            slot: remote.headSlot,
            root: remote.headRoot,
        },
    };
}
//# sourceMappingURL=remoteSyncType.js.map