"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;
};
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const logger_1 = require("../logger");
const InternalRouteEnum_1 = require("../types/enum/InternalRouteEnum");
const RequestErrorEnum_1 = require("../types/enum/RequestErrorEnum");
const TypeIdentifierEnum_1 = require("../types/enum/TypeIdentifierEnum");
const Helpers_1 = require("../types/Helpers");
const SendCachedAppDataReq_1 = require("../types/SendCachedAppDataReq");
const utils = __importStar(require("../utils"));
const utils_1 = require("../utils");
const profiler_1 = require("../utils/profiler");
const shardFunctions_1 = __importDefault(require("./shardFunctions"));
const nestedCounters_1 = require("../utils/nestedCounters");
const GetCachedAppDataReq_1 = require("../types/GetCachedAppDataReq");
const GetCachedAppDataResp_1 = require("../types/GetCachedAppDataResp");
const lib_types_1 = require("@shardeum-foundation/lib-types");
const fastAggregatedCorrespondingTell_1 = require("../utils/fastAggregatedCorrespondingTell");
const NodeList = __importStar(require("../p2p/NodeList"));
class CachedAppDataManager {
    constructor(stateManager, profiler, app, logger, crypto, p2p, config) {
        this.crypto = crypto;
        this.app = app;
        this.logger = logger;
        this.config = config;
        this.profiler = profiler;
        this.p2p = p2p;
        if (logger == null) {
            return; // for debug
        }
        this.mainLogger = logger.getLogger('main');
        this.fatalLogger = logger.getLogger('fatal');
        this.shardLogger = logger.getLogger('shardDump');
        this.statsLogger = logger.getLogger('statsDump');
        this.statemanager_fatal = stateManager.statemanager_fatal;
        this.stateManager = stateManager;
        this.cacheTopicMap = new Map();
        setInterval(this.pruneCachedItems.bind(this), 1000 * config.p2p.cycleDuration); // prune every cycle
    }
    setupHandlers() {
        // this.p2p.registerInternal('send_cachedAppData', async (payload: CacheAppDataResponse) => {
        //   profilerInstance.scopedProfileSectionStart('send_cachedAppData')
        //   try {
        //     /* prettier-ignore */ if (logFlags.net_trace && logFlags.console) console.log(`send_cachedAppData full payload`, Utils.safeStringify(payload));
        //     const cachedAppData = payload.cachedAppData
        //     const existingCachedAppData = this.getCachedItem(payload.topic, cachedAppData.dataID)
        //     if (existingCachedAppData) {
        //       /* prettier-ignore */ if(logFlags.shardedCache) console.log(`cachedAppData: We have already processed this cached data`, cachedAppData, Date.now())
        //       return
        //     }
        //     // insert cachedAppData
        //     this.insertCachedItem(payload.topic, cachedAppData.dataID, cachedAppData.appData, cachedAppData.cycle)
        //   } catch (e) {
        //     this.mainLogger.error(`cachedAppData: Error while processing send_cacheAppData`, e)
        //   } finally {
        //     profilerInstance.scopedProfileSectionEnd('send_cachedAppData')
        //   }
        // })
        const send_cacheAppDataBinarySerializedHandler = {
            name: InternalRouteEnum_1.InternalRouteEnum.binary_send_cachedAppData,
            handler: (payload, respond, header, sign) => {
                const route = InternalRouteEnum_1.InternalRouteEnum.binary_send_cachedAppData;
                profiler_1.profilerInstance.scopedProfileSectionStart(route, false, payload.length);
                nestedCounters_1.nestedCountersInstance.countEvent('internal', route);
                const errorHandler = (errorType, opts) => (0, Helpers_1.requestErrorHandler)(route, errorType, header, opts);
                try {
                    const requestStream = (0, Helpers_1.getStreamWithTypeCheck)(payload, TypeIdentifierEnum_1.TypeIdentifierEnum.cSendCachedAppDataReq);
                    if (!requestStream)
                        return errorHandler(RequestErrorEnum_1.RequestErrorEnum.InvalidRequest);
                    const req = (0, SendCachedAppDataReq_1.deserializeSendCachedAppDataReq)(requestStream);
                    const cachedAppData = req.cachedAppData;
                    if (cachedAppData == null) {
                        return errorHandler(RequestErrorEnum_1.RequestErrorEnum.InvalidRequest);
                    }
                    if (this.config.p2p.useFactCorrespondingTell) {
                        const isValidSender = this.factValidateCorrespondingCachedAppDataSender(cachedAppData.dataID, header.sender_id, req.executionShardKey, req.txId);
                        if (isValidSender === false) {
                            /* prettier-ignore */ if (logger_1.logFlags.error)
                                this.mainLogger.error(`send_cachedAppData invalid sender ${header.sender_id} for data: ${cachedAppData.dataID}`);
                            return;
                        }
                    }
                    const existingCachedAppData = this.getCachedItem(req.topic, cachedAppData.dataID);
                    if (existingCachedAppData) {
                        /* prettier-ignore */ if (logger_1.logFlags.error)
                            console.log(`We have already processed this cached data`, cachedAppData);
                        return;
                    }
                    this.insertCachedItem(req.topic, cachedAppData.dataID, cachedAppData.appData, cachedAppData.cycle);
                }
                catch (e) {
                    nestedCounters_1.nestedCountersInstance.countEvent('internal', `${route}-exception`);
                    /* prettier-ignore */ if (logger_1.logFlags.error)
                        this.mainLogger.error(`${route}: Exception executing request: ${utils.errorToStringFull(e)}`);
                }
                finally {
                    profiler_1.profilerInstance.scopedProfileSectionEnd(route);
                }
            },
        };
        this.p2p.registerInternalBinary(send_cacheAppDataBinarySerializedHandler.name, send_cacheAppDataBinarySerializedHandler.handler);
        // this.p2p.registerInternal(
        //   'get_cached_app_data',
        //   async (payload: CacheAppDataRequest, respond: (arg0: CachedAppData) => Promise<void>) => {
        //     profilerInstance.scopedProfileSectionStart('get_cached_app_data')
        //     try {
        //       const { topic, dataId } = payload
        //       const foundCachedAppData = this.getCachedItem(topic, dataId)
        //       if (foundCachedAppData == null) {
        //         this.mainLogger.error(
        //           `cachedAppData: Cannot find cached data for topic: ${topic}, dataId: ${dataId}`
        //         )
        //         /* prettier-ignore */ if(logFlags.shardedCache) console.log(`cachedAppData: Cannot find cached data for topic: ${topic}, dataId: ${dataId}`)
        //       }
        //       await respond(foundCachedAppData)
        //       profilerInstance.scopedProfileSectionEnd('get_cached_app_data')
        //       return
        //     } catch (e) {
        //       this.mainLogger.error(`cachedAppData: Error while processing get_cachedAppData`, e)
        //       /* prettier-ignore */ if(logFlags.shardedCache) console.log(`cachedAppData: Error while processing get_cachedAppData`, e)
        //     } finally {
        //       profilerInstance.scopedProfileSectionEnd('get_cached_app_data')
        //     }
        //   }
        // )
        const getCachedAppDataBinaryHandler = {
            name: InternalRouteEnum_1.InternalRouteEnum.binary_get_cached_app_data,
            handler: async (payloadBuffer, respond, header, sign) => {
                const route = InternalRouteEnum_1.InternalRouteEnum.binary_get_cached_app_data;
                profiler_1.profilerInstance.scopedProfileSectionStart(route, false, payloadBuffer.length);
                nestedCounters_1.nestedCountersInstance.countEvent('internal', route);
                const response = {
                    cachedAppData: null,
                };
                try {
                    const requestStream = (0, Helpers_1.getStreamWithTypeCheck)(payloadBuffer, TypeIdentifierEnum_1.TypeIdentifierEnum.cGetCachedAppDataReq);
                    if (!requestStream) {
                        nestedCounters_1.nestedCountersInstance.countEvent('internal', `${route}-invalid_request`);
                        this.mainLogger.error(`Invalid input stream for ${route}`);
                        respond(response, GetCachedAppDataResp_1.serializeGetCachedAppDataResp);
                        return;
                    }
                    const readableReq = (0, GetCachedAppDataReq_1.deserializeGetCachedAppDataReq)(requestStream);
                    const foundCachedAppData = this.getCachedItem(readableReq.topic, readableReq.dataId);
                    response.cachedAppData = foundCachedAppData;
                    if (foundCachedAppData == null) {
                        /* prettier-ignore */ if (logger_1.logFlags.error)
                            this.mainLogger.error(`Cannot find cached data for topic: ${readableReq.topic}, dataId: ${readableReq.dataId}`);
                    }
                    respond(response, GetCachedAppDataResp_1.serializeGetCachedAppDataResp);
                }
                catch (e) {
                    nestedCounters_1.nestedCountersInstance.countEvent('internal', `${route}-exception`);
                    this.mainLogger.error(`Error in getCachedAppDataBinray Handler ${e.message}`);
                    respond(response, GetCachedAppDataResp_1.serializeGetCachedAppDataResp);
                }
                finally {
                    profiler_1.profilerInstance.scopedProfileSectionEnd(route, payloadBuffer.length);
                }
            },
        };
        this.p2p.registerInternalBinary(getCachedAppDataBinaryHandler.name, getCachedAppDataBinaryHandler.handler);
    }
    registerTopic(topic, maxCycleAge, maxCacheElements) {
        const cacheTopic = {
            topic,
            maxCycleAge,
            maxCacheElements,
            cacheAppDataMap: new Map(),
            cachedAppDataArray: [],
            // default item size limit
            maxItemSize: Number.MAX_VALUE,
        };
        if (this.cacheTopicMap.has(topic))
            return false;
        this.cacheTopicMap.set(topic, cacheTopic);
        if (logger_1.logFlags.verbose)
            this.mainLogger.debug(`Cache topic ${topic} is successfully registered.`);
        return true;
    }
    getCachedItem(topic, dataID) {
        const cacheTopic = this.cacheTopicMap.get(topic);
        if (!cacheTopic)
            return;
        const cachedAppData = cacheTopic.cacheAppDataMap.get(dataID);
        return cachedAppData;
    }
    // Check and prune cache items for each topic
    pruneCachedItems() {
        for (const [, cacheTopic] of this.cacheTopicMap.entries()) {
            let count = 0;
            const { maxCycleAge, maxCacheElements } = cacheTopic;
            const prunedCachedAppDataArray = [];
            for (const cachedAppData of (0, utils_1.reversed)(cacheTopic.cachedAppDataArray)) {
                count += 1;
                const cycleAge = this.stateManager.currentCycleShardData.cycleNumber - cachedAppData.cycle;
                if (cycleAge > maxCycleAge || count > maxCacheElements) {
                    /* prettier-ignore */ if (logger_1.logFlags.verbose)
                        this.mainLogger.debug(`cachedAppData: Deleting dataId ${cachedAppData.dataID} from cache map. cycleAge: ${cycleAge}, count: ${count}`);
                    /* prettier-ignore */ if (logger_1.logFlags.shardedCache)
                        console.log(`cachedAppData: Deleting dataId ${cachedAppData.dataID} from cache map. cycleAge: ${cycleAge}, count: ${count}`);
                    cacheTopic.cacheAppDataMap.delete(cachedAppData.dataID);
                }
                else {
                    prunedCachedAppDataArray.push(cachedAppData);
                }
            }
            cacheTopic.cachedAppDataArray = prunedCachedAppDataArray.reverse();
            /* prettier-ignore */ if (logger_1.logFlags.verbose)
                this.mainLogger.debug(`cachedAppData: Updated cached array size: ${cacheTopic.cachedAppDataArray.length}, cacheMapSize: ${cacheTopic.cacheAppDataMap.size}`);
            /* prettier-ignore */ if (logger_1.logFlags.shardedCache)
                console.log((`cachedAppData: Updated cached array size: ${cacheTopic.cachedAppDataArray.length}, cacheMapSize: ${cacheTopic.cacheAppDataMap.size}`));
        }
    }
    insertCachedItem(topic, dataID, appData, cycle) {
        const cachedAppData = {
            dataID,
            appData,
            cycle,
        };
        const cacheTopic = this.cacheTopicMap.get(topic);
        if (!cacheTopic) {
            // not safe to log such a large object in prod. commented out:
            /* prettier-ignore */ if (logger_1.logFlags.shardedCache)
                this.statemanager_fatal('insertCachedItem', `Topic ${topic} is not registered yet.`); // ${JSON.stringify(this.cacheTopicMap)}` )
            return;
        }
        if (!cacheTopic.cacheAppDataMap.has(dataID)) {
            if (cacheTopic.maxCacheElements < cacheTopic.cachedAppDataArray.length + 1) {
                /* prettier-ignore */ if (logger_1.logFlags.shardedCache)
                    this.statemanager_fatal('insertCachedItem', `Topic ${topic} is at max cache count limit`);
                return;
            }
            const dataSize = (0, Helpers_1.estimateBinarySizeOfObject)(cachedAppData);
            // cache size per topic is at max by default
            if (dataSize > cacheTopic.maxItemSize) {
                /* prettier-ignore */ if (logger_1.logFlags.shardedCache)
                    this.statemanager_fatal('insertCachedItem', `Topic ${topic} is at max size limit`);
                return;
            }
            /* prettier-ignore */ if (logger_1.logFlags.shardedCache)
                console.log(`cachedAppData: insert cache app data`, dataID, Date.now());
            cacheTopic.cacheAppDataMap.set(dataID, cachedAppData);
            cacheTopic.cachedAppDataArray.push(cachedAppData);
        }
    }
    setMemoryLimit(topic, maxItemSize) {
        const cacheTopic = this.cacheTopicMap.get(topic);
        if (!cacheTopic) {
            // not safe to log such a large object in prod. commented out:
            /* prettier-ignore */ if (logger_1.logFlags.shardedCache)
                this.statemanager_fatal('setMemoryLimit', `Topic ${topic} is not registered yet.`); // ${JSON.stringify(this.cacheTopicMap)}` )
            return;
        }
        cacheTopic.maxItemSize = maxItemSize;
    }
    factValidateCorrespondingCachedAppDataSender(dataID, senderNodeId, executionShardKey, txId) {
        const senderGroup = this.stateManager.transactionQueue.getConsenusGroupForAccount(executionShardKey);
        const senderNode = NodeList.nodes.get(senderNodeId);
        const logID = utils.stringifyReduce(txId);
        if (senderNode === null) {
            /* prettier-ignore */ if (logger_1.logFlags.error)
                this.mainLogger.error(`factValidateCorrespondingCachedAppDataSender: logId: ${logID} sender node is null`);
            nestedCounters_1.nestedCountersInstance.countEvent('stateManager', 'factValidateCorrespondingCachedAppDataSender: sender node is null');
            return false;
        }
        const senderIsInExecutionGroup = senderGroup.some((node) => node.id === senderNodeId);
        if (senderIsInExecutionGroup === false) {
            /* prettier-ignore */ if (logger_1.logFlags.error)
                this.mainLogger.error(`factValidateCorrespondingCachedAppDataSender: logId: ${logID} sender is not in the execution group sender:${senderNodeId}`);
            nestedCounters_1.nestedCountersInstance.countEvent('stateManager', 'factValidateCorrespondingCachedAppDataSender: sender is not in the execution group');
            return false;
        }
        const targetGroup = this.stateManager.transactionQueue.getConsenusGroupForAccount(dataID);
        const allNodes = Array.from(new Set([...senderGroup, ...targetGroup])).sort((a, b) => a.id.localeCompare(b.id));
        const senderIndexInTxGroup = allNodes.findIndex((node) => node.id === senderNodeId);
        const ourIndexInTxGroup = allNodes.findIndex((node) => node.id === this.stateManager.currentCycleShardData.nodeShardData.node.id);
        const senderGroupSize = senderGroup.length;
        const targetGroupSize = targetGroup.length;
        const { startIndex: targetStartIndex, endIndex: targetEndIndex } = this.stateManager.transactionQueue.getStartAndEndIndexOfTargetGroup(targetGroup.map((node) => node.id), allNodes);
        const globalOffset = parseInt(txId.slice(-4), 16);
        // check if it is a FACT sender
        const isValidFactSender = (0, fastAggregatedCorrespondingTell_1.verifyCorrespondingSender)(ourIndexInTxGroup, senderIndexInTxGroup, globalOffset, targetGroupSize, senderGroupSize, targetStartIndex, targetEndIndex, allNodes.length, false, `cachedAppData ${logID}`);
        // it is not a FACT corresponding node
        if (isValidFactSender === false) {
            /* prettier-ignore */ if (logger_1.logFlags.error)
                this.mainLogger.error(`factValidateCorrespondingCachedAppDataSender: logId: logId: ${logID} sender is not a valid sender isValidSender:  ${isValidFactSender}`);
            nestedCounters_1.nestedCountersInstance.countEvent('stateManager', 'factValidateCorrespondingCachedAppDataSender: sender is not a valid sender or a neighbour node');
            return false;
        }
        return true;
    }
    factSendCorrespondingCachedAppData(topic, dataID, appData, cycle, _formId, txId) {
        if (this.stateManager.currentCycleShardData == null) {
            throw new Error('factSendCorrespondingCachedAppData: currentCycleShardData == null');
        }
        if (dataID == null) {
            throw new Error('factSendCorrespondingCachedAppData: dataId == null');
        }
        const queueEntry = this.stateManager.transactionQueue.getQueueEntry(txId);
        if (!queueEntry.executionGroup) {
            return;
        }
        const ourNodeData = this.stateManager.currentCycleShardData.nodeShardData;
        const senderGroup = this.stateManager.transactionQueue.getConsenusGroupForAccount(queueEntry.executionShardKey);
        const targetGroup = this.stateManager.transactionQueue.getConsenusGroupForAccount(dataID);
        const allNodes = Array.from(new Set([...senderGroup, ...targetGroup])).sort((a, b) => a.id.localeCompare(b.id));
        const senderIndexInTxGroup = allNodes.findIndex((node) => node.id === ourNodeData.node.id);
        const senderGroupSize = senderGroup.length;
        const targetGroupSize = targetGroup.length;
        const { startIndex: targetStartIndex, endIndex: targetEndIndex } = this.stateManager.transactionQueue.getStartAndEndIndexOfTargetGroup(targetGroup.map((node) => node.id), allNodes);
        const globalOffset = parseInt(txId.slice(-4), 16);
        const correspondingIndices = (0, fastAggregatedCorrespondingTell_1.getCorrespondingNodes)(senderIndexInTxGroup, targetStartIndex, targetEndIndex, globalOffset, targetGroupSize, senderGroupSize, allNodes.length);
        const correspondingNodes = [];
        for (const index of correspondingIndices) {
            const node = allNodes[index];
            if (targetGroup.includes(node)) {
                correspondingNodes.push(node);
            }
        }
        const cacheAppDataToSend = {
            dataID,
            appData,
            cycle,
        };
        const message = { topic, cachedAppData: cacheAppDataToSend };
        if (correspondingNodes.length > 0) {
            // Handle edge case where we send to own node
            if (correspondingNodes.length === 1 && correspondingNodes[0].id === ourNodeData.node.id) {
                const existingCachedAppData = this.getCachedItem(topic, dataID);
                if (existingCachedAppData) {
                    /* prettier-ignore */ if (logger_1.logFlags.shardedCache)
                        console.log(`We have already processed this cached data`, existingCachedAppData);
                    return;
                }
                this.insertCachedItem(topic, dataID, appData, cycle);
                return;
            }
            // Filter nodes before we send tell()
            const filteredNodes = this.stateManager.filterValidNodesForInternalMessage(correspondingNodes, 'factSendCorrespondingCachedAppData', true, true);
            if (filteredNodes.length === 0) {
                /* prettier-ignore */
                if (logger_1.logFlags.error)
                    this.mainLogger.error("cachedAppData: factSendCorrespondingCachedAppData: filterValidNodesForInternalMessage no valid nodes left to try");
                /* prettier-ignore */ if (logger_1.logFlags.shardedCache)
                    console.log("cachedAppData: factSendCorrespondingCachedAppData: filterValidNodesForInternalMessage no valid nodes left to try");
                return null;
            }
            const filteredCorrespondingAccNodes = filteredNodes;
            // if (this.config.p2p.useBinarySerializedEndpoints && this.config.p2p.sendCachedAppDataBinary) {
            if (logger_1.logFlags.shardedCache)
                console.log(`cachedAddData: factSendCorrespondingCachedAppData sending ${dataID}`);
            const sendCacheAppDataReq = {
                topic,
                txId,
                executionShardKey: queueEntry.executionShardKey,
                cachedAppData: {
                    dataID: message.cachedAppData.dataID,
                    appData: message.cachedAppData.appData,
                    cycle: message.cachedAppData.cycle,
                },
            };
            this.p2p.tellBinary(filteredCorrespondingAccNodes, InternalRouteEnum_1.InternalRouteEnum.binary_send_cachedAppData, sendCacheAppDataReq, SendCachedAppDataReq_1.serializeSendCachedAppDataReq, {});
            // return
            // }
            // this.p2p.tell(filteredCorrespondingAccNodes, 'send_cachedAppData', message)
        }
    }
    async sendCorrespondingCachedAppData(topic, dataID, appData, cycle, _formId, txId) {
        if (this.stateManager.currentCycleShardData == null) {
            throw new Error('sendCorrespondingCachedAppData: currentCycleShardData == null');
        }
        if (dataID == null) {
            throw new Error('sendCorrespondingCachedAppData: dataId == null');
        }
        const queueEntry = this.stateManager.transactionQueue.getQueueEntry(txId);
        const fromKey = queueEntry.executionShardKey ? queueEntry.executionShardKey : queueEntry.txKeys.allKeys[0];
        const uniqueKeys = [fromKey, dataID];
        const ourNodeData = this.stateManager.currentCycleShardData.nodeShardData;
        let correspondingAccNodes = [];
        const dataKeysWeHave = [];
        const dataValuesWeHave = [];
        const dataMap = {}; // eslint-disable-line @typescript-eslint/no-explicit-any
        const remoteShardsByKey = {}; // shard home nodes that we do not have the data for.
        let loggedPartition = false;
        const localHomeNode = shardFunctions_1.default.findHomeNode(this.stateManager.currentCycleShardData.shardGlobals, fromKey, this.stateManager.currentCycleShardData.parititionShardDataMap);
        const remoteHomeNode = shardFunctions_1.default.findHomeNode(this.stateManager.currentCycleShardData.shardGlobals, dataID, this.stateManager.currentCycleShardData.parititionShardDataMap);
        for (const key of uniqueKeys) {
            let hasKey = false;
            if (remoteHomeNode.node.id === ourNodeData.node.id) {
                hasKey = true;
            }
            else {
                //perf todo: this seems like a slow calculation, coult improve this
                for (const node of remoteHomeNode.nodeThatStoreOurParitionFull) {
                    if (node.id === ourNodeData.node.id) {
                        hasKey = true;
                        break;
                    }
                }
            }
            const isGlobalKey = false;
            if (hasKey === false) {
                if (loggedPartition === false) {
                    loggedPartition = true;
                    /* prettier-ignore */ if (logger_1.logFlags.verbose)
                        this.mainLogger.debug(`cachedAppData: sendCorrespondingCachedAppData hasKey=false: ${utils.stringifyReduce(remoteHomeNode.nodeThatStoreOurParitionFull.map((v) => v.id))}`);
                    /* prettier-ignore */ if (logger_1.logFlags.verbose)
                        this.mainLogger.debug(`sendCorrespondingCachedAppData hasKey=false: full: ${utils.stringifyReduce(remoteHomeNode.nodeThatStoreOurParitionFull)}`);
                    /* prettier-ignore */ if (logger_1.logFlags.shardedCache)
                        console.log(`cachedAppData: sendCorrespondingCachedAppData hasKey=false: ${utils.stringifyReduce(remoteHomeNode.nodeThatStoreOurParitionFull.map((v) => v.id))}`);
                    /* prettier-ignore */ if (logger_1.logFlags.shardedCache)
                        console.log(`sendCorrespondingCachedAppData hasKey=false: full: ${utils.stringifyReduce(remoteHomeNode.nodeThatStoreOurParitionFull)}`);
                }
                /* prettier-ignore */ if (logger_1.logFlags.verbose)
                    this.mainLogger.debug(`cachedAppData: sendCorrespondingCachedAppData hasKey=false  key: ${utils.stringifyReduce(key)}`);
                /* prettier-ignore */ if (logger_1.logFlags.shardedCache)
                    console.log(`cachedAppData: sendCorrespondingCachedAppData hasKey=false  key: ${utils.stringifyReduce(key)}`);
            }
            if (hasKey) {
                const data = appData;
                if (isGlobalKey === false) {
                    dataMap[key] = data; // eslint-disable-line security/detect-object-injection
                    dataKeysWeHave.push(key);
                    dataValuesWeHave.push(data);
                }
                this.insertCachedItem(topic, dataID, data, cycle);
            }
            else {
                remoteShardsByKey[key] = remoteHomeNode; // eslint-disable-line security/detect-object-injection
            }
        }
        let edgeNodeIds = [];
        let consensusNodeIds = [];
        const nodesToSendTo = {};
        const doOnceNodeAccPair = new Set(); //can skip  node+acc if it happens more than once.
        for (const key of uniqueKeys) {
            for (const key2 of uniqueKeys) {
                if (key !== key2) {
                    const ourLocalConsensusIndex = localHomeNode.consensusNodeForOurNodeFull.findIndex((a) => a.id === ourNodeData.node.id);
                    if (ourLocalConsensusIndex === -1) {
                        continue;
                    }
                    edgeNodeIds = [];
                    consensusNodeIds = [];
                    correspondingAccNodes = [];
                    const ourSendingGroupSize = localHomeNode.consensusNodeForOurNodeFull.length;
                    const targetConsensusGroupSize = remoteHomeNode.consensusNodeForOurNodeFull.length;
                    const targetEdgeGroupSize = remoteHomeNode.edgeNodes.length;
                    const patchedListSize = remoteHomeNode.patchedOnNodes.length;
                    const indices = shardFunctions_1.default.debugFastStableCorrespondingIndicies(ourSendingGroupSize, targetConsensusGroupSize, ourLocalConsensusIndex + 1);
                    const edgeIndices = shardFunctions_1.default.debugFastStableCorrespondingIndicies(ourSendingGroupSize, targetEdgeGroupSize, ourLocalConsensusIndex + 1);
                    let patchIndices = [];
                    if (remoteHomeNode.patchedOnNodes.length > 0) {
                        patchIndices = shardFunctions_1.default.debugFastStableCorrespondingIndicies(ourSendingGroupSize, remoteHomeNode.patchedOnNodes.length, ourLocalConsensusIndex + 1);
                    }
                    for (const index of indices) {
                        const node = remoteHomeNode.consensusNodeForOurNodeFull[index - 1]; // fastStableCorrespondingIndicies is one based so adjust for 0 based array
                        if (node != null && node.id !== ourNodeData.node.id) {
                            nodesToSendTo[node.id] = node;
                            consensusNodeIds.push(node.id);
                        }
                    }
                    for (const index of edgeIndices) {
                        const node = remoteHomeNode.edgeNodes[index - 1]; // fastStableCorrespondingIndicies is one based so adjust for 0 based array
                        if (node != null && node.id !== ourNodeData.node.id) {
                            nodesToSendTo[node.id] = node;
                            edgeNodeIds.push(node.id);
                        }
                    }
                    for (const index of patchIndices) {
                        const node = remoteHomeNode.edgeNodes[index - 1]; // fastStableCorrespondingIndicies is one based so adjust for 0 based array
                        if (node != null && node.id !== ourNodeData.node.id) {
                            nodesToSendTo[node.id] = node;
                        }
                    }
                    const cacheAppDataToSend = {
                        dataID,
                        appData,
                        cycle,
                    };
                    const message = { topic, cachedAppData: cacheAppDataToSend };
                    for (const [accountID, node] of Object.entries(nodesToSendTo)) {
                        const keyPair = accountID + key;
                        if (node != null && doOnceNodeAccPair.has(keyPair) === false) {
                            doOnceNodeAccPair.add(keyPair);
                            correspondingAccNodes.push(node);
                        }
                    }
                    /* prettier-ignore */ if (logger_1.logFlags.playback && logger_1.logFlags.verbose)
                        this.logger.playbackLogNote('sendCorrespondingCachedAppData', dataID, `cachedAppData: sendCorrespondingCachedAppData nodesToSendTo:${Object.keys(nodesToSendTo).length} doOnceNodeAccPair:${doOnceNodeAccPair.size} indices:${lib_types_1.Utils.safeStringify(indices)} edgeIndicies:${lib_types_1.Utils.safeStringify(edgeIndices)} patchIndicies:${lib_types_1.Utils.safeStringify(patchIndices)}  doOnceNodeAccPair: ${lib_types_1.Utils.safeStringify([...doOnceNodeAccPair.keys(),])} ourLocalConsensusIndex:${ourLocalConsensusIndex} ourSendingGroupSize:${ourSendingGroupSize} targetEdgeGroupSize:${targetEdgeGroupSize} targetEdgeGroupSize:${targetEdgeGroupSize} patchedListSize:${patchedListSize}`);
                    /* prettier-ignore */ if (logger_1.logFlags.shardedCache)
                        console.log('sendCorrespondingCachedAppData', dataID, `cachedAppData: sendCorrespondingCachedAppData nodesToSendTo:${Object.keys(nodesToSendTo).length} doOnceNodeAccPair:${doOnceNodeAccPair.size} indices:${lib_types_1.Utils.safeStringify(indices)} edgeIndicies:${lib_types_1.Utils.safeStringify(edgeIndices)} patchIndicies:${lib_types_1.Utils.safeStringify(patchIndices)}  doOnceNodeAccPair: ${lib_types_1.Utils.safeStringify([...doOnceNodeAccPair.keys(),])} ourLocalConsensusIndex:${ourLocalConsensusIndex} ourSendingGroupSize:${ourSendingGroupSize} targetEdgeGroupSize:${targetEdgeGroupSize} targetEdgeGroupSize:${targetEdgeGroupSize} patchedListSize:${patchedListSize}`);
                    if (correspondingAccNodes.length > 0) {
                        const remoteRelation = shardFunctions_1.default.getNodeRelation(remoteHomeNode, this.stateManager.currentCycleShardData.ourNode.id);
                        const localRelation = shardFunctions_1.default.getNodeRelation(localHomeNode, this.stateManager.currentCycleShardData.ourNode.id);
                        /* prettier-ignore */
                        if (logger_1.logFlags.playback)
                            this.logger.playbackLogNote("shrd_sendCorrespondingCachedAppData", `${dataID}`, `cachedAppData: remoteRel: ${remoteRelation} localRel: ${localRelation} qId: ${dataID} AccountBeingShared: ${utils.makeShortHash(key)} EdgeNodes:${utils.stringifyReduce(edgeNodeIds)} ConsensusNodes${utils.stringifyReduce(consensusNodeIds)}`);
                        /* prettier-ignore */ if (logger_1.logFlags.shardedCache)
                            console.log("shrd_sendCorrespondingCachedAppData", `${dataID}`, `cachedAppData: remoteRel: ${remoteRelation} localRel: ${localRelation} qId: ${dataID} AccountBeingShared: ${utils.makeShortHash(key)} EdgeNodes:${utils.stringifyReduce(edgeNodeIds)} ConsensusNodes${utils.stringifyReduce(consensusNodeIds)}`);
                        // Filter nodes before we send tell()
                        const filteredNodes = this.stateManager.filterValidNodesForInternalMessage(correspondingAccNodes, 'tellCorrespondingNodes', true, true);
                        if (filteredNodes.length === 0) {
                            /* prettier-ignore */
                            if (logger_1.logFlags.error)
                                this.mainLogger.error("cachedAppData: tellCorrespondingNodes: filterValidNodesForInternalMessage no valid nodes left to try");
                            /* prettier-ignore */ if (logger_1.logFlags.shardedCache)
                                console.log("cachedAppData: tellCorrespondingNodes: filterValidNodesForInternalMessage no valid nodes left to try");
                            return null;
                        }
                        const filteredCorrespondingAccNodes = filteredNodes;
                        // if (this.config.p2p.useBinarySerializedEndpoints && this.config.p2p.sendCachedAppDataBinary) {
                        const sendCacheAppDataReq = {
                            topic,
                            txId,
                            executionShardKey: queueEntry.executionShardKey,
                            cachedAppData: {
                                dataID: message.cachedAppData.dataID,
                                appData: message.cachedAppData.appData,
                                cycle: message.cachedAppData.cycle,
                            },
                        };
                        this.p2p.tellBinary(filteredCorrespondingAccNodes, InternalRouteEnum_1.InternalRouteEnum.binary_send_cachedAppData, sendCacheAppDataReq, SendCachedAppDataReq_1.serializeSendCachedAppDataReq, {});
                        // return
                        // }
                        // this.p2p.tell(filteredCorrespondingAccNodes, 'send_cachedAppData', message)
                    }
                }
            }
        }
    }
    async getLocalOrRemoteCachedAppData(topic, dataId) {
        let cachedAppData = null;
        if (this.stateManager.currentCycleShardData == null) {
            await this.stateManager.waitForShardData();
        }
        if (this.stateManager.currentCycleShardData == null) {
            throw new Error('getLocalOrRemoteCachedAppData: network not ready');
        }
        const address = dataId;
        let forceLocalGlobalLookup = false;
        if (this.stateManager.accountGlobals.isGlobalAccount(address)) {
            forceLocalGlobalLookup = true;
        }
        let accountIsRemote = this.stateManager.transactionQueue.isAccountRemote(address);
        if (this.stateManager.currentCycleShardData.nodes.length <=
            this.stateManager.currentCycleShardData.shardGlobals.consensusRadius) {
            accountIsRemote = false;
        }
        if (forceLocalGlobalLookup) {
            accountIsRemote = false;
        }
        if (accountIsRemote) {
            const validNodeRetries = 5;
            let randomConsensusNode = null;
            for (let i = 0; i < validNodeRetries; i++) {
                const nodeCheck = this.stateManager.transactionQueue.getRandomConsensusNodeForAccount(address);
                if (nodeCheck == null) {
                    throw new Error('getLocalOrRemoteAccount: no consensus node found');
                }
                // Node Precheck!
                /* prettier-ignore */
                if (this.stateManager.isNodeValidForInternalMessage(nodeCheck.id, 'getLocalOrRemoteCachedAppData', true, true, true) === true) {
                    //this node is valid for use
                    randomConsensusNode = nodeCheck;
                    break;
                }
            }
            if (randomConsensusNode == null) {
                //we did not find a valid node
                /* prettier-ignore */ if (logger_1.logFlags.verbose)
                    this.stateManager.getAccountFailDump(address, "getLocalOrRemoteCachedAppData: isNodeValidForInternalMessage failed, no retry");
                nestedCounters_1.nestedCountersInstance.countEvent('cached-app-data', 'No valid node found to ask');
                return null;
            }
            const message = { topic, dataId };
            let r;
            try {
                // if (this.config.p2p.useBinarySerializedEndpoints && this.config.p2p.getCachedAppDataBinary) {
                const resp = await this.p2p.askBinary(randomConsensusNode, InternalRouteEnum_1.InternalRouteEnum.binary_get_cached_app_data, message, GetCachedAppDataReq_1.serializeGetCachedAppDataReq, GetCachedAppDataResp_1.deserializeGetCachedAppDataResp, {});
                r = resp?.cachedAppData ?? false;
                // } else {
                //   r = await this.p2p.ask(randomConsensusNode, 'get_cached_app_data', message)
                // }
            }
            catch (e) {
                if (logger_1.logFlags.error)
                    this.mainLogger.error(`cachedAppData: ASK exception getLocalOrRemoteCachedAppData`, e);
                nestedCounters_1.nestedCountersInstance.countEvent('cached-app-data', 'ask exception');
                return null;
            }
            if (r === false) {
                if (logger_1.logFlags.error)
                    this.mainLogger.error(`cachedAppData: ASK FAIL getLocalOrRemoteCachedAppData r === false`);
                nestedCounters_1.nestedCountersInstance.countEvent('cached-app-data', 'result is false');
                return null;
            }
            const result = r;
            if (result != null && result.appData != null) {
                nestedCounters_1.nestedCountersInstance.countEvent('cached-app-data', 'Remote Data: hit');
                return result;
            }
            else {
                nestedCounters_1.nestedCountersInstance.countEvent('cached-app-data', 'Remote Data: miss');
                if (logger_1.logFlags.verbose)
                    this.stateManager.getAccountFailDump(address, 'remote request missing data: result == null');
                /* prettier-ignore */ if (logger_1.logFlags.shardedCache)
                    console.log(`cachedAppData: remote result failed: ${lib_types_1.Utils.safeStringify(r)}`); //todo dont check in
            }
        }
        else {
            // we are local!
            cachedAppData = this.getCachedItem(topic, dataId);
            if (cachedAppData != null) {
                nestedCounters_1.nestedCountersInstance.countEvent('cached-app-data', 'local data hit');
                return cachedAppData;
            }
            else {
                nestedCounters_1.nestedCountersInstance.countEvent('cached-app-data', 'local data miss');
            }
        }
        return null;
    }
}
exports.default = CachedAppDataManager;
//# sourceMappingURL=CachedAppDataManager.js.map