UNPKG

bitcore-node

Version:

A blockchain indexing node with extended capabilities using bitcore

421 lines (377 loc) 14.3 kB
import { ObjectId } from 'bson'; import { expect } from 'chai'; import { Request, Response } from 'express-serve-static-core'; import * as sinon from 'sinon'; import { Transform, Writable } from 'stream'; import Web3 from 'web3'; import { MongoBound } from '../../../src/models/base'; import { CacheStorage } from '../../../src/models/cache'; import { IWallet, WalletStorage } from '../../../src/models/wallet'; import { WalletAddressStorage } from '../../../src/models/walletAddress'; import { MATIC } from '../../../src/modules/matic/api/csp'; import { IEVMTransactionInProcess } from '../../../src/providers/chain-state/evm//types'; import { EVMBlockStorage } from '../../../src/providers/chain-state/evm/models/block'; import { EVMTransactionStorage } from '../../../src/providers/chain-state/evm/models/transaction'; import { StreamWalletTransactionsParams } from '../../../src/types/namespaces/ChainStateProvider'; import { intAfterHelper, intBeforeHelper } from '../../helpers/integration'; describe('Polygon/MATIC API', function() { const chain = 'MATIC'; const network = 'regtest'; const suite = this; this.timeout(30000); before(intBeforeHelper); after(async () => intAfterHelper(suite)); it('should return undefined for garbage data', () => { const data = 'garbage'; const decoded = EVMTransactionStorage.abiDecode(data); expect(decoded).to.be.undefined; }); it('should be able to classify ERC20 data', () => { const data = '0x095ea7b300000000000000000000000052de8d3febd3a06d3c627f59d56e6892b80dcf1200000000000000000000000000000000000000000000000000000000000f4240'; EVMTransactionStorage.abiDecode(data); const decoded = EVMTransactionStorage.abiDecode(data); expect(decoded).to.exist; expect(decoded!.type).to.eq('ERC20'); }); it('should be able to classify ERC721 data', () => { const data = '0xa22cb465000000000000000000000000efc70a1b18c432bdc64b596838b4d138f6bc6cad0000000000000000000000000000000000000000000000000000000000000001'; const decoded = EVMTransactionStorage.abiDecode(data); expect(decoded).to.exist; expect(decoded!.type).to.eq('ERC721'); }); it('should be able to classify Invoice data', () => { const data = '0xb6b4af0500000000000000000000000000000000000000000000000000000000000f4240000000000000000000000000000000000000000000000000000000033ec500800000000000000000000000000000000000000000000000000000016e00f7b3d3c72c929edaf203cfabf7a0513cb8cee277a84ec3fd56bcf3f396b6d665c8abe6c4432f916bacafc94982b45050513de2ee5544aa855d9b5b60e8c1c94e71ffca000000000000000000000000000000000000000000000000000000000000001cfd9150848849c7aff74939535afe5e56dcac5f2f553467ae0e9181d14c0e49c9799433220e288e282376b86aae1bc1d683af4708b38999d59b5d65ff29a85705000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48'; const decoded = EVMTransactionStorage.abiDecode(data); expect(decoded).to.exist; expect(decoded!.type).to.eq('INVOICE'); }); it('should handle multiple decodes', () => { const data = '0x095ea7b300000000000000000000000052de8d3febd3a06d3c627f59d56e6892b80dcf1200000000000000000000000000000000000000000000000000000000000f4240'; EVMTransactionStorage.abiDecode(data); const decoded = EVMTransactionStorage.abiDecode(data); expect(decoded).to.exist; expect(decoded!.type).to.eq('ERC20'); const data2 = '0xa22cb465000000000000000000000000efc70a1b18c432bdc64b596838b4d138f6bc6cad0000000000000000000000000000000000000000000000000000000000000001'; EVMTransactionStorage.abiDecode(data); const decoded2 = EVMTransactionStorage.abiDecode(data2); expect(decoded2).to.exist; expect(decoded2!.type).to.eq('ERC721'); }); it('should not crash when called with almost correct data', () => { const data = '0xa9059cbb0000000000000000000000000797350000000000000000000000000000000000000000000005150ac4c39a6f3f0000'; const decoded = EVMTransactionStorage.abiDecode(data); expect(decoded).to.be.undefined; }); it('should be able to get the fees', async () => { const chain = 'MATIC'; const network = 'testnet'; let target = 1; while (target <= 4) { const cacheKey = `getFee-${chain}-${network}-${target}`; const fee = await MATIC.getFee({ chain, network, target }); expect(fee).to.exist; const cached = await CacheStorage.getGlobal(cacheKey); expect(fee).to.deep.eq(cached); target++; } }); it('should estimate fees by most recent transactions', async () => { const chain = 'MATIC'; const network = 'testnet'; const txs = new Array(4000).fill({}).map(_ => { return { chain, network, blockHeight: 1, gasPrice: 10 * 1e9 } as IEVMTransactionInProcess; }); await CacheStorage.collection.remove({}); await EVMTransactionStorage.collection.deleteMany({}); await EVMTransactionStorage.collection.insertMany(txs); const estimates = await Promise.all([1, 2, 3, 4].map(target => MATIC.getFee({ network, target }))); for (const estimate of estimates) { expect(estimate.feerate).to.be.gt(0); expect(estimate.feerate).to.be.eq(10000000000); } }); it('should return cached fee for a minute', async () => { const chain = 'MATIC'; const network = 'testnet'; const txs = new Array(4000).fill({}).map(_ => { return { chain, network, blockHeight: 1, gasPrice: 10 * 1e9 } as IEVMTransactionInProcess; }); await CacheStorage.collection.remove({}) await EVMTransactionStorage.collection.deleteMany({}); await EVMTransactionStorage.collection.insertMany(txs); let estimates = await Promise.all([1, 2, 3, 4].map(target => MATIC.getFee({ network, target }))); await EVMTransactionStorage.collection.deleteMany({}); estimates = await Promise.all([1, 2, 3, 4].map(target => MATIC.getFee({ network, target }))); for (const estimate of estimates) { expect(estimate.feerate).to.be.gt(0); expect(estimate.feerate).to.be.eq(10000000000); } }); it('should be able to get address token balance', async () => { const sandbox = sinon.createSandbox(); const address = '0xb8fd14fb0e0848cb931c1e54a73486c4b968be3d'; const token = { name: 'Test Token', decimals: 10, symbol: 'TST' }; const tokenStub = { methods: { name: () => ({ call: sandbox.stub().resolves(token.name) }), decimals: () => ({ call: sandbox.stub().resolves(token.decimals) }), symbol: () => ({ call: sandbox.stub().resolves(token.symbol) }), balanceOf: () => ({ call: sandbox.stub().resolves(0) }) } }; sandbox.stub(MATIC, 'erc20For').resolves(tokenStub); const balance = await MATIC.getBalanceForAddress({ chain, network, address, args: { tokenAddress: address } }); expect(balance).to.deep.eq({ confirmed: 0, unconfirmed: 0, balance: 0 }); sandbox.restore(); }); it('should be able to get address MATIC balance', async () => { const address = '0xb8fd14fb0e0848cb931c1e54a73486c4b968be3d'; const balance = await MATIC.getBalanceForAddress({ chain, network, address, args: {} }); expect(balance).to.deep.eq({ confirmed: 0, unconfirmed: 0, balance: 0 }); }); it('should stream MATIC transactions for address', async () => { const address = '0xb8fd14fb0e0848cb931c1e54a73486c4b968be3d'; const txCount = 100; const txs = new Array(txCount).fill({}).map(() => { return { chain, network, blockHeight: 1, gasPrice: 10 * 1e9, data: Buffer.from(''), from: address } as IEVMTransactionInProcess; }); await EVMTransactionStorage.collection.deleteMany({}); await EVMTransactionStorage.collection.insertMany(txs); const res = (new Transform({ transform: (data, _, cb) => cb(null, data) }) as unknown) as Response; res.type = () => res; const req = (new Transform({ transform: (_data, _, cb) => cb(null) }) as unknown) as Request; await MATIC.streamAddressTransactions({ chain, network, address, res, req, args: {} }); let counter = 0; await new Promise(r => { res .on('data', () => counter++) .on('end', r); }); const commaCount = txCount - 1; const bracketCount = 2; const expected = txCount + commaCount + bracketCount; expect(counter).to.eq(expected); }); it('should stream MATIC transactions for block', async () => { const txCount = 100; const txs = new Array(txCount).fill({}).map(() => { return { chain, network, blockHeight: 1, gasPrice: 10 * 1e9, data: Buffer.from('') } as IEVMTransactionInProcess; }); await EVMTransactionStorage.collection.deleteMany({}); await EVMTransactionStorage.collection.insertMany(txs); const res = (new Transform({ transform: (data, _, cb) => { cb(null, data); } }) as unknown) as Response; res.type = () => res; const req = (new Transform({ transform: (_data, _, cb) => { cb(null); } }) as unknown) as Request; await MATIC.streamTransactions({ chain, network, res, req, args: { blockHeight: 1 } }); let counter = 0; await new Promise<void>(r => { res .on('data', () => { counter++; }) .on('end', () => { r(); }); }); const commaCount = txCount - 1; const bracketCount = 2; const expected = txCount + commaCount + bracketCount; expect(counter).to.eq(expected); }); it('should stream MATIC transactions for blockHash', async () => { const txCount = 100; const txs = new Array(txCount).fill({}).map(() => { return { chain, network, blockHash: '12345', gasPrice: 10 * 1e9, data: Buffer.from('') } as IEVMTransactionInProcess; }); await EVMTransactionStorage.collection.deleteMany({}); await EVMTransactionStorage.collection.insertMany(txs); const res = (new Transform({ transform: (data, _, cb) => { cb(null, data); } }) as unknown) as Response; res.type = () => res; const req = (new Transform({ transform: (_data, _, cb) => { cb(null); } }) as unknown) as Request; await MATIC.streamTransactions({ chain, network, res, req, args: { blockHash: '12345' } }); let counter = 0; await new Promise<void>(r => { res .on('data', () => { counter++; }) .on('end', () => { r(); }); }); const commaCount = txCount - 1; const bracketCount = 2; const expected = txCount + commaCount + bracketCount; expect(counter).to.eq(expected); }); describe('#streamWalletTransactions', () => { let sandbox = sinon.createSandbox(); let chain = 'MATIC'; let network = 'mainnet'; let address = '0x1Eee23160Db790ee48Fd39871A64b13e76Fc2C3C'; let wallet: IWallet = { chain, network, pubKey: '', name: 'this-name', singleAddress: false, path: 'm/0/0' } let web3 = new Web3(); before(async () => { const res = await WalletStorage.collection.findOneAndUpdate({ name: wallet.name }, { $set: wallet }, { returnOriginal: false, upsert: true }); wallet = res.value as IWallet; await WalletAddressStorage.collection.updateOne({ network, address }, { $set: { chain, network, wallet: (wallet._id as ObjectId), processed: true, address } }, { upsert: true }) sandbox.stub(MATIC, 'getWeb3').resolves({ web3 }); }); afterEach(async () => { await EVMBlockStorage.collection.deleteMany({}); await EVMTransactionStorage.collection.deleteMany({}); }); after(async () => { sandbox.restore(); }); it('should stream wallet\'s valid MATIC transactions', async () => await streamWalletTransactionsTest(chain, network) ); it('should stream wallet\'s valid & invalid MATIC transactions', async () => await streamWalletTransactionsTest(chain, network, true) ); }); }); const streamWalletTransactionsTest = async (chain: string, network: string, includeInvalidTxs: boolean = false) => { const sandbox = sinon.createSandbox(); // Constants const address = '0x7F17aF79AABC4A297A58D389ab5905fEd4Ec9502'; const objectId = ObjectId.createFromHexString('60f9abed0e32086bf9903bb5'); const wallet = { _id: objectId, chain, network, name: 'Ganache', pubKey: '0x029ec2ebdebe6966259cf3c6f35c4f126b82fe072bf9d0e81dad375f1d6d2d9054', path: 'm/44\'/60\'/0\'/0/0', singleAddress: true } as MongoBound<IWallet>; const txCount = 100; // Valid Transactions const txs = new Array(txCount).fill({}).map(() => { return { chain, network, blockHeight: 1, gasPrice: 10 * 1e9, data: Buffer.from(''), from: address } as IEVMTransactionInProcess; }); // Invalid Transactions for (let i = 0; i < txCount; i++) { txs.push({ ...txs[0], blockHeight: -3 }) } // Add wallet object ID to transactions for (const tx of txs) { tx.wallets = [objectId]; } // Stubs sandbox.stub(MATIC, 'getWalletAddresses').resolves([address]); sandbox.stub(MATIC, 'isP2p').returns(true); // Test await EVMTransactionStorage.collection.deleteMany({}); await EVMTransactionStorage.collection.insertMany(txs); let counter = 0; const req = (new Writable({ write: function(data, _, cb) { data && counter++; cb(); } }) as unknown) as Request; const res = (new Writable({ write: function(data, _, cb) { data && counter++; cb(); } }) as unknown) as Response; res.type = () => res; const err = await new Promise(r => { res .on('error', r) .on('finish', r); MATIC.streamWalletTransactions({ chain, network, wallet, req, res, args: { includeInvalidTxs } } as StreamWalletTransactionsParams) .catch(e => r(e)); }); expect(err).to.not.exist; expect(counter).to.eq(includeInvalidTxs ? txCount * 2 : txCount); sandbox.restore(); };