"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.EVMVerificationPeer = void 0;
const logger_1 = __importDefault(require("../../../../logger"));
const block_1 = require("../models/block");
const p2p_1 = require("./p2p");
class EVMVerificationPeer extends p2p_1.EVMP2pWorker {
    constructor() {
        super(...arguments);
        this.prevBlockNum = 0;
        this.prevHash = '';
        this.nextBlockHash = '';
        this.deepScan = false;
    }
    enableDeepScan() {
        this.deepScan = true;
    }
    disableDeepScan() {
        this.deepScan = false;
    }
    async setupListeners() {
        this.txSubscription = await this.web3.eth.subscribe('pendingTransactions');
        this.txSubscription.subscribe((_err, tx) => {
            this.events.emit('transaction', tx);
        });
        this.blockSubscription = await this.web3.eth.subscribe('newBlockHeaders');
        this.blockSubscription.subscribe((_err, block) => {
            this.events.emit('block', block);
        });
    }
    async resync(start, end) {
        const { chain, network } = this;
        let currentHeight = Math.max(1, start);
        while (currentHeight <= end) {
            let lastLog = Date.now();
            const block = await this.getBlock(currentHeight);
            const { convertedBlock, convertedTxs } = await this.convertBlock(block);
            const nextBlock = await block_1.EVMBlockStorage.collection.findOne({ chain, network, previousBlockHash: block.hash });
            if (nextBlock) {
                convertedBlock.nextBlockHash = nextBlock.hash;
            }
            await this.blockModel.processBlock({
                chain: this.chain,
                network: this.network,
                forkHeight: this.chainConfig.forkHeight,
                parentChain: this.chainConfig.parentChain,
                initialSyncComplete: this.initialSyncComplete,
                block: convertedBlock,
                transactions: convertedTxs
            });
            currentHeight++;
            if (Date.now() - lastLog > 100) {
                logger_1.default.info('Re-Sync: %o', {
                    chain,
                    network,
                    height: currentHeight
                });
                lastLog = Date.now();
            }
        }
    }
    async getBlockForNumber(blockNum) {
        return this.getBlock(blockNum);
    }
    async validateDataForBlock(blockNum, tipHeight, log = false) {
        let success = true;
        const { chain, network } = this;
        const atTipOfChain = blockNum === tipHeight;
        const errors = new Array();
        const [block, blockTxs] = await Promise.all([
            this.blockModel.collection.findOne({
                chain,
                network,
                height: blockNum,
                processed: true
            }),
            this.txModel.collection.find({ chain, network, blockHeight: blockNum }).toArray()
        ]);
        if (!block) {
            success = false;
            const error = {
                model: 'block',
                err: true,
                type: 'MISSING_BLOCK',
                payload: { blockNum }
            };
            errors.push(error);
            if (log) {
                console.log(JSON.stringify(error));
            }
            return { success, errors };
        }
        const blockTxids = blockTxs.map(t => t.txid);
        const firstHash = blockTxs[0] ? blockTxs[0].blockHash : block.hash;
        const [mempoolTxs, blocksForHash, blocksForHeight] = await Promise.all([
            this.txModel.collection.find({ chain, network, blockHeight: -1, txid: { $in: blockTxids } }).toArray(),
            this.blockModel.collection.countDocuments({ chain, network, hash: firstHash }),
            this.blockModel.collection.countDocuments({
                chain,
                network,
                height: blockNum,
                processed: true
            })
        ]);
        const seenTxs = {};
        const linearProgress = this.prevBlockNum && this.prevBlockNum == blockNum - 1;
        const prevHashMismatch = this.prevHash && block.previousBlockHash != this.prevHash;
        const nextHashMismatch = this.nextBlockHash && block.hash != this.nextBlockHash;
        this.prevHash = block.hash;
        this.nextBlockHash = block.nextBlockHash;
        this.prevBlockNum = blockNum;
        const missingLinearData = linearProgress && (prevHashMismatch || nextHashMismatch);
        const missingNextBlockHash = !atTipOfChain && !block.nextBlockHash;
        const missingPrevBlockHash = !block.previousBlockHash;
        const missingData = missingNextBlockHash || missingPrevBlockHash || missingLinearData;
        if (!block || block.transactionCount != blockTxs.length || missingData) {
            success = false;
            const error = {
                model: 'block',
                err: true,
                type: 'CORRUPTED_BLOCK',
                payload: { blockNum, txCount: block.transactionCount, foundTxs: blockTxs.length }
            };
            errors.push(error);
            if (log) {
                console.log(JSON.stringify(error));
            }
        }
        for (const tx of mempoolTxs) {
            success = false;
            const error = { model: 'transaction', err: true, type: 'DUPE_TRANSACTION', payload: { tx, blockNum } };
            errors.push(error);
            if (log) {
                console.log(JSON.stringify(error));
            }
        }
        for (const tx of blockTxs) {
            if (tx.fee < 0) {
                success = false;
                const error = { model: 'transaction', err: true, type: 'NEG_FEE', payload: { tx, blockNum } };
                errors.push(error);
                if (log) {
                    console.log(JSON.stringify(error));
                }
            }
            if (seenTxs[tx.txid]) {
                success = false;
                const error = { model: 'transaction', err: true, type: 'DUPE_TRANSACTION', payload: { tx, blockNum } };
                errors.push(error);
                if (log) {
                    console.log(JSON.stringify(error));
                }
            }
            else {
                seenTxs[tx.txid] = tx;
            }
        }
        if (blocksForHeight === 0) {
            success = false;
            const error = {
                model: 'block',
                err: true,
                type: 'MISSING_BLOCK',
                payload: { blockNum }
            };
            errors.push(error);
            if (log) {
                console.log(JSON.stringify(error));
            }
        }
        if (blocksForHeight > 1) {
            success = false;
            const error = {
                model: 'block',
                err: true,
                type: 'DUPE_BLOCKHEIGHT',
                payload: { blockNum, blocksForHeight }
            };
            errors.push(error);
            if (log) {
                console.log(JSON.stringify(error));
            }
        }
        // blocks with same hash
        if (blockTxs.length > 0) {
            const hashFromTx = blockTxs[0].blockHash;
            if (blocksForHash > 1) {
                success = false;
                const error = { model: 'block', err: true, type: 'DUPE_BLOCKHASH', payload: { hash: hashFromTx, blockNum } };
                errors.push(error);
                if (log) {
                    console.log(JSON.stringify(error));
                }
            }
        }
        return { success, errors };
    }
}
exports.EVMVerificationPeer = EVMVerificationPeer;
//# sourceMappingURL=EVMVerificationPeer.js.map