"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 });
const bitcore_client_1 = require("@bitpay-labs/bitcore-client");
const chai_1 = require("chai");
const io = __importStar(require("socket.io-client"));
const config_1 = __importDefault(require("../../src/config"));
const coin_1 = require("../../src/models/coin");
const transaction_1 = require("../../src/models/transaction");
const wallet_1 = require("../../src/models/wallet");
const walletAddress_1 = require("../../src/models/walletAddress");
const p2p_1 = require("../../src/modules/bitcoin/p2p");
const rpc_1 = require("../../src/rpc");
const api_1 = require("../../src/services/api");
const event_1 = require("../../src/services/event");
const utils_1 = require("../../src/utils");
const wallet_benchmark_1 = require("../benchmark/wallet-benchmark");
const helpers_1 = require("../helpers");
const integration_1 = require("../helpers/integration");
describe('Wallet Benchmark', function () {
    const chain = 'BTC';
    const network = 'regtest';
    let chainConfig;
    let creds;
    let rpc;
    async function checkWalletExists(pubKey, expectedAddress) {
        // Check the database for the first wallet
        const dbWallet = await wallet_1.WalletStorage.collection.findOne({
            chain,
            network,
            pubKey
        });
        // Verify the addresses match
        const foundAddresses = await walletAddress_1.WalletAddressStorage.collection
            .find({
            chain,
            network,
            wallet: dbWallet._id
        })
            .toArray();
        (0, chai_1.expect)(foundAddresses.length).to.eq(1);
        (0, chai_1.expect)(foundAddresses[0].address).to.eq(expectedAddress);
        return dbWallet;
    }
    async function getWalletUtxos(wallet) {
        const utxos = new Array();
        return new Promise(resolve => wallet
            .getUtxos()
            .pipe(new bitcore_client_1.ParseApiStream())
            .on('data', (utxo) => {
            utxos.push(utxo);
        })
            .on('end', () => resolve(utxos)));
    }
    async function checkWalletUtxos(wallet, expectedAddress) {
        const utxos = await getWalletUtxos(wallet);
        (0, chai_1.expect)(utxos.length).to.eq(1);
        (0, chai_1.expect)(utxos[0].address).to.eq(expectedAddress);
        return utxos;
    }
    async function verifyCoinSpent(coin, spentTxid, wallet) {
        const wallet1Coin = await coin_1.CoinStorage.collection.findOne({
            chain: coin.chain,
            network: coin.network,
            mintTxid: coin.mintTxid,
            mintIndex: coin.mintIndex,
        });
        (0, chai_1.expect)(wallet1Coin.spentTxid).to.eq(spentTxid);
        (0, chai_1.expect)(wallet1Coin.wallets[0].toHexString()).to.eq(wallet._id.toHexString());
    }
    async function checkWalletReceived(receivingWallet, txid, address, sendingWallet) {
        const broadcastedOutput = await coin_1.CoinStorage.collection.findOne({
            chain,
            network,
            mintTxid: txid,
            address
        });
        (0, chai_1.expect)(broadcastedOutput.address).to.eq(address);
        (0, chai_1.expect)(broadcastedOutput.wallets.length).to.eq(1);
        (0, chai_1.expect)(broadcastedOutput.wallets[0].toHexString()).to.eq(receivingWallet._id.toHexString());
        const broadcastedTransaction = await transaction_1.TransactionStorage.collection.findOne({ chain, network, txid });
        (0, chai_1.expect)(broadcastedTransaction.txid).to.eq(txid);
        (0, chai_1.expect)(broadcastedTransaction.fee).gt(0);
        const txWallets = broadcastedTransaction.wallets.map(w => w.toHexString());
        (0, chai_1.expect)(txWallets.length).to.eq(2);
        (0, chai_1.expect)(txWallets).to.include(receivingWallet._id.toHexString());
        (0, chai_1.expect)(txWallets).to.include(sendingWallet._id.toHexString());
    }
    // eslint-disable-next-line @typescript-eslint/no-this-alias
    const suite = this;
    this.timeout(5000000);
    let p2pWorker;
    before(async function () {
        chainConfig = config_1.default.chains[chain][network];
        creds = chainConfig.rpc;
        rpc = new rpc_1.AsyncRPC(creds.username, creds.password, creds.host, creds.port);
        await (0, integration_1.intBeforeHelper)();
        await event_1.Event.start();
        await api_1.Api.start();
    });
    after(async () => {
        await event_1.Event.stop();
        await api_1.Api.stop();
        await (0, integration_1.intAfterHelper)(suite);
    });
    beforeEach(async () => {
        await (0, helpers_1.resetDatabase)();
    });
    afterEach(async () => {
        if (p2pWorker) {
            await p2pWorker.stop();
        }
    });
    describe('Wallet import', () => {
        it('should be able to create two wallets and have them interact', async () => {
            const seenCoins = new Set();
            const socket = io.connect('http://localhost:3000', { transports: ['websocket'] });
            const connected = new Promise(r => {
                socket.on('connect', () => {
                    const room = `/${chain}/${network}/inv`;
                    socket.emit('room', room);
                    console.log('Connected to socket');
                    r();
                });
            });
            await connected;
            socket.on('coin', (coin) => {
                seenCoins.add(coin.mintTxid);
            });
            p2pWorker = new p2p_1.BitcoinP2PWorker({
                chain,
                network,
                chainConfig
            });
            await p2pWorker.start();
            const address1 = await rpc.getnewaddress('');
            const address2 = await rpc.getnewaddress('');
            const anAddress = 'mkzAfSHtmTh5Xsc352jf6TBPj55Lne5g21';
            try {
                await rpc.call('generatetoaddress', [1, address1]);
                await rpc.call('generatetoaddress', [1, address2]);
                await rpc.call('generatetoaddress', [100, anAddress]);
                await p2pWorker.syncDone();
                const wallet1 = await (0, wallet_benchmark_1.createWallet)([address1], 0, network);
                const wallet2 = await (0, wallet_benchmark_1.createWallet)([address2], 1, network);
                const dbWallet1 = await checkWalletExists(wallet1.authPubKey, address1);
                const dbWallet2 = await checkWalletExists(wallet2.authPubKey, address2);
                const utxos = await checkWalletUtxos(wallet1, address1);
                await checkWalletUtxos(wallet2, address2);
                const tx = await rpc.call('createrawtransaction', [
                    utxos.map(utxo => ({ txid: utxo.mintTxid, vout: utxo.mintIndex })),
                    { [address1]: 0.1, [address2]: 0.1 }
                ]);
                const fundedTx = await rpc.call('fundrawtransaction', [tx]);
                const signedTx = await rpc.signrawtx(fundedTx.hex);
                const broadcastedTx = await rpc.call('sendrawtransaction', [signedTx.hex]);
                while (!seenCoins.has(broadcastedTx)) {
                    console.log('...WAITING...'); // TODO
                    await (0, utils_1.wait)(1000);
                }
                await verifyCoinSpent(utxos[0], broadcastedTx, dbWallet1);
                await checkWalletReceived(dbWallet1, broadcastedTx, address1, dbWallet2);
                await checkWalletReceived(dbWallet2, broadcastedTx, address2, dbWallet1);
                await (0, utils_1.wait)(1000);
                await socket.disconnect();
                await p2pWorker.stop();
            }
            catch (e) {
                console.log('Error : ', e);
                (0, chai_1.expect)(e).to.be.undefined;
            }
        });
        it('should be able to create two wallets and have them interact, while syncing', async () => {
            const seenCoins = new Set();
            const socket = io.connect('http://localhost:3000', { transports: ['websocket'] });
            const connected = new Promise(r => {
                socket.on('connect', () => {
                    const room = `/${chain}/${network}/inv`;
                    socket.emit('room', room);
                    console.log('Connected to socket');
                    r();
                });
            });
            await connected;
            socket.on('coin', (coin) => {
                seenCoins.add(coin.mintTxid);
            });
            p2pWorker = new p2p_1.BitcoinP2PWorker({
                chain,
                network,
                chainConfig
            });
            await p2pWorker.start();
            const address1 = await rpc.getnewaddress('');
            const address2 = await rpc.getnewaddress('');
            const anAddress = 'mkzAfSHtmTh5Xsc352jf6TBPj55Lne5g21';
            try {
                await rpc.call('generatetoaddress', [1, address1]);
                await rpc.call('generatetoaddress', [1, address2]);
                // mature coins
                await rpc.call('generatetoaddress', [100, anAddress]);
                await p2pWorker.syncDone();
                const wallet1 = await (0, wallet_benchmark_1.createWallet)([address1], 2, network);
                const wallet2 = await (0, wallet_benchmark_1.createWallet)([address2], 3, network);
                const dbWallet1 = await checkWalletExists(wallet1.authPubKey, address1);
                const dbWallet2 = await checkWalletExists(wallet2.authPubKey, address2);
                const utxos = await checkWalletUtxos(wallet1, address1);
                await checkWalletUtxos(wallet2, address2);
                const tx = await rpc.call('createrawtransaction', [
                    utxos.map(utxo => ({ txid: utxo.mintTxid, vout: utxo.mintIndex })),
                    { [address1]: 0.1, [address2]: 0.1 }
                ]);
                const fundedTx = await rpc.call('fundrawtransaction', [tx]);
                const signedTx = await rpc.signrawtx(fundedTx.hex);
                await rpc.call('generatetoaddress', [100, anAddress]);
                p2pWorker.sync();
                (0, chai_1.expect)(p2pWorker.isSyncing).to.be.true;
                // Generate some blocks for the node to process
                const broadcastedTx = await rpc.call('sendrawtransaction', [signedTx.hex]);
                (0, chai_1.expect)(p2pWorker.isSyncing).to.be.true;
                while (!seenCoins.has(broadcastedTx)) {
                    console.log('...WAITING...'); // TODO
                    await (0, utils_1.wait)(1000);
                }
                await verifyCoinSpent(utxos[0], broadcastedTx, dbWallet1);
                await checkWalletReceived(dbWallet1, broadcastedTx, address1, dbWallet2);
                await checkWalletReceived(dbWallet2, broadcastedTx, address2, dbWallet1);
                await (0, utils_1.wait)(1000);
                await socket.disconnect();
                await p2pWorker.stop();
            }
            catch (e) {
                console.log('Error : ', e);
                (0, chai_1.expect)(e).to.be.undefined;
            }
        });
        it('should import all addresses and verify in database while below 300 mb of heapUsed memory', async () => {
            const smallAddressBatch = new Array();
            const mediumAddressBatch = new Array();
            const largeAddressBatch = new Array();
            console.log('Generating small batch of addresses');
            for (let i = 0; i < 10; i++) {
                const address = await rpc.getnewaddress('');
                smallAddressBatch.push(address);
            }
            console.log('Generating medium batch of addresses');
            (0, chai_1.expect)(smallAddressBatch.length).to.deep.equal(10);
            for (let i = 0; i < 100; i++) {
                const address = await rpc.getnewaddress('');
                mediumAddressBatch.push(address);
            }
            (0, chai_1.expect)(mediumAddressBatch.length).to.deep.equal(100);
            console.log('Generating large batch of addresses');
            for (let i = 0; i < 1000; i++) {
                const address = await rpc.getnewaddress('');
                largeAddressBatch.push(address);
            }
            (0, chai_1.expect)(largeAddressBatch.length).to.deep.equal(1000);
            console.log('Checking');
            const importedWallet1 = await (0, wallet_benchmark_1.createWallet)(smallAddressBatch, 0, network);
            const importedWallet2 = await (0, wallet_benchmark_1.createWallet)(mediumAddressBatch, 1, network);
            const importedWallet3 = await (0, wallet_benchmark_1.createWallet)(largeAddressBatch, 2, network);
            (0, chai_1.expect)(importedWallet1).to.not.be.null;
            (0, chai_1.expect)(importedWallet2).to.not.be.null;
            (0, chai_1.expect)(importedWallet3).to.not.be.null;
            const foundSmallAddressBatch = await walletAddress_1.WalletAddressStorage.collection
                .find({
                chain,
                network,
                address: { $in: smallAddressBatch }
            })
                .toArray();
            const smallAddresses = foundSmallAddressBatch.map(wa => wa.address);
            for (const address of smallAddressBatch) {
                (0, chai_1.expect)(smallAddresses.includes(address)).to.be.true;
            }
            (0, chai_1.expect)(foundSmallAddressBatch.length).to.have.deep.equal(smallAddressBatch.length);
            const foundMediumAddressBatch = await walletAddress_1.WalletAddressStorage.collection
                .find({
                chain,
                network,
                address: { $in: mediumAddressBatch }
            })
                .toArray();
            const mediumAddresses = foundMediumAddressBatch.map(wa => wa.address);
            for (const address of mediumAddressBatch) {
                (0, chai_1.expect)(mediumAddresses.includes(address)).to.be.true;
            }
            (0, chai_1.expect)(foundMediumAddressBatch.length).to.have.deep.equal(mediumAddressBatch.length);
            const foundLargeAddressBatch = await walletAddress_1.WalletAddressStorage.collection
                .find({
                chain,
                network,
                address: { $in: largeAddressBatch }
            })
                .toArray();
            const largeAddresses = foundLargeAddressBatch.map(wa => wa.address);
            for (const address of largeAddressBatch) {
                (0, chai_1.expect)(largeAddresses.includes(address)).to.be.true;
            }
            (0, chai_1.expect)(foundLargeAddressBatch.length).to.have.deep.equal(largeAddressBatch.length);
            const { heapUsed } = process.memoryUsage();
            (0, chai_1.expect)(heapUsed).to.be.below(3e8);
        });
    });
});
//# sourceMappingURL=wallet-benchmark.test.js.map