"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
    if (mod && mod.__esModule) return mod;
    var result = {};
    if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
    __setModuleDefault(result, mod);
    return result;
};
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.changeNodeListInRestore = exports.getLastHashedNodeList = exports.getNodeListHash = exports.computeNewNodeListHash = exports.getAgeIndexForNodeId = exports.getAgeIndex = exports.getDebug = exports.ipPort = exports.createNode = exports.isNodeRefuted = exports.isNodeLeftNetworkEarly = exports.updateProblematicNodeTracking = exports.updateNodes = exports.updateNode = exports.removeNodes = exports.emitSyncTimeoutEvent = exports.removeNode = exports.removeReadyNode = exports.removeSyncingNode = exports.removeSelectedNode = exports.getRemovedNodePubKeyFromCache = exports.addNodes = exports.addNode = exports.reset = exports.init = exports.removedNodeIDCache = exports.selectedById = exports.potentiallyRemoved = exports.activeOthersByIdOrder = exports.readyByTimeAndIdOrder = exports.standbyByIdOrder = exports.selectedByIdOrder = exports.syncingByIdOrder = exports.activeIdToPartition = exports.activeByIdOrder = exports.othersByIdOrder = exports.byIdOrder = exports.byJoinOrder = exports.byIpPort = exports.byPubKey = exports.nodes = void 0;
const lib_types_1 = require("@shardeum-foundation/lib-types");
const debugMiddleware_1 = require("../network/debugMiddleware");
const utils_1 = require("../utils");
const Comms = __importStar(require("./Comms"));
const Context_1 = require("./Context");
const CycleChain = __importStar(require("./CycleChain"));
const Self_1 = require("./Self");
const rfdc_1 = __importDefault(require("rfdc"));
const logger_1 = require("../logger");
const __1 = require("..");
const network_1 = require("../network");
const v2_1 = require("./Join/v2");
const CycleAutoScale_1 = require("./CycleAutoScale");
const lib_types_2 = require("@shardeum-foundation/lib-types");
const Modes_1 = require("./Modes");
const ProblemNodeHandler = __importStar(require("./ProblemNodeHandler"));
const clone = (0, rfdc_1.default)();
/** STATE */
let p2pLogger;
let mainLogger;
const VERBOSE = false; // Use to dump complete NodeList and CycleChain data
reset('init');
/** FUNCTIONS */
function init() {
    p2pLogger = Context_1.logger.getLogger('p2p');
    mainLogger = Context_1.logger.getLogger('main');
    Context_1.network.registerExternalGet('network-stats', (req, res) => {
        try {
            // todo: reject if request is not coming from node operator dashboard
            const networkStats = {
                active: exports.activeByIdOrder.length,
                syncing: exports.syncingByIdOrder.length,
                ready: exports.readyByTimeAndIdOrder.length,
                standby: (0, v2_1.getStandbyNodesInfoMap)().size,
                desired: (0, CycleAutoScale_1.getDesiredCount)(),
            };
            res.json(networkStats);
        }
        catch (e) {
            console.log(`Error getting load: ${e.message}`);
        }
        return;
    });
    Context_1.network.registerExternalGet('age-index', debugMiddleware_1.isDebugModeMiddlewareLow, (req, res) => {
        try {
            res.json(getAgeIndex());
        }
        catch (e) {
            console.log(`Error getting age index: ${e.message}`);
        }
        return;
    });
}
exports.init = init;
function reset(caller) {
    //this counter intance may not exist yet
    __1.nestedCountersInstance?.countEvent('p2p', `NodeList reset: ${caller} ${(0, network_1.shardusGetTime)()}`);
    exports.nodes = new Map();
    exports.byPubKey = new Map();
    exports.byIpPort = new Map();
    exports.byJoinOrder = [];
    exports.byIdOrder = [];
    exports.othersByIdOrder = [];
    exports.activeByIdOrder = [];
    exports.activeIdToPartition = new Map();
    exports.syncingByIdOrder = [];
    exports.selectedByIdOrder = [];
    exports.standbyByIdOrder = [];
    exports.readyByTimeAndIdOrder = [];
    exports.activeOthersByIdOrder = [];
    exports.potentiallyRemoved = new Set();
    exports.selectedById = new Map();
}
exports.reset = reset;
const isRefuteCyclesEnabled = (cycle) => {
    if (cycle === null) {
        __1.nestedCountersInstance.countEvent('p2p', `initRefuteCycles: cycle is null when initializing refute cycles`);
        /* prettier-ignore */ if (logger_1.logFlags.p2pNonFatal)
            console.log('initRefuteCycles: cycle is null when initializing refute cycles');
        return false;
    }
    if (Context_1.config.p2p.enableProblematicNodeRemoval && cycle.counter >= Context_1.config.p2p.enableProblematicNodeRemovalOnCycle) {
        return true;
    }
    return false;
};
const initRefuteCyclesForNode = (node, cycle) => {
    if (isRefuteCyclesEnabled(cycle)) {
        // This if check is a critical fix.  This is because initRefuteCyclesForNode gets called
        // as part of addNode and addNode gets called in syncV2 after we download the nodelist and then
        // pass it to addNodes.   having the check here will make sure we dont wipe out existing
        // data in refuteCycles that we just downloaded.
        if (node.refuteCycles == null)
            node.refuteCycles = [];
    }
};
function addNode(node, caller, cycle) {
    if (node == null) {
        //warn(`NodeList.addNode: tried to add null node ${caller}`)
        __1.nestedCountersInstance.countEvent('p2p', `addNode rejecting null node from: ${caller}`);
        return;
    }
    // Don't add duplicates
    if (exports.nodes.has(node.id)) {
        warn(`NodeList.addNode: tried to add duplicate ${node.externalPort}: ${lib_types_2.Utils.safeStringify(node)}\n` + `${caller}`);
        return;
    }
    initRefuteCyclesForNode(node, cycle);
    exports.nodes.set(node.id, node);
    exports.byPubKey.set(node.publicKey, node);
    exports.byIpPort.set(ipPort(node.internalIp, node.internalPort), node);
    // Insert sorted by syncingTimestamp into byJoinOrder
    // in the past this used joinRequestTimestamp, but joinRequestTimestamp now is the time when a node is put into
    // the standbylist
    // this will contain nodes that are selected, syncing, ready, and active
    (0, utils_1.linearInsertSorted)(exports.byJoinOrder, node, (0, utils_1.propComparator2)('syncingTimestamp', 'id'));
    // Insert sorted by id into byIdOrder
    (0, utils_1.insertSorted)(exports.byIdOrder, node, (0, utils_1.propComparator)('id'));
    // Dont insert yourself into othersbyIdOrder
    if (node.id !== Self_1.id) {
        (0, utils_1.insertSorted)(exports.othersByIdOrder, node, (0, utils_1.propComparator)('id'));
    }
    // If standby, insert into standbyByIdOrder
    if (node.status === lib_types_1.P2P.P2PTypes.NodeStatus.STANDBY) {
        (0, utils_1.insertSorted)(exports.standbyByIdOrder, node, (0, utils_1.propComparator)('id'));
    }
    // If selected, insert into selectedByIdOrder
    if (node.status === lib_types_1.P2P.P2PTypes.NodeStatus.SELECTED) {
        (0, utils_1.insertSorted)(exports.selectedByIdOrder, node, (0, utils_1.propComparator)('id'));
        exports.selectedById.set(node.id, node.counterRefreshed);
    }
    // If syncing, insert sorted by id into syncingByIdOrder
    if (node.status === lib_types_1.P2P.P2PTypes.NodeStatus.SYNCING) {
        (0, utils_1.insertSorted)(exports.syncingByIdOrder, node, (0, utils_1.propComparator)('id'));
    }
    // If node is READY status, insert sorted by readyTimestamp and id to tiebreak into readyByTimeAndIdOrder
    if (node.status === lib_types_1.P2P.P2PTypes.NodeStatus.READY) {
        (0, utils_1.linearInsertSorted)(exports.readyByTimeAndIdOrder, node, (0, utils_1.propComparator2)('readyTimestamp', 'id'));
    }
    // If active, insert sorted by id into activeByIdOrder
    if (node.status === lib_types_1.P2P.P2PTypes.NodeStatus.ACTIVE) {
        (0, utils_1.insertSorted)(exports.activeByIdOrder, node, (0, utils_1.propComparator)('id'));
        for (let i = 0; i < exports.activeByIdOrder.length; i++) {
            exports.activeIdToPartition.set(exports.activeByIdOrder[i].id, i);
        }
        // Dont insert yourself into activeOthersByIdOrder
        if (node.id !== Self_1.id) {
            (0, utils_1.insertSorted)(exports.activeOthersByIdOrder, node, (0, utils_1.propComparator)('id'));
        }
        // remove active node from syncing list
        removeSyncingNode(node.id);
        removeReadyNode(node.id);
    }
}
exports.addNode = addNode;
function addNodes(newNodes, caller, cycle) {
    for (const node of newNodes) {
        addNode(node, caller, cycle);
    }
}
exports.addNodes = addNodes;
function getRemovedNodePubKeyFromCache(nodeId) {
    return exports.removedNodeIDCache.get(nodeId);
}
exports.getRemovedNodePubKeyFromCache = getRemovedNodePubKeyFromCache;
function removeSelectedNode(id) {
    exports.selectedById.delete(id);
    const idx = (0, utils_1.binarySearch)(exports.selectedByIdOrder, { id }, (0, utils_1.propComparator)('id'));
    /* prettier-ignore */ if (logger_1.logFlags.verbose)
        console.log('Removing selected node', id, idx);
    if (idx >= 0)
        exports.selectedByIdOrder.splice(idx, 1);
}
exports.removeSelectedNode = removeSelectedNode;
function removeSyncingNode(id) {
    const idx = (0, utils_1.binarySearch)(exports.syncingByIdOrder, { id }, (0, utils_1.propComparator)('id'));
    /* prettier-ignore */ if (logger_1.logFlags.verbose)
        console.log('Removing syncing node', id, idx);
    if (idx >= 0)
        exports.syncingByIdOrder.splice(idx, 1);
}
exports.removeSyncingNode = removeSyncingNode;
function removeReadyNode(id) {
    let idx = -1;
    for (let i = 0; i < exports.readyByTimeAndIdOrder.length; i++) {
        if (exports.readyByTimeAndIdOrder[i].id === id) {
            idx = i;
            break;
        }
    }
    /* prettier-ignore */ if (logger_1.logFlags.p2pNonFatal && logger_1.logFlags.console)
        console.log('Removing ready node', id, idx);
    if (idx >= 0)
        exports.readyByTimeAndIdOrder.splice(idx, 1);
}
exports.removeReadyNode = removeReadyNode;
function removeNode(id, raiseEvents, cycle) {
    let idx;
    // Omar added this so we don't crash if a node gets remove more than once
    if (!exports.nodes.has(id)) {
        console.log('Tried to delete a node that is not in the nodes list.', id);
        console.trace();
        return;
    }
    // Remove from arrays
    idx = (0, utils_1.binarySearch)(exports.selectedByIdOrder, { id }, (0, utils_1.propComparator)('id'));
    if (idx >= 0)
        exports.selectedByIdOrder.splice(idx, 1);
    idx = (0, utils_1.binarySearch)(exports.standbyByIdOrder, { id }, (0, utils_1.propComparator)('id'));
    if (idx >= 0)
        exports.standbyByIdOrder.splice(idx, 1);
    idx = (0, utils_1.binarySearch)(exports.activeOthersByIdOrder, { id }, (0, utils_1.propComparator)('id'));
    if (idx >= 0)
        exports.activeOthersByIdOrder.splice(idx, 1);
    idx = (0, utils_1.binarySearch)(exports.activeByIdOrder, { id }, (0, utils_1.propComparator)('id'));
    if (idx >= 0) {
        exports.activeByIdOrder.splice(idx, 1);
        exports.activeIdToPartition.delete(id);
    }
    idx = (0, utils_1.binarySearch)(exports.othersByIdOrder, { id }, (0, utils_1.propComparator)('id'));
    if (idx >= 0)
        exports.othersByIdOrder.splice(idx, 1);
    idx = (0, utils_1.binarySearch)(exports.byIdOrder, { id }, (0, utils_1.propComparator)('id'));
    if (idx >= 0)
        exports.byIdOrder.splice(idx, 1);
    idx = (0, utils_1.binarySearch)(exports.syncingByIdOrder, { id }, (0, utils_1.propComparator)('id'));
    if (idx >= 0)
        exports.syncingByIdOrder.splice(idx, 1);
    if (Context_1.config.p2p.hardenNewSyncingProtocol) {
        removeReadyNode(id);
    }
    else {
        idx = (0, utils_1.binarySearch)(exports.readyByTimeAndIdOrder, { id }, (0, utils_1.propComparator)('id'));
        if (idx >= 0)
            exports.readyByTimeAndIdOrder.splice(idx, 1);
    }
    const syncingTimestamp = exports.nodes.get(id).syncingTimestamp;
    idx = (0, utils_1.binarySearch)(exports.byJoinOrder, { syncingTimestamp, id }, (0, utils_1.propComparator2)('syncingTimestamp', 'id'));
    if (idx >= 0) {
        exports.byJoinOrder.splice(idx, 1);
    }
    // Remove from maps
    const node = exports.nodes.get(id);
    exports.byIpPort.delete(ipPort(node.internalIp, node.internalPort));
    exports.byPubKey.delete(node.publicKey);
    exports.nodes.delete(id);
    exports.selectedById.delete(id);
    //readyByTimeAndIdOrder = readyByTimeAndIdOrder.filter((node) => node.id !== id)
    // add to removed node cache
    if (!exports.removedNodeIDCache) {
        exports.removedNodeIDCache = new utils_1.FIFOCache(Context_1.config.p2p.removedNodeIDCacheSize);
    }
    exports.removedNodeIDCache.set(id, node.publicKey);
    Comms.evictCachedSockets([node]);
    if (raiseEvents) {
        // check if this node is marked as "lost" prev cycle
        if (isNodeLeftNetworkEarly(node)) {
            const emitParams = {
                nodeId: node.id,
                reason: 'Node left early',
                time: cycle.start,
                publicKey: node.publicKey,
                cycleNumber: cycle.counter,
            };
            Self_1.emitter.emit('node-left-early', emitParams);
        }
        const emitParams = {
            nodeId: node.id,
            reason: 'Node deactivated',
            time: cycle.start,
            publicKey: node.publicKey,
            cycleNumber: cycle.counter,
            activeCycle: node.activeCycle,
        };
        if (cycle.mode === 'shutdown' || Modes_1.networkMode === 'shutdown') {
            return;
        }
        Self_1.emitter.emit('node-deactivated', emitParams);
    }
}
exports.removeNode = removeNode;
function emitSyncTimeoutEvent(node, cycle) {
    const emitParams = {
        nodeId: node.id,
        reason: 'Node sync timeout',
        time: cycle.start,
        publicKey: node.publicKey,
        cycleNumber: cycle.counter,
    };
    Self_1.emitter.emit('node-sync-timeout', emitParams);
}
exports.emitSyncTimeoutEvent = emitSyncTimeoutEvent;
function removeNodes(ids, raiseEvents, cycle) {
    for (const id of ids)
        removeNode(id, raiseEvents, cycle);
    // Prune inactive nodes from problematic node cache after bulk removal
    if (Context_1.config.p2p.enableProblematicNodeCacheBuilding && ids.length > 0) {
        ProblemNodeHandler.pruneInactiveNodesFromCache();
    }
}
exports.removeNodes = removeNodes;
function updateNode(update, raiseEvents, cycle) {
    const node = exports.nodes.get(update.id);
    if (node) {
        initRefuteCyclesForNode(node, cycle);
        // Update node properties
        for (const key of Object.keys(update)) {
            node[key] = update[key];
        }
        // add node to syncing list if its status is changed to syncing
        if (update.status === lib_types_1.P2P.P2PTypes.NodeStatus.SYNCING) {
            (0, utils_1.insertSorted)(exports.syncingByIdOrder, node, (0, utils_1.propComparator)('id'));
            removeSelectedNode(node.id);
        }
        else if (update.status === lib_types_1.P2P.P2PTypes.NodeStatus.READY) {
            (0, utils_1.linearInsertSorted)(exports.readyByTimeAndIdOrder, node, (0, utils_1.propComparator2)('readyTimestamp', 'id'));
            if (Context_1.config.p2p.hardenNewSyncingProtocol) {
                if (exports.selectedById.has(node.id))
                    removeSelectedNode(node.id); // in case we missed the sync-started gossip
            }
            removeSyncingNode(node.id);
        }
        else if (update.status === lib_types_1.P2P.P2PTypes.NodeStatus.ACTIVE) {
            //test if this node is in the active list already.  if it is not, then we can add it
            let idx = (0, utils_1.binarySearch)(exports.activeByIdOrder, { id: node.id }, (0, utils_1.propComparator)('id'));
            if (idx < 0) {
                (0, utils_1.insertSorted)(exports.activeByIdOrder, node, (0, utils_1.propComparator)('id'));
                for (let i = 0; i < exports.activeByIdOrder.length; i++) {
                    exports.activeIdToPartition.set(exports.activeByIdOrder[i].id, i);
                }
                // Don't add yourself to activeOthersByIdOrder
                if (node.id !== Self_1.id) {
                    (0, utils_1.insertSorted)(exports.activeOthersByIdOrder, node, (0, utils_1.propComparator)('id'));
                }
                // remove active node from ready list
                /* prettier-ignore */ if (logger_1.logFlags.verbose)
                    console.log('updateNode: removing active node from ready list');
                removeReadyNode(node.id);
                if (raiseEvents) {
                    const emitParams = {
                        nodeId: node.id,
                        reason: 'Node activated',
                        time: cycle.start,
                        publicKey: node.publicKey,
                        cycleNumber: cycle.counter,
                    };
                    if (cycle.mode === 'shutdown' || Modes_1.networkMode === 'shutdown') {
                        return;
                    }
                    Self_1.emitter.emit('node-activated', emitParams);
                }
            }
        }
    }
}
exports.updateNode = updateNode;
function updateNodes(updates, raiseEvents, cycle) {
    for (const update of updates)
        updateNode(update, raiseEvents, cycle);
    /* //LOCAL_OOS_TEST_SUPPORT
    let stats = {init:0, push:0}
    const refuteCyclesEnabled = isRefuteCyclesEnabled(cycle)
    // every cycle mess with every node
    for (const node of activeByIdOrder){
      if (refuteCyclesEnabled) {
        if(node.refuteCycles == null){
          node.refuteCycles = []
          stats.init++
        } else {
          node.refuteCycles.push(cycle.counter)
          stats.push++
        }
      }
    }
  
    info(`NodeList.updateNodes: ${updates.length} ${Utils.safeStringify(stats)} enableProblematicNodeRemoval: ${config.p2p.enableProblematicNodeRemoval} cycle: ${cycle?.counter} refuteCyclesEnabled: ${refuteCyclesEnabled} enableProblematicNodeRemovalOnCycle: ${config.p2p.enableProblematicNodeRemovalOnCycle}`)
    */
}
exports.updateNodes = updateNodes;
function updateProblematicNodeTracking(cycle) {
    if (isRefuteCyclesEnabled(cycle)) {
        if (logger_1.logFlags.p2pNonFatal)
            console.log('p2p: updating refute cycles');
        for (const node of exports.nodes.values()) {
            if (!node.refuteCycles) {
                __1.nestedCountersInstance.countEvent('p2p', `updateProblematicNodeTracking: initializing refute cycles for node`);
                if (logger_1.logFlags.p2pNonFatal)
                    console.log('p2p: initializing refute cycles for node', node.id);
                node.refuteCycles = [];
            }
            // Track refutes if this update is from a cycle record
            if (cycle && cycle.refuted?.includes(node.id)) {
                if (!node.refuteCycles.includes(cycle.counter)) {
                    __1.nestedCountersInstance.countEvent('p2p', `updateProblematicNodeTracking: tracking refute cycle for node`);
                    if (logger_1.logFlags.p2pNonFatal)
                        console.log(`p2p: tracking refute cycle for node ${node.id} - ${cycle.counter}`);
                    node.refuteCycles.push(cycle.counter);
                }
            }
            const numRefuteCyclesBeforeClean = node.refuteCycles.length;
            // Clean up old refutes using sliding window
            const windowStart = Math.max(1, cycle.counter - Context_1.config.p2p.problematicNodeHistoryLength);
            node.refuteCycles = node.refuteCycles.filter((c) => c >= windowStart);
            const numRefuteCyclesAfterClean = node.refuteCycles.length;
            if (numRefuteCyclesBeforeClean > numRefuteCyclesAfterClean) {
                __1.nestedCountersInstance.countEvent('p2p', `updateProblematicNodeTracking: cleaned up refute cycles for node`);
                if (logger_1.logFlags.p2pNonFatal)
                    console.log(`p2p: cleaned up refute cycles for node ${node.id} - ${numRefuteCyclesBeforeClean} -> ${numRefuteCyclesAfterClean}`);
            }
        }
    }
}
exports.updateProblematicNodeTracking = updateProblematicNodeTracking;
function isNodeLeftNetworkEarly(node) {
    return CycleChain.newest && CycleChain.newest.lost.includes(node.id);
}
exports.isNodeLeftNetworkEarly = isNodeLeftNetworkEarly;
function isNodeRefuted(node) {
    return CycleChain.newest && CycleChain.newest.refuted.includes(node.id);
}
exports.isNodeRefuted = isNodeRefuted;
function createNode(joined) {
    const node = {
        ...joined,
        curvePublicKey: Context_1.crypto.convertPublicKeyToCurve(joined.publicKey),
        status: lib_types_1.P2P.P2PTypes.NodeStatus.SELECTED,
    };
    return node;
}
exports.createNode = createNode;
function ipPort(ip, port) {
    return ip + ':' + port;
}
exports.ipPort = ipPort;
function idTrim(id) {
    return id.substr(0, 4);
}
function getDebug() {
    let output = `
    NODES:
      hash:                  ${Context_1.crypto.hash(exports.byJoinOrder).slice(0, 5)}
      byJoinOrder:           [${exports.byJoinOrder
        .map((node) => `${node.externalIp}:${node.externalPort}-${node.counterRefreshed}`)
        .join()}]
      byIdOrder:             [${exports.byIdOrder
        .map((node) => `${node.externalIp}:${node.externalPort}` + '-x' + idTrim(node.id))
        .join()}]
      othersByIdOrder:       [${exports.othersByIdOrder.map((node) => `${node.externalIp}:${node.externalPort}`)}]
      activeByIdOrder:       [${exports.activeByIdOrder.map((node) => `${node.externalIp}:${node.externalPort}`)}]
      activeOthersByIdOrder: [${exports.activeOthersByIdOrder.map((node) => `${node.externalIp}:${node.externalPort}`)}]
      `;
    if (VERBOSE)
        output += `
    NODELIST:   ${lib_types_2.Utils.safeStringify(exports.byJoinOrder)}
    CYCLECHAIN: ${lib_types_2.Utils.safeStringify(CycleChain.cycles)}
  `;
    return output;
}
exports.getDebug = getDebug;
/**
 * The index starts at 1, not 0.  1 is the youngest node, idx === total is the oldest node.
 * @returns {idx: number, total: number} - idx is the index of the node in the list, total is the total number of nodes in the list
 */
