"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.errorForInvestigateArchiverMsg = exports.errorForArchiverRefutesLostMsg = exports.errorForArchiverUpMsg = exports.errorForArchiverDownMsg = exports.tellNetworkArchiverIsUp = exports.tellNetworkArchiverIsDown = exports.informInvestigator = exports.getInvestigator = exports.investigateArchiver = exports.reportLostArchiver = exports.createLostArchiverRecord = void 0;
const http = __importStar(require("../../http"));
const CycleChain = __importStar(require("../../p2p/CycleChain"));
const Archivers = __importStar(require("../Archivers"));
const Comms = __importStar(require("../Comms"));
const Context = __importStar(require("../Context"));
const NodeList = __importStar(require("../NodeList"));
const state_1 = require("./state");
const logging_1 = require("./logging");
const Self_1 = require("../Self");
const arrays_1 = require("../../utils/functions/arrays");
const NodeList_1 = require("../NodeList");
const util_1 = require("util");
const utils_1 = require("../../utils");
const __1 = require("../..");
const network_1 = require("../../network");
const LostArchiverInvestigateReq_1 = require("../../types/LostArchiverInvestigateReq");
const InternalRouteEnum_1 = require("../../types/enum/InternalRouteEnum");
const lib_types_1 = require("@shardeum-foundation/lib-types");
const promises_1 = require("../../utils/functions/promises");
/** Lost Archivers Functions */
function createLostArchiverRecord(obj) {
    if (obj.isInvestigator == null)
        obj.isInvestigator = false;
    if (obj.gossippedDownMsg == null)
        obj.gossippedDownMsg = false;
    if (obj.gossippedUpMsg == null)
        obj.gossippedUpMsg = false;
    if (obj.target == null)
        throw 'Must specify a target for LostArchiverRecord';
    if (obj.status == null)
        obj.status = 'reported';
    if (obj.cyclesToWait == null)
        obj.cyclesToWait = Context.config.p2p.lostArchiversCyclesToWait;
    return obj;
}
exports.createLostArchiverRecord = createLostArchiverRecord;
/**
 * Marks an Archiver as lost in our internal map.
 * This function gets called anytime communication with an Archiver breaks down
 * @param publicKey - The public key of the lost Archiver
 * @param errorMsg - The error message received when communication broke down
 * Called by Archivers.ts
 */
function reportLostArchiver(publicKey, errorMsg) {
    if (Context.config.p2p.enableLostArchiversCycles === false) {
        (0, logging_1.info)(`reportLostArchiver: not enabled, publicKey: ${publicKey}, errorMsg: ${errorMsg}`);
        return;
    }
    (0, logging_1.info)(`reportLostArchiver: publicKey: ${publicKey}, errorMsg: ${errorMsg}`);
    // Add new entry to lostArchiversMap for reported Archiver if it doesn't exist
    // This is to ensure that we don't overwrite existing entries
    if (state_1.lostArchiversMap.has(publicKey)) {
        (0, logging_1.info)('reportLostArchiver: already have LostArchiverRecord');
    }
    else {
        (0, logging_1.info)('reportLostArchiver: adding new LostArchiverRecord');
        // Set status to 'reported'
        // This status indicates that the Archiver has been reported as lost, but not yet investigated
        state_1.lostArchiversMap.set(publicKey, createLostArchiverRecord({
            target: publicKey,
            status: 'reported',
        }));
    }
    // don't gossip here; that is initiated in sendRequests()
}
exports.reportLostArchiver = reportLostArchiver;
/**
 * Investigates a reported lost Archiver.
 * This function gets called to verify if an Archiver is indeed lost
 * @param publicKey - The public key of the Archiver to investigate
 */
async function investigateArchiver(investigateMsg) {
    (0, logging_1.info)(`investigateArchiver: investigateMsg: ${(0, util_1.inspect)(investigateMsg)}`);
    const publicKey = investigateMsg.target;
    const archiver = Archivers.archivers.get(publicKey);
    if (!archiver) {
        // don't know the archiver
        (0, logging_1.warn)(`investigateArchiver: asked to investigate archiver '${publicKey}', but it's not in the archivers list`);
        return;
    }
    // Retrieve the record of the Archiver from the lostArchiversMap
    (0, logging_1.info)(`investigateArchiver: publicKey: ${publicKey}`);
    let record = state_1.lostArchiversMap.get(publicKey);
    if (record && ['investigating', 'down', 'up'].includes(record.status)) {
        (0, logging_1.info)('investigateArchiver: already have LostArchiverRecord');
        // already investigated
        return;
    }
    // starting investigation
    record = createLostArchiverRecord({
        isInvestigator: true,
        target: publicKey,
        status: 'investigating',
        gossippedDownMsg: false,
        investigateMsg,
    });
    // record it
    state_1.lostArchiversMap.set(publicKey, record);
    // ping the archiver
    const isReachable = await pingArchiver(archiver.ip, archiver.port);
    // handle the result
    if (isReachable) {
        (0, logging_1.info)(`investigateArchiver: archiver is reachable`);
        state_1.lostArchiversMap.delete(publicKey);
    }
    else {
        (0, logging_1.info)(`investigateArchiver: archiver is not reachable`);
        record.status = 'down';
    }
    // don't gossip here; that is initiated in sendRequests()
}
exports.investigateArchiver = investigateArchiver;
/**
 * Picks an investigator node for lost archiver detection
 * @param record record in from the lostArchiverRecordMap
 * @returns The node ID of the investigator for that specific record
 */
