"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.setAcceptInternal = setAcceptInternal;
exports.init = init;
exports.tell = tell;
exports.tellBinary = tellBinary;
exports.ask = ask;
exports.askBinary = askBinary;
exports.evictCachedSockets = evictCachedSockets;
exports.registerInternal = registerInternal;
exports.registerInternalBinary = registerInternalBinary;
exports.unregisterInternal = unregisterInternal;
exports.isNodeValidForInternalMessage = isNodeValidForInternalMessage;
exports.modeAllowsValidNodeChecks = modeAllowsValidNodeChecks;
exports.sendGossip = sendGossip;
exports.sendGossipAll = sendGossipAll;
exports.handleGossip = handleGossip;
exports.registerGossipHandler = registerGossipHandler;
exports.unregisterGossipHandler = unregisterGossipHandler;
const logger_1 = require("../logger");
const network_1 = require("../network");
const Lost_1 = require("../p2p/Lost");
const Helpers_1 = require("../types/Helpers");
const WrappedReq_1 = require("../types/WrappedReq");
const utils = __importStar(require("../utils"));
const nestedCounters_1 = require("../utils/nestedCounters");
const profiler_1 = require("../utils/profiler");
const VectorBufferStream_1 = require("../utils/serialization/VectorBufferStream");
const Context_1 = require("./Context");
const NodeList = __importStar(require("./NodeList"));
const Self = __importStar(require("./Self"));
const CycleChain = __importStar(require("./CycleChain"));
const GossipReq_1 = require("../types/GossipReq");
const InternalRouteEnum_1 = require("../types/enum/InternalRouteEnum");
const RequestErrorEnum_1 = require("../types/enum/RequestErrorEnum");
const Helpers_2 = require("../types/Helpers");
const TypeIdentifierEnum_1 = require("../types/enum/TypeIdentifierEnum");
const ResponseError_1 = require("../types/ResponseError");
const lib_types_1 = require("@shardeum-foundation/lib-types");
// const gossipInternalRoute: P2P.P2PTypes.InternalHandler<GossipReq> = async (
//   payload,
//   _respond,
//   sender,
//   tracker,
//   msgSize
// ) => {
//   await handleGossip(payload, sender, tracker, msgSize)
// }
const gossipInternalBinaryRoute = {
    name: InternalRouteEnum_1.InternalRouteEnum.binary_gossip,
    handler: async (payload, response, header, sign) => {
        const route = InternalRouteEnum_1.InternalRouteEnum.binary_gossip;
        nestedCounters_1.nestedCountersInstance.countEvent('internal', route);
        profiler_1.profilerInstance.scopedProfileSectionStart(route);
        const errorHandler = (errorType, opts) => (0, Helpers_2.requestErrorHandler)(route, errorType, header, opts);
        let gossipType = 'unknown';
        try {
            const requestStream = (0, Helpers_2.getStreamWithTypeCheck)(payload, TypeIdentifierEnum_1.TypeIdentifierEnum.cGossipReq);
            if (!requestStream) {
                return errorHandler(RequestErrorEnum_1.RequestErrorEnum.InvalidRequest);
            }
            const req = (0, GossipReq_1.deserializeGossipReq)(requestStream);
            gossipType = req.type;
            await handleGossip(req, header.sender_id, header.tracker_id, payload.length);
        }
        catch (e) {
            nestedCounters_1.nestedCountersInstance.countEvent('internal', `${route}-${gossipType}-exception`);
            Context_1.logger
                .getLogger('comms-route')
                .error(`${route}: Exception executing gossip type: ${gossipType}, request: ${utils.errorToStringFull(e)}`);
        }
        finally {
            profiler_1.profilerInstance.scopedProfileSectionEnd(route);
        }
    },
};
const routes = {
    internal: {
    // gossip: gossipInternalRoute,
    },
    internalBinary: {
        gossip: gossipInternalBinaryRoute,
    },
};
/** STATE */
let p2pLogger;
let seqLogger;
let acceptInternal = false;
let keyCounter = 0;
let internalRecvCounter = 0;
// const gossipedHashesSent = new Map()     // No longer used
// const gossipedHashesRecv = new Map()    // No longer used
const gossipHandlers = {};
let gossipSent = 0;
let gossipRecv = 0;
const gossipTypeSent = {};
const gossipTypeRecv = {};
let commsCounters = true;
/** FUNCTIONS */
function setAcceptInternal(enabled) {
    acceptInternal = enabled;
}
function init() {
    p2pLogger = Context_1.logger.getLogger('p2p');
    seqLogger = Context_1.logger.getLogger('seq');
    // Register routes
    for (const [name, handler] of Object.entries(routes.internal)) {
        registerInternal(name, handler);
    }
    // Register binary routes
    for (const route of Object.values(routes.internalBinary)) {
        registerInternalBinary(route.name, route.handler);
    }
    // Catch Q1 start events and log size of hashes
    Self.emitter.on('cycle_q1_start', pruneGossipHashes);
}
// Finds a node either in nodelist or in seedNodes list
function _findNodeInGroup(nodeId, group) {
    if (!group) {
        const errMsg = 'No group given for _findNodeInGroup()';
        warn(errMsg);
        throw new Error(errMsg);
    }
    for (const node of group) {
        if (node.id === nodeId)
            return node;
    }
    warn(`Node ID not found in group: ${nodeId}`);
    return false;
}
// Verifies that the received internal message was signed by the stated node
function _authenticateByNode(message, node) {
    let result;
    try {
        if (!node.curvePublicKey) {
            error('Node object did not contain curve public key for authenticateByNode()!');
            return false;
        }
        result = Context_1.config.p2p.useSignaturesForAuth
            ? Context_1.crypto.verify(message, node.publicKey)
            : Context_1.crypto.authenticate(message, node.curvePublicKey);
    }
    catch (e) {
        /* prettier-ignore */ if (logger_1.logFlags.verbose)
            error(`Invalid or missing authentication/signature tag on message: ${lib_types_1.Utils.safeStringify(message)}`);
        return false;
    }
    return result;
}
// Extracts the payload from binary serialized wrapped messages.
// This method could have done a complete deserialization into the specific type but,
// that is avoided as we want to delay parsing of payload until header checks succeed.
function _extractPayloadBinary(wrappedPayload) {
    /* prettier-ignore */ if (logger_1.logFlags.verbose)
        console.log('_extractPayloadBinary: wrappedPayload', lib_types_1.Utils.safeStringify(wrappedPayload));
    let buffer = null;
    if (wrappedPayload instanceof Buffer) {
        /* prettier-ignore */ if (logger_1.logFlags.verbose)
            info(`_extractPayloadBinary: wrappedPayload is a buffer: ${wrappedPayload}`);
        buffer = wrappedPayload;
    }
    else if (wrappedPayload.type === 'Buffer' && Array.isArray(wrappedPayload.data)) {
        /* prettier-ignore */ if (logger_1.logFlags.verbose)
            info(`_extractPayloadBinary: wrappedPayload is a buffer struct: ${wrappedPayload}`);
        buffer = Buffer.from(wrappedPayload.data);
    }
    else {
        nestedCounters_1.nestedCountersInstance.countEvent('comms-route', `_extractPayloadBinary: bad wrappedPayload`);
        throw new Error(`Unsupported wrappedPayload type: ${wrappedPayload.type}`);
    }
    const stream = VectorBufferStream_1.VectorBufferStream.fromBuffer(buffer);
    const payloadType = stream.readUInt16();
    switch (payloadType) {
        case 3:
            const wrappedReq = (0, WrappedReq_1.deserializeWrappedReq)(stream);
            return wrappedReq.payload;
        default:
            throw new Error(`Unsupported payload type: ${payloadType}`);
    }
}
function _extractPayload(wrappedPayload, nodeGroup) {
    let err = utils.validateTypes(wrappedPayload, { error: 's?' });
    if (err) {
        warn('extractPayload: bad wrappedPayload: ' + err + ' ' + lib_types_1.Utils.safeStringify(wrappedPayload));
        return [null];
    }
    if (wrappedPayload.error) {
        const error = wrappedPayload.error;
        warn(`_extractPayload Failed to extract payload. Error: ${error}`);
        return [null];
    }
    err = utils.validateTypes(wrappedPayload, Context_1.config.p2p.useSignaturesForAuth
        ? {
            sender: 's',
            payload: 'o',
            sign: 'o',
            tracker: 's?',
        }
        : {
            sender: 's',
            payload: 'o',
            tag: 's',
            tracker: 's?',
        });
    if (err) {
        warn('extractPayload: bad wrappedPayload: ' + err + ' ' + lib_types_1.Utils.safeStringify(wrappedPayload));
        return [null];
    }
    // Check to see if node is in expected node group
    const node = _findNodeInGroup(wrappedPayload.sender, nodeGroup);
    if (!node) {
        warn(`_extractPayload Invalid sender on internal payload. sender: ${wrappedPayload.sender} payload: ${utils.stringifyReduceLimit(wrappedPayload)}`);
        return [null];
    }
    const authenticatedByNode = _authenticateByNode(wrappedPayload, node);
    // Check if actually signed by that node
    if (!authenticatedByNode) {
        warn('_extractPayload Internal payload not authenticated by an expected node.');
        return [null];
    }
    const payload = wrappedPayload.payload;
    const sender = wrappedPayload.sender;
    const tracker = wrappedPayload.tracker;
    const msgSize = wrappedPayload.msgSize;
    return [payload, sender, tracker, msgSize];
}
function _wrapMessage(msg, tracker = '') {
    if (!msg)
        throw new Error('No message given to wrap and tag!');
    if (logger_1.logFlags.verbose) {
        warn(`Attaching sender ${Self.id} to the message: ${utils.stringifyReduceLimit(msg)}`);
    }
    return {
        payload: msg,
        sender: Self.id,
        tracker,
        msgSize: 0,
    };
}
function _wrapAndTagMessage(msg, tracker = '', recipientNode) {
    const wrapped = _wrapMessage(msg, tracker);
    return Context_1.crypto.tagWithSize(wrapped, recipientNode.curvePublicKey);
}
function _wrapAndSignMessage(msg, tracker = '') {
    const wrapped = _wrapMessage(msg, tracker);
    return Context_1.crypto.signWithSize(wrapped);
}
function createMsgTracker(route = '') {
    return 'key_' + route + '_' + utils.makeShortHash(Self.id) + '_' + (0, network_1.shardusGetTime)() + '_' + keyCounter++;
}
function createGossipTracker() {
    return 'gkey_' + utils.makeShortHash(Self.id) + '_' + (0, network_1.shardusGetTime)() + '_' + keyCounter++;
}
// Our own P2P version of the network tell, with a sign added
async function tell(nodes, route, message, logged = false, tracker = '', subRoute = '' // used by gossip to differentiate between different gossip types
) {
    profiler_1.profilerInstance.profileSectionStart('p2p-tell');
    profiler_1.profilerInstance.profileSectionStart(`p2p-tell-${route}`);
    let msgSize = profiler_1.cUninitializedSize;
    if (tracker === '') {
        tracker = createMsgTracker(route);
    }
    if (commsCounters) {
        nestedCounters_1.nestedCountersInstance.countEvent('comms-route', `tell ${route}`, nodes.length);
        /* prettier-ignore */ nestedCounters_1.nestedCountersInstance.countEvent('comms-route x recipients', `tell ${route} recipients:${nodes.length}`, nodes.length);
        nestedCounters_1.nestedCountersInstance.countEvent('comms-recipients', `tell recipients: ${nodes.length}`, nodes.length);
        /* prettier-ignore */ nestedCounters_1.nestedCountersInstance.countEvent('comms-route x recipients (logical count)', `tell ${route} recipients:${nodes.length}`);
    }
    msgSize = Context_1.config.p2p.useSignaturesForAuth
        ? await signedMultiTell(nodes, message, tracker, msgSize, route, logged, subRoute)
        : await taggedMultiTell(nodes, message, tracker, msgSize, route, logged, subRoute);
    profiler_1.profilerInstance.profileSectionEnd('p2p-tell');
    profiler_1.profilerInstance.profileSectionEnd(`p2p-tell-${route}`);
    return msgSize;
}
async function tellBinary(nodes, route, message, serializerFunc, appHeader, logged = false, tracker = '') {
    profiler_1.profilerInstance.profileSectionStart('p2p-tellBinary');
    profiler_1.profilerInstance.profileSectionStart(`p2p-tellBinary-${route}`);
    let msgSize = profiler_1.cUninitializedSize;
    if (tracker === '' || !appHeader.tracker_id) {
        tracker = createMsgTracker(route);
        appHeader.tracker_id = tracker;
    }
    appHeader.sender_id = Self.id;
    if (commsCounters) {
        nestedCounters_1.nestedCountersInstance.countEvent('comms-route', `tellBinary ${route}`, nodes.length);
        /* prettier-ignore */ nestedCounters_1.nestedCountersInstance.countEvent('comms-route x recipients', `tellBinary ${route} recipients:${nodes.length}`, nodes.length);
        nestedCounters_1.nestedCountersInstance.countEvent('comms-recipients', `tellBinary recipients: ${nodes.length}`, nodes.length);
        /* prettier-ignore */ nestedCounters_1.nestedCountersInstance.countEvent('comms-route x recipients (logical count)', `tellBinary ${route} recipients:${nodes.length}`);
    }
    const typecasted_nodes = nodes;
    const nonSelfNodes = typecasted_nodes.filter((node) => node.id !== Self.id);
    try {
        const wrappedReq = (0, Helpers_1.requestSerializer)(message, serializerFunc);
        await Context_1.network.tellBinary(nonSelfNodes, route, wrappedReq.getBuffer(), appHeader, tracker, logged);
    }
    catch (err) {
        warn(`tellBinary: network.tellBinary: P2P TELL_BINARY: failed. route: ${route}, error: ${err}`);
    }
    profiler_1.profilerInstance.profileSectionEnd('p2p-tellBinary');
    profiler_1.profilerInstance.profileSectionEnd(`p2p-tellBinary-${route}`);
    return msgSize;
}
async function taggedMultiTell(nodes, message, tracker, msgSize, route, logged, subRoute = '') {
    const promises = [];
    for (const node of nodes) {
        if (node.id === Self.id) {
            if (logger_1.logFlags.p2pNonFatal)
                info('p2p/Comms:tell: Not telling self');
            continue;
        }
        const signedMessage = _wrapAndTagMessage(message, tracker, node);
        msgSize = signedMessage.msgSize;
        /* prettier-ignore */ if (logger_1.logFlags.p2pNonFatal)
            info(`taggedMultiTell: signed and tagged gossip`, utils.stringifyReduceLimit(signedMessage));
        promises.push(Context_1.network.tell([node], route, signedMessage, logged, subRoute));
    }
    try {
        await Promise.all(promises);
    }
    catch (err) {
        warn('taggedMultiTell: P2P TELL: failed', err);
    }
    return msgSize;
}
async function signedMultiTell(nodes, message, tracker, msgSize, route, logged, subRoute = '') {
    const signedMessage = _wrapAndSignMessage(message, tracker);
    msgSize = signedMessage.msgSize;
    const nonSelfNodes = nodes.filter((node) => node.id !== Self.id);
    /* prettier-ignore */ if (logger_1.logFlags.p2pNonFatal)
        info(`signedMultiTell: signed and tagged gossip`, utils.stringifyReduceLimit(signedMessage));
    try {
        await Context_1.network.tell(nonSelfNodes, route, signedMessage, logged, subRoute);
    }
    catch (err) {
        warn('signedMultiTell: P2P TELL: failed', err);
    }
    return msgSize;
}
// Our own P2P version of the network ask, with a sign added, and sign verified on other side
async function ask(node, route, message = {}, logged = false, tracker = '', extraTime = 0) {
    if (tracker === '') {
        tracker = createMsgTracker(route);
    }
    if (node.id === Self.id) {
        if (logger_1.logFlags.p2pNonFatal)
            info('p2p/Comms:ask: Not asking self');
        return false;
    }
    if (commsCounters) {
        nestedCounters_1.nestedCountersInstance.countEvent('comms-route', `ask ${route}`);
        nestedCounters_1.nestedCountersInstance.countEvent('comms-route x recipients', `ask ${route} recipients: 1`);
        nestedCounters_1.nestedCountersInstance.countEvent('comms-recipients', `ask recipients: 1`);
        /* prettier-ignore */ nestedCounters_1.nestedCountersInstance.countEvent('comms-route x recipients (logical count)', `ask ${route} recipients: 1`);
    }
    const msgWithAuth = Context_1.config.p2p.useSignaturesForAuth
        ? await _wrapAndSignMessage(message, tracker)
        : await _wrapAndTagMessage(message, tracker, node);
    let respWithAuth;
    try {
        respWithAuth = await Context_1.network.ask(node, route, msgWithAuth, logged, extraTime);
    }
    catch (err) {
        error('P2P: ask: network.ask: ' + err);
        return false;
    }
    try {
        const [response] = _extractPayload(respWithAuth, [node]);
        if (!response) {
            throw new Error(`Unable to verify response to ask request: ${route} -- ${lib_types_1.Utils.safeStringify(message)} from node: ${node.id}`);
        }
        return response;
    }
    catch (err) {
        error('P2P: ask: _extractPayload: ' + err);
        return false;
    }
}
async function askBinary(node, route, message, reqSerializerFunc, respDeserializerFunc, appHeader, tracker = '', logged = false, extraTime = 0) {
    if (tracker === '') {
        tracker = createMsgTracker(route);
        appHeader.tracker_id = tracker;
    }
    if (!appHeader.sender_id)
        appHeader.sender_id = Self.id;
    if (node.id === Self.id) {
        if (logger_1.logFlags.p2pNonFatal)
            info('p2p/Comms: askBinary: Not asking self');
        throw new Error('Not asking self');
    }
    if (commsCounters) {
        nestedCounters_1.nestedCountersInstance.countEvent('comms-route', `askBinary ${route}`);
        nestedCounters_1.nestedCountersInstance.countEvent('comms-route x recipients', `askBinary ${route} recipients: 1`);
        nestedCounters_1.nestedCountersInstance.countEvent('comms-recipients', `askBinary recipients: 1`);
        /* prettier-ignore */ nestedCounters_1.nestedCountersInstance.countEvent('comms-route x recipients (logical count)', `askBinary ${route} recipients: 1`);
    }
    let res, header, sign;
    try {
        const wrappedReq = (0, Helpers_1.requestSerializer)(message, reqSerializerFunc);
        ({ res, header, sign } = await Context_1.network.askBinary(node, route, wrappedReq.getBuffer(), appHeader, tracker, logged, extraTime));
    }
    catch (err) {
        nestedCounters_1.nestedCountersInstance.countEvent('comms-route', `askBinary ${route} request error`);
        /* prettier-ignore */ console.log(`P2P: askBinary: network.askBinary: route: ${route} request: ${lib_types_1.Utils.safeStringify(message)} error: ${err}`);
        /* prettier-ignore */ error(`P2P: askBinary: network.askBinary: route: ${route} request: ${lib_types_1.Utils.safeStringify(message)} error: ${err}`);
        throw err;
    }
    try {
        if (!res)
            /* prettier-ignore */ throw new Error(`Empty response to askBinary request: ${route} -- ${lib_types_1.Utils.safeStringify(message)} from node: ${node.id}`);
        if (header && sign)
            if (header.sender_id !== node.id || sign.owner !== node.publicKey) {
                warn('askBinary: response did not come from the expected node.');
                throw new Error('askBinary: response did not come from the expected node.');
            }
        const respStream = VectorBufferStream_1.VectorBufferStream.fromBuffer(res);
        const deserializedResp = (0, Helpers_1.responseDeserializer)(respStream, respDeserializerFunc);
        return deserializedResp;
    }
    catch (err) {
        if (err instanceof ResponseError_1.ResponseError) {
            nestedCounters_1.nestedCountersInstance.countEvent('comms-route', `askBinary ${route} error ${err.Code}`);
        }
        else {
            nestedCounters_1.nestedCountersInstance.countEvent('comms-route', `askBinary ${route} response error`);
            /* prettier-ignore */ error(`P2P: askBinary: response extraction route: ${route} res: ${lib_types_1.Utils.safeStringify(res)} error: ${err}`);
            /* prettier-ignore */ console.log(`P2P: askBinary: response extraction route: ${route} res: ${lib_types_1.Utils.safeStringify(res)} error: ${err}`);
        }
        throw err;
    }
}
function evictCachedSockets(nodes) {
    profiler_1.profilerInstance.scopedProfileSectionStart('p2p-evictCachedSockets');
    Context_1.network.evictCachedSockets(nodes);
    profiler_1.profilerInstance.scopedProfileSectionEnd('p2p-evictCachedSockets');
}
function registerInternal(route, handler) {
    // Create function that wraps handler function
    const wrappedHandler = async (wrappedPayload, respond) => {
        /* prettier-ignore */ if (logger_1.logFlags.p2pNonFatal)
            info("registerInternal wrappedPayload", utils.stringifyReduceLimit(wrappedPayload));
        internalRecvCounter++;
        // We have internal requests turned off until we have a node id
        if (!acceptInternal) {
            if (logger_1.logFlags.p2pNonFatal)
                info(`${route} We are not currently accepting internal requests...`);
            await respond({ error: `${route} error: Not accepting internal requests` });
            return;
        }
        let msgSize = 0;
        let tracker = '';
        let hasHandlerResponded = false;
        // Create wrapped respond function for sending back signed data
        const respondWrapped = async (response) => {
            /**
             * [TODO] [AS]
             * If sender is not found in nodelist, _wrapAndTagMessage will try to access
             * a property of undefined and error out. This might cause some trouble for
             * registerInternal handlers that use the respond fn handed to their callbacks
             * to reply to requests. They might have to be try/catched to avoid crashing
             * shardus
             */
            const node = NodeList.nodes.get(sender);
            const message = { ...response, isResponse: true };
            const respWithAuth = Context_1.config.p2p.useSignaturesForAuth
                ? await _wrapAndSignMessage(message, tracker)
                : await _wrapAndTagMessage(message, tracker, node);
            if (logger_1.logFlags.verbose && logger_1.logFlags.p2pNonFatal) {
                /* prettier-ignore */ info(`The signed wrapped response to send back: ${utils.stringifyReduceLimit(respWithAuth)} size:${respWithAuth.msgSize}`);
            }
            if (route !== 'gossip') {
                /* prettier-ignore */ if (logger_1.logFlags.playback)
                    Context_1.logger.playbackLog(sender, 'self', 'InternalRecvResp', route, tracker, response);
            }
            await respond(respWithAuth);
            hasHandlerResponded = true;
            //return the message size in bytes
            return respWithAuth.msgSize;
        };
        // Checks to see if we can extract the actual payload from the wrapped message
        const payloadArray = _extractPayload(wrappedPayload, NodeList.byIdOrder // [TODO] Maybe this should be othersByIdOrder
        );
        const [payload, sender] = payloadArray;
        tracker = payloadArray[2] || '';
        msgSize = payloadArray[3] || profiler_1.cNoSizeTrack;
        if (!payload) {
            warn(`${route} Payload unable to be extracted, possible missing signature...`);
            await respond({ error: `${route} error: Payload unable to be extracted` });
            return;
        }
        if (!NodeList.nodes.has(sender)) {
            warn(`${route} Internal routes can only be used by nodes in the network...`);
            await respond({ error: `${route} error: Sender not in node list` });
            return;
        }
        if (route !== 'gossip') {
            /* prettier-ignore */ if (logger_1.logFlags.playback)
                Context_1.logger.playbackLog(sender, 'self', 'InternalRecv', route, tracker, payload);
        }
        //mark that we got a message from this node (for isNodeUpRecent)
        (0, Lost_1.setIsUpTs)(sender);
        await handler(payload, respondWrapped, sender, tracker, msgSize);
        // If the handler didn't call respondWrapped, we need to send an empty response
        if (!hasHandlerResponded && route !== 'gossip') {
            nestedCounters_1.nestedCountersInstance.countEvent('comms-route', `no-response`);
            nestedCounters_1.nestedCountersInstance.countEvent('comms-route', `no-response ${route}`);
            await respond({ error: `${route} error: No response from handler` });
        }
    };
    // Include that in the handler function that is passed
    Context_1.network.registerInternal(route, wrappedHandler);
    /* prettier-ignore */ if (logger_1.logFlags.p2pNonFatal)
        info(`registerInternal wrappedPayload, ${route} endpoint registeration complete`);
}
function registerInternalBinary(route, handler) {
    const wrappedHandler = async (wrappedPayload, respond, header, sign) => {
        /* prettier-ignore */ if (logger_1.logFlags.p2pNonFatal)
            info('registerInternalBinary: wrappedPayload', utils.stringifyReduceLimit(wrappedPayload));
        internalRecvCounter++;
        // We have internal requests turned off until we have a node id
        if (!acceptInternal) {
            if (logger_1.logFlags.p2pNonFatal)
                info('registerInternalBinary: we are not currently accepting internal requests...');
            return;
        }
        let hasHandlerResponded = false;
        // Create wrapped respond function for sending back data
        const respondWrapped = async (response, serializerFunc, responseHeaders = {}) => {
            try {
                const wrappedRespStream = (0, Helpers_1.responseSerializer)(response, serializerFunc);
                responseHeaders.sender_id = Self.id;
                responseHeaders.tracker_id = header.tracker_id;
                /* prettier-ignore */ if (logger_1.logFlags.verbose && logger_1.logFlags.p2pNonFatal)
                    info(`registerInternalBinary: wrapped response to send back: ${wrappedRespStream.getBuffer()} size: ${wrappedRespStream.getBufferLength()}`);
                if (route !== 'gossip') {
                    /* prettier-ignore */ if (logger_1.logFlags.playback)
                        Context_1.logger.playbackLog(header.sender_id, 'self', 'InternalRecvResp', route, header.tracker_id, response);
                }
                hasHandlerResponded = true;
                await respond(wrappedRespStream.getBuffer(), responseHeaders);
                return wrappedRespStream.getBufferLength();
            }
            catch (err) {
                /* prettier-ignore */ error(`registerInternalBinary: route: ${route} responseHeaders: ${lib_types_1.Utils.safeStringify(responseHeaders)}, Response: ${lib_types_1.Utils.safeStringify(response)}, Error: ${utils.formatErrorMessage(err)}`);
                /* prettier-ignore */ nestedCounters_1.nestedCountersInstance.countEvent('registerInternalBinary', `respondWrapped ${route} error`);
                return 0;
            }
        };
        /* prettier-ignore */ if (logger_1.logFlags.verbose && logger_1.logFlags.p2pNonFatal)
            console.log('header:', header);
        /* prettier-ignore */ if (logger_1.logFlags.verbose && logger_1.logFlags.p2pNonFatal)
            info(`registerInternalBinary: request info: route: ${route} header: ${lib_types_1.Utils.safeStringify(header)} sign: ${lib_types_1.Utils.safeStringify(sign)}`);
        const isSignerKnown = NodeList.byPubKey.has(sign.owner);
        const isSenderKnown = NodeList.nodes.has(header.sender_id);
        const bothKnown = isSignerKnown && isSenderKnown;
        const isTestMode = Context_1.config.debug.enableTestMode;
        if (bothKnown === false && !isTestMode) {
            nestedCounters_1.nestedCountersInstance.countEvent('comms-route', `signer_or_esender_missing`);
            /* prettier-ignore */ if (logger_1.logFlags.p2pNonFatal)
                warn('registerInternalBinary: internal route missing signer or sender...');
            return;
        }
        const isSignerSenderMismatch = NodeList.byPubKey.get(sign.owner).id !== header.sender_id;
        if (isSignerSenderMismatch && !isTestMode) {
            nestedCounters_1.nestedCountersInstance.countEvent('comms-route', `signer_sender_mismatch`);
            /* prettier-ignore */ if (logger_1.logFlags.p2pNonFatal)
                warn('registerInternalBinary: internal routes can only be used by nodes in the network...');
            return;
        }
        // Checks to see if we can extract the actual payload from the wrapped message
        const requestPayload = _extractPayloadBinary(wrappedPayload);
        if (!requestPayload) {
            /* prettier-ignore */ if (logger_1.logFlags.p2pNonFatal)
                warn('registerInternalBinary: payload unable to be extracted, possible missing signature...');
            return;
        }
        if (route !== 'gossip') {
            /* prettier-ignore */ if (logger_1.logFlags.playback)
                Context_1.logger.playbackLog(header.sender_id, 'self', 'InternalRecv', route, header.tracker_id, requestPayload);
        }
        //mark that we got a message from this node (for isNodeUpRecent)
        (0, Lost_1.setIsUpTs)(header.sender_id); //is this correct?
        await handler(requestPayload, respondWrapped, header, sign);
        if (!hasHandlerResponded && (0, InternalRouteEnum_1.isAskRoute)(route)) {
            nestedCounters_1.nestedCountersInstance.countEvent('comms-route', `no-response`);
            nestedCounters_1.nestedCountersInstance.countEvent('comms-route', `no-response ${route}`);
            const wrappedError = (0, Helpers_1.responseSerializer)((0, ResponseError_1.InternalError)('Handler failed to respond'), ResponseError_1.serializeResponseError);
            const responseHeaders = {};
            responseHeaders.sender_id = Self.id;
            responseHeaders.tracker_id = header.tracker_id;
            await respond(wrappedError.getBuffer(), responseHeaders);
        }
    };
    Context_1.network.registerInternal(route, wrappedHandler);
}
function unregisterInternal(route) {
    Context_1.network.unregisterInternal(route);
}
function sortByID(first, second) {
    return utils.sortAscProp(first, second, 'id');
}
function isNodeValidForInternalMessage(node, debugMsg, checkForNodeDown = true, checkForNodeLost = true, checkIsUpRecent = true, checkNodesRotationBounds = false, txId = 'N/A') {
    const logErrors = logger_1.logFlags.debug;
    if (node == null) {
        /* prettier-ignore */ if (logger_1.logFlags.seqdiagram)
            seqLogger.info(`0x53455103 ${(0, network_1.shardusGetTime)()} tx:${txId} Note over ${NodeList.activeIdToPartition.get(Self.id)}: validnode ${NodeList.activeIdToPartition.get(node.id)} null`);
        if (logErrors)
            if (logger_1.logFlags.error)
                /* prettier-ignore */ error(`isNodeValidForInternalMessage node == null ${utils.stringifyReduce(node.id)} ${debugMsg}`);
        return false;
    }
    // Some modes are not compatible with doing a valid node check for outgoing messages
    // if that is the case just return true and allow the message
    if (modeAllowsValidNodeChecks() === false) {
        /* prettier-ignore */ if (logger_1.logFlags.seqdiagram)
            seqLogger.info(`0x53455103 ${(0, network_1.shardusGetTime)()} tx:${txId} Note over ${NodeList.activeIdToPartition.get(Self.id)}: validnode ${NodeList.activeIdToPartition.get(node.id)} nochecks`);
        return true;
    }
    const nodeStatus = node.status;
    // need to consider if these statuses should be allowed for internal messages by this function!
    // maybe we are doing ourselves a favor by filtering these out.
    // SELECTED = "selected",
    // SYNCING = "syncing",
    // READY = "ready",
    // this could cause different cycle records based on potentiallyRemoved
    // we should create a new if check called checkPotentiallyRemoved and review
    // each calling site (or site that calls the gossip, yay more parameters)
    // Also may add a flag to change if we also allow other statuses
    if (nodeStatus != 'active') {
        /* prettier-ignore */ if (logger_1.logFlags.seqdiagram)
            seqLogger.info(`0x53455103 ${(0, network_1.shardusGetTime)()} tx:${txId} Note over ${NodeList.activeIdToPartition.get(Self.id)}: validnode ${NodeList.activeIdToPartition.get(node.id)} notactive`);
    }
    if (NodeList.potentiallyRemoved.has(node.id)) {
        /* prettier-ignore */ if (logger_1.logFlags.seqdiagram)
            seqLogger.info(`0x53455103 ${(0, network_1.shardusGetTime)()} tx:${txId} Note over ${NodeList.activeIdToPartition.get(Self.id)}: validnode ${NodeList.activeIdToPartition.get(node.id)} remove`);
    }
    // This is turned off and likely to be deleted.
    // This is because filtering gossip nodes after sending the list is causing holes to appear in gossip and some nodes do not get the gossip at all which causes stuck txn.
    // Do not turn this back on without talking to Andrew.
    //
    //if (nodeStatus != 'active' || NodeList.potentiallyRemoved.has(node.id)) {
    //  if (logErrors)
    //    if (logFlags.error)
    //      /* prettier-ignore */ error(`isNodeValidForInternalMessage node not active. ${nodeStatus} ${utils.stringifyReduce(node.id)} ${debugMsg}`)
    //  return false
    //}
    //
    //const isInRotationBounds = checkNodesRotationBounds && isNodeInRotationBounds(node.id)
    //if (isInRotationBounds) {
    //  /* prettier-ignore */ if (logFlags.seqdiagram) seqLogger.info(`0x53455103 ${shardusGetTime()} tx:${txId} Note over ${NodeList.activeIdToPartition.get(Self.id)}: validnode ${NodeList.activeIdToPartition.get(node.id)} rotation`)
    //  return false
    //}
    // check up recent first which will tell us if we have gossip from this node in the last 5 seconds
    // consider a larger amount of time
    if (checkIsUpRecent) {
        const { upRecent, age } = (0, Lost_1.isNodeUpRecent)(node.id, 5000);
        if (upRecent === true) {
            if (checkForNodeDown) {
                const { down, state } = (0, Lost_1.isNodeDown)(node.id);
                if (down === true) {
                    if (logErrors)
                        info(`isNodeUpRecentOverride: ${age} isNodeValidForInternalMessage isNodeDown == true state:${state} ${utils.stringifyReduce(node.id)} ${debugMsg}`);
                }
            }
            if (checkForNodeLost) {
                if ((0, Lost_1.isNodeLost)(node.id) === true) {
                    if (logErrors)
                        info(`isNodeUpRecentOverride: ${age} isNodeValidForInternalMessage isNodeLost == true ${utils.stringifyReduce(node.id)} ${debugMsg}`);
                }
            }
            return true;
        }
        else {
            if (logErrors)
                warn(`isNodeUpRecentOverride: ${age} upRecent = false. no recent TX, but this is not a fail conditions`);
        }
    }
    if (Context_1.config.p2p.downNodeFilteringEnabled && checkForNodeDown) {
        const { down, state } = (0, Lost_1.isNodeDown)(node.id);
        if (down === true) {
            if (logErrors)
                if (logger_1.logFlags.error)
                    /* prettier-ignore */ error(`isNodeValidForInternalMessage isNodeDown == true state:${state} ${utils.stringifyReduce(node.id)} ${debugMsg}`);
            /* prettier-ignore */ if (logger_1.logFlags.seqdiagram)
                seqLogger.info(`0x53455103 ${(0, network_1.shardusGetTime)()} tx:${txId} Note over ${NodeList.activeIdToPartition.get(Self.id)}: validnode ${NodeList.activeIdToPartition.get(node.id)} down`);
            return false;
        }
    }
    if (checkForNodeLost) {
        if ((0, Lost_1.isNodeLost)(node.id) === true) {
            if (logErrors)
                if (logger_1.logFlags.error)
                    /* prettier-ignore */ error(`isNodeValidForInternalMessage isNodeLost == true ${utils.stringifyReduce(node.id)} ${debugMsg}`);
            /* prettier-ignore */ if (logger_1.logFlags.seqdiagram)
                seqLogger.info(`0x53455103 ${(0, network_1.shardusGetTime)()} tx:${txId} Note over ${NodeList.activeIdToPartition.get(Self.id)}: validnode ${NodeList.activeIdToPartition.get(node.id)} lost`);
            return false;
        }
    }
    return true;
}
/**
 * Are we in a mode that should be doing additional "validNode" checks
 * before sending an outgoing message
 * @returns
 */