function getAgeIndex() {
    return getAgeIndexForNodeId(Self_1.id);
}
exports.getAgeIndex = getAgeIndex;
function getAgeIndexForNodeId(nodeId) {
    const totalNodes = exports.activeByIdOrder.length;
    let index = 1;
    for (let i = exports.byJoinOrder.length - 1; i >= 0; i--) {
        if (exports.byJoinOrder[i].id === nodeId && exports.byJoinOrder[i].status === lib_types_1.P2P.P2PTypes.NodeStatus.ACTIVE) {
            return { idx: index, total: totalNodes };
        }
        else if (exports.byJoinOrder[i].status === lib_types_1.P2P.P2PTypes.NodeStatus.ACTIVE) {
            index++;
        }
    }
    return { idx: -1, total: totalNodes };
}
exports.getAgeIndexForNodeId = getAgeIndexForNodeId;
/** Returns the validator list hash. It is a hash of the NodeList sorted by join order. This will also update the recorded `lastHashedList` of nodes, which can be retrieved via `getLastHashedNodeList`. */
function computeNewNodeListHash(log = false) {
    // set the lastHashedList to the current list by join order, then hash.
    // deep cloning is necessary as validator information may be mutated by
    // reference.
    lastHashedList = clone(exports.byJoinOrder);
    /* prettier-ignore */ if (logger_1.logFlags.verbose || log)
        info('hashing validator list:', lib_types_2.Utils.safeStringify(lastHashedList));
    let hash = Context_1.crypto.hash(lastHashedList);
    /* prettier-ignore */ if (logger_1.logFlags.verbose || log)
        info('the new validator list hash is', hash);
    return hash;
}
exports.computeNewNodeListHash = computeNewNodeListHash;
/**
 * For Sync v2, returns the validator list hash from the last complete cycle, if available. If you
 * want to compute a new hash instead, use `computeNewNodeListHash`.
 *
 * If Sync v2 is not enabled, it returns the hash of a sorted list of active node IDs.
 */