function getInvestigator(target, marker) {
    // Implement hashing target + marker and returning node from Nodelist with id closest to hash
    // This is to ensure that the investigator node is chosen in a deterministic manner
    const obj = { target, marker };
    const near = Context.crypto.hash(obj);
    let idx = (0, arrays_1.binarySearch)(NodeList_1.activeByIdOrder, near, (i, r) => i.localeCompare(r.id));
    if (idx < 0)
        idx = (-1 - idx) % NodeList_1.activeByIdOrder.length;
    // eslint-disable-next-line security/detect-object-injection
    const foundNode = NodeList_1.activeByIdOrder[idx];
    // eslint-disable-next-line security/detect-object-injection
    if (foundNode == null) {
        throw new Error(`activeByIdOrder idx:${idx} length: ${NodeList_1.activeByIdOrder.length}`);
    }
    if (foundNode.id === Self_1.id)
        idx = (idx + 1) % NodeList_1.activeByIdOrder.length; // skip to next node if the selected node is target
    return foundNode;
}
exports.getInvestigator = getInvestigator;
/**
 * Informs the investigator node that an Archiver is lost
 * @param target - The public key of the lost Archiver
 */
function informInvestigator(target) {
    // This is to initiate the investigation process
    try {
        // Compute investigator based off of hash(target + cycle marker)
        const cycle = CycleChain.getCurrentCycleMarker();
        const investigator = getInvestigator(target, cycle);
        // Don't send yourself an InvestigateArchiverMsg
        if (Self_1.id === investigator.id) {
            (0, logging_1.info)(`informInvestigator: investigator is self, not sending InvestigateArchiverMsg`);
            return;
        }
        // Form msg
        const investigateMsg = Context.crypto.sign({
            type: 'investigate',
            target,
            investigator: investigator.id,
            sender: Self_1.id,
            cycle,
        });
        // Send message to investigator
        (0, logging_1.info)(`informInvestigator: sending InvestigateArchiverMsg: ${(0, util_1.inspect)(investigateMsg)}`);
        // if (this.config.p2p.useBinarySerializedEndpoints && this.config.p2p.lostArchiverInvestigateBinary) {
        (0, promises_1.fireAndForget)(() => Comms.tellBinary([investigator], InternalRouteEnum_1.InternalRouteEnum.binary_lost_archiver_investigate, investigateMsg, LostArchiverInvestigateReq_1.serializeLostArchiverInvestigateReq, {}));
        // } else {
        //   Comms.tell([investigator], 'lost-archiver-investigate', investigateMsg)
        // }
    }
    catch (ex) {
        __1.nestedCountersInstance.countEvent('p2p', `informInvestigator error ${(0, network_1.shardusGetTime)()}`);
        (0, logging_1.error)('informInvestigator: ' + (0, utils_1.formatErrorMessage)(ex));
    }
}
exports.informInvestigator = informInvestigator;
/**
 * Tells the network that an Archiver is down
 * @param archiverKey - The public key of the down Archiver
 */
function tellNetworkArchiverIsDown(record) {
    const archiverKey = record.target;
    (0, logging_1.info)(`tellNetworkArchiverIsDown: archiverKey: ${archiverKey}`);
    const downMsg = Context.crypto.sign({
        type: 'down',
        cycle: CycleChain.getCurrentCycleMarker(),
        investigateMsg: record.investigateMsg,
    });
    (0, logging_1.info)(`tellNetworkArchiverIsDown: downMsg: ${lib_types_1.Utils.safeStringify(downMsg)}`);
    record.archiverDownMsg = downMsg;
    (0, promises_1.fireAndForget)(() => Comms.sendGossip('lost-archiver-down', downMsg, '', null, NodeList.byIdOrder, /* isOrigin */ true));
    // This is to inform the rest of the network that the Archiver is down
}
exports.tellNetworkArchiverIsDown = tellNetworkArchiverIsDown;
/**
 * Tells the network thtellNetworkArchiverIsUpat an Archiver as up.
 * @param publicKey - The public key of the Archiver that is now up
 */
