"use strict";
/**
 * SyncV2 a p2p module that contains all of the functionality for the new
 * Node List Sync v2.
 */
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 () {
    var ownKeys = function(o) {
        ownKeys = Object.getOwnPropertyNames || function (o) {
            var ar = [];
            for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
            return ar;
        };
        return ownKeys(o);
    };
    return function (mod) {
        if (mod && mod.__esModule) return mod;
        var result = {};
        if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
        __setModuleDefault(result, mod);
        return result;
    };
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.init = init;
exports.syncV2 = syncV2;
const neverthrow_1 = require("neverthrow");
const lib_types_1 = require("@shardeum-foundation/lib-types");
const ProblemNodeHandler = __importStar(require("../ProblemNodeHandler"));
const queries_1 = require("./queries");
const verify_1 = require("./verify");
const Archivers = __importStar(require("../Archivers"));
const NodeList = __importStar(require("../NodeList"));
const CycleChain = __importStar(require("../CycleChain"));
const ServiceQueue = __importStar(require("../ServiceQueue"));
const routes_1 = require("./routes");
const Sync_1 = require("../Sync");
const v2_1 = require("../Join/v2");
const logger_1 = require("../../logger");
const CycleCreator_1 = require("../CycleCreator");
const queries_2 = require("./queries");
const Context_1 = require("../Context");
/** Initializes logging and endpoints for Sync V2. */
function init() {
    (0, queries_1.initLogger)();
    (0, routes_1.initRoutes)();
}
/**
 * This function synchronizes nodes, archivers and cycle records.
 *
 * @export
 * @param {P2P.SyncTypes.ActiveNode[]} activeNodes - An array of active nodes that can be queried for synchronization. The function first synchronizes the validator list, the archiver list, and then the latest cycle record respectively.
 *
 * Only active nodes are added to the NodeList, and all the archivers are added to the Archivers list.
 * The cycle record is then appended to the CycleChain.
 *
 * @returns {ResultAsync<void, Error>} - A ResultAsync object. On success, it will contain void and on
 * error, it will contain an Error object. The function is asynchronous and can be awaited.
 */
function syncV2(activeNodes, shardus) {
    return syncValidValidatorList(activeNodes).andThen(([validatorList, validatorListHash]) => syncArchiverList(activeNodes).andThen(([archiverList, archiverListHash]) => syncStandbyNodeList(activeNodes).andThen((standbyNodeList) => syncTxList(activeNodes).andThen((txList) => syncLatestCycleRecord(activeNodes).andThen((cycle) => {
        if (cycle.nodeListHash !== validatorListHash) {
            return (0, neverthrow_1.errAsync)(new Error(`validator list hash from received cycle (${cycle.nodeListHash}) does not match the hash received from robust query (${validatorListHash})`));
        }
        else if (cycle.archiverListHash !== archiverListHash) {
            return (0, neverthrow_1.errAsync)(new Error(`archiver list hash from received cycle (${cycle.archiverListHash}) does not match the hash received from robust query (${archiverListHash})`));
        }
        // Log config value before patching (for comparison)
        info(`syncV2: syncV2HistoricalCyclesCount before patch: ${Context_1.config.p2p.syncV2HistoricalCyclesCount}`);
        return neverthrow_1.ResultAsync.fromPromise(shardus.earlyConfigFetchAndPatch(cycle.counter), (error) => new Error(`Failed to fetch and patch config: ${error}`)).andThen(() => {
            // Log config value after patching
            info(`syncV2: syncV2HistoricalCyclesCount after patch: ${Context_1.config.p2p.syncV2HistoricalCyclesCount}`);
            NodeList.reset('syncV2');
            // log the counts of the nodes, archivers, and standby nodes
            /* prettier-ignore */ if (logger_1.logFlags.important_as_fatal)
                console.log(`syncV2: nodes: ${validatorList.length}, archivers: ${archiverList.length}, standby nodes: ${standbyNodeList.length}`);
            // add validators
            NodeList.addNodes(validatorList, 'syncV2', cycle);
            // add archivers
            for (const archiver of archiverList) {
                Archivers.archivers.set(archiver.publicKey, archiver);
            }
            // add standby nodes
            (0, v2_1.addStandbyJoinRequests)(standbyNodeList, true);
            // add txList
            ServiceQueue.setTxList(txList);
            // add latest cycle
            CycleChain.reset();
            // earlyConfigFetchAndPatch() is an async call so we have to do some
            // funky stuff to call it in this neverthow style code:
            info('syncV2: cycle.counter ', cycle.counter);
            info('syncV2: cycle.marker ', (0, CycleCreator_1.makeCycleMarker)(cycle));
            info('syncV2: nodelist hash ', cycle.nodeListHash);
            info('syncV2: archiverList hash ', cycle.archiverListHash);
            info('syncV2: standbyNodeList hash ', cycle.standbyNodeListHash);
            info('syncV2: cycle ', lib_types_1.Utils.safeStringify(cycle));
            (0, Sync_1.digestCycle)(cycle, 'syncV2');
            info('syncV2: CycleChain.newest.counter ', CycleChain.newest.counter);
            info('syncV2: CycleChain.newest.marker ', (0, CycleCreator_1.makeCycleMarker)(CycleChain.newest));
            info('syncV2: nodelist hash ', CycleChain.newest.nodeListHash);
            info('syncV2: archiverList hash ', CycleChain.newest.archiverListHash);
            info('syncV2: standbyNodeList hash ', CycleChain.newest.standbyNodeListHash);
            info('syncV2: CycleChain.newest ', lib_types_1.Utils.safeStringify(CycleChain.newest));
            // Sync historical cycles after the latest cycle
            return syncHistoricalCycles(activeNodes, cycle).andThen((historicalCycles) => {
                if (historicalCycles.length > 0) {
                    info(`syncV2: Adding ${historicalCycles.length} historical cycles to CycleChain`);
                    // Use batch prepend for efficiency
                    CycleChain.prependMultiple(historicalCycles);
                    info(`syncV2: CycleChain now has ${CycleChain.cycles.length} cycles`);
                    info(`syncV2: Oldest cycle counter: ${CycleChain.oldest.counter}`);
                    info(`syncV2: Newest cycle counter: ${CycleChain.newest.counter}`);
                    info(`syncV2: Historical sync complete - using network config value of ${Context_1.config.p2p.syncV2HistoricalCyclesCount}`);
                    // Rebuild problematic node cache with all available cycles
                    if (Context_1.config.p2p.enableProblematicNodeCacheBuilding) {
                        ProblemNodeHandler.rebuildCacheFromCycleChain();
                        info(`syncV2: Rebuilt ProblematicNodeCache with ${CycleChain.cycles.length} cycles`);
                        // Validate we have sufficient history for problematic node detection
                        const availableHistory = CycleChain.cycles.length;
                        const requiredHistory = Context_1.config.p2p.problematicNodeHistoryLength;
                        if (availableHistory < requiredHistory) {
                            warn(`syncV2: Insufficient cycle history for problematic node detection. Have ${availableHistory}, need ${requiredHistory}`);
                        }
                    }
                }
                else {
                    info(`syncV2: No historical cycles synced (network config: ${Context_1.config.p2p.syncV2HistoricalCyclesCount})`);
                }
                return (0, neverthrow_1.okAsync)(void 0);
            });
        });
    })))));
}
/**
 * This function queries for a validator list from other active nodes.
 *
 * @param {P2P.SyncTypes.ActiveNode[]} activeNodes - An array of active nodes to be queried.
 * The function first performs a robust query for the latest node list hash.
 * Then, it requests a full list from one of the winning nodes using the hash
 * retrieved. The node receiving the request may or may not have the list whose
 * hash matches the one requested.
 *
 * @returns {ResultAsync<[P2P.NodeListTypes.Node[], hexstring], Error>} - A
 * ResultAsync object. On success, it will contain an array of Node objects and
 * the validator list hash, and on error, it will contain an Error object. The
 * function is asynchronous and can be awaited.
 */
function syncValidValidatorList(activeNodes) {
    // run a robust query for the lastest node list hash
    return (0, queries_1.robustQueryForValidatorListHash)(activeNodes).andThen(({ value, winningNodes }) => 
    // get full node list from one of the winning nodes
    (0, queries_1.getValidatorListFromNode)(winningNodes[0], value.nodeListHash).andThen((nodeList) => 
    // verify a hash of the retrieved node list matches the hash from before.
    // if it does, return the node list
    (0, verify_1.verifyValidatorList)(nodeList, value.nodeListHash).map(() => [nodeList, value.nodeListHash])));
}
/**
 * This function queries for an archiver list from other active nodes.
 *
 * @param {P2P.SyncTypes.ActiveNode[]} activeNodes - An array of active nodes to be queried.
 * The function first performs a robust query for the latest archiver list hash.
 * Then, it requests a full list from one of the winning nodes using the hash
 * retrieved. The node receiving the request may or may not have the list whose
 * hash matches the one requested.
 *
 * @returns {ResultAsync<[P2P.ArchiversTypes.JoinedArchiver[], hexstring], Error>} - A ResultAsync object. On success, it will contain an array of
 * JoinedArchiver objects and the archiver list hash, and on error, it will contain an Error object. The function is asynchronous and can be awaited.
 */
function syncArchiverList(activeNodes) {
    // run a robust query for the lastest archiver list hash
    return (0, queries_1.robustQueryForArchiverListHash)(activeNodes).andThen(({ value, winningNodes }) => 
    // get full archiver list from one of the winning nodes
    (0, queries_1.getArchiverListFromNode)(winningNodes[0], value.archiverListHash).andThen((archiverList) => 
    // verify a hash of the retrieved archiver list matches the hash from before.
    // if it does, return the archiver list
    (0, verify_1.verifyArchiverList)(archiverList, value.archiverListHash).map(() => [archiverList, value.archiverListHash])));
}
/**
 * This function queries for a valid standby node list.
 *
 * @param {P2P.SyncTypes.ActiveNode[]} activeNodes - An array of active nodes to be queried.
 * The function first performs a robust query for the latest standby node list hash.
 * Then, it requests a full list from one of the winning nodes using the hash
 * retrieved. The node receiving the request may or may not have the list whose
 * hash matches the one requested.
 *
 * @returns {ResultAsync<P2P.ArchiversTypes.JoinedArchiver[], Error>} - A ResultAsync object. On success, it will contain
 * an array of JoinRequest objects, and on error, it will contain an Error object. The function is asynchronous
 * and can be awaited.
 */
function syncStandbyNodeList(activeNodes) {
    // run a robust query for the lastest archiver list hash
    return (0, queries_1.robustQueryForStandbyNodeListHash)(activeNodes).andThen(({ value, winningNodes }) => {
        // get full archiver list from one of the winning nodes
        return (0, queries_1.getStandbyNodeListFromNode)(winningNodes[0], value.standbyNodeListHash);
    });
}
function syncTxList(activeNodes) {
    return (0, queries_1.robustQueryForTxListHash)(activeNodes).andThen(({ value, winningNodes }) => (0, queries_1.getTxListFromNode)(winningNodes[0], value.txListHash).andThen((txList) => (0, verify_1.verifyTxList)(txList, value.txListHash).map(() => txList)));
}
/**
 * Synchronizes the latest cycle record from a list of active nodes.
 *
 * @param {P2P.SyncTypes.ActiveNode[]} activeNodes - An array of active nodes to be queried.
 * The function first performs a robust query for the latest cycle record hash.
 * After obtaining the hash, it retrieves the current cycle data from one of the winning nodes.
 * It then verifies whether the cycle record marker matches the previously obtained hash.
 * If it matches, the cycle record is returned.
 *
 * @returns {ResultAsync<P2P.CycleCreatorTypes.CycleRecord, Error>} - A ResultAsync object.
 * On success, it will contain a CycleRecord object, and on error, it will contain an Error object.
 * The function is asynchronous and can be awaited.
 */
function syncLatestCycleRecord(activeNodes) {
    // run a robust query for the latest cycle record hash
    return (0, queries_1.robustQueryForCycleRecordHash)(activeNodes).andThen(({ value, winningNodes }) => 
    // get current cycle record from node
    (0, queries_1.getCycleDataFromNode)(winningNodes[0], value.currentCycleHash).andThen((cycle) => 
    // verify the cycle record marker matches the hash from before. if it
    // does, return the cycle
    (0, verify_1.verifyCycleRecord)(cycle, value.currentCycleHash).map(() => cycle)));
}
/**
 * Synchronizes historical cycles from the network.
 *
 * @param {P2P.SyncTypes.ActiveNode[]} activeNodes - Active nodes to query
 * @param {P2P.CycleCreatorTypes.CycleRecord} latestCycle - The latest cycle already synced
 * @returns {ResultAsync<P2P.CycleCreatorTypes.CycleRecord[], Error>} Array of historical cycles
 */
function syncHistoricalCycles(activeNodes, latestCycle) {
    // Get the value from config (which should be patched by earlyConfigFetchAndPatch)
    // Note: The config object from Context is the same reference that gets patched,
    // so this will automatically use the network's value after earlyConfigFetchAndPatch runs
    const rawHistoricalCount = Context_1.config.p2p.syncV2HistoricalCyclesCount || 0;
    // Log the config value being used (helps verify network vs local config)
    info(`syncV2HistoricalCyclesCount from config: ${rawHistoricalCount}`);
    // Validate the count is within reasonable bounds (0-100)
    const MAX_HISTORICAL_CYCLES = 100;
    const historicalCount = Math.min(Math.max(0, rawHistoricalCount), MAX_HISTORICAL_CYCLES);
    if (rawHistoricalCount !== historicalCount) {
        info(`Historical cycles count adjusted from ${rawHistoricalCount} to ${historicalCount} (max: ${MAX_HISTORICAL_CYCLES})`);
    }
    if (historicalCount <= 0) {
        info('Historical cycles sync disabled (count = 0)');
        return (0, neverthrow_1.okAsync)([]);
    }
    info(`Starting historical cycles sync with network config value: ${historicalCount} cycles`);
    // Run robust query for recent cycle markers with error handling for backward compatibility
    return (0, queries_1.robustQueryForRecentCycleMarkers)(activeNodes)
        .andThen(({ value, winningNodes }) => {
        const { cycleMarkers, oldestCounter } = value;
        if (cycleMarkers.length === 0) {
            info('No historical cycles available from network');
            return (0, neverthrow_1.okAsync)([]);
        }
        info(`Found ${cycleMarkers.length} cycle markers available, oldest counter: ${oldestCounter}`);
        // Filter out the latest cycle we already have
        const latestMarker = (0, CycleCreator_1.makeCycleMarker)(latestCycle);
        const historicalMarkers = cycleMarkers.filter((marker) => marker !== latestMarker);
        // Limit to configured count
        const markersToFetch = historicalMarkers.slice(-historicalCount);
        if (markersToFetch.length === 0) {
            info('No additional historical cycles to fetch');
            return (0, neverthrow_1.okAsync)([]);
        }
        info(`Fetching ${markersToFetch.length} historical cycles`);
        // Fetch cycles in batches
        const batchSize = 10;
        const batches = [];
        for (let i = 0; i < markersToFetch.length; i += batchSize) {
            batches.push(markersToFetch.slice(i, i + batchSize));
        }
        // Fetch all batches
        const batchPromises = batches.map((batch) => (0, queries_1.getCyclesBatchFromNode)(winningNodes[0], batch));
        return neverthrow_1.ResultAsync.combine(batchPromises).andThen((results) => {
            const allCycles = [];
            for (const result of results) {
                allCycles.push(...result.cycles);
            }
            // Sort cycles by counter (oldest to newest)
            allCycles.sort((a, b) => a.counter - b.counter);
            // Validate cycle chain continuity
            for (let i = 1; i < allCycles.length; i++) {
                const prev = allCycles[i - 1];
                const curr = allCycles[i];
                if (!CycleChain.validate(prev, curr)) {
                    return (0, neverthrow_1.errAsync)(new Error(`Invalid cycle chain: cycle ${curr.counter} does not follow ${prev.counter}`));
                }
            }
            // Validate connection to latest cycle
            if (allCycles.length > 0) {
                const lastHistorical = allCycles[allCycles.length - 1];
                if (lastHistorical.counter !== latestCycle.counter - 1) {
                    // Check if we can still validate the chain
                    const expectedPrevMarker = (0, CycleCreator_1.makeCycleMarker)(lastHistorical);
                    if (latestCycle.previous !== expectedPrevMarker) {
                        return (0, neverthrow_1.errAsync)(new Error(`Historical cycles do not connect to latest cycle`));
                    }
                }
            }
            info(`Successfully fetched and validated ${allCycles.length} historical cycles`);
            return (0, neverthrow_1.okAsync)(allCycles);
        });
    })
        .orElse((error) => {
        // Handle backward compatibility - older nodes won't have the endpoint
        if (error.message.includes('404') || error.message.includes('not found')) {
            info('Historical cycles sync not supported by network (backward compatibility)');
            return (0, neverthrow_1.okAsync)([]);
        }
        // Re-throw other errors
        return (0, neverthrow_1.errAsync)(error);
    });
}
function info(...msg) {
    const entry = `SyncV2: ${msg.join(' ')}`;
    queries_2.p2pLogger.info(entry);
}
function warn(...msg) {
    const entry = `SyncV2: ${msg.join(' ')}`;
    queries_2.p2pLogger.warn(entry);
}
//# sourceMappingURL=index.js.map