"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;
    };
})();
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.SyncWorker = void 0;
const worker = __importStar(require("worker_threads"));
const crypto_rpc_1 = require("@bitpay-labs/crypto-rpc");
const logger_1 = __importDefault(require("../../../../logger"));
const config_1 = require("../../../../services/config");
const storage_1 = require("../../../../services/storage");
const utils_1 = require("../../../../utils");
const block_1 = require("../models/block");
const transaction_1 = require("../models/transaction");
const rpcs_1 = require("./rpcs");
class SyncWorker {
    constructor() {
        this.chain = worker.workerData.chain;
        this.network = worker.workerData.network;
        this.parentPort = worker.parentPort;
        this.txModel = transaction_1.EVMTransactionStorage;
        this.stopping = false;
        this.isWorking = false;
        this.chainConfig = config_1.Config.get().chains[this.chain][this.network];
    }
    async start() {
        await this.connect();
        await storage_1.Storage.start();
        this.parentPort.on('message', this.messageHandler.bind(this));
    }
    async stop() {
        this.stopping = true;
        logger_1.default.info('Stopping syncing thread ' + worker.threadId);
        while (this.isWorking) {
            await (0, utils_1.wait)(1000);
        }
        await storage_1.Storage.stop();
        await this.web3?.currentProvider?.disconnect();
        process.exit(0);
    }
    async messageHandler(msg) {
        switch (msg.message) {
            case 'shutdown':
                this.stop();
                return;
            default:
                this.syncBlock(msg);
                return;
        }
    }
    async syncBlock({ blockNum }) {
        try {
            if (this.stopping) {
                return;
            }
            this.isWorking = true;
            const block = await this.rpc.getBlock(blockNum);
            if (!block) {
                worker.parentPort.postMessage({ message: 'sync', notFound: true, blockNum, threadId: worker.threadId });
                return;
            }
            const { convertedBlock, convertedTxs } = await this.convertBlock(block);
            await this.processBlock(convertedBlock, convertedTxs);
            worker.parentPort.postMessage({
                message: 'sync',
                notFound: !block,
                blockNum: Number(block.number),
                threadId: worker.threadId
            });
        }
        catch (err) {
            logger_1.default.debug(`Syncing thread ${worker.threadId} error: ${err.stack}`);
            let error = err.message;
            if (error === 'Invalid JSON RPC response: ""') {
                error = null;
            }
            if (error.includes('connect')) {
                error = null;
                logger_1.default.info(`Syncing thread ${worker.threadId} lost connection to the node. Reconnecting.`);
                await this.connect();
            }
            worker.parentPort.postMessage({ message: 'sync', notFound: true, blockNum, threadId: worker.threadId, error });
        }
        finally {
            this.isWorking = false;
        }
    }
    async getClient() {
        const nodeVersion = await this.web3.eth.getNodeInfo();
        const client = nodeVersion.split('/')[0].toLowerCase();
        if (client !== 'erigon' && client !== 'geth') {
            // assume it's a geth fork, or at least more like geth.
            // this is helpful when using a dev solution like ganache.
            return 'geth';
        }
        return client;
    }
    async connect() {
        const providerIdx = worker.threadId % (this.chainConfig.providers || []).length;
        const providerConfig = this.chainConfig.provider || this.chainConfig.providers[providerIdx];
        const rpcConfig = { ...providerConfig, chain: this.chain, currencyConfig: {} };
        this.web3 = new crypto_rpc_1.CryptoRpc(rpcConfig).get(this.chain).web3;
        this.client = await this.getClient();
        this.rpc = new rpcs_1.Rpcs[this.client](this.web3);
        return { web3: this.web3, rpc: this.rpc };
    }
    async processBlock(block, transactions) {
        await block_1.EVMBlockStorage.addBlock({
            chain: this.chain,
            network: this.network,
            forkHeight: this.chainConfig.forkHeight,
            parentChain: this.chainConfig.parentChain,
            initialSyncComplete: false,
            block,
            transactions
        });
    }
    getBlockReward(block) {
        block;
        return 0;
    }
    async convertBlock(block) {
        const blockTime = Number(block.timestamp) * 1000;
        const hash = block.hash;
        const height = block.number;
        const reward = this.getBlockReward(block);
        const convertedBlock = {
            chain: this.chain,
            network: this.network,
            height: Number(height),
            hash,
            coinbase: Buffer.from(block.miner),
            merkleRoot: Buffer.from(block.transactionsRoot),
            time: new Date(blockTime),
            timeNormalized: new Date(blockTime),
            nonce: Buffer.from(block.extraData),
            previousBlockHash: block.parentHash,
            difficulty: Number(block.difficulty).toString(),
            totalDifficulty: block.totalDifficulty != undefined ? Number(block.totalDifficulty).toString() : undefined,
            nextBlockHash: '',
            transactionCount: block.transactions.length,
            size: Number(block.size),
            reward,
            logsBloom: Buffer.from(block.logsBloom),
            sha3Uncles: Buffer.from(block.sha3Uncles),
            receiptsRoot: Buffer.from(block.receiptsRoot),
            processed: false,
            gasLimit: Number(block.gasLimit),
            gasUsed: Number(block.gasUsed),
            stateRoot: Buffer.from(block.stateRoot)
        };
        const convertedTxs = block.transactions.map(t => this.txModel.convertRawTx(this.chain, this.network, t, convertedBlock));
        const traceTxs = await this.rpc.getTransactionsFromBlock(convertedBlock.height);
        this.rpc.reconcileTraces(convertedBlock, convertedTxs, traceTxs);
        this.txModel.addEffectsToTxs(convertedTxs);
        return { convertedBlock, convertedTxs };
    }
}
exports.SyncWorker = SyncWorker;
worker.parentPort.once('message', async function (msg) {
    if (msg.message !== 'start') {
        throw new Error('Unknown startup message');
    }
    await new SyncWorker().start();
    return worker.parentPort.postMessage({ message: 'ready' });
});
//# sourceMappingURL=syncWorker.js.map