"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.removeUnjoinRequest = exports.deleteStandbyNode = exports.drainNewUnjoinRequests = exports.validateUnjoinRequest = exports.processNewUnjoinRequest = exports.submitUnjoin = void 0;
const Context_1 = require("../../Context");
const neverthrow_1 = require("neverthrow");
const lib_types_1 = require("@shardeum-foundation/lib-types");
const utils = __importStar(require("../../../utils"));
const http = __importStar(require("../../../http"));
const NodeList = __importStar(require("../../NodeList"));
const _1 = require(".");
const Utils_1 = require("../../Utils");
const logger_1 = require("../../../logger");
const CycleChain = __importStar(require("../../CycleChain"));
const Self_1 = require("../../Self");
/** A Set of new public keys of nodes that have submitted unjoin requests. */
const newUnjoinRequests = new Set();
/**
 * Submits a request to leave the network's standby node list.
 */
async function submitUnjoin() {
    const publicKey = Context_1.crypto.keypair.publicKey;
    if ((0, Self_1.getPublicNodeInfo)(true).status !== lib_types_1.P2P.P2PTypes.NodeStatus.STANDBY) {
        return (0, neverthrow_1.err)(new Error('node is not in standby. Do not send unjoin request'));
    }
    const archiver = (0, Utils_1.getRandomAvailableArchiver)();
    try {
        const activeNodesResult = await (0, Utils_1.getActiveNodesFromArchiver)(archiver);
        if (activeNodesResult.isErr()) {
            return (0, neverthrow_1.err)(new Error(`couldn't get active nodes: ${activeNodesResult.error}`));
        }
        const activeNodes = activeNodesResult.value;
        // If we submit to a node that is about to be rotated out, then that node will queue our request for the next cycle
        // but will not send it since it will be removed from the network. Since we are a standby node, we dont have access
        // to the potentiallyRemoved list, so I dont think there's an easy solution to this. We need to think if this is even
        // worth solving. In a live network with a 1000 nodes, the chances of this happening are 1/1000. having 0.1% of standby
        // not being properly removed isn't that big a deal. Standby refresh will take care of them anyways.
        // If we really want to solve this, can do so by sending request to numRotatedOut + 1 nodes.
        const node = utils.getRandom(activeNodes.nodeList, 1)[0];
        const cycleRecord = await http.get(`${node.ip}:${node.port}/newest-cycle-record`);
        if (!cycleRecord?.counter) {
            return (0, neverthrow_1.err)(new Error('No cycle counter found. Do not send unjoin request'));
        }
        const unjoinRequest = Context_1.crypto.sign({
            publicKey: publicKey,
            cycleNumber: cycleRecord.counter,
        });
        await http.post(`${node.ip}:${node.port}/unjoin`, unjoinRequest);
        return (0, neverthrow_1.ok)(void 0);
    }
    catch (e) {
        throw new Error(`submitUnjoin: Error posting unjoin request: ${e}`);
    }
}
exports.submitUnjoin = submitUnjoin;
/**
 * Process a new unjoin request, adding it to the set of new unjoin requests
 * that will be recorded in the next cycle.
 *
 * Returns with an error if the unjoin request is invalid.
 */
function processNewUnjoinRequest(unjoinRequest) {
    console.log('processing unjoin request for', unjoinRequest.publicKey);
    // validate the unjoin request and then add it if it is valid
    return validateUnjoinRequest(unjoinRequest).map(() => {
        newUnjoinRequests.add(unjoinRequest);
    });
}
exports.processNewUnjoinRequest = processNewUnjoinRequest;
/**
 * Validates an unjoin request by its signature.
 */
function validateUnjoinRequest(unjoinRequest) {
    // ignore if the unjoin request already exists
    const newUnjoinRequestsArray = Array.from(newUnjoinRequests);
    if (newUnjoinRequestsArray.some((req) => req.publicKey === unjoinRequest.publicKey)) {
        return (0, neverthrow_1.err)(new Error(`unjoin request from ${unjoinRequest.publicKey} already exists`));
    }
    // cycle number check
    const cycleNumber = CycleChain.getNewest().counter;
    if (Math.abs(cycleNumber - unjoinRequest.cycleNumber) > 1) {
        return (0, neverthrow_1.err)(new Error(`cycle number in SignedUnjoinRequest request is not close enough to the current cycle`));
    }
    const wasSelectedLastCycle = CycleChain.newest.joinedConsensors.find((node) => node.publicKey === unjoinRequest.publicKey)
        ? true
        : false;
    // ignore if the unjoin request is from a node that is active
    // only exception is if it was selected last cycle
    const foundInActiveNodes = NodeList.byPubKey.has(unjoinRequest.publicKey);
    if (foundInActiveNodes && wasSelectedLastCycle === false) {
        return (0, neverthrow_1.err)(new Error(`unjoin request from ${unjoinRequest.publicKey} is from an active node that can't unjoin`));
    }
    // ignore if the unjoin request is from a node that is not in standby
    const foundInStandbyNodes = (0, _1.getStandbyNodesInfoMap)().has(unjoinRequest.publicKey);
    if (!foundInStandbyNodes && wasSelectedLastCycle === false) {
        return (0, neverthrow_1.err)(new Error(`unjoin request from ${unjoinRequest.publicKey} is from a node not in standby (doesn't exist?)`));
    }
    // lastly, verify the signature of the join request
    if (!Context_1.crypto.verify(unjoinRequest, unjoinRequest.publicKey)) {
        return (0, neverthrow_1.err)(new Error('unjoin request signature is invalid'));
    }
    return (0, neverthrow_1.ok)(void 0);
}
exports.validateUnjoinRequest = validateUnjoinRequest;
function drainNewUnjoinRequests() {
    const drained = [...newUnjoinRequests.values()];
    newUnjoinRequests.clear();
    return drained;
}
exports.drainNewUnjoinRequests = drainNewUnjoinRequests;
function deleteStandbyNode(publicKey) {
    if ((0, _1.deleteStandbyNodeFromMap)(publicKey)) {
        /* prettier-ignore */ if (logger_1.logFlags.verbose)
            console.log(`--removed standby node ${publicKey} count: ${(0, _1.getStandbyNodesInfoMap)().size}`);
    }
    else {
        console.log(`--failed to remove standby node ${publicKey} count: ${(0, _1.getStandbyNodesInfoMap)().size}`);
    }
}
exports.deleteStandbyNode = deleteStandbyNode;
function removeUnjoinRequest(publicKey) {
    newUnjoinRequests.forEach((unjoinRequest) => {
        if (unjoinRequest.publicKey === publicKey) {
            newUnjoinRequests.delete(unjoinRequest);
        }
    });
}
exports.removeUnjoinRequest = removeUnjoinRequest;
//# sourceMappingURL=unjoin.js.map