function getNodeListHash() {
    if (Context_1.config.p2p.writeSyncProtocolV2 || Context_1.config.p2p.useSyncProtocolV2) {
        /* prettier-ignore */ if (logger_1.logFlags.verbose)
            info('returning validator list hash:', CycleChain.newest?.nodeListHash);
        return CycleChain.newest?.nodeListHash;
    }
    else {
        // this is how the `nodelistHash` is computed before Sync v2
        const nodelistIDs = exports.activeByIdOrder.map((node) => node.id);
        return Context_1.crypto.hash(nodelistIDs);
    }
}
exports.getNodeListHash = getNodeListHash;
let lastHashedList = [];
/** Returns the last list of nodes that had its hash computed. */
function getLastHashedNodeList() {
    /* prettier-ignore */ if (logger_1.logFlags.verbose)
        info('returning last hashed validator list:', lib_types_2.Utils.safeStringify(lastHashedList));
    return lastHashedList;
}
exports.getLastHashedNodeList = getLastHashedNodeList;
function changeNodeListInRestore(cycleStartTimestamp) {
    info(`changeNodeListInRestore: ${cycleStartTimestamp}`);
    __1.nestedCountersInstance.countEvent('p2p', `changeNodeListInRestore: ${cycleStartTimestamp}`);
    if (exports.activeByIdOrder.length === 0)
        return;
    // Combine activeByIdOrder to syncingByIdOrder nodelist; Clear activeByIdOrder and activeOthersByIdOrder nodelists
    for (const node of exports.activeByIdOrder) {
        node.syncingTimestamp = cycleStartTimestamp;
        (0, utils_1.insertSorted)(exports.syncingByIdOrder, node, (0, utils_1.propComparator)('id'));
    }
    exports.activeByIdOrder = [];
    exports.activeOthersByIdOrder = [];
    // change active status nodes to syncing status
    for (const [, node] of exports.nodes) {
        if (node.status === lib_types_1.P2P.P2PTypes.NodeStatus.ACTIVE) {
            node.status = lib_types_1.P2P.P2PTypes.NodeStatus.SYNCING;
            node.syncingTimestamp = cycleStartTimestamp;
        }
    }
    for (const [, node] of exports.byPubKey) {
        if (node.status === lib_types_1.P2P.P2PTypes.NodeStatus.ACTIVE) {
            node.status = lib_types_1.P2P.P2PTypes.NodeStatus.SYNCING;
            node.syncingTimestamp = cycleStartTimestamp;
        }
    }
    for (const [, node] of exports.byIpPort) {
        if (node.status === lib_types_1.P2P.P2PTypes.NodeStatus.ACTIVE) {
            node.status = lib_types_1.P2P.P2PTypes.NodeStatus.SYNCING;
            node.syncingTimestamp = cycleStartTimestamp;
        }
    }
    for (const node of exports.byJoinOrder) {
        if (node.status === lib_types_1.P2P.P2PTypes.NodeStatus.ACTIVE) {
            node.status = lib_types_1.P2P.P2PTypes.NodeStatus.SYNCING;
            node.syncingTimestamp = cycleStartTimestamp;
        }
    }
    for (const node of exports.byIdOrder) {
        if (node.status === lib_types_1.P2P.P2PTypes.NodeStatus.ACTIVE) {
            node.status = lib_types_1.P2P.P2PTypes.NodeStatus.SYNCING;
            node.syncingTimestamp = cycleStartTimestamp;
        }
    }
    for (const node of exports.othersByIdOrder) {
        if (node.status === lib_types_1.P2P.P2PTypes.NodeStatus.ACTIVE) {
            node.status = lib_types_1.P2P.P2PTypes.NodeStatus.SYNCING;
            node.syncingTimestamp = cycleStartTimestamp;
        }
    }
}
exports.changeNodeListInRestore = changeNodeListInRestore;
/** ROUTES */
function info(...msg) {
    const entry = `NodeList: ${msg.join(' ')}`;
    p2pLogger.info(entry);
}
function warn(...msg) {
    const entry = `NodeList: ${msg.join(' ')}`;
    p2pLogger.warn(entry);
}
function error(...msg) {
    const entry = `NodeList: ${msg.join(' ')}`;
    p2pLogger.error(entry);
}
//# sourceMappingURL=NodeList.js.map