function tellNetworkArchiverIsUp(record) {
    // Create an ArchiverUpMsg using the saved ArchiverRefutedLostMsg
    const upMsg = Context.crypto.sign({
        type: 'up',
        downMsg: record.archiverDownMsg,
        refuteMsg: record.archiverRefuteMsg,
        cycle: CycleChain.getCurrentCycleMarker(),
    });
    record.archiverUpMsg = upMsg;
    // Gossip the ArchiverUpMsg to the rest of the network
    (0, logging_1.info)(`tellNetworkArchiverIsUp: upMsg: ${lib_types_1.Utils.safeStringify(upMsg)}`);
    Comms.sendGossip('lost-archiver-up', upMsg, '', null, NodeList.byIdOrder, /* isOrigin */ true);
}
exports.tellNetworkArchiverIsUp = tellNetworkArchiverIsUp;
/**
 * Returns true if the archiver can be pinged.
 * This function is used to check if an Archiver is reachable
 * @param host - The host of the Archiver
 * @param port - The port of the Archiver
 * @returns A promise that resolves to true if the Archiver is reachable, and false otherwise
 */
async function pingArchiver(host, port) {
    // The Archiver is considered reachable if we can get its info
    return (await getArchiverInfo(host, port)) !== null;
}
/**
 * Returns the JSON object from the archiver's nodeInfo endpoint, or null if the archiver is not reachable.
 * Keys include publicKey, ip, port, version and time.
 * This function is used to get the info of an Archiver
 * @param host - The host of the Archiver
 * @param port - The port of the Archiver
 * @returns A promise that resolves to the info of the Archiver if it is reachable, and null otherwise
 */
async function getArchiverInfo(host, port) {
    try {
        // Try to get the info of the Archiver
        return await http.get(`http://${host}:${port}/nodeInfo`);
    }
    catch (e) {
        // If an error occurs (e.g., the Archiver is not reachable), return null
        return null;
    }
}
function missingProperties(obj, keys) {
    if (typeof keys === 'string')
        keys = keys.split(/\s+/);
    if (obj == null)
        return keys;
    const missing = [];
    for (const key of keys) {
        if (!(key in obj))
            missing.push(key);
        // eslint-disable-next-line security/detect-object-injection
        if (obj[key] === undefined)
            missing.push(key);
    }
    return missing;
}
/**
 * Checks for errors in an ArchiverDownMsg
 * @param msg - The ArchiverDownMsg to check
 * @returns null if there are no errors, and a string describing the error otherwise
 */
function errorForArchiverDownMsg(msg) {
    if (msg == null)
        return 'null message';
    if (msg.sign == null)
        return 'no signature';
    const missing = missingProperties(msg, 'type investigateMsg cycle');
    if (missing.length)
        return `missing properties: ${missing.join(', ')}`;
    return _errorForInvestigateArchiverMsg(msg.investigateMsg);
    // to-do: check for valid signature
}
exports.errorForArchiverDownMsg = errorForArchiverDownMsg;
/**
 * Checks for errors in an ArchiverUpMsg
 * @param msg - The ArchiverUpMsg to check
 * @returns null if there are no errors, and a string describing the error otherwise
 */
function errorForArchiverUpMsg(msg) {
    if (msg == null)
        return 'null message';
    if (msg.sign == null)
        return 'no signature';
    const missing = missingProperties(msg, 'type downMsg refuteMsg cycle');
    if (missing.length)
        return `missing properties: ${missing.join(', ')}`;
    /*
    to-do:
      downMsg: ArchiverDownMsg;
      refuteMsg: ArchiverRefutesLostMsg;
    */
    // to-do: check for valid signature
    return null;
}
exports.errorForArchiverUpMsg = errorForArchiverUpMsg;
/**
 * Checks for errors in an ArchiverRefutesLostMsg
 * @param msg - The ArchiverUpMsg to check
 * @returns null if there are no errors, and a string describing the error otherwise
 */
function errorForArchiverRefutesLostMsg(msg) {
    if (msg == null)
        return 'null message';
    if (msg.sign == null)
        return 'no signature';
    const missing = missingProperties(msg, 'archiver cycle');
    if (missing.length)
        return `missing properties: ${missing.join(', ')}`;
    // to-do: check for valid signature
    return null;
}
exports.errorForArchiverRefutesLostMsg = errorForArchiverRefutesLostMsg;
/**
 * Checks for errors in an InvestigateArchiverMsg
 * @param msg - The InvestigateArchiverMsg to check
 * @returns null if there are no errors, and a string describing the error otherwise
 */
function errorForInvestigateArchiverMsg(msg) {
    if (msg == null)
        return 'null message';
    if (msg.sign == null)
        return 'no signature';
    const error = _errorForInvestigateArchiverMsg(msg);
    if (error)
        return error;
    // to-do: check for valid signature
    return null;
}
exports.errorForInvestigateArchiverMsg = errorForInvestigateArchiverMsg;
function _errorForInvestigateArchiverMsg(msg) {
    if (msg == null)
        return 'null message';
    const missing = missingProperties(msg, 'type target investigator sender cycle');
    if (missing.length)
        return `missing properties: ${missing.join(', ')}`;
    if (msg.type !== 'investigate')
        return `invalid type: ${msg.type}`;
    return null;
}
//# sourceMappingURL=functions.js.map