"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
    if (mod && mod.__esModule) return mod;
    var result = {};
    if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
    __setModuleDefault(result, mod);
    return result;
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.requestActive = exports.queueRequest = exports.sendRequests = exports.parseRecord = exports.updateRecord = exports.dropInvalidTxs = exports.validateRecordTypes = exports.getTxs = exports.reset = exports.init = exports.neverGoActive = exports.enableSkipActivatedCert = exports.activated = void 0;
const logger_1 = require("../logger");
const lib_types_1 = require("@shardeum-foundation/lib-types");
const utils = __importStar(require("../utils"));
const utils_1 = require("../utils");
const Comms = __importStar(require("./Comms"));
const Context_1 = require("./Context");
const CycleChain = __importStar(require("./CycleChain"));
const NodeList = __importStar(require("./NodeList"));
const Self = __importStar(require("./Self"));
const profiler_1 = require("../utils/profiler");
const P2PTypes_1 = require("@shardeum-foundation/lib-types/build/src/p2p/P2PTypes");
const nestedCounters_1 = require("../utils/nestedCounters");
const v2_1 = require("./Join/v2");
const syncFinished_1 = require("./Join/v2/syncFinished");
const debugMiddleware_1 = require("../network/debugMiddleware");
const lib_types_2 = require("@shardeum-foundation/lib-types");
const Join_1 = require("./Join");
const GossipValidation_1 = require("../utils/GossipValidation");
const promises_1 = require("../utils/functions/promises");
let syncTimes = [];
let lastCheckedCycleForSyncTimes = 0;
/** ROUTES */
const gossipActiveRoute = (payload, sender, tracker) => {
    profiler_1.profilerInstance.scopedProfileSectionStart('gossip-active', true);
    try {
        if (logger_1.logFlags.p2pNonFatal)
            info(`Got active request: ${lib_types_2.Utils.safeStringify(payload)}`);
        // Ignore gossip outside of Q1 and Q2 and check if the payload structure is valid
        // If the sender is the original sender check if in Q1 to accept the request
        if (!(0, GossipValidation_1.checkGossipPayload)(payload, { nodeId: 's', status: 's', timestamp: 'n', sign: 'o' }, 'gossip-active', sender)) {
            return;
        }
        if (addActiveTx(payload)) {
            (0, promises_1.fireAndForget)(() => Comms.sendGossip('gossip-active', payload, tracker, sender, (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));
        }
    }
    finally {
        profiler_1.profilerInstance.scopedProfileSectionEnd('gossip-active');
    }
};
const routes = {
    internal: {},
    gossip: {
        'gossip-active': gossipActiveRoute,
    },
};
/** STATE */
let p2pLogger;
let activeRequests;
let queuedRequest;
exports.activated = [];
exports.enableSkipActivatedCert = process.env.enableSkipActivatedCert === 'true' ? true : false;
console.log('[restore-415] enableSkipActivatedCert', exports.enableSkipActivatedCert);
exports.neverGoActive = false;
/** FUNCTIONS */
/** CycleCreator Functions */
function init() {
    // Init logger
    p2pLogger = Context_1.logger.getLogger('p2p');
    // Init state
    reset();
    // Register routes
    for (const [name, handler] of Object.entries(routes.internal)) {
        Comms.registerInternal(name, handler);
    }
    for (const [name, handler] of Object.entries(routes.gossip)) {
        Comms.registerGossipHandler(name, handler);
    }
    Context_1.network.registerExternalGet('debug-neverGoActive', debugMiddleware_1.isDebugModeMiddleware, (req, res) => {
        exports.neverGoActive = !exports.neverGoActive;
        res.json({ status: 'ok', neverGoActive: exports.neverGoActive });
    });
}
exports.init = init;
function reset() {
    activeRequests = new Map();
    exports.activated = [];
}
exports.reset = reset;
function getTxs() {
    return {
        active: [...activeRequests.values()],
    };
}
exports.getTxs = getTxs;
function validateRecordTypes(rec) {
    let err = (0, utils_1.validateTypes)(rec, {
        active: 'n',
        activated: 'a',
        activatedPublicKeys: 'a',
        standby: 'n',
        maxSyncTime: 'n',
    });
    if (err)
        return err;
    for (const item of rec.activated) {
        if (typeof item !== 'string')
            return 'items of activated array must be strings';
    }
    for (const item of rec.activatedPublicKeys) {
        if (typeof item !== 'string')
            return 'items of activatedPublicKeys array must be strings';
    }
    return '';
}
exports.validateRecordTypes = validateRecordTypes;
function dropInvalidTxs(txs) {
    const active = txs.active.filter((request) => validateActiveRequest(request));
    return { active };
}
exports.dropInvalidTxs = dropInvalidTxs;
function updateRecord(txs, record, _prev) {
    const active = NodeList.activeByIdOrder.length;
    exports.activated = [];
    const activatedPublicKeys = [];
    if (NodeList.readyByTimeAndIdOrder.length > 0) {
        // ITN3 example if processing this will pick allowActivePerCycle = 1 nodes
        const selectedNodes = (0, syncFinished_1.selectNodesFromReadyList)(_prev.mode);
        for (const node of selectedNodes) {
            /* prettier-ignore */ nestedCounters_1.nestedCountersInstance.countEvent('p2p', `active:updateRecord node added to activated`);
            exports.activated.push(node.id);
            activatedPublicKeys.push(node.publicKey);
        }
    }
    // for (const request of txs.active) {
    //   const publicKey = request.sign.owner
    //   const node = NodeList.byPubKey.get(publicKey)
    //   // Check if the node is the onReadyList and one of N of oldest node on the list
    //   const isNodeSelected = isNodeSelectedReadyList(node?.id)
    //   if (isNodeSelected) {
    //     /* prettier-ignore */ nestedCountersInstance.countEvent('p2p', `active:updateRecord node added to activated`)
    //     activated.push(node.id)
    //     activatedPublicKeys.push(node.publicKey)
    //   } else {
    //     /* prettier-ignore */ if(logFlags.important_as_error) warn(`active:updateRecord: node not found or not ready: ${publicKey}`)
    //     /* prettier-ignore */ nestedCountersInstance.countEvent('p2p', `active:updateRecord node not found or not ready`)
    //   }
    // }
    record.active = active;
    record.activated = exports.activated.sort();
    record.activatedPublicKeys = activatedPublicKeys.sort();
    record.standby = (0, v2_1.getSortedStandbyJoinRequests)().length;
    try {
        let cycleCounter = CycleChain.newest ? CycleChain.newest.counter : 0;
        let addedCount = 0;
        // make sure there are no nodes in syncTimes that are not in activeByIdOrder
        syncTimes = syncTimes.filter((item) => NodeList.activeByIdOrder.find((node) => node.id === item.nodeId));
        for (const node of NodeList.activeByIdOrder) {
            // not sure why we used to skip our own node. it seems like it could cause nodes to end upw ith different maxSyncTimes
            // if (node.id === Self.id) continue
            const included = syncTimes.filter((item) => item.nodeId === node.id && item.activeTimestamp === node.activeTimestamp);
            if (included && included.length > 0)
                continue;
            const syncTime = node.activeTimestamp - node.syncingTimestamp;
            syncTimes.push({
                nodeId: node.id,
                activeTimestamp: node.activeTimestamp,
                syncStartTimestamp: node.syncingTimestamp,
                syncTime,
                refreshedCounter: cycleCounter,
            });
            addedCount += 1;
        }
        if (addedCount > 0) {
            syncTimes = syncTimes.sort((a, b) => b.activeTimestamp - a.activeTimestamp);
            if (syncTimes.length > Context_1.config.p2p.maxNodeForSyncTime) {
                syncTimes = syncTimes.slice(0, Context_1.config.p2p.maxNodeForSyncTime);
            }
        }
        if (syncTimes.length > 0)
            lastCheckedCycleForSyncTimes = syncTimes[0].refreshedCounter; // updated last checked cycle
        const syncDurations = syncTimes
            .map((syncTime) => syncTime.activeTimestamp - syncTime.syncStartTimestamp)
            .sort((a, b) => a - b);
        const medianIndex = Math.floor(syncDurations.length / 2);
        const medianSyncTime = syncDurations[medianIndex];
        /* prettier-ignore */ if (logger_1.logFlags.p2pNonFatal)
            info('Sync time records sorted', syncTimes.length, lib_types_2.Utils.safeStringify(syncTimes));
        if (CycleChain.newest) {
            /* prettier-ignore */ if (logger_1.logFlags.p2pNonFatal)
                info(`Median sync time at cycle ${cycleCounter} is ${medianSyncTime} s.`);
        }
        let maxSyncTime = medianSyncTime ? medianSyncTime * 2 : 0;
        if (maxSyncTime < Context_1.config.p2p.maxSyncTimeFloor) {
            maxSyncTime = Context_1.config.p2p.maxSyncTimeFloor;
        }
        record.maxSyncTime = maxSyncTime;
        // record.maxSyncTime = Math.min(config.p2p.maxSyncTimeFloor, maxSyncTime)
        //higher pri on this log so we can analyze it and correct as needed
        /* prettier-ignore */ if (logger_1.logFlags.important_as_error)
            info(`maxSyncTime ${cycleCounter} is ${maxSyncTime} medianSyncTime*2: ${medianSyncTime}`);
    }
    catch (e) {
        record.maxSyncTime = Context_1.config.p2p.maxSyncTimeFloor;
        /* prettier-ignore */ if (logger_1.logFlags.error)
            error(`calculateMaxSyncTime: Unable to calculate max sync time`, e);
    }
}
exports.updateRecord = updateRecord;
function parseRecord(record) {
    // Look at the activated id's and make Self emit 'active' if your own id is there
    if (record.activated.includes(Self.id) && !exports.neverGoActive) {
        Self.setActive();
        Self.emitter.emit('active', Self.id);
        Self.updateNodeState(lib_types_1.P2P.P2PTypes.NodeStatus.ACTIVE);
    }
    // For all nodes described by activated, make an update to change their status to active
    const updated = record.activated.map((id) => ({
        id,
        activeTimestamp: record.start,
        activeCycle: record.counter,
        status: lib_types_1.P2P.P2PTypes.NodeStatus.ACTIVE,
    }));
    return {
        added: [],
        removed: [],
        updated,
    };
}
exports.parseRecord = parseRecord;
function sendRequests() {
    if (queuedRequest && !exports.neverGoActive) {
        const activeTx = Context_1.crypto.sign(queuedRequest);
        queuedRequest = undefined;
        if (logger_1.logFlags.important_as_fatal)
            info(`Gossiping active request: ${lib_types_2.Utils.safeStringify(activeTx)}`);
        nestedCounters_1.nestedCountersInstance.countEvent('p2p', `p2p:sendRequests Gossiping active request`);
        if (addActiveTx(activeTx) === false) {
            /* prettier-ignore */ nestedCounters_1.nestedCountersInstance.countEvent('p2p', `active:sendRequests failed to add our own request`);
        }
        (0, promises_1.fireAndForget)(() => Comms.sendGossip('gossip-active', activeTx, '', null, (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));
        // Check if we went active and try again if we didn't in 1 cycle duration
        const activeTimeout = setTimeout(requestActive, Context_1.config.p2p.cycleDuration * 1000 + 500);
        Self.emitter.once('active', () => {
            info(`Went active!`);
            nestedCounters_1.nestedCountersInstance.countEvent('p2p', `p2p:sendRequests went active`);
            clearTimeout(activeTimeout);
        });
    }
}
exports.sendRequests = sendRequests;
function queueRequest(request) {
    queuedRequest = request;
}
exports.queueRequest = queueRequest;
/** Module Functions */
function requestActive() {
    // Create an active request and queue it to be sent
    const request = createActiveRequest();
    queueRequest(request);
}
exports.requestActive = requestActive;
function createActiveRequest() {
    const request = {
        nodeId: Self.id,
        status: 'active',
        timestamp: utils.getTime(),
    };
    return request;
}
function addActiveTx(request) {
    if (!request) {
        return false;
    }
    if (!validateActiveRequest(request)) {
        return false;
    }
    if (activeRequests.has(request.sign.owner)) {
        return false;
    }
    activeRequests.set(request.sign.owner, request);
    return true;
}
function validateActiveRequest(request) {
    const node = NodeList.nodes.get(request.nodeId);
    if (!node) {
        /* prettier-ignore */ if (logger_1.logFlags.important_as_error)
            warn(`validateActiveRequest: node not found, nodeId: ${request.nodeId}`);
        /* prettier-ignore */ nestedCounters_1.nestedCountersInstance.countEvent('p2p', `active:validateActiveRequest node not found`);
        return false;
    }
    if (!Context_1.crypto.verify(request, node.publicKey)) {
        /* prettier-ignore */ if (logger_1.logFlags.important_as_error)
            warn(`validateActiveRequest: bad signature, request: ${lib_types_2.Utils.safeStringify(request)} ${request.nodeId}`);
        /* prettier-ignore */ nestedCounters_1.nestedCountersInstance.countEvent('p2p', `active:validateActiveRequests bad signature`);
        return false;
    }
    if (Context_1.config.p2p.validateActiveRequests === true) {
        // Do not accept active request if node is already active
        const existing = NodeList.nodes.get(request.nodeId);
        if (existing && existing.status === P2PTypes_1.NodeStatus.ACTIVE) {
            /* prettier-ignore */ if (logger_1.logFlags.important_as_error)
                warn(`validateActiveRequest: already active , nodeId: ${request.nodeId}`);
            /* prettier-ignore */ nestedCounters_1.nestedCountersInstance.countEvent('p2p', `active:validateActiveRequest already active`);
            return false;
        }
        // [TODO] Discuss and implement more active request validation
    }
    return true;
}
function info(...msg) {
    const entry = `Active: ${msg.join(' ')}`;
    p2pLogger.info(entry);
}
function warn(...msg) {
    const entry = `Active: ${msg.join(' ')}`;
    p2pLogger.warn(entry);
}
function error(...msg) {
    const entry = `Active: ${msg.join(' ')}`;
    p2pLogger.error(entry);
}
//# sourceMappingURL=Active.js.map