"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 });
exports.startWitnessMode = exports.startSnapshotting = exports.initSafetyModeVals = exports.getSummaryBlob = exports.getReceiptMap = exports.getSummaryHashes = exports.getReceiptHashes = exports.getStateHashes = exports.setOldDataPath = exports.initLogger = exports.snapshotLogger = exports.lastSnapshotCycle = exports.safetyModeVals = exports.oldDataPath = void 0;
const http = __importStar(require("../http"));
const logger_1 = require("../logger");
const Active = __importStar(require("../p2p/Active"));
const Comms = __importStar(require("../p2p/Comms"));
const Context = __importStar(require("../p2p/Context"));
const CycleChain = __importStar(require("../p2p/CycleChain"));
const NodeList = __importStar(require("../p2p/NodeList"));
const Self = __importStar(require("../p2p/Self"));
const shardFunctions_1 = __importDefault(require("../state-manager/shardFunctions"));
const lib_types_1 = require("@shardeum-foundation/lib-types");
const utils = __importStar(require("../utils"));
const profiler_1 = require("../utils/profiler");
const partitionGossip = __importStar(require("./partition-gossip"));
const SnapshotFunctions = __importStar(require("./snapshotFunctions"));
const Sync_1 = require("../p2p/Sync");
const lib_types_2 = require("@shardeum-foundation/lib-types");
const promises_1 = require("../utils/functions/promises");
console.log('StateManager', lib_types_1.StateManager);
console.log('StateManager type', lib_types_1.StateManager.StateManagerTypes);
let disableSummarySnapshot = true; //with 4096 stats regions the snapshot module grinds to a halt.  This is a temporary hack to stop
let oldDataMap = new Map();
const dataToMigrate = new Map();
const oldPartitionHashMap = new Map();
let missingPartitions = [];
const notNeededRepliedNodes = new Map();
const alreadyOfferedNodes = new Map();
let stateHashesByCycle = new Map();
let receiptHashesByCycle = new Map();
let summaryHashesByCycle = new Map();
const partitionBlockMapByCycle = new Map();
const statesClumpMapByCycle = new Map();
let safetySyncing = false; // to set true when data exchange occurs during safetySync
exports.safetyModeVals = {
    safetyMode: false,
    safetyNum: 0,
    networkStateHash: '',
};
exports.lastSnapshotCycle = 0;
/** FUNCTIONS */
function initLogger() {
    exports.snapshotLogger = Context.logger.getLogger('snapshot');
}
exports.initLogger = initLogger;
function setOldDataPath(path) {
    exports.oldDataPath = path;
    log('set old-data-path', exports.oldDataPath);
}
exports.setOldDataPath = setOldDataPath;
function getStateHashes(start = 0, end) {
    const collector = [];
    for (const [key] of stateHashesByCycle) {
        if (key >= start) {
            // check against end cycle only if it's provided
            collector.push(stateHashesByCycle.get(key));
        }
    }
    return collector;
}
exports.getStateHashes = getStateHashes;
function getReceiptHashes(start = 0, end) {
    const collector = [];
    for (const [key] of receiptHashesByCycle) {
        if (key >= start) {
            // check against end cycle only if it's provided
            collector.push(receiptHashesByCycle.get(key));
        }
    }
    return collector;
}
exports.getReceiptHashes = getReceiptHashes;
function getSummaryHashes(start = 0, end) {
    const collector = [];
    for (const [key] of summaryHashesByCycle) {
        if (key >= start) {
            // check against end cycle only if it's provided
            collector.push(summaryHashesByCycle.get(key));
        }
    }
    return collector;
}
exports.getSummaryHashes = getSummaryHashes;
function getReceiptMap(start = 0, end) {
    const collector = {};
    for (const [key] of partitionBlockMapByCycle) {
        if (key >= start) {
            // check against end cycle only if it's provided
            collector[key] = partitionBlockMapByCycle.get(key);
        }
    }
    return collector;
}
exports.getReceiptMap = getReceiptMap;
function getSummaryBlob(start = 0, end) {
    const collector = {};
    for (const [key] of statesClumpMapByCycle) {
        if (key >= start) {
            // check against end cycle only if it's provided
            collector[key] = statesClumpMapByCycle.get(key);
        }
    }
    return collector;
}
exports.getSummaryBlob = getSummaryBlob;
function hashPartitionBlocks(partitionId, partitionBlocks) {
    const partitionBlock = partitionBlocks.find((b) => b.partition === partitionId);
    return Context.crypto.hash(partitionBlock || {});
}
async function initSafetyModeVals() {
    const oldCycleRecord = await SnapshotFunctions.readOldCycleRecord();
    const oldNetworkHash = await SnapshotFunctions.readOldNetworkHash();
    const oldPartitionHashes = await SnapshotFunctions.readOldPartitionHashes();
    // Turn safety node on
    exports.safetyModeVals.safetyMode = true;
    // Set the safetyNum to the number of active nodes in the last cycle saved in the old data
    if (oldCycleRecord) {
        exports.safetyModeVals.safetyNum = oldCycleRecord.active;
        exports.lastSnapshotCycle = oldCycleRecord.counter - 1;
    }
    // Set networkStateHash to the last network state hash saved in the old data
    if (oldNetworkHash)
        exports.safetyModeVals.networkStateHash = oldNetworkHash.hash;
    for (const row of oldPartitionHashes) {
        oldPartitionHashMap.set(parseInt(row.partitionId), row.hash);
    }
}
exports.initSafetyModeVals = initSafetyModeVals;
function startSnapshotting() {
    partitionGossip.initGossip();
    Context.stateManager.eventEmitter.on('cycleTxsFinalized', async (shard, receiptMapResults, statsClump, mainHashResults //unused, accounts are queried manually below.
    ) => {
        try {
            profiler_1.profilerInstance.profileSectionStart('snapshot');
            const debugStrs = [];
            // store receiptMap for this cycle number
            partitionBlockMapByCycle.set(shard.cycleNumber, receiptMapResults);
            // store summary blob map for this cycle
            const statsClumpForThisCycle = statsClump;
            statesClumpMapByCycle.set(shard.cycleNumber, statsClumpForThisCycle);
            // create our own partition hashes for that cycle number
            const partitionRanges = getPartitionRanges(shard);
            const partitionHashes = new Map();
            for (const partition of shard.ourStoredPartitions) {
                const range = partitionRanges.get(partition);
                if (range) {
                    const accountsInPartition = await Context.storage.getAccountCopiesByCycleAndRange(shard.cycleNumber, range.low, range.high);
                    if (logger_1.logFlags.debug)
                        exports.snapshotLogger.debug('Accounts in partition: ', partition, accountsInPartition);
                    const hash = Context.crypto.hash(accountsInPartition);
                    partitionHashes.set(partition, hash);
                    if (logger_1.logFlags.debug) {
                        try {
                            //DBG: Print all accounts in mem
                            const wrappedAccts = await Context.stateManager.app.getAccountData(range.low, range.high, 10000000);
                            debugStrs.push(`  PARTITION ${partition}\n` +
                                wrappedAccts
                                    .map((acct) => {
                                    const id = acct.accountId === null ? 'null' : acct.accountId.substr(0, 8);
                                    const hash = acct.stateId === null ? 'null' : acct.stateId.substr(0, 8);
                                    return `    ID: ${id} HASH: ${hash}`;
                                })
                                    .join('\n'));
                        }
                        catch (e) {
                            console.log(e);
                        }
                    }
                }
            }
            try {
                if (logger_1.logFlags.debug)
                    exports.snapshotLogger.debug(`
        MEM ACCOUNTS C${shard.cycleNumber}:
        ${debugStrs.join('\n')}
        `);
            }
            catch (e) {
                console.log(e);
            }
            const globalAccounts = await Context.storage.getGlobalAccountCopies(shard.cycleNumber);
            const globalAccountHash = Context.crypto.hash(globalAccounts);
            // partition hash for globalAccounts
            partitionHashes.set(-1, globalAccountHash);
            if (logger_1.logFlags.debug)
                exports.snapshotLogger.debug('Global Accounts: ', globalAccounts);
            if (logger_1.logFlags.debug)
                exports.snapshotLogger.debug('Partition Hashes: ', partitionHashes);
            // process gossip from the queue for that cycle number
            const collector = partitionGossip.newCollector(shard);
            // registering event handler for 'gotAllHashes'
            collector.once('gotAllHashes', (allHashes) => {
                const { partitionHashes, receiptHashes, summaryHashes } = allHashes;
                // create a network state hash once we have all partition hashes for that cycle number
                const networkStateHash = SnapshotFunctions.createNetworkHash(partitionHashes);
                const networkReceiptMapHash = SnapshotFunctions.createNetworkHash(receiptHashes);
                const networkSummaryHash = SnapshotFunctions.createNetworkHash(summaryHashes);
                // log('Got all partition hashes: ', partitionHashes)
                // log('Got all receipt hashes: ', receiptHashes)
                // log('Got all summary hashes: ', summaryHashes)
                exports.safetyModeVals.networkStateHash = networkStateHash;
                // save the partition and network hashes for that cycle number to the DB
                (0, promises_1.fireAndForget)(() => SnapshotFunctions.savePartitionAndNetworkHashes(shard, partitionHashes, networkStateHash));
                (0, promises_1.fireAndForget)(() => SnapshotFunctions.saveReceiptAndNetworkHashes(shard, receiptHashes, networkReceiptMapHash));
                (0, promises_1.fireAndForget)(() => SnapshotFunctions.saveSummaryAndNetworkHashes(shard, summaryHashes, networkSummaryHash));
                // Update stateHashes by Cycle map
                const newStateHash = {
                    counter: shard.cycleNumber,
                    partitionHashes,
                    networkHash: networkStateHash,
                };
                stateHashesByCycle = SnapshotFunctions.updateStateHashesByCycleMap(shard.cycleNumber, newStateHash, stateHashesByCycle);
                // Update receiptHashes by Cycle map
                const newReceiptHash = {
                    counter: shard.cycleNumber,
                    receiptMapHashes: receiptHashes,
                    networkReceiptHash: networkReceiptMapHash,
                };
                receiptHashesByCycle = SnapshotFunctions.updateReceiptHashesByCycleMap(shard.cycleNumber, newReceiptHash, receiptHashesByCycle);
                // Update summaryHashes by Cycle map
                const newSummaryHash = {
                    counter: shard.cycleNumber,
                    summaryHashes: summaryHashes,
                    networkSummaryHash: networkSummaryHash,
                };
                summaryHashesByCycle = SnapshotFunctions.updateSummaryHashesByCycleMap(shard.cycleNumber, newSummaryHash, summaryHashesByCycle);
                // clean up gossip and collector for that cycle number
                partitionGossip.clean(shard.cycleNumber);
                log(`Network State Hash for cycle ${shard.cycleNumber}`, networkStateHash);
                log(`Network Receipt Hash for cycle ${shard.cycleNumber}`, networkReceiptMapHash);
                log(`Network Summary Hash for cycle ${shard.cycleNumber}`, networkSummaryHash);
            });
            partitionGossip.processMessagesInGossipQueue(shard, collector);
            // gossip our partitition hashes and receipt map to the rest of the network with that cycle number
            const message = {
                cycle: shard.cycleNumber,
                data: {
                    partitionHash: {},
                    receiptMapHash: {},
                    summaryHash: {},
                },
                sender: Self.id,
            };
            // attach partition and receipt hashes to the message to be gossiped
            for (const [partitionId, hash] of partitionHashes) {
                message.data.partitionHash[partitionId] = hash; //this could be a hash over hash( list of trie chunks  )
                message.data.receiptMapHash[partitionId] = hashPartitionBlocks(partitionId, partitionBlockMapByCycle.get(shard.cycleNumber));
            }
            if (disableSummarySnapshot === true) {
                //no summary hash info shared
            }
            else {
                // attach summary hashes to the message to be gossiped
                const summaryDataStatHash = {};
                const summarytxStatsHash = {};
                for (const blob of statsClumpForThisCycle.dataStats) {
                    summaryDataStatHash[blob.partition] = blob.opaqueBlob;
                }
                for (const blob of statsClumpForThisCycle.txStats) {
                    summarytxStatsHash[blob.partition] = blob.opaqueBlob;
                }
                for (const partition of statsClumpForThisCycle.covered) {
                    const summaryObj = {
                        dataStats: summaryDataStatHash[partition] ? summaryDataStatHash[partition] : {},
                        txStats: summarytxStatsHash[partition] ? summarytxStatsHash[partition] : {},
                    };
                    message.data.summaryHash[partition] = Context.crypto.hash(summaryObj);
                    if (logger_1.logFlags.console)
                        console.log(`Summary Obj for partition ${partition}`, summaryObj);
                    if (summaryObj) {
                        if (logger_1.logFlags.console)
                            console.log('summaryObj', summaryObj);
                        if (logger_1.logFlags.console)
                            console.log('summaryObj stringified', lib_types_2.Utils.safeStringify(summaryObj));
                        if (logger_1.logFlags.console)
                            console.log('summaryObj hash', Context.crypto.hash(summaryObj));
                    }
                }
            }
            const signedMessage = Context.crypto.sign(message);
            (0, promises_1.fireAndForget)(() => Comms.sendGossip('snapshot_gossip', signedMessage, '', null, NodeList.byIdOrder, true));
            partitionGossip.forwardedGossips.set(message.sender, true);
            collector.process([message]);
            // Clean partition gossip that's older than 10 cycles
            partitionGossip.cleanOld(shard.cycleNumber, 10);
        }
        catch (e) {
            console.log(e);
        }
        finally {
            profiler_1.profilerInstance.profileSectionEnd('snapshot');
        }
    });
}
exports.startSnapshotting = startSnapshotting;
function createOffer() {
    const partitionsToOffer = [];
    for (const [partitionId] of oldDataMap) {
        partitionsToOffer.push(partitionId);
    }
    return {
        networkStateHash: exports.safetyModeVals.networkStateHash,
        partitions: partitionsToOffer,
        downloadUrl: `http://${Self.ip}:${Self.port}/download-snapshot-data`,
    };
}
function getNodesThatCoverPartition(partitionId, shardGlobals, nodeShardDataMap) {
    const nodesInPartition = [];
    if (partitionId === -1) {
        nodeShardDataMap.forEach((data, nodeId) => {
            if (nodeId === Self.id)
                return;
            const node = data.node;
            nodesInPartition.push(node);
        });
    }
    else {
        nodeShardDataMap.forEach((data, nodeId) => {
            if (nodeId === Self.id)
                return;
            const node = data.node;
            const homePartition = data.homePartition;
            const { partitionStart, partitionEnd } = shardFunctions_1.default.calculateStoredPartitions2(shardGlobals, homePartition);
            if (partitionId >= partitionStart && partitionId <= partitionEnd) {
                nodesInPartition.push(node);
            }
        });
    }
    return nodesInPartition;
}
function getPartitionRanges(shard) {
    const partitionRanges = new Map();
    for (const partition of shard.ourStoredPartitions) {
        partitionRanges.set(partition, shardFunctions_1.default.partitionToAddressRange2(shard.shardGlobals, partition));
    }
    return partitionRanges;
}
async function goActiveIfDataComplete() {
    log('Missing partitions: ', missingPartitions);
    if (missingPartitions.length === 0) {
        log('We have complete data. Ready to go active');
        // store account data to new database
        storeDataToNewDB(dataToMigrate);
        // Start state-manager (skip syncing)
        Context.stateManager.accountSync.skipSync();
        // Go active
        Active.requestActive();
        // Set state-manager to skip data syncing
        await Context.stateManager.accountSync.initialSyncMain(3);
        await Context.stateManager.waitForShardCalcs();
        Context.stateManager.appFinishedSyncing = true;
        Context.stateManager.appFinishedSyncing = true;
        Context.stateManager.startProcessingCycleSummaries();
        log('Syncing flags are set true after network recovery.');
    }
}
async function startWitnessMode() {
    log('Starting in witness mode...');
    const archiver = Context.config.p2p.existingArchivers[0];
    /**
     * [TODO] [AS] Instead of an interval, make this get the latest cycle,
     * calculate timstamp of the next one, and schedule a callback to run at that
     * time (like how CycleCreator works). That should prove more robust.
     */
    const witnessInterval = setInterval(async () => {
        try {
            const fullNodesSigned = await Self.getFullNodesFromArchiver();
            if (!Context.crypto.verify(fullNodesSigned, archiver.publicKey)) {
                throw Error('Fatal: Full Node list was not signed by archiver!');
            }
            const nodeList = fullNodesSigned.nodeList;
            const newestCycle = await (0, Sync_1.getNewestCycle)(nodeList);
            const oldNetworkHash = await SnapshotFunctions.readOldNetworkHash();
            if (newestCycle.safetyMode === false || notNeededRepliedNodes.size >= nodeList.length) {
                log('Num of not_needed replied nodes => ', notNeededRepliedNodes.size);
                log('Node will exit witness mode and shutdown.');
                clearInterval(witnessInterval);
                await Context.shardus.shutdown();
                return;
            }
            if (newestCycle.safetyMode && newestCycle.networkStateHash === oldNetworkHash.hash) {
                log('Network is in safety mode and our network state hashes matches with newest cycle record');
                // caculate which partitions data this node hold
                const shardGlobals = shardFunctions_1.default.calculateShardGlobals(newestCycle.safetyNum, Context.config.sharding.nodesPerConsensusGroup, Context.config.sharding.nodesPerConsensusGroup);
                const nodeShardDataMap = new Map();
                oldDataMap = await SnapshotFunctions.calculateOldDataMap(shardGlobals, nodeShardDataMap, oldPartitionHashMap, exports.lastSnapshotCycle);
                SnapshotFunctions.registerDownloadRoutes(Context.network, oldDataMap, oldPartitionHashMap);
                const offer = createOffer();
                // send offer to each syncing + active nodes unless data is already offered
                for (let i = 0; i < nodeList.length; i++) {
                    const node = nodeList[i];
                    const ip = ('ip' in node && node.ip) || node.externalIp;
                    const port = ('port' in node && node.port) || node.externalIp;
                    if (!alreadyOfferedNodes.has(node.id)) {
                        try {
                            log(`Sending witness offer to new node ${ip}:${port}`);
                            (0, promises_1.fireAndForget)(() => sendOfferToNode(node, offer));
                            alreadyOfferedNodes.set(node.id, true);
                        }
                        catch (e) {
                            log('ERROR: ', e);
                        }
                    }
                }
            }
        }
        catch (e) {
            log('ERROR: ', e);
        }
    }, Context.config.p2p.cycleDuration * 1000);
}
exports.startWitnessMode = startWitnessMode;
async function sendOfferToNode(node, offer, isSuggestedByNetwork = false) {
    let answer;
    let res;
    try {
        res = await http.post(`${node.ip}:${node.port}/snapshot-witness-data`, offer);
        answer = res.answer;
    }
    catch (e) {
        log('ERROR: unable to send offer to node');
        log('ERROR: ', e);
    }
    log(`Offer response from ${node.ip}:${node.port} is => `, answer);
    // If a node reply us as 'needed', send requested data for requested partitions
    if (answer === lib_types_1.P2P.SnapshotTypes.offerResponse.needed) {
        const requestedPartitions = res.partitions;
        const dataToSend = {};
        for (const partitionId of requestedPartitions) {
            dataToSend[partitionId] = {
                data: oldDataMap.get(partitionId),
                hash: oldPartitionHashMap.get(parseInt(partitionId)),
            };
        }
        await http.post(`${node.ip}:${node.port}/snapshot-data`, dataToSend).catch((e) => {
            log('ERROR: unable to send snapshot data to node');
            log('ERROR: ', e);
        });
        // If a node reply us 'try_later', wait X amount of time provided by node (OR) cycleDuration/2
    }
    else if (answer === lib_types_1.P2P.SnapshotTypes.offerResponse.tryLater) {
        const waitTime = res.waitTime || 5 * Context.config.p2p.cycleDuration * 1000;
        setTimeout(() => {
            log(`Trying again to send to ${node.ip}:${node.port} after waiting ${waitTime} ms`);
            (0, promises_1.fireAndForget)(() => sendOfferToNode(node, offer, isSuggestedByNetwork));
        }, waitTime);
        // If a node reply us 'send_to', send data to those provided nodes
    }
    else if (answer === lib_types_1.P2P.SnapshotTypes.offerResponse.sendTo) {
        if (isSuggestedByNetwork) {
            // this node is suggesed by other nodes in the network, so send_to response is ignored to prevent infinite loop
            return;
        }
        const suggestedNodes = res.nodes;
        if (suggestedNodes && suggestedNodes.length > 0) {
            for (let i = 0; i < suggestedNodes.length; i++) {
                const suggestedNode = suggestedNodes[i];
                if (!alreadyOfferedNodes.has(suggestedNode.id)) {
                    log(`Sending offer to suggested Node ${node.ip}:${node.port}`);
                    (0, promises_1.fireAndForget)(() => sendOfferToNode(suggestedNode, offer, true));
                }
            }
        }
    }
    else if (answer === lib_types_1.P2P.SnapshotTypes.offerResponse.notNeeded) {
        increaseNotNeededNodes(node.id);
    }
}
function increaseNotNeededNodes(id) {
    notNeededRepliedNodes.set(id, true);
}
async function storeDataToNewDB(dataMap) {
    const currentCycle = CycleChain.getNewest();
    // store data to new DB
    log('Storing data to new DB');
    const accountCopies = [];
    for (const [, data] of dataMap) {
        if (data && data.length > 0) {
            data.forEach((accountData) => {
                accountCopies.push({
                    ...accountData,
                    cycleNumber: currentCycle.counter,
                });
            });
        }
    }
    await Context.stateManager._commitAccountCopies(accountCopies);
}
function processDownloadedMissingData(missingData) {
    if (missingPartitions.length === 0)
        return; // we don't have missing data anymore
    log('Processing downloaded data');
    for (const partitionId in missingData) {
        // check and store offered data
        const partitionData = missingData[partitionId];
        const accountsInPartition = partitionData.data.map((acc) => {
            return {
                accountId: acc.accountId,
                data: typeof acc.data === 'object' ? lib_types_2.Utils.safeStringify(acc.data) : acc.data,
                timestamp: acc.timestamp,
                hash: acc.hash,
                isGlobal: acc.isGlobal,
            };
        });
        const computedHash = Context.crypto.hash(accountsInPartition);
        log(`Received data for partition ${partitionId} => `, accountsInPartition);
        log(computedHash, partitionData.hash);
        if (computedHash === partitionData.hash) {
            log(`Computed hash and received hash matches for partition ${partitionId}`);
            // store into dataToMigrate temporarily. Will use it to store data to new DB
            if (!dataToMigrate.has(parseInt(partitionId))) {
                dataToMigrate.set(parseInt(partitionId), partitionData.data);
                // remove partition id from missing partition list
                const index = missingPartitions.indexOf(parseInt(partitionId));
                missingPartitions.splice(index, 1);
                goActiveIfDataComplete();
            }
        }
        else {
            log(`Computed hash and received hash are DIFFERENT for ${partitionId}`);
        }
    }
}
function registerSnapshotRoutes() {
    const snapshotWitnessDataOfferRoute = {
        method: 'POST',
        name: 'snapshot-witness-data',
        handler: (req, res) => {
            const err = utils.validateTypes(req, { body: 'o' });
            if (err) {
                log('snapshot-witness-data bad req ' + err);
                res.json([]);
                return;
            }
            if (Self.isActive) {
                res.json({ answer: lib_types_1.P2P.SnapshotTypes.offerResponse.notNeeded });
                return;
            }
            const offerRequest = req.body;
            const neededPartitonIds = [];
            const neededHashes = [];
            if (!safetySyncing || !exports.safetyModeVals.networkStateHash) {
                if (!safetySyncing)
                    log('We are not doing data exchange yet. Try agian later');
                if (!exports.safetyModeVals.networkStateHash)
                    log('We have empty network state hash. Try agian later');
                res.json({
                    answer: lib_types_1.P2P.SnapshotTypes.offerResponse.tryLater,
                    waitTime: Context.config.p2p.cycleDuration * 1000 * 0.5,
                });
                return;
            }
            if (offerRequest.networkStateHash === exports.safetyModeVals.networkStateHash) {
                // ask witnessing node to try offering data later
                for (const partitionId of offerRequest.partitions) {
                    // request only the needed partitions
                    const isNeeded = missingPartitions.includes(partitionId);
                    if (isNeeded) {
                        neededPartitonIds.push(partitionId);
                        const hasHashForPartition = oldPartitionHashMap.has(partitionId);
                        // if node does not have partition_hash for needed partition, request it too.
                        if (!hasHashForPartition)
                            neededHashes.push(partitionId);
                    }
                }
                if (neededPartitonIds.length > 0) {
                    res.json({
                        answer: lib_types_1.P2P.SnapshotTypes.offerResponse.needed,
                        partitions: neededPartitonIds,
                        hashes: neededHashes,
                    });
                    return;
                }
            }
            res.json({ answer: lib_types_1.P2P.SnapshotTypes.offerResponse.notNeeded });
            return;
        },
    };
    const routes = [snapshotWitnessDataOfferRoute];
    for (const route of routes) {
        Context.network._registerExternal(route.method, route.name, route.handler);
    }
}
function log(...things) {
    if (logger_1.logFlags.console)
        console.log('DBG', 'SNAPSHOT', ...things);
}
//# sourceMappingURL=index.js.map