function modeAllowsValidNodeChecks() {
    const newestCycle = CycleChain.newest;
    if (newestCycle) {
        if (newestCycle.mode === 'processing') {
            return true;
        }
        if (newestCycle.mode === 'forming') {
            return true;
        }
        // really important to not run these extra checks in the following modes as they can break if they ignore
        // sending gossipe.  the valid node tests may also not have the correct logic to work with these modes
        if (Self.isRestartNetwork ||
            newestCycle.mode === 'recovery' ||
            newestCycle.mode === 'restart' ||
            newestCycle.mode === 'restore') {
            return false;
        }
    }
    return true;
}
function calculateGossipFactor(numberOfNodes) {
    if (numberOfNodes === 0)
        return Context_1.config.p2p.gossipFactor;
    function getBaseLog(x, y) {
        return Math.log(y) / Math.log(x);
    }
    const gossipFactor = Math.ceil(getBaseLog(3, numberOfNodes));
    return gossipFactor;
}
/**
 * Send Gossip to all nodes, using gossip in
 */
// [TODO] This function should not sort nodes; they should be pre-sorted
async function sendGossip(type, payload, tracker = '', sender = null, inpNodes = NodeList.byIdOrder, // Joining nodes need gossip too; we don't
// send to ourself
isOrigin = false, factor = -1, txId = '', context = '', commonOrigin = false) {
    //console.log('entered sendGossip gossiping ', type)
    let msgSize = profiler_1.cUninitializedSize;
    // [TODO] Don't copy the node list once sorted lists are passed in
    const nodes = [...inpNodes];
    if (nodes.length === 0) {
        /* prettier-ignore */ nestedCounters_1.nestedCountersInstance.countEvent('gossip-filter', `no recipients for ${type}`);
        return;
    }
    if (tracker === '') {
        tracker = createGossipTracker();
    }
    if (logger_1.logFlags.verbose && logger_1.logFlags.p2pNonFatal) {
        info(`Start of sendGossipIn(${utils.stringifyReduce(payload)})`);
    }
    const gossipPayload = { type, data: payload };
    /*
    const gossipHash = crypto.hash(gossipPayload)
    if (gossipedHashesSent.has(gossipHash)) {
      if (logFlags.verbose) {
        warn(`Gossip already sent: ${gossipHash.substring(0, 5)}`)
      }
      return
    }
    */
    // nodes.sort((first, second) => first.id.localeCompare(second.id, 'en', { sensitivity: 'variant' }))
    nodes.sort(sortByID);
    const nodeIdxs = new Array(nodes.length).fill(0).map((curr, idx) => idx); // [TODO]  - we need to make sure that we never iterate, or copy the full nodes list. Assume it could be a million nodes.
    //console.log('nodeIdxs ', nodeIdxs)
    // Find out your own index in the nodes array
    const myIdx = nodes.findIndex((node) => node.id === Self.id);
    //console.log('myIdx ', myIdx)
    if (myIdx < 0) {
        //console.log(`Failed to sendGossip. Could not find self in nodes array ${type}`)
        // throw new Error('Could not find self in nodes array')
        error(`Failed to sendGossip. Could not find self in nodes array ${type}`);
        return msgSize;
    }
    let gossipFactor = Context_1.config.p2p.gossipFactor;
    if (Context_1.config.p2p.dynamicGossipFactor) {
        gossipFactor = calculateGossipFactor(nodes.length);
    }
    if (factor > 0) {
        gossipFactor = factor;
    }
    let recipientIdxs;
    let originNode;
    let originIdx;
    if (payload.sign) {
        originNode = NodeList.byPubKey.get(payload.sign.owner);
        if (originNode)
            originIdx = nodes.findIndex((node) => node.id === originNode.id);
    }
    //console.log('originIdx ', originIdx)
    if (context != '')
        if (logger_1.logFlags.seqdiagram)
            /* prettier-ignore */ seqLogger.info(`0x53455103 ${(0, network_1.shardusGetTime)()} tx:${txId} Note over ${NodeList.activeIdToPartition.get(Self.id)}: gossipContext:${context}`);
    if (originIdx !== undefined && originIdx >= 0 && !commonOrigin) {
        // If it is protocol tx signed by a node in the network
        recipientIdxs = utils.getLinearGossipBurstList(nodeIdxs.length, gossipFactor, myIdx, originIdx);
        if (logger_1.logFlags.seqdiagram && txId != '') {
            /* prettier-ignore */ if (logger_1.logFlags.seqdiagram)
                seqLogger.info(`0x53455103 ${(0, network_1.shardusGetTime)()} tx:${txId} Note over ${NodeList.activeIdToPartition.get(Self.id)}: gossipBin:${nodeIdxs.length},${gossipFactor},${myIdx},${originIdx}`);
            /* prettier-ignore */ if (logger_1.logFlags.seqdiagram)
                seqLogger.info(`0x53455103 ${(0, network_1.shardusGetTime)()} tx:${txId} Note over ${NodeList.activeIdToPartition.get(Self.id)}: gossipBout:${recipientIdxs}`);
        }
    }
    else {
        // If it is app tx which is not signed by a node in the network
        recipientIdxs = utils.getLinearGossipList(nodeIdxs.length, gossipFactor, myIdx, isOrigin);
        if (logger_1.logFlags.seqdiagram && txId != '') {
            /* prettier-ignore */ if (logger_1.logFlags.seqdiagram)
                seqLogger.info(`0x53455103 ${(0, network_1.shardusGetTime)()} tx:${txId} Note over ${NodeList.activeIdToPartition.get(Self.id)}: gossipLin:${nodeIdxs.length},${gossipFactor},${myIdx},${isOrigin}`);
            /* prettier-ignore */ if (logger_1.logFlags.seqdiagram)
                seqLogger.info(`0x53455103 ${(0, network_1.shardusGetTime)()} tx:${txId} Note over ${NodeList.activeIdToPartition.get(Self.id)}: gossipLout:${recipientIdxs}`);
        }
    }
    //console.log('recipientIdxs ', recipientIdxs)
    // Map back recipient idxs to node objects
    let recipients = recipientIdxs.map((idx) => nodes[idx]);
    if (sender != null) {
        recipients = utils.removeNodesByID(recipients, [sender]);
    }
    try {
        //console.log('entered try')
        if (logger_1.logFlags.verbose && logger_1.logFlags.p2pNonFatal) {
            info(`GossipingIn ${type} request to these nodes: ${utils.stringifyReduce(recipients.map((node) => utils.makeShortHash(node.id) + ':' + node.externalPort))}`);
        }
        for (const node of recipients) {
            /* prettier-ignore */ if (logger_1.logFlags.playback)
                Context_1.logger.playbackLog('self', node.id, 'GossipInSend', type, tracker, gossipPayload);
            gossipSent++;
            gossipTypeSent[type] = gossipTypeSent[type] ? gossipTypeSent[type] + 1 : 1;
        }
        if (commsCounters) {
            nestedCounters_1.nestedCountersInstance.countEvent('comms-route', `sendGossip ${type}`, recipients.length);
            /* prettier-ignore */ nestedCounters_1.nestedCountersInstance.countEvent('comms-route x recipients', `sendGossip ${type} recipients: ${recipients.length}`, recipients.length);
            /* prettier-ignore */ nestedCounters_1.nestedCountersInstance.countEvent('comms-recipients', `sendGossip recipients: ${recipients.length}`, recipients.length);
            /* prettier-ignore */ nestedCounters_1.nestedCountersInstance.countEvent('comms-route x recipients (logical count)', `sendGossip ${type} recipients: ${recipients.length}`);
        }
        //console.log('recipients before filter')
        //recipients.forEach(node => console.log(node.externalPort))
        // Filter recipients to only include those that are valid
        if (Context_1.config.p2p.preGossipNodeCheck) {
            const oldCount = recipients.length;
            recipients = recipients.filter((node) => {
                if (isNodeValidForInternalMessage(node, 'sendGossip', Context_1.config.p2p.preGossipDownCheck, Context_1.config.p2p.preGossipLostCheck, Context_1.config.p2p.preGossipRecentCheck, false, txId)) {
                    return true;
                }
                else {
                    /* prettier-ignore */ nestedCounters_1.nestedCountersInstance.countEvent('p2p-skip-send', 'skipping gossip');
                    /* prettier-ignore */ nestedCounters_1.nestedCountersInstance.countEvent('p2p-skip-send', `skipping gossip ${node.internalIp}:${node.externalPort}`);
                    /* prettier-ignore */ if (logger_1.logFlags.seqdiagram)
                        seqLogger.info(`0x53455103 ${(0, network_1.shardusGetTime)()} tx:${txId} Note over ${NodeList.activeIdToPartition.get(Self.id)}: gossipSkip:${NodeList.activeIdToPartition.get(node.id)}:${node.internalIp}:${node.externalPort}`);
                }
            });
            const newCount = recipients.length;
            if (oldCount != newCount) {
                /* prettier-ignore */ nestedCounters_1.nestedCountersInstance.countEvent('gossip-filter', `filter ${type} oldCount ${oldCount} newCount${newCount}`);
            }
            if (oldCount > 0 && newCount === 0) {
                /* prettier-ignore */ nestedCounters_1.nestedCountersInstance.countEvent('gossip-filter', `all recipients removed from ${type} oldCount ${oldCount}`);
            }
        }
        //console.log('recipients after filter')
        //recipients.forEach(node => console.log(node.externalPort))
        if (logger_1.logFlags.seqdiagram && txId != '') {
            let prefix = '';
            let suffix = '';
            if (isOrigin)
                prefix = 'orig:';
            if (context != '')
                suffix = `:${suffix}`;
            for (const node of recipients) {
                /* prettier-ignore */ if (logger_1.logFlags.seqdiagram)
                    seqLogger.info(`0x53455103 ${(0, network_1.shardusGetTime)()} tx:${txId} ${NodeList.activeIdToPartition.get(Self.id)}-->>${NodeList.activeIdToPartition.get(node.id)}: g:${prefix}${type}${suffix}`);
            }
        }
        // if (config.p2p.useBinarySerializedEndpoints === true) {
        msgSize = await tellBinary(recipients, InternalRouteEnum_1.InternalRouteEnum.binary_gossip, gossipPayload, GossipReq_1.serializeGossipReq, {
            tracker_id: tracker,
        }, true, tracker);
        // } else {
        //   msgSize = await tell(recipients, 'gossip', gossipPayload, true, tracker, type)
        // }
    }
    catch (ex) {
        //console.log('entered catch ', ex)
        if (logger_1.logFlags.verbose) {
            error(`Failed to sendGossip(${type}, ${utils.stringifyReduce(payload)}) Exception => ${ex}`);
        }
        fatal(`sendGossipIn: ${type}: ` + ex.name + ': ' + ex.message + ' at ' + ex.stack);
    }
    // gossipedHashesSent.set(gossipHash, currentCycle)    // No longer used
    if (logger_1.logFlags.verbose && logger_1.logFlags.p2pNonFatal) {
        info(`End of sendGossipIn(${utils.stringifyReduce(payload)})`);
    }
    return msgSize;
}
async function sendGossipAll(type, payload, tracker = '', sender = null, inpNodes = NodeList.byIdOrder // Joining nodes need gossip too; we don't send to ourself
) {
    let msgSize = profiler_1.cUninitializedSize;
    // [TODO] Don't copy the node list once sorted lists are passed in
    const nodes = [...inpNodes];
    if (nodes.length === 0)
        return;
    if (tracker === '') {
        tracker = createGossipTracker();
    }
    if (logger_1.logFlags.verbose) {
        p2pLogger.debug(`Start of sendGossipIn(${utils.stringifyReduce(payload)})`);
    }
    const gossipPayload = { type, data: payload };
    const gossipHash = Context_1.crypto.hash(gossipPayload);
    // if (gossipedHashesSent.has(gossipHash)) {
    //   if (logFlags.verbose) {
    //     p2pLogger.debug(`Gossip already sent: ${gossipHash.substring(0, 5)}`)
    //   }
    //   return
    // }
    // nodes.sort((first, second) => first.id.localeCompare(second.id, 'en', { sensitivity: 'variant' }))
    nodes.sort(sortByID);
    const nodeIdxs = new Array(nodes.length).fill(0).map((curr, idx) => idx);
    // Find out your own index in the nodes array
    const myIdx = nodes.findIndex((node) => node.id === Self.id);
    if (myIdx < 0) {
        // throw new Error('Could not find self in nodes array')
        error(`Failed to sendGossip. Could not find self in nodes array ${type}`);
        return msgSize;
    }
    let recipients = nodes;
    if (sender != null) {
        recipients = utils.removeNodesByID(recipients, [sender]);
    }
    try {
        if (logger_1.logFlags.verbose) {
            p2pLogger.debug(`GossipingIn ${type} request to these nodes: ${utils.stringifyReduce(recipients.map((node) => utils.makeShortHash(node.id) + ':' + node.externalPort))}`);
        }
        for (const node of recipients) {
            /* prettier-ignore */ if (logger_1.logFlags.playback)
                Context_1.logger.playbackLog('self', node.id, 'GossipInSend', type, tracker, gossipPayload);
        }
        if (commsCounters) {
            nestedCounters_1.nestedCountersInstance.countEvent('comms-route', `sendGossipAll ${type}`, recipients.length);
            /* prettier-ignore */ nestedCounters_1.nestedCountersInstance.countEvent('comms-route x recipients', `sendGossipAll ${type} recipients: ${recipients.length}`, recipients.length);
            /* prettier-ignore */ nestedCounters_1.nestedCountersInstance.countEvent('comms-recipients', `sendGossipAll recipients: ${recipients.length}`, recipients.length);
            /* prettier-ignore */ nestedCounters_1.nestedCountersInstance.countEvent('comms-route x recipients (logical count)', `sendGossipAll ${type} recipients: ${recipients.length}`);
        }
        // Filter recipients to only include those that are valid
        if (Context_1.config.p2p.preGossipNodeCheck) {
            recipients = recipients.filter((node) => {
                if (isNodeValidForInternalMessage(node, 'sendGossip', Context_1.config.p2p.preGossipDownCheck, Context_1.config.p2p.preGossipLostCheck, Context_1.config.p2p.preGossipRecentCheck)) {
                    return true;
                }
                else {
                    nestedCounters_1.nestedCountersInstance.countEvent('p2p-skip-send', 'skipping gossip');
                    nestedCounters_1.nestedCountersInstance.countEvent('p2p-skip-send', `skipping gossip ${node.internalIp}:${node.externalPort}`);
                }
            });
        }
        msgSize = await tellBinary(recipients, InternalRouteEnum_1.InternalRouteEnum.binary_gossip, gossipPayload, GossipReq_1.serializeGossipReq, {
            tracker_id: tracker,
        }, true, tracker);
        // msgSize = await tell(recipients, 'gossip', gossipPayload, true, tracker, type)
    }
    catch (ex) {
        if (logger_1.logFlags.verbose) {
            p2pLogger.error(`Failed to sendGossip(${type} ${utils.stringifyReduce(payload)}) Exception => ${ex}`);
        }
        p2pLogger.fatal(`sendGossipIn: ${type}:` + ex.name + ': ' + ex.message + ' at ' + ex.stack);
    }
    //gossipedHashesSent.set(gossipHash, false)
    if (logger_1.logFlags.verbose) {
        p2pLogger.debug(`End of sendGossipIn(${utils.stringifyReduce(payload)})`);
    }
    return msgSize;
}
/**
 * Handle Goosip Transactions
 * Payload: {type: ['receipt', 'trustedTransaction'], data: {}}
 */
