"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;
    };
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.init = init;
exports.reset = reset;
exports.getTxs = getTxs;
exports.validateRecordTypes = validateRecordTypes;
exports.updateRecord = updateRecord;
exports.parseRecord = parseRecord;
exports.sendRequests = sendRequests;
exports.containsTx = containsTx;
exports.containsTxData = containsTxData;
exports.registerShutdownHandler = registerShutdownHandler;
exports.registerBeforeAddVerifier = registerBeforeAddVerifier;
exports.registerApplyVerifier = registerApplyVerifier;
exports.addNetworkTx = addNetworkTx;
exports.getLatestNetworkTxEntryForSubqueueKey = getLatestNetworkTxEntryForSubqueueKey;
exports._removeNetworkTx = _removeNetworkTx;
exports.processNetworkTransactions = processNetworkTransactions;
exports.syncTxListFromArchiver = syncTxListFromArchiver;
exports.getTxListHash = getTxListHash;
exports.getTxList = getTxList;
exports.setTxList = setTxList;
const Context_1 = require("./Context");
const CycleChain = __importStar(require("./CycleChain"));
const lib_types_1 = require("@shardeum-foundation/lib-types");
const utils_1 = require("../utils");
const Comms = __importStar(require("./Comms"));
const profiler_1 = require("../utils/profiler");
const Self = __importStar(require("./Self"));
const CycleCreator_1 = require("./CycleCreator");
const logger_1 = require("../logger");
const nestedCounters_1 = require("../utils/nestedCounters");
const Archivers_1 = require("./Archivers");
const Utils_1 = require("./Utils");
const debugMiddleware_1 = require("../network/debugMiddleware");
const Join_1 = require("./Join");
const Nodelist = __importStar(require("./NodeList"));
const debug_1 = require("../debug");
const shardus_types_1 = require("../shardus/shardus-types");
const crypto_1 = require("crypto");
const promises_1 = require("../utils/functions/promises");
/** STATE */
let p2pLogger;
let txList = [];
let txAdd = [];
let txRemove = [];
const shutdownHandlers = new Map();
const addProposals = [];
const removeProposals = [];
const beforeAddVerifier = new Map();
const applyVerifier = new Map();
const tryCounts = new Map();
const debugDropNGTs = [];
/** ROUTES */
const addTxGossipRoute = async (payload, sender, tracker) => {
    profiler_1.profilerInstance.scopedProfileSectionStart('serviceQueue - addTx');
    if ([1, 2].includes(CycleCreator_1.currentQuarter) === false) {
        /* prettier-ignore */ if (logger_1.logFlags.error)
            info('gossip-addtx: Got request after quarter 2');
        return;
    }
    try {
        /* prettier-ignore */ if (logger_1.logFlags.p2pNonFatal)
            info(`Got addTx gossip: ${lib_types_1.Utils.safeStringify(payload)}`);
        let err = '';
        err = (0, utils_1.validateTypes)(payload, { type: 's', txData: 'o', cycle: 'n', sign: 'o' });
        if (err) {
            warn('addTxGossipRoute bad payload: ' + err);
            return;
        }
        err = (0, utils_1.validateTypes)(payload.sign, { owner: 's', sig: 's' });
        if (err) {
            /* prettier-ignore */ if (logger_1.logFlags.error)
                warn('gossip-addtx: bad input sign ' + err);
            return;
        }
        const signer = Nodelist.byPubKey.get(payload.sign.owner);
        if (!signer) {
            /* prettier-ignore */ if (logger_1.logFlags.error)
                warn('gossip-addtx: Got request from unknown node');
            return;
        }
        if (txAdd.some((entry) => entry.hash === payload.hash)) {
            return;
        }
        if (!Context_1.crypto.verify(payload, payload.sign.owner)) {
            if (logger_1.logFlags.console)
                console.log(`addTxGossipRoute(): signature invalid`, payload.sign.owner);
            /* prettier-ignore */ nestedCounters_1.nestedCountersInstance.countEvent('serviceQueue', `addTxGossipRoute(): signature invalid`);
            return;
        }
        const { sign, ...unsignedAddNetworkTx } = payload;
        if (await _addNetworkTx(unsignedAddNetworkTx)) {
            if (!txAdd.some((entry) => entry.hash === payload.hash)) {
                const addTxCopy = structuredClone(unsignedAddNetworkTx);
                const { sign, ...txDataWithoutSign } = addTxCopy.txData;
                addTxCopy.txData = txDataWithoutSign;
                txAdd.push(addTxCopy);
                (0, promises_1.fireAndForget)(() => Comms.sendGossip('gossip-addtx', payload, tracker, Self.id, (0, Join_1.nodeListFromStates)([
                    lib_types_1.P2P.P2PTypes.NodeStatus.ACTIVE,
                    lib_types_1.P2P.P2PTypes.NodeStatus.READY,
                    lib_types_1.P2P.P2PTypes.NodeStatus.SYNCING,
                ]), false)); // use Self.id so we don't gossip to ourself
            }
        }
    }
    finally {
        profiler_1.profilerInstance.scopedProfileSectionEnd('serviceQueue - addTx');
    }
};
const removeTxGossipRoute = async (payload, sender, tracker) => {
    profiler_1.profilerInstance.scopedProfileSectionStart('serviceQueue - removeTx');
    if ([1, 2].includes(CycleCreator_1.currentQuarter) === false) {
        /* prettier-ignore */ if (logger_1.logFlags.error)
            info('gossip-removetx: Got request after quarter 2');
        return;
    }
    try {
        // this will be checked in _removeNetworkTx, but more performant to check it before crypto.verify as well
        const index = txList.findIndex((entry) => entry.hash === payload.txHash);
        if (index === -1) {
            /* prettier-ignore */ if (logger_1.logFlags.p2pNonFatal)
                warn(`TxHash ${payload.txHash} does not exist in txList`);
            return false;
        }
        /* prettier-ignore */ if (logger_1.logFlags.p2pNonFatal)
            info(`Got removeTx gossip: ${lib_types_1.Utils.safeStringify(payload)}`);
        let err = (0, utils_1.validateTypes)(payload, { txHash: 's', cycle: 'n', sign: 'o' });
        if (err) {
            warn('removeTxGossipRoute bad payload: ' + err);
            return;
        }
        err = (0, utils_1.validateTypes)(payload.sign, { owner: 's', sig: 's' });
        if (err) {
            /* prettier-ignore */ if (logger_1.logFlags.error)
                warn('gossip-removetx: bad input sign ' + err);
            return;
        }
        const signer = Nodelist.byPubKey.get(payload.sign.owner);
        if (!signer) {
            /* prettier-ignore */ if (logger_1.logFlags.error)
                warn('gossip-removetx: Got request from unknown node');
            return;
        }
        if (txRemove.some((entry) => entry.txHash === payload.txHash)) {
            return;
        }
        if (!Context_1.crypto.verify(payload, payload.sign.owner)) {
            if (logger_1.logFlags.console)
                console.log(`removeTxGossipRoute(): signature invalid`, payload.sign.owner);
            /* prettier-ignore */ nestedCounters_1.nestedCountersInstance.countEvent('serviceQueue', `removeTxGossipRoute(): signature invalid`);
            return;
        }
        const { sign, ...unsignedRemoveNetworkTx } = payload;
        if (await _removeNetworkTx(unsignedRemoveNetworkTx)) {
            // could also place check inside _removeNetworkTx
            if (!txRemove.some((entry) => entry.txHash === payload.txHash)) {
                txRemove.push(unsignedRemoveNetworkTx);
                (0, promises_1.fireAndForget)(() => Comms.sendGossip('gossip-removetx', payload, tracker, Self.id, (0, Join_1.nodeListFromStates)([
                    lib_types_1.P2P.P2PTypes.NodeStatus.ACTIVE,
                    lib_types_1.P2P.P2PTypes.NodeStatus.READY,
                    lib_types_1.P2P.P2PTypes.NodeStatus.SYNCING,
                ]), false)); // use Self.id so we don't gossip to ourself
            }
        }
    }
    finally {
        profiler_1.profilerInstance.scopedProfileSectionEnd('serviceQueue - removeTx');
    }
};
const debugDropNGTGossipRoute = async (payload, sender, tracker) => {
    profiler_1.profilerInstance.scopedProfileSectionStart('serviceQueue - debugDropNGT');
    if (payload.txHash == null) {
        /* prettier-ignore */ if (logger_1.logFlags.p2pNonFatal)
            error('debug-drop-network-tx: txHash not provided');
        return;
    }
    if ([1, 2].includes(CycleCreator_1.currentQuarter) === false) {
        /* prettier-ignore */ if (logger_1.logFlags.p2pNonFatal)
            warn('debug-drop-network-tx: Got request after quarter 2');
        return;
    }
    const index = txList.findIndex((entry) => entry.hash === payload.txHash);
    if (index === -1) {
        /* prettier-ignore */ if (logger_1.logFlags.p2pNonFatal)
            warn('debug-drop-network-tx: txHash not found');
        return;
    }
    if (txRemove.some((entry) => entry.txHash === payload.txHash)) {
        /* prettier-ignore */ if (logger_1.logFlags.p2pNonFatal)
            info('debug-drop-network-tx: txHash already exists in txRemove');
        return;
    }
    const cycle = CycleChain.newest;
    const verificationResult = verifyDebugDropNGT(payload, cycle);
    if (verificationResult.success === false) {
        if (logger_1.logFlags.important_as_error)
            console.log(`debug-drop-ngt - ${verificationResult.message}`);
        nestedCounters_1.nestedCountersInstance.countEvent('serviceQueue', `debug-drop-ngt - verification of debug drop NGT payload failed: ${verificationResult.message}`);
        return;
    }
    const unsignedRemoveNetworkTx = {
        txHash: payload.txHash,
        cycle: verificationResult.cycle,
    };
    txRemove.push(unsignedRemoveNetworkTx);
    (0, promises_1.fireAndForget)(() => Comms.sendGossip('debug-drop-ngt', payload, tracker, Self.id, (0, Join_1.nodeListFromStates)([
        lib_types_1.P2P.P2PTypes.NodeStatus.ACTIVE,
        lib_types_1.P2P.P2PTypes.NodeStatus.READY,
        lib_types_1.P2P.P2PTypes.NodeStatus.SYNCING,
    ]), false)); // use Self.id so we don't gossip to ourself
};
const routes = {
    external: [],
    internal: [],
    internalBinary: [],
    gossip: {
        ['gossip-addtx']: addTxGossipRoute,
        ['gossip-removetx']: removeTxGossipRoute,
        ['debug-drop-ngt']: debugDropNGTGossipRoute,
    },
};
/** FUNCTIONS */
/** CycleCreator Functions */
function init() {
    p2pLogger = Context_1.logger.getLogger('p2p');
    reset();
    for (const [name, handler] of Object.entries(routes.gossip)) {
        Comms.registerGossipHandler(name, handler);
    }
    Context_1.network.registerExternalGet('debug-network-txlist', debugMiddleware_1.isDebugModeMiddleware, (req, res) => {
        res.send({ status: 'ok', txList });
    });
    Context_1.network.registerExternalGet('debug-network-txlisthash', debugMiddleware_1.isDebugModeMiddleware, (req, res) => {
        res.send({ status: 'ok', txListHash: Context_1.crypto.hash(txList) });
    });
    Context_1.network.registerExternalGet('debug-drop-network-txhash', debugMiddleware_1.isDebugModeMiddleware, (req, res) => {
        const txHash = req?.query?.txHash;
        if (txHash == null) {
            res.send({ status: 'fail', error: 'txHash not provided' });
            return;
        }
        const index = txList.findIndex((entry) => entry.hash === txHash);
        if (index === -1) {
            res.send({ status: 'fail', error: 'txHash not found' });
            return;
        }
        if (Context_1.config.p2p.dropNGTByGossipEnabled) {
            const reqParamsDropNGT = {
                txHash,
                url: req.originalUrl,
                sigCounter: req.query.sig_counter,
                pubKeys: req.query.nodePubkeys,
                sig: req.query.sig,
                owner: '',
            };
            const verificationResult = verifyDebugDropNGT(reqParamsDropNGT, CycleChain.newest);
            if (verificationResult.success === false) {
                res.send({ status: 'fail', error: verificationResult.message });
                return;
            }
            debugDropNGTs.push({ ...reqParamsDropNGT, cycle: verificationResult.cycle });
            res.json({ status: 'ok', message: verificationResult.message });
        }
        else {
            txList.splice(index, 1);
            res.send({ status: 'ok' });
        }
    });
    Context_1.network.registerExternalGet('debug-clear-network-txlist', debugMiddleware_1.isDebugModeMiddleware, (req, res) => {
        txList = [];
        res.send({ status: 'ok' });
    });
    Context_1.network.registerExternalGet('debug-network-txcount', debugMiddleware_1.isDebugModeMiddleware, (req, res) => {
        const copy = txList.map((entry) => ({
            ...entry,
            count: tryCounts.get(entry.hash) || 0,
        }));
        res.send({ status: 'ok', tryCounts: copy });
    });
}
function reset() {
    txAdd = [];
    txRemove = [];
}
function getTxs() {
    return {
        txadd: [...txAdd],
        txremove: [...txRemove],
    };
}
function validateRecordTypes() {
    return '';
}
function updateRecord(txs, record, prev) {
    record.txadd = txAdd.sort((a, b) => a.hash.localeCompare(b.hash));
    record.txremove = txRemove.sort((a, b) => a.txHash.localeCompare(b.txHash));
    // we need to get the hash of the txlist after the txadd and txremove
    // but we dont want to alter the txList, so we make a copy
    const txListCopy = structuredClone(txList);
    function applyTxAdd(txAddList) {
        for (const txadd of txAddList) {
            const { sign, ...txDataWithoutSign } = txadd.txData;
            sortedInsert(txListCopy, {
                hash: txadd.hash,
                tx: {
                    hash: txadd.hash,
                    txData: txDataWithoutSign,
                    type: txadd.type,
                    cycle: txadd.cycle,
                    priority: txadd.priority,
                    ...(txadd.subQueueKey && { subQueueKey: txadd.subQueueKey }),
                },
            });
        }
    }
    applyTxAdd(record.txadd);
    for (const txremove of record.txremove) {
        const index = txListCopy.findIndex((entry) => entry.hash === txremove.txHash);
        if (index === -1) {
            error(`updateRecord: TxHash ${txremove.txHash} does not exist in txListCopy`);
        }
        else {
            txListCopy.splice(index, 1);
        }
    }
    function processShutdownHandlers(cycleRecord) {
        Nodelist.activeByIdOrder.forEach((node) => {
            for (let [key, handler] of shutdownHandlers) {
                try {
                    const txAddData = handler(node, cycleRecord);
                    if (txAddData?.txData == null)
                        continue;
                    const hash = Context_1.crypto.hash(txAddData.txData);
                    if (txListCopy.some((listEntry) => listEntry.hash === hash)) {
                        warn(`shutdown - TxHash ${hash} already exists in txListCopy`);
                        continue;
                    }
                    const addTx = {
                        ...txAddData,
                        hash,
                        cycle: CycleCreator_1.currentCycle,
                    };
                    const entry = {
                        hash,
                        tx: addTx,
                    };
                    sortedInsert(txListCopy, entry);
                    record.txadd.push(addTx);
                }
                catch (e) {
                    error(`Failed to call shutdownHandler (${key}): ${e instanceof Error ? e.stack : e}`);
                }
            }
        });
    }
    if (record.mode === 'shutdown') {
        processShutdownHandlers(prev);
        processShutdownHandlers(record);
        record.txadd = txAdd.sort((a, b) => a.hash.localeCompare(b.hash));
    }
    record.txlisthash = Context_1.crypto.hash(txListCopy);
}
function parseRecord(record) {
    if (record.joinedConsensors.some((entry) => entry.id === Self.id) && hasCycleBeenParsed(record)) {
        info('txList received from syncing has already applied the changes from parsing');
        return {
            added: [],
            removed: [],
            updated: [],
        };
    }
    for (const txadd of record.txadd) {
        if (txList.some((entry) => entry.hash === txadd.hash)) {
            error(`TxHash ${txadd.hash} already exists in txList`);
        }
        else {
            info(`Adding network tx of type ${txadd.type} and payload ${(0, utils_1.stringifyReduce)(txadd.txData)}`);
            const { sign, ...txDataWithoutSign } = txadd.txData;
            sortedInsert(txList, {
                hash: txadd.hash,
                tx: {
                    hash: txadd.hash,
                    txData: txDataWithoutSign,
                    type: txadd.type,
                    cycle: txadd.cycle,
                    priority: txadd.priority,
                    ...(txadd.subQueueKey && { subQueueKey: txadd.subQueueKey }),
                },
            });
        }
    }
    for (const txremove of record.txremove) {
        const index = txList.findIndex((entry) => entry.hash === txremove.txHash);
        if (index === -1) {
            error(`TxHash ${txremove.txHash} does not exist in txList`);
        }
        else {
            txList.splice(index, 1);
        }
    }
    return {
        added: [],
        removed: [],
        updated: [],
    };
}
function sendRequests() {
    for (const add of addProposals) {
        if (!txAdd.some((entry) => entry.hash === add.hash)) {
            const { sign: sign1, ...unsignedAddNetworkTx } = add;
            const addTxCopy = structuredClone(unsignedAddNetworkTx);
            const { sign: sign2, ...txDataWithoutSign } = addTxCopy.txData;
            addTxCopy.txData = txDataWithoutSign;
            txAdd.push(addTxCopy);
        }
        /* prettier-ignore */ if (Context_1.config.debug.verboseNestedCounters)
            nestedCounters_1.nestedCountersInstance.countEvent(`gossip-addtx`, `gossip send - ${add.hash}`);
        (0, promises_1.fireAndForget)(() => Comms.sendGossip('gossip-addtx', add, '', Self.id, (0, Join_1.nodeListFromStates)([
            lib_types_1.P2P.P2PTypes.NodeStatus.ACTIVE,
            lib_types_1.P2P.P2PTypes.NodeStatus.READY,
            lib_types_1.P2P.P2PTypes.NodeStatus.SYNCING,
        ]), true));
    }
    for (const remove of removeProposals) {
        const notInTxRemove = !txRemove.some((entry) => entry.txHash === remove.txHash);
        const inTxList = txList.some((entry) => entry.hash === remove.txHash);
        if (inTxList) {
            if (notInTxRemove) {
                const { sign, ...unsignedRemoveNetworkTx } = remove;
                txRemove.push(unsignedRemoveNetworkTx);
            }
            /* prettier-ignore */ if (Context_1.config.debug.verboseNestedCounters)
                nestedCounters_1.nestedCountersInstance.countEvent(`gossip-removetx`, `gossip send - ${remove.txHash}`);
            (0, promises_1.fireAndForget)(() => Comms.sendGossip('gossip-removetx', remove, '', Self.id, (0, Join_1.nodeListFromStates)([
                lib_types_1.P2P.P2PTypes.NodeStatus.ACTIVE,
                lib_types_1.P2P.P2PTypes.NodeStatus.READY,
                lib_types_1.P2P.P2PTypes.NodeStatus.SYNCING,
            ]), true));
        }
    }
    addProposals.length = 0;
    removeProposals.length = 0;
    for (const dropNGTInfo of debugDropNGTs) {
        const unsignedRemoveNetworkTx = {
            txHash: dropNGTInfo.txHash,
            cycle: dropNGTInfo.cycle,
        };
        txRemove.push(unsignedRemoveNetworkTx);
        delete dropNGTInfo.cycle;
        (0, promises_1.fireAndForget)(() => Comms.sendGossip('debug-drop-ngt', dropNGTInfo, '', Self.id, (0, Join_1.nodeListFromStates)([
            lib_types_1.P2P.P2PTypes.NodeStatus.ACTIVE,
            lib_types_1.P2P.P2PTypes.NodeStatus.READY,
            lib_types_1.P2P.P2PTypes.NodeStatus.SYNCING,
        ]), true));
    }
    debugDropNGTs.length = 0;
}
/** Module Functions */
function containsTx(txHash) {
    return txList.some((entry) => entry.hash === txHash);
}
function containsTxData(txData) {
    const hash = Context_1.crypto.hash(txData);
    return containsTx(hash);
}
function registerShutdownHandler(type, handler) {
    shutdownHandlers.set(type, handler);
}
function registerBeforeAddVerifier(type, verifier) {
    beforeAddVerifier.set(type, verifier);
}
function registerApplyVerifier(type, verifier) {
    applyVerifier.set(type, verifier);
}
async function addNetworkTx(type, tx, subQueueKey, priority = 0) {
    const hash = Context_1.crypto.hash(tx);
    const networkTx = {
        hash,
        type,
        txData: tx,
        cycle: CycleCreator_1.currentCycle,
        priority,
        subQueueKey,
    };
    if (await _addNetworkTx(networkTx)) {
        makeAddNetworkTxProposals(networkTx);
    }
}
function getLatestNetworkTxEntryForSubqueueKey(subqueueKey) {
    for (let i = txList.length - 1; i >= 0; i--) {
        if (txList[i].tx.subQueueKey === subqueueKey) {
            return txList[i];
        }
    }
    return undefined;
}
function makeAddNetworkTxProposals(networkTx) {
    addProposals.push(Context_1.crypto.sign(networkTx));
}
function makeRemoveNetworkTxProposals(networkTx) {
    removeProposals.push(Context_1.crypto.sign(networkTx));
}
async function _addNetworkTx(addTx) {
    try {
        if (!addTx || !addTx.txData) {
            /* prettier-ignore */ if (logger_1.logFlags.p2pNonFatal)
                warn('Invalid addTx or missing addTx.txData', addTx);
            return false;
        }
        const hash = Context_1.crypto.hash(addTx.txData);
        const hashBuffer = Buffer.from(hash);
        const txHashBuffer = Buffer.from(addTx.hash);
        // Compare lengths first to avoid timing attacks
        if (hashBuffer.length !== txHashBuffer.length || !(0, crypto_1.timingSafeEqual)(Buffer.from(addTx.hash), Buffer.from(hash))) {
            /* prettier-ignore */ if (logger_1.logFlags.p2pNonFatal)
                warn('Hash mismatch', addTx.hash, hash);
            return false;
        }
        if (addTx.cycle < CycleCreator_1.currentCycle - 1 || addTx.cycle > CycleCreator_1.currentCycle) {
            /* prettier-ignore */ if (logger_1.logFlags.p2pNonFatal)
                warn(`Invalid cycle ${addTx.cycle} for current cycle ${CycleCreator_1.currentCycle}`);
            return false;
        }
        if (txList.some((entry) => entry.hash === addTx.hash)) {
            if (logger_1.logFlags.p2pNonFatal) {
                /* prettier-ignore */ if (logger_1.logFlags.p2pNonFatal)
                    info('Transaction already exists in txList', addTx.hash);
            }
            return false;
        }
        if (!beforeAddVerifier.has(addTx.type)) {
            /* prettier-ignore */ if (logger_1.logFlags.p2pNonFatal)
                warn('Adding network tx without a verify function!');
            return false;
        }
        const verifyFunction = beforeAddVerifier.get(addTx.type);
        if (!verifyFunction) {
            /* prettier-ignore */ if (logger_1.logFlags.p2pNonFatal)
                error('Verify function is undefined');
            return false;
        }
        if (!(await verifyFunction(addTx))) {
            /* prettier-ignore */ if (logger_1.logFlags.p2pNonFatal)
                error(`Failed add network tx verification of type ${addTx.type} \n tx: ${(0, utils_1.stringifyReduce)(addTx.txData)}`);
            return false;
        }
        /* prettier-ignore */ if (logger_1.logFlags.p2pNonFatal)
            console.log('add network tx', addTx.type, addTx.txData.publicKey, addTx);
        return true;
    }
    catch (e) {
        /* prettier-ignore */ if (logger_1.logFlags.p2pNonFatal)
            error(`Failed add network tx verification of type ${addTx.type} \n tx: ${(0, utils_1.stringifyReduce)(addTx.txData)}\n error: ${e instanceof Error ? e.stack : e}`);
        return false;
    }
}
async function _removeNetworkTx(removeTx) {
    const index = txList.findIndex((entry) => entry.hash === removeTx.txHash);
    if (index === -1) {
        /* prettier-ignore */ if (logger_1.logFlags.p2pNonFatal)
            warn(`TxHash ${removeTx.txHash} does not exist in txList`);
        return false;
    }
    if (removeProposals.some((entry) => entry.txHash === removeTx.txHash)) {
        /* prettier-ignore */ if (logger_1.logFlags.p2pNonFatal)
            warn(`Remove proposal already exists for ${removeTx.txHash}`);
        return false;
    }
    // eslint-disable-next-line security/detect-object-injection
    const listEntry = txList[index];
    try {
        if (!applyVerifier.has(listEntry.tx.type)) {
            // todo: should this throw or not?
            /* prettier-ignore */ if (logger_1.logFlags.p2pNonFatal)
                warn('Remove network tx without a verify function!');
        }
        else if (!(await applyVerifier.get(listEntry.tx.type)(listEntry.tx))) {
            /* prettier-ignore */ if (logger_1.logFlags.p2pNonFatal)
                error(`Failed remove network tx verification of type ${listEntry.tx.type} \n
                     tx: ${(0, utils_1.stringifyReduce)(listEntry.tx.txData)}`);
            return false;
        }
    }
    catch (e) {
        /* prettier-ignore */ if (logger_1.logFlags.p2pNonFatal)
            error(`Failed remove network tx verification of type ${listEntry.tx.type} \n
                   tx: ${(0, utils_1.stringifyReduce)(listEntry.tx.txData)}\n 
                   error: ${e instanceof Error ? e.stack : e}`);
        return false;
    }
    return true;
}
async function processNetworkTransactions(record) {
    info('Process Network Transactions');
    if (record.mode !== 'processing' || !Context_1.config.p2p.allowEndUserTxnInjections) {
        return;
    }
    const processedSubQueueKeys = new Set();
    let length = Math.min(txList.length, Context_1.config.p2p.networkTransactionsToProcessPerCycle);
    for (let i = 0; i < length && CycleCreator_1.currentQuarter === 3; i++) {
        // eslint-disable-next-line security/detect-object-injection
        if (!txList[i]) {
            warn(`txList[${i}] is undefined`);
            continue;
        }
        // eslint-disable-next-line security/detect-object-injection
        const record = txList[i].tx;
        try {
            if (record.subQueueKey != null && processedSubQueueKeys.has(record.subQueueKey)) {
                if (length < txList.length) {
                    length += 1;
                }
                continue;
            }
            if (applyVerifier.has(record.type) && !(await applyVerifier.get(record.type)(record))) {
                const emitParams = {
                    nodeId: record.txData.nodeId,
                    reason: 'Try Network Transaction',
                    time: CycleChain.newest.start,
                    publicKey: record.txData.publicKey,
                    cycleNumber: record.cycle,
                    additionalData: record,
                };
                /* prettier-ignore */ if (logger_1.logFlags.p2pNonFatal)
                    info('emit network transaction event', lib_types_1.Utils.safeStringify(emitParams));
                Self.emitter.emit('try-network-transaction', emitParams);
                countTry(txList[i].hash);
                if (record.subQueueKey != null) {
                    processedSubQueueKeys.add(record.subQueueKey);
                }
            }
            else {
                // eslint-disable-next-line security/detect-object-injection
                /* prettier-ignore */ if (logger_1.logFlags.p2pNonFatal)
                    info('removeNetworkTx', txList[i].hash);
                // eslint-disable-next-line security/detect-object-injection
                const removeTx = { txHash: txList[i].hash, cycle: CycleCreator_1.currentCycle };
                if (await _removeNetworkTx(removeTx)) {
                    makeRemoveNetworkTxProposals(removeTx);
                }
            }
        }
        catch (e) {
            countTry(txList[i].hash);
            if (record.subQueueKey != null) {
                processedSubQueueKeys.add(record.subQueueKey);
            }
            // eslint-disable-next-line security/detect-object-injection
            error(`Failed to process network transaction ${txList[i]?.hash}: ${e instanceof Error ? e.stack : e}`);
        }
    }
}
async function syncTxListFromArchiver() {
    const archiver = (0, Utils_1.getRandomAvailableArchiver)();
    if (!archiver) {
        throw Error('Fatal: Could not get random archiver');
    }
    const txListResult = await (0, Archivers_1.getFromArchiver)(archiver, 'network-txs-list', undefined, 10000);
    if (txListResult.isErr()) {
        const nodeListUrl = `http://${archiver.ip}:${archiver.port}/network-txs-list`;
        throw Error(`Fatal: Could not get tx list from archiver ${nodeListUrl}: ` + txListResult.error.message);
    }
    const latestTxListHash = CycleChain?.newest?.txlisthash;
    if (!latestTxListHash) {
        warn('failed to get hash of latest tx list from cycle record');
        throw Error('Fatal: Failed to get hash of latest tx list from cycle record');
    }
    if (latestTxListHash === Context_1.crypto.hash(txListResult.value)) {
        txList = txListResult.value;
        info('first nodes successfully synced tx list from archiver in restart mode');
    }
    else {
        throw Error('Fatal: Hash of tx list from archiver does not match hash of latest tx list from cycle record');
    }
}
function hasCycleBeenParsed(record) {
    for (const txAdd of record.txadd) {
        if (!txList.some((entry) => entry.hash === txAdd.hash)) {
            return false;
        }
    }
    for (const txRemove of record.txremove) {
        if (txList.some((entry) => entry.hash === txRemove.txHash)) {
            return false;
        }
    }
    return true;
}
function countTry(txHash) {
    if (tryCounts.has(txHash)) {
        tryCounts.set(txHash, tryCounts.get(txHash) + 1);
    }
    else {
        tryCounts.set(txHash, 1);
    }
}
function getTxListHash() {
    return Context_1.crypto.hash(txList);
}
function getTxList() {
    return txList;
}
function setTxList(_txList) {
    txList = _txList;
}
function sortedInsert(list, entry) {
    const index = list.findIndex((item) => item.tx.cycle > entry.tx.cycle ||
        (item.tx.cycle === entry.tx.cycle && item.tx.priority < entry.tx.priority) || // Compare by priority if cycle is the same
        (item.tx.cycle === entry.tx.cycle && item.tx.priority === entry.tx.priority && item.hash > entry.hash) // Compare by hash if both cycle and priority are the same
    );
    if (index === -1) {
        list.push(entry);
    }
    else {
        list.splice(index, 0, entry);
    }
}
function verifyDebugDropNGT(reqParamsDropNGT, cycle) {
    const payload = {
        route: (0, debugMiddleware_1.stripQueryParams)(reqParamsDropNGT.url, ['sig', 'sig_counter', 'nodePubkeys']), //<- we're gonna hash, these query artificats need to be excluded from the hash
        count: reqParamsDropNGT.sigCounter,
        nodes: reqParamsDropNGT.pubKeys,
        networkId: cycle.networkId,
        cycleCounter: cycle.counter,
    };
    const hash = Context_1.crypto.hash(lib_types_1.Utils.safeStringify(payload));
    const devPublicKeys = (0, debug_1.getDevPublicKeys)(); // This should return list of public keys
    const requestSig = reqParamsDropNGT.sig;
    if (reqParamsDropNGT.owner !== '') {
        const ownerPk = reqParamsDropNGT.owner;
        const sign = { owner: ownerPk, sig: requestSig };
        const hashIncluded = {
            route: payload.route,
            count: payload.count,
            nodes: payload.nodes,
            networkId: payload.networkId,
            cycleCounter: payload.cycleCounter,
            requestHash: hash,
            sign,
        };
        let verified = Context_1.crypto.verify(hashIncluded, hashIncluded.sign.owner);
        if (verified === false) {
            delete hashIncluded.requestHash;
            delete hashIncluded.sign;
            // decrementing counter to check the sig in case the sig was made on the object with the prev counter
            hashIncluded.cycleCounter--;
            const hash = Context_1.crypto.hash(lib_types_1.Utils.safeStringify(hashIncluded));
            hashIncluded.requestHash = hash;
            hashIncluded.sign = sign;
            verified = Context_1.crypto.verify(hashIncluded, hashIncluded.sign.owner);
        }
        if (verified === true) {
            const authorized = (0, debug_1.ensureKeySecurity)(ownerPk, shardus_types_1.DevSecurityLevel.High);
            if (authorized) {
                return {
                    success: true,
                    message: 'Signature is correct and signer is authorized',
                    cycle: hashIncluded.cycleCounter,
                };
            }
            else {
                /* prettier-ignore */ if (logger_1.logFlags.verbose)
                    console.log('Authorization failed for security level HIGH');
                /* prettier-ignore */ nestedCounters_1.nestedCountersInstance.countEvent('security', 'Authorization failed for security level HIGH');
                return {
                    success: false,
                    message: 'Authorization failed for security level HIGH',
                };
            }
        }
        else {
            /* prettier-ignore */ if (logger_1.logFlags.verbose)
                console.log('Signature verification failed');
            return {
                success: false,
                message: 'Signature verification failed',
            };
        }
    }
    // Check if signature is valid for any of the public keys
    for (const ownerPk in devPublicKeys) {
        const sign = { owner: ownerPk, sig: requestSig };
        const hashIncluded = {
            route: payload.route,
            count: payload.count,
            nodes: payload.nodes,
            networkId: payload.networkId,
            cycleCounter: payload.cycleCounter,
            requestHash: hash,
            sign,
        };
        let verified = Context_1.crypto.verify(hashIncluded, hashIncluded.sign.owner);
        if (verified === false) {
            delete hashIncluded.requestHash;
            delete hashIncluded.sign;
            // decrementing counter to check the sig in case the sig was made on the object with the prev counter
            hashIncluded.cycleCounter--;
            const hash = Context_1.crypto.hash(lib_types_1.Utils.safeStringify(hashIncluded));
            hashIncluded.requestHash = hash;
            hashIncluded.sign = sign;
            verified = Context_1.crypto.verify(hashIncluded, hashIncluded.sign.owner);
        }
        if (verified === true) {
            const authorized = (0, debug_1.ensureKeySecurity)(ownerPk, shardus_types_1.DevSecurityLevel.High);
            if (authorized) {
                reqParamsDropNGT.owner = ownerPk;
                return {
                    success: true,
                    message: 'Signature is correct and signer is authorized',
                };
            }
            else {
                /* prettier-ignore */ if (logger_1.logFlags.verbose)
                    console.log('Authorization failed for security level HIGH');
                /* prettier-ignore */ nestedCounters_1.nestedCountersInstance.countEvent('security', 'Authorization failed for security level HIGH');
                return {
                    success: false,
                    message: 'Authorization failed for security level HIGH',
                };
            }
        }
        else {
            /* prettier-ignore */ if (logger_1.logFlags.verbose)
                console.log('Signature verification failed');
            return {
                success: false,
                message: 'Signature verification failed',
            };
        }
    }
}
function info(...msg) {
    const entry = `ServiceQueue: ${msg.join(' ')}`;
    p2pLogger.info(entry);
}
function warn(...msg) {
    const entry = `ServiceQueue: ${msg.join(' ')}`;
    p2pLogger.warn(entry);
}
function error(...msg) {
    const entry = `ServiceQueue: ${msg.join(' ')}`;
    p2pLogger.error(entry);
}
function omitKey(obj, keyToOmit) {
    // deep emit key from object
    const newObj = { ...obj };
    for (const key in newObj) {
        if (key === keyToOmit) {
            delete newObj[key];
        }
        else if (typeof newObj[key] === 'object') {
            newObj[key] = omitKey(newObj[key], keyToOmit);
        }
    }
}
//# sourceMappingURL=ServiceQueue.js.map