"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 () {
    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;
    };
})();
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.init = init;
exports.sync = sync;
exports.syncNewCycles = syncNewCycles;
exports.extraSyncLogsEnabled = extraSyncLogsEnabled;
exports.digestCycle = digestCycle;
exports.getNewestCycle = getNewestCycle;
exports.activeNodeCount = activeNodeCount;
exports.showNodeCount = showNodeCount;
exports.totalNodeCount = totalNodeCount;
const util_1 = __importDefault(require("util"));
const http = __importStar(require("../http"));
const utils_1 = require("../utils");
const Context_1 = require("./Context");
const Archivers = __importStar(require("./Archivers"));
const CycleChain = __importStar(require("./CycleChain"));
const CycleCreator = __importStar(require("./CycleCreator"));
const CycleParser_1 = require("./CycleParser");
const NodeList = __importStar(require("./NodeList"));
const Self = __importStar(require("./Self"));
const Utils_1 = require("./Utils");
const ProblemNodeHandler = __importStar(require("./ProblemNodeHandler"));
const profiler_1 = require("../utils/profiler");
const nestedCounters_1 = require("../utils/nestedCounters");
const NodeList_1 = require("./NodeList");
const v2_1 = require("./Join/v2");
const JoinV2 = __importStar(require("./Join/v2"));
const unjoin_1 = require("./Join/v2/unjoin");
const logger_1 = require("../logger");
const fs_1 = __importDefault(require("fs"));
const path_1 = __importDefault(require("path"));
const lib_types_1 = require("@shardeum-foundation/lib-types");
const Helpers_1 = require("../types/ajv/Helpers");
const AJVSchemaEnum_1 = require("../types/enum/AJVSchemaEnum");
/** STATE */
let p2pLogger;
const filePath = path_1.default.join(process.cwd(), 'data-logs', 'cycleRecords2.txt');
/** ROUTES */
const newestCycleRoute = {
    method: 'GET',
    name: 'sync-newest-cycle',
    handler: (_req, res) => {
        profiler_1.profilerInstance.scopedProfileSectionStart('sync-newest-cycle');
        const newestCycle = CycleChain.newest || null;
        res.json({ newestCycle });
        profiler_1.profilerInstance.scopedProfileSectionEnd('sync-newest-cycle');
    },
};
const cyclesRoute = {
    method: 'POST',
    name: 'sync-cycles',
    handler: (req, res) => {
        profiler_1.profilerInstance.scopedProfileSectionStart('sync-cycles');
        try {
            let err = (0, utils_1.validateTypes)(req, { body: 'o' });
            if (err) {
                warn('sync-cycles bad req ' + err);
                // use res.send({ }) if returning an object
                res.json([]);
                return;
            }
            err = (0, utils_1.validateTypes)(req.body, { start: 'n?', end: 'n?' });
            if (err) {
                warn('sync-cycles bad req.body ' + err);
                // use res.send({ }) if returning an object
                res.json([]);
                return;
            }
            const start = req.body.start | 0;
            const end = req.body.end;
            // const cycles = p2p.state.getCycles(start, end)
            const cycles = CycleChain.getCycleChain(start, end);
            res.json(cycles);
        }
        catch (e) {
            warn('sync-cycles', e);
        }
        finally {
            profiler_1.profilerInstance.scopedProfileSectionEnd('sync-cycles');
        }
    },
};
const routes = {
    external: [newestCycleRoute /** unfinishedCycleRoute */, cyclesRoute],
};
/** FUNCTIONS */
function init() {
    p2pLogger = Context_1.logger.getLogger('p2p');
    for (const route of routes.external) {
        Context_1.network._registerExternal(route.method, route.name, route.handler);
    }
}
async function sync(activeNodes) {
    // Flush existing cycles/nodes
    CycleChain.reset();
    NodeList.reset('sync');
    nestedCounters_1.nestedCountersInstance.countEvent('p2p', `sync-start`);
    // Get the networks newest cycle as the anchor point for sync
    info('Getting newest cycle...');
    const cycleToSyncTo = await getNewestCycle(activeNodes);
    info(`Syncing till cycle ${cycleToSyncTo.counter}...`);
    const cyclesToGet = 2 * Math.floor(Math.sqrt(cycleToSyncTo.active)) + 2;
    info(`Cycles to get is ${cyclesToGet}`);
    // Sync old cycles until your active nodes === network active nodes
    const squasher = new CycleParser_1.ChangeSquasher();
    CycleChain.prepend(cycleToSyncTo);
    squasher.addChange((0, CycleParser_1.parse)(CycleChain.oldest));
    nestedCounters_1.nestedCountersInstance.countEvent('p2p', `sync-to-cycle ${cycleToSyncTo.counter}`);
    do {
        // Get prevCycles from the network
        const end = CycleChain.oldest.counter - 1;
        const start = end - cyclesToGet;
        info(`Getting cycles ${start} - ${end}...`);
        nestedCounters_1.nestedCountersInstance.countEvent('p2p', `sync-getting-cycles ${start} - ${end}`);
        const { cycles: prevCycles } = await getCycles(activeNodes, start, end);
        info(`Got cycles ${lib_types_1.Utils.safeStringify(prevCycles.map((cycle) => cycle.counter))}`);
        info(`  ${lib_types_1.Utils.safeStringify(prevCycles)}`);
        // If prevCycles is empty, start over
        if (prevCycles.length < 1) {
            nestedCounters_1.nestedCountersInstance.countEvent('p2p', `sync-getting-cycles failed to get ${start} - ${end}`);
            //add some code to detect which nodes we are actually missing
            let missing = [];
            for (let node of NodeList_1.byJoinOrder) {
                if (squasher.addedIds.has(node.id) === false) {
                    missing.push(node);
                }
            }
            let totalNodes = totalNodeCount(cycleToSyncTo);
            let squasherTotal = squasher.final.added.length;
            let expectedMissing = totalNodes - squasherTotal;
            let missingListCount = missing.length;
            //if there are more than five nodes in the missingn list truncate it to just five
            if (missing.length > 5) {
                missing = missing.slice(0, 5);
            }
            let missingList = missing.map((node) => {
                return { id: node.id, externalIp: node.externalIp, externalPort: node.externalPort };
            });
            let missingStr = 'none detected';
            if (missingListCount > 0) {
                missingStr = lib_types_1.Utils.safeStringify(missingList[0]);
            }
            /* prettier-ignore */ nestedCounters_1.nestedCountersInstance.countEvent('p2p', `sync-getting-cycles failed to get ${start} - ${end}  expectedMissing:${expectedMissing} missingListCount:${missingListCount} missing:${missingStr}`);
            /* prettier-ignore */ error(`sync-getting-cycles failed to get ${start} - ${end}  expectedMissing:${expectedMissing} missingListCount:${missingListCount}` + lib_types_1.Utils.safeStringify(missingList));
            if (Context_1.config.p2p.hackForceCycleSyncComplete === true && missingListCount === 0) {
                /* prettier-ignore */ nestedCounters_1.nestedCountersInstance.countEvent('p2p', `hack-fix: go active missing count is 0`);
                /* prettier-ignore */ error(`hack-fix: go active missing count is 0`);
                return true;
            }
            throw new Error('Got empty previous cycles');
        }
        // Add prevCycles to our cycle chain
        let prepended = 0;
        for (const prevCycle of (0, utils_1.reversed)(prevCycles)) {
            const marker = CycleChain.computeCycleMarker(prevCycle);
            // If you already have this cycle, skip it
            if (CycleChain.cyclesByMarker[marker]) {
                warn(`Record ${prevCycle.counter} already in cycle chain`);
                continue;
            }
            // Stop prepending prevCycles if one of them is invalid
            if (CycleChain.validate(prevCycle, CycleChain.oldest) === false) {
                warn(`Record ${prevCycle.counter} failed validation`);
                break;
            }
            // Prepend the cycle to our cycle chain
            CycleChain.prepend(prevCycle);
            squasher.addChange((0, CycleParser_1.parse)(prevCycle));
            prepended++;
            if (squasher.final.updated.length >= activeNodeCount(cycleToSyncTo) &&
                squasher.final.added.length >= totalNodeCount(cycleToSyncTo)) {
                break;
            }
        }
        info(`Got ${squasher.final.updated.length} active nodes, need ${activeNodeCount(cycleToSyncTo)}`);
        info(`Got ${squasher.final.added.length} total nodes, need ${totalNodeCount(cycleToSyncTo)}`);
        if (squasher.final.added.length < totalNodeCount(cycleToSyncTo))
            info('Short on nodes. Need to get more cycles. Cycle:' + cycleToSyncTo.counter);
        //    showNodeCount(cycleToSyncTo)
        // If you weren't able to prepend any of the prevCycles, start over
        if (prepended < 1)
            throw new Error('Unable to prepend any previous cycles');
    } while (squasher.final.updated.length < activeNodeCount(cycleToSyncTo) ||
        squasher.final.added.length < totalNodeCount(cycleToSyncTo));
    // Now that our node list is synced, validate the anchor cycle's cert
    // [TODO] [AS]
    /**
     * Commented this out for now; we need to change the logic to look at the cert
     * instead of the marker, and to compare:
     *   (anchor cycle) with (anchor cycle - 1)
     * instead of:
     *   (anchor cycle) with (latest cycle)
     */
    /*
    let nodesToQuery = squasher.final.added.map(node => ({
      ip: node.externalIp,
      port: node.externalPort,
      publicKey: node.publicKey
    }))
    let anchorCycleFromNetwork = await getNewestCycle(nodesToQuery)
    let cycleToSyncMarker = CycleChain.computeCycleMarker(cycleToSyncTo)
    let markerFromNetwork = CycleChain.computeCycleMarker(anchorCycleFromNetwork)
  
    info('cycleToSyncMarker', cycleToSyncMarker)
    info('markerFromNetwork', markerFromNetwork)
  
    // If the anchor cycle was invalid, start over
    if (cycleToSyncMarker !== markerFromNetwork) {
      warn(`Anchor cycle's cert is different from other nodes in the network`)
      return false
    }
    */
    applyNodeListChange(squasher.final, false, null);
    info('Synced to cycle', cycleToSyncTo.counter);
    info(`Sync complete; ${NodeList.activeByIdOrder.length} active nodes; ${CycleChain.cycles.length} cycles`);
    info(`NodeList after sync: ${lib_types_1.Utils.safeStringify([...NodeList.nodes.entries()])}`);
    info(`CycleChain after sync: ${lib_types_1.Utils.safeStringify(CycleChain.cycles)}`);
    const p2pSyncReport = {
        cycle: cycleToSyncTo.counter,
        activeNodes: NodeList.activeByIdOrder.length,
        cyclesSynced: CycleChain.cycles.length,
    };
    nestedCounters_1.nestedCountersInstance.countEvent('p2p', `Sync cyclechain complete; ${lib_types_1.Utils.safeStringify(p2pSyncReport)}`);
    // Rebuild problematic node cache with all available cycles
    if (Context_1.config.p2p.enableProblematicNodeCacheBuilding) {
        ProblemNodeHandler.rebuildCacheFromCycleChain();
        info(`Rebuilt ProblematicNodeCache with ${CycleChain.cycles.length} cycles after sync`);
        // 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(`Insufficient cycle history for problematic node detection. Have ${availableHistory}, need ${requiredHistory}`);
        }
    }
    return true;
}
async function syncNewCycles(activeNodes) {
    info('syncNewCycles: inside');
    let newestCycle = await getNewestCycle(activeNodes);
    info(`syncNewCycles: myNewest=${CycleChain.newest.counter} netNewest=${newestCycle.counter}`);
    const progressHistory = 5;
    const maxAttempts = 10;
    let progress = [];
    let attempt = 0;
    while (CycleChain.newest.counter < newestCycle.counter) {
        info('syncNewCycles: attempt ', attempt);
        info('syncNewCycles: progress ', progress);
        const { cycles: nextCycles, responders } = await getCycles(activeNodes, CycleChain.newest.counter + 1 // [DONE] maybe we should +1 so that we don't get the record we already have
        );
        info(`syncNewCycles: nextCycles `, lib_types_1.Utils.safeStringify(nextCycles));
        const oldCounter = CycleChain.newest.counter;
        info(`syncNewCycles: oldCounter=${oldCounter}`);
        for (const nextCycle of nextCycles) {
            info(`syncNewCycles: nextCycle=${nextCycle.counter}`);
            //      CycleChain.validate(CycleChain.newest, newestCycle)
            // for join v2, also get the standby node list hash
            // if (config.p2p.useJoinProtocolV2) {
            //   const standbyNodeListHash = JoinV2.computeNewStandbyListHash()
            //   if(standbyNodeListHash !== nextCycle.standbyNodeListHash){
            //     /* prettier-ignore */ error( `sync:digestCycle cycle: ${nextCycle.counter} standbyNodeListHash: ${standbyNodeListHash} nextCycle.standbyNodeListHash: ${nextCycle.standbyNodeListHash}` )
            //     // [TODO] We can remove `source !== 'syncV2'` once we shut down ITN2
            //     nextCycle.standbyNodeListHash = standbyNodeListHash
            //   }
            // }
            if (CycleChain.validate(CycleChain.newest, nextCycle)) {
                info(`syncNewCycles: before digesting nextCycle=${nextCycle.counter}`);
                info(`syncNewCycles: before nextCycle.standbyNodeListHash: ${nextCycle.standbyNodeListHash}`);
                await digestCycle(nextCycle, 'syncNewCycles');
                info(`syncNewCycles: after nextCycle.standbyNodeListHash: ${nextCycle.standbyNodeListHash}`);
                info(`syncNewCycles: digested nextCycle=${nextCycle.counter}`);
            }
            else {
                /* prettier-ignore */ error(`syncNewCycles: next record does not fit with prev record.\nprev: ${lib_types_1.Utils.safeStringify(CycleChain.newest)}\nnext: ${lib_types_1.Utils.safeStringify(newestCycle)}`);
                // Suggestion from Shawn:
                // get previous cycle(what they have that corresponds to our CycleChain.newest) from winning nodes and log them here
                for (const responder of responders) {
                    const ip = responder.ip ? responder.ip : responder.externalIp;
                    const port = responder.port ? responder.port : responder.externalPort;
                    const data = {
                        start: CycleChain.newest.counter,
                        end: CycleChain.newest.counter,
                    };
                    const resp = await http.post(`${ip}:${port}/sync-cycles`, data);
                    info(`syncNewCycles: responder ${ip}:${port} cycle ${CycleChain.newest.counter} ${lib_types_1.Utils.safeStringify(resp[0])}`);
                }
                //20230730: comment below is from 3 years ago, is it something that needs to be handled.
                //          was not getting to this spot even when our node failed to stay up to date with cycles
                //          so this may not be a current point of failure
                // [TODO] If we ever hit this, we should return from here after setting
                //        a timeout to run syncNewCycles function again. Maybe also through away
                //        the most recent record we have in case it was bad.
            }
        }
        const newCounter = CycleChain.newest.counter;
        // There seems to be a bug in the below logic. it will return early on the first
        // attempt if there is no progress, but the log says we tried progressHistory
        // number of times. we'd need to add a condtion that checks if attempt is >=
        // than progressHistory before returning.
        // Check progress history and number of attempts to stop tight loops
        progress[attempt % progressHistory] = newCounter - oldCounter;
        if (progress.reduce((prev, curr) => prev + curr, 0) <= 0) {
            warn(`syncNewCycles: no progress in the last ${progressHistory} attempts, in reality, on attempt ${attempt}`);
            return;
        }
        if (attempt >= maxAttempts - 1) {
            warn(`syncNewCycles: exceeded max ${maxAttempts} attempts`);
            return;
        }
        attempt++;
        newestCycle = await getNewestCycle(activeNodes);
        info(`syncNewCycles: new counter of newestCycle=${newestCycle.counter}`);
    }
}
function extraSyncLogsEnabled(source = '') {
    // a few flags can result in more logging here
    let extraSyncLogs = logger_1.logFlags.p2pSyncDebug || logger_1.logFlags.p2pNonFatal || logger_1.logFlags.important_as_error;
    // always log more as a node is starting up
    if (source === 'syncV2' || source === 'syncNewCycles') {
        extraSyncLogs = true;
    }
    return extraSyncLogs;
}
function digestCycle(cycle, source) {
    info(`digestCycle: c${CycleCreator.currentCycle}q${CycleCreator.currentQuarter} marker of cycle${cycle.counter} from ${source} before digest is ${CycleChain.computeCycleMarker(cycle)}`);
    const extraSyncLogs = extraSyncLogsEnabled(source);
    // get the node list hashes *before* applying node changes
    if (Context_1.config.p2p.useSyncProtocolV2 || Context_1.config.p2p.writeSyncProtocolV2) {
        // would this cause issues if called from syncV2?
        // suppose a node syncs the latest cycle from the network which will already have the ndoelist hashes filled in,
        // but now we re-calculate them. what if the nodelists that we synced(and are calculating hashes based off of) is different
        // than the nodelists were when the active nodes originally calculated the hashes and put it in the cycle record that we
        // already synced. we would end up with a different hash than the other nodes. this seems to be handled in the case of the
        // standby list, but not with the validator and archivers lists
        const newNodeListHash = NodeList.computeNewNodeListHash(extraSyncLogs);
        if (newNodeListHash !== cycle.nodeListHash)
            warn(`sync:digestCycle source: ${source} cycle: ${cycle.counter} patching nodelisthash ${cycle.nodeListHash} -> ${newNodeListHash}`);
        cycle.nodeListHash = newNodeListHash;
        const newArchiverListHash = Archivers.computeNewArchiverListHash();
        if (newArchiverListHash !== cycle.archiverListHash)
            warn(`sync:digestCycle source: ${source} cycle: ${cycle.counter} patching archiverlisthash ${cycle.archiverListHash} -> ${newArchiverListHash}`);
        cycle.archiverListHash = newArchiverListHash;
        // for join v2, also get the standby node list hash
        if (Context_1.config.p2p.useJoinProtocolV2) {
            const standbyNodeListHash = JoinV2.computeNewStandbyListHash();
            /* prettier-ignore */ if (extraSyncLogs)
                info(`sync:digestCycle source: ${source} cycle: ${cycle.counter} standbyNodeListHash: ${standbyNodeListHash} cycle.standbyNodeListHash: ${cycle.standbyNodeListHash}`);
            // [TODO] We can remove `source !== 'syncV2'` once we shut down ITN2
            if (source !== 'syncV2') {
                if (standbyNodeListHash !== cycle.standbyNodeListHash) {
                    /* prettier-ignore */ if (extraSyncLogs)
                        info(`sync:digestCycle source:${source} cycle: ${cycle.counter} patching standbylisthash ${cycle.counter} standbyNodeListHash: ${standbyNodeListHash} -> cycle.standbyNodeListHash: ${cycle.standbyNodeListHash}`);
                    cycle.standbyNodeListHash = standbyNodeListHash;
                }
            }
        }
    }
    if (Context_1.config.debug.enableCycleRecordDebugTool || Context_1.config.debug.localEnableCycleRecordDebugTool) {
        if (Self.isActive) {
            const cycleData = lib_types_1.Utils.safeStringify({
                port: Self.port,
                cycleNumber: cycle.counter,
                cycleRecord: cycle,
            }) + '\n';
            fs_1.default.appendFile(filePath, cycleData, (err) => {
                if (err) {
                    console.error('Error appending to file:', err);
                }
            });
        }
    }
    const marker = CycleCreator.makeCycleMarker(cycle);
    if (CycleChain.cyclesByMarker[marker]) {
        warn(`Tried to digest cycle record twice: ${lib_types_1.Utils.safeStringify(cycle)}\n` + `${new Error().stack}`);
        return;
    }
    /* prettier-ignore */ if (extraSyncLogs)
        info(`digestCycle ${lib_types_1.Utils.safeStringify(cycle)} from ${source}... note: CycleChain.newest.counter: ${lib_types_1.Utils.safeStringify(CycleChain.newest)} CycleCreator.currentCycle: ${CycleCreator.currentCycle}`);
    const changes = (0, CycleParser_1.parse)(cycle);
    applyNodeListChange(changes, true, cycle);
    let newNodeListHash;
    if (extraSyncLogs) {
        newNodeListHash = Context_1.crypto.hash(NodeList.byJoinOrder); //computeNewNodeListHash not safe due to side effects
        warn(`sync:digestCycle after applyNodeListChange source: ${source} cycle: ${cycle.counter} prev nodelisthash ${cycle.nodeListHash} next ${newNodeListHash}`);
    }
    // for join v2, also add any new standby nodes to the standy node list
    // and remove any standby nodes that have unjoined.
    if (Context_1.config.p2p.useJoinProtocolV2) {
        if (cycle.standbyAdd) {
            (0, v2_1.addStandbyJoinRequests)(cycle.standbyAdd);
        }
        if (cycle.standbyRemove) {
            for (const publicKey of cycle.standbyRemove) {
                console.log(`sync:digestCycle cycle: ${cycle.counter} removed standby node ${publicKey}`);
                (0, unjoin_1.deleteStandbyNode)(publicKey);
            }
        }
    }
    //note this logging may be super heavy.  you must turn two keys
    if (logger_1.logFlags.p2pExtraHeavyLogs && logger_1.logFlags.p2pSyncDebug) {
        const standbyList = (0, v2_1.getLastHashedStandbyList)();
        (0, v2_1.debugDumpJoinRequestList)(standbyList, `sync.digestCycle: last-hashed ${cycle.counter}`);
        (0, v2_1.debugDumpJoinRequestList)(Array.from((0, v2_1.getStandbyNodesInfoMap)().values()), `sync.digestCycle: standby-map ${cycle.counter}`);
    }
    CycleChain.append(cycle);
    const digestedCycleMarker = CycleChain.computeCycleMarker(cycle);
    info(`digestCycle: marker of cycle${cycle.counter} from ${source} after digest is ${digestedCycleMarker}`);
    // Update problematic node cache in shadow mode
    ProblemNodeHandler.updateProblematicNodeCache(cycle);
    /* prettier-ignore */ if (extraSyncLogs)
        info(`digestCycle: cycle: ${lib_types_1.Utils.safeStringify(cycle)}`);
    // TODO: This seems like a possible location to inetvene if our node
    // is getting far behind on what it thinks the current cycle is
    // first would like to know how it is getting behind.
    let nodeLimit = 2; //todo set this to a higher number, but for now I want to make sure it works in a small test
    if (NodeList.activeByIdOrder.length <= nodeLimit) {
        /* prettier-ignore */ if (extraSyncLogs) {
            info(`
      Digested C${cycle.counter}   marker: ${digestedCycleMarker}
        cycle record: ${lib_types_1.Utils.safeStringify(cycle)}
        cycle changes: ${lib_types_1.Utils.safeStringify(changes)}
        cycle.nodeListHash: ${cycle.nodeListHash}
        node list hash: ${newNodeListHash}
        node list: ${lib_types_1.Utils.safeStringify([...NodeList.nodes.values()])}
        active nodes: ${lib_types_1.Utils.safeStringify(NodeList.activeByIdOrder)}
    `);
        }
    }
    else {
        /* prettier-ignore */ if (extraSyncLogs) {
            info(`
    Digested C${cycle.counter}    marker: ${digestedCycleMarker}
      cycle record: ${lib_types_1.Utils.safeStringify(cycle)}
      cycle changes: ${lib_types_1.Utils.safeStringify(changes)}
      cycle.nodeListHash: ${cycle.nodeListHash}
      node list hash: ${newNodeListHash}
      node list: too many to list: ${NodeList.nodes.size}
      active nodes: too many to list: ${NodeList.activeByIdOrder.length}
    `);
        }
    }
}
function applyNodeListChange(change, raiseEvents, cycle) {
    NodeList.addNodes(change.added.map((joined) => NodeList.createNode(joined)), 'applyNodeListChange', cycle);
    NodeList.updateNodes(change.updated, raiseEvents, cycle);
    NodeList.updateProblematicNodeTracking(cycle);
    if (change.removed[0] !== 'all')
        NodeList.removeNodes(change.removed, raiseEvents, cycle);
}
async function getNewestCycle(activeNodes) {
    if (activeNodes == null)
        throw new Error('null activeNodes');
    if (activeNodes.length < 1)
        throw new Error('empty activeNodes');
    const queryFn = async (node) => {
        const ip = node.ip ? node.ip : node.externalIp;
        const port = node.port ? node.port : node.externalPort;
        // the queryFunction must return null if the given node is our own
        // while syncing nodeList we dont have node.id, so use ip and port
        if (ip === Self.ip && port === Self.port)
            return null;
        const resp = await http.get(`${ip}:${port}/sync-newest-cycle`);
        return resp;
    };
    const eqFn = (item1, item2) => {
        /* prettier-ignore */ if (logger_1.logFlags.p2pNonFatal && logger_1.logFlags.console)
            console.log(`getNewestCycle: eqFn: item1 is: ${lib_types_1.Utils.safeStringify(item1)}`);
        try {
            if (item1.newestCycle.counter === item2.newestCycle.counter)
                return true;
            return false;
        }
        catch (err) {
            return false;
        }
    };
    let redundancy = 1;
    if (activeNodes.length > 5)
        redundancy = 2;
    if (activeNodes.length > 10)
        redundancy = 3;
    const { topResult: response, winningNodes: _responders } = await (0, Utils_1.robustQuery)(activeNodes, queryFn, eqFn, redundancy, true);
    /* prettier-ignore */ if (logger_1.logFlags.p2pNonFatal && logger_1.logFlags.console)
        console.log(`getNewestCycle: robustQuery response is: ${lib_types_1.Utils.safeStringify(response)}`);
    // [TODO] Validate response
    if (!response?.newestCycle)
        throw new Error('warning: getNewestCycle: no newestCycle yet');
    const newestCycle = response.newestCycle;
    return newestCycle;
}
// This tries to get the cycles with counter from start to end inclusively.
async function getCycles(activeNodes, start, end) {
    if (start < 0)
        start = 0;
    if (end !== undefined) {
        if (start > end)
            start = end;
    }
    const data = { start };
    if (end !== undefined)
        data.end = end;
    const queryFn = async (node) => {
        const ip = node.ip ? node.ip : node.externalIp;
        const port = node.port ? node.port : node.externalPort;
        const resp = await http.post(`${ip}:${port}/sync-cycles`, data);
        return resp;
    };
    /* prettier-ignore */ if (logger_1.logFlags.p2pNonFatal)
        info(`getCycles: ${start} - ${end}...`);
    // use robust query so we can ask less nodes to get the cycles
    let redundancy = 1;
    if (activeNodes.length > 5)
        redundancy = 2;
    if (activeNodes.length > 10)
        redundancy = 3;
    const { topResult: response, winningNodes: _responders } = await (0, Utils_1.robustQuery)(activeNodes, queryFn, util_1.default.isDeepStrictEqual, redundancy, true);
    const cycles = response;
    const valid = validateCycles(cycles);
    if (valid)
        return { cycles, responders: _responders };
}
function activeNodeCount(cycle) {
    return (cycle.active +
        cycle.activated.length +
        -cycle.apoptosized.length +
        -cycle.removed.length +
        -cycle.appRemoved.length +
        -cycle.lost.length);
}
function showNodeCount(cycle) {
    /* prettier-ignore */ if (logger_1.logFlags.error)
        warn(` syncing + joined + active - apop - rem - lost
    ${cycle.syncing} +
    ${cycle.joinedConsensors.length} +
    ${cycle.active} +
    ${cycle.apoptosized.length} -
    ${cycle.removed.length} -
    ${cycle.appRemoved.length} -
    ${cycle.lost.length}
    ${cycle.counter}
  `);
    //    ${cycle.activated.length} -
}
function totalNodeCount(cycle) {
    return (cycle.syncing +
        cycle.joinedConsensors.length +
        cycle.active +
        //    cycle.activated.length -      // don't count activated because it was already counted in syncing
        -cycle.apoptosized.length +
        -cycle.removed.length -
        cycle.appRemoved.length
    // -cycle.lost.length
    );
}
function validateCycles(cycles) {
    try {
        if (Context_1.config.p2p.useAjvCycleRecordValidation) {
            const errors = (0, Helpers_1.verifyPayload)(AJVSchemaEnum_1.AJVSchemaEnum.CycleRecords, cycles);
            if (errors && errors.length > 0) {
                warn(`cycleRecord validation errors: ${errors}`);
                return false;
            }
        }
        else {
            const archiverType = {
                publicKey: 's',
                ip: 's',
                port: 'n',
                curvePk: 's',
            };
            for (const cycleRecord of cycles) {
                let err = (0, utils_1.validateTypes)(cycleRecord, {
                    safetyMode: 'b?',
                    safetyNum: 'n?',
                    networkStateHash: 's',
                    refreshedArchivers: 'a',
                    refreshedConsensors: 'a',
                    joinedArchivers: 'a',
                    leavingArchivers: 'a',
                    syncing: 'n',
                    joinedConsensors: 'a',
                    active: 'n',
                    activated: 'a',
                    activatedPublicKeys: 'a',
                    lost: 'a',
                    refuted: 'a',
                    joined: 'a',
                    returned: 'a',
                    apoptosized: 'a',
                    networkDataHash: 'a',
                    networkReceiptHash: 'a',
                    networkSummaryHash: 'a',
                    desired: 'n',
                });
                if (err) {
                    warn('Type validation failed for cycleRecord: ' + err);
                }
                for (const refreshedArchiver of cycleRecord.refreshedArchivers) {
                    err = (0, utils_1.validateTypes)(refreshedArchiver, archiverType);
                    if (err) {
                        warn('Validation failed for cycleRecord.refreshedArchivers: ' + err);
                        return false;
                    }
                }
                for (const refreshedConsensor of cycleRecord.refreshedConsensors) {
                    err = (0, utils_1.validateTypes)(refreshedConsensor, {
                        curvePublicKey: 's',
                        status: 's',
                    });
                    if (err) {
                        warn('Validation failed for cycleRecord.refreshedConsensors: ' + err);
                        return false;
                    }
                }
                for (const joinedArchiver of cycleRecord.joinedArchivers) {
                    err = (0, utils_1.validateTypes)(joinedArchiver, archiverType);
                    if (err) {
                        warn('Validation failed for cycleRecord.joinedArchivers: ' + err);
                        return false;
                    }
                }
                for (const leavingArchiver of cycleRecord.leavingArchivers) {
                    err = (0, utils_1.validateTypes)(leavingArchiver, archiverType);
                    if (err) {
                        warn('Validation failed for cycleRecord.leavingArchivers: ' + err);
                        return false;
                    }
                }
                for (const joinedConsensor of cycleRecord.joinedConsensors) {
                    err = (0, utils_1.validateTypes)(joinedConsensor, {
                        cycleJoined: 's',
                        counterRefreshed: 'n',
                        id: 's',
                    });
                    if (err) {
                        warn('Validation failed for cycleRecord.joinedConsensors: ' + err);
                        return false;
                    }
                }
                for (const nodeId of cycleRecord.activated) {
                    if (typeof nodeId !== 'string') {
                        warn('Validation failed for cycleRecord.refreshedConsensors: ' + err);
                        return false;
                    }
                }
                for (const activatedPublicKey of cycleRecord.activatedPublicKeys) {
                    if (typeof activatedPublicKey !== 'string') {
                        warn('Validation failed for cycleRecord.activatedPublicKeys: ' + err);
                        return false;
                    }
                }
                for (const nodeId of cycleRecord.lost) {
                    if (typeof nodeId !== 'string') {
                        warn('Validation failed for cycleRecord.lost: ' + err);
                        return false;
                    }
                }
                for (const nodeId of cycleRecord.refuted) {
                    if (typeof nodeId !== 'string') {
                        warn('Validation failed for cycleRecord.refuted: ' + err);
                        return false;
                    }
                }
                for (const nodeId of cycleRecord.joined) {
                    if (typeof nodeId !== 'string') {
                        warn('Validation failed for cycleRecord.joined: ' + err);
                        return false;
                    }
                }
                for (const nodeId of cycleRecord.returned) {
                    if (typeof nodeId !== 'string') {
                        warn('Validation failed for cycleRecord.returned: ' + err);
                        return false;
                    }
                }
                for (const nodeId of cycleRecord.apoptosized) {
                    if (typeof nodeId !== 'string') {
                        warn('Validation failed for cycleRecord.apoptosized: ' + err);
                        return false;
                    }
                }
                if (!Context_1.config.p2p.useNetworkModes && cycleRecord.mode === undefined) {
                    for (const networkHash of cycleRecord.networkDataHash) {
                        err = (0, utils_1.validateTypes)(networkHash, {
                            cycle: 'n',
                            hash: 's',
                        });
                        if (err) {
                            warn('Validation failed for cycleRecord.networkDataHash: ' + err);
                            return false;
                        }
                    }
                    for (const networkHash of cycleRecord.networkReceiptHash) {
                        err = (0, utils_1.validateTypes)(networkHash, {
                            cycle: 'n',
                            hash: 's',
                        });
                        if (err) {
                            warn('Validation failed for cycleRecord.networkReceiptHash: ' + err);
                            return false;
                        }
                    }
                    for (const networkHash of cycleRecord.networkSummaryHash) {
                        err = (0, utils_1.validateTypes)(networkHash, {
                            cycle: 'n',
                            hash: 's',
                        });
                        if (err) {
                            warn('Validation failed for cycleRecord.networkSummaryHash: ' + err);
                            return false;
                        }
                    }
                }
            }
        }
    }
    catch (e) {
        if (e instanceof Error) {
            warn('Validation failed for cycleRecord: ' + e.message);
        }
        else {
            warn('Validation failed for cycleRecord: ' + e);
        }
        return false;
    }
    return true;
}
function info(...msg) {
    const entry = `Sync: ${msg.join(' ')}`;
    p2pLogger.info(entry);
}
function warn(...msg) {
    const entry = `Sync: ${msg.join(' ')}`;
    p2pLogger.warn(entry);
}
function error(...msg) {
    const entry = `Sync: ${msg.join(' ')}`;
    p2pLogger.error(entry);
}
//# sourceMappingURL=Sync.js.map