async function handleGossip(payload, sender, tracker = '', msgSize = profiler_1.cNoSizeTrack) {
    if (logger_1.logFlags.verbose && logger_1.logFlags.p2pNonFatal) {
        info(`Start of handleGossip(${utils.stringifyReduce(payload)})`);
    }
    const err = utils.validateTypes(payload, { type: 's', data: 'o' });
    if (err) {
        warn('handleGossip: bad payload: ' + err);
        return;
    }
    // Simulating bad network by dropping received gossip
    //   set the propability of dropping to a number between 0 to 1
    // if (Math.random() < 0.0) {
    //   warn('Dropped gossip to simulate bad network')
    //   return
    // }
    const type = payload.type;
    const data = payload.data;
    const gossipHandler = gossipHandlers[type];
    if (!gossipHandler) {
        warn(`Gossip Handler not found: type ${type}, data: ${lib_types_1.Utils.safeStringify(data)}`);
        return;
    }
    /*
    const gossipHash = crypto.hash(payload)
    if (gossipedHashesSent.has(gossipHash)) {
      return
    }
    */
    /*
    2020.05.12 Omar decided that we should not try to block received gossip from reaching the gossip handlers.
  .                         It is possible that the handler ignored a gossip message when it was first seen, but could apply
  .                         it later if the quarter changed or an internal message received from another node changes the context.
  .                         Or who the gossip is received from could make a difference. For example in Q2 the handler ignores
  .                          gossip from the originator, but accepts it from others.
  .                     *  Also we should not block gossip for messages we have sent. We may need the message to come
  .                          in through the handler so that we apply it.
  .                    *   Also we should not set a timeout for every gossip hash to delete it later. We should delete old
  .                         hashes once per cycle.
  .                    *   Also there is cost of doing a hash on every message received. But at the handler we could easily
  .                          check a field in the message to decide if we processed this before.
  .                    *   For all of the above reasones we should not try to block gossip that we have seen before at this
  .                         layer and let the handlers deal with it.
  .                    *   We should  block sending the same gossip message we have already sent before. However, some
  .                          logging found that we don't typically send the same gossip message again. So the effort to compute,
  .                          save and delete the hashes is wasted.
    */
    /*
    if (gossipedHashesRecv.has(gossipHash)) {
      if (logFlags.verbose) {
        warn(`Got old gossip: ${gossipHash.substring(0, 5)}`)
      }
      if (!gossipedHashesRecv.get(gossipHash)) {  // [TODO] - double check logic; gossipHash should be gettable here since has() is true; so we never setTimeout()
        setTimeout(
          () => gossipedHashesRecv.delete(gossipHash),    // [TODO] - we should not be using setTimeout to delete old gossipHashes; it can be deleted before Q1 starts;
          config.p2p.gossipTimeout
        )
        gossipedHashesRecv.set(gossipHash, true)
        if (logFlags.verbose) {
          warn(
            `Marked old gossip for deletion: ${gossipHash.substring(0, 5)} in ${
              config.p2p.gossipTimeout
            } ms`
          )
        }
      }
      return
    }
    gossipedHashesRecv.set(gossipHash, false)    // [TODO] - double check logic; we setTimeout to delete gossipHash if we get it a second time, but not first time ???
    */
    //mark that we got a message from this node (for isNodeUpRecent)
    (0, Lost_1.setIsUpTs)(sender); //may not need this since gossip is goes through the internal route, and that now flags this
    gossipRecv++;
    gossipTypeRecv[type] = gossipTypeRecv[type] ? gossipTypeRecv[type] + 1 : 1;
    /* prettier-ignore */ if (logger_1.logFlags.playback)
        Context_1.logger.playbackLog(sender, 'self', 'GossipRcv', type, tracker, data);
    // [TODO] - maybe we don't need to await the following line
    await gossipHandler(data, sender, tracker, msgSize);
    if (logger_1.logFlags.verbose && logger_1.logFlags.p2pNonFatal) {
        info(`End of handleGossip(${utils.stringifyReduce(payload)})`);
    }
}
/**
 * Callback for handling gossip.
 *
 * @callback handleGossipCallback
 * @param {any} data the data response of the callback
 * @param {Node} sender
 * @param {string} tracker the tracking string
 */
/**
 * @param {string} type
 * @param {handleGossipCallback} handler
 */
function registerGossipHandler(type, handler) {
    gossipHandlers[type] = handler;
}
function unregisterGossipHandler(type) {
    if (gossipHandlers[type]) {
        delete gossipHandlers[type];
    }
}
// We don't need to prune gossip hashes since we are not creating them anymore.
function pruneGossipHashes() {
    //  warn(`gossipedHashesRecv:${gossipedHashesRecv.size} gossipedHashesSent:${gossipedHashesSent.size}`)
    if (logger_1.logFlags.p2pNonFatal) {
        info(`Total  gossipSent:${gossipSent} gossipRecv:${gossipRecv}`);
        info(`Sent gossip by type: ${lib_types_1.Utils.safeStringify(gossipTypeSent)}`);
        info(`Recv gossip by type: ${lib_types_1.Utils.safeStringify(gossipTypeRecv)}`);
    }
}
function info(...msg) {
    const entry = `Comms: ${msg.join(' ')}`;
    p2pLogger.info(entry);
}
function warn(...msg) {
    const entry = `Comms: ${msg.join(' ')}`;
    p2pLogger.warn(entry);
}
function error(...msg) {
    const entry = `Comms: ${msg.join(' ')}`;
    p2pLogger.error(entry);
}
function fatal(...msg) {
    const entry = `Comms: ${msg.join(' ')}`;
    p2pLogger.fatal(entry);
}
//# sourceMappingURL=Comms.js.map