UNPKG

bitcore-node

Version:

A blockchain indexing node with extended capabilities using bitcore

301 lines (271 loc) 10.9 kB
import { ObjectId } from 'bson'; import { expect } from 'chai'; import * as _ from 'lodash'; import * as sinon from 'sinon'; import request from 'request'; import { WalletAddressStorage } from '../../../src/models/walletAddress'; import { XRP } from '../../../src/modules/ripple/api/csp'; import { XrpBlockStorage } from '../../../src/modules/ripple/models/block'; import { XrpTransactionStorage } from '../../../src/modules/ripple/models/transaction'; import { IXrpCoin, IXrpTransaction } from '../../../src/modules/ripple/types'; import { RippleTxs } from '../../fixtures/rippletxs.fixture'; import { resetDatabase } from '../../helpers'; import { intAfterHelper, intBeforeHelper } from '../../helpers/integration'; describe('Ripple Api', function() { const suite = this; const network = 'testnet'; this.timeout(30000); before(intBeforeHelper); after(async () => { await intAfterHelper(suite); const client = await XRP.getClient(network); client.rpc.disconnect(); }); beforeEach(async () => { await resetDatabase(); }); it('should be able to get the ledger', async () => { const client = await XRP.getClient(network); const { ledger } = await client.getBlock(); expect(ledger).to.exist; expect(ledger.ledger_hash).to.exist; }); it('should be able to get local tip', async () => { const chain = 'XRP'; await XrpBlockStorage.collection.insertOne({ chain, network, height: 5, hash: '528f01c17829622ed6a4af51b3b3f6c062f304fa60e66499c9cbb8622c8407f7', time: new Date(1526326784), timeNormalized: new Date(1526326784), transactionCount: 1, reward: 50, previousBlockHash: '64bfb3eda276ae4ae5b64d9e36c9c0b629bc767fb7ae66f9d55d2c5c8103a929', nextBlockHash: '', size: 264, processed: true }); const tip = await XRP.getLocalTip({ chain, network }); expect(tip).to.exist; expect(tip.hash).to.exist; expect(tip.hash).to.eq('528f01c17829622ed6a4af51b3b3f6c062f304fa60e66499c9cbb8622c8407f7'); }); for (const tx of RippleTxs) { it('should transform a ripple rpc response into a bitcore transaction: ' + tx.hash, async () => { const bitcoreTx = await XRP.transform(tx, 'testnet'); expect(bitcoreTx).to.have.property('chain'); expect(tx.Account).to.eq(bitcoreTx.from); expect(tx.ledger_index).to.eq(bitcoreTx.blockHeight); expect(tx.Fee).to.eq((bitcoreTx.fee).toString()); const nodes = tx.meta.AffectedNodes.filter(node => 'ModifiedNode' in node && node.ModifiedNode.FinalFields?.Account == tx.Account); const sentVal = nodes.reduce((acc, node) => acc += 'ModifiedNode' in node ? Number(node.ModifiedNode.FinalFields?.Balance) - Number(node.ModifiedNode.PreviousFields?.Balance) : 0, 0); expect(sentVal).to.be.lt(0); if (tx.meta.delivered_amount) { const modNodes = tx.meta.AffectedNodes.filter(n => 'ModifiedNode' in n && n.ModifiedNode.FinalFields?.Account === bitcoreTx.to); const createNodes = tx.meta.AffectedNodes.filter(n => 'CreatedNode' in n && n.CreatedNode.NewFields.Account === bitcoreTx.to); expect(modNodes.length + createNodes.length > 0).to.equal(true); expect(tx.meta.delivered_amount).to.eq(bitcoreTx.value.toString()); let receivedVal = modNodes.reduce((acc, node) => acc += 'ModifiedNode' in node ? Number(node.ModifiedNode.FinalFields?.Balance) - Number(node.ModifiedNode.PreviousFields?.Balance) : 0, 0); receivedVal += createNodes.reduce((acc, node) => acc += 'CreatedNode' in node ? Number(node.CreatedNode.NewFields.Balance) : 0, 0); expect(receivedVal).to.be.gt(0); } }); } it('should tag txs from a wallet', async () => { const chain = 'XRP'; const network = 'testnet'; const wallet = new ObjectId(); const address = 'rN33DVnneYUUgTmcxXnXvgAL1BECuLZ8pm'; await WalletAddressStorage.collection.insertOne({ chain, network, wallet, address, processed: true }); for (const tx of RippleTxs) { const bitcoreTx = (await XRP.transform(tx, network)) as IXrpTransaction; const bitcoreCoins = XRP.transformToCoins(tx, network); const { transaction, coins } = await XRP.tag(chain, network, bitcoreTx, bitcoreCoins); expect(transaction.wallets.length).eq(1); expect(transaction.wallets[0].equals(wallet)); let hasACoin = false; for (const coin of coins) { if (coin.address == address) { hasACoin = true; expect(coin.wallets.length).eq(1); expect(coin.wallets[0].equals(wallet)); } } expect(hasACoin).eq(true); } }); it('should get sequence', async () => { const sequence = await XRP.getAccountNonce(network, 'rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh'); expect(sequence).to.exist; expect(sequence).to.be.a('number'); }); it('should get flags', async () => { const flags = await XRP.getAccountFlags(network, 'rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh'); expect(flags).to.exist; expect(flags).to.haveOwnProperty('requireDestinationTag'); }); it('should save tagged transactions to the database', async () => { const chain = 'XRP'; const network = 'testnet'; const wallet = new ObjectId(); const address = 'rN33DVnneYUUgTmcxXnXvgAL1BECuLZ8pm'; await WalletAddressStorage.collection.insertOne({ chain, network, wallet, address, processed: true }); const blockTxs = new Array<IXrpTransaction>(); const blockCoins = new Array<IXrpCoin>(); for (const tx of RippleTxs) { const bitcoreTx = XRP.transform(tx, network) as IXrpTransaction; const bitcoreCoins = XRP.transformToCoins(tx, network); const { transaction, coins } = await XRP.tag(chain, network, bitcoreTx, bitcoreCoins); blockTxs.push(transaction); blockCoins.push(...coins); } await XrpTransactionStorage.batchImport({ txs: blockTxs, coins: blockCoins, chain, network, initialSyncComplete: false }); const walletTxs = await XrpTransactionStorage.collection.find({ chain, network, wallets: wallet }).toArray(); expect(walletTxs.length).eq(RippleTxs.length); }); describe('getBlockBeforeTime', () => { // For these tests, we simulate ledgers with a 1 second interval, so the close_time is xrpEpoch + (ledger_index * 1000) const xrpEpoch = new Date('2000-01-01T00:00:00.000Z'); const getCloseTime = (ledgerIndex: number) => new Date(xrpEpoch.getTime() + (ledgerIndex * 1000)); const sandbox = sinon.createSandbox(); let requestStub; let validBody = { result: { status: 'success', ledger: { ledger_hash: 'abc123', ledger_index: 12, parent_hash: 'abc122', close_time: getCloseTime(12).getTime() / 1000, close_time_human: getCloseTime(12).toUTCString(), closed: true } } }; let invalidBody = { result: { status: 'error' } }; let time; const _configBak = XRP.config; before(async () => { await XrpBlockStorage.collection.deleteMany({}); }); beforeEach(async () => { requestStub = sandbox.stub(request, 'post'); await XrpBlockStorage.collection.insertMany([{ chain: 'XRP', network: 'testnet', height: 12, timeNormalized: getCloseTime(12), hash: 'abc123', time: getCloseTime(12), transactionCount: 1, reward: 50, previousBlockHash: 'abc122', nextBlockHash: 'abc124', size: 264, processed: true }, { chain: 'XRP', network: 'testnet', height: 13, timeNormalized: getCloseTime(13), hash: 'abc124', time: getCloseTime(13), transactionCount: 1, reward: 50, previousBlockHash: 'abc123', nextBlockHash: '', size: 264, processed: true }]); time = getCloseTime(12).toISOString(); }); afterEach(async () => { sandbox.restore(); await XrpBlockStorage.collection.deleteMany({}); XRP.config = _configBak; }); it('should return block', async () => { requestStub.callsFake(function(req, cb) { validBody.result.ledger.ledger_index = req.body.params[0].ledger_index; validBody.result.ledger.close_time = getCloseTime(req.body.params[0].ledger_index).getTime() / 1000; validBody.result.ledger.close_time_human = new Date(validBody.result.ledger.close_time * 1000).toUTCString(); return cb(null, { body: validBody }); }); const res = await XRP.getBlockBeforeTime({ chain: 'XRP', network: 'testnet', time }); expect(res).to.deep.equal({ chain: 'XRP', network: 'testnet', hash: 'abc123', height: 12, previousBlockHash: 'abc122', processed: true, time: getCloseTime(12), timeNormalized: getCloseTime(12), reward: 0, size: 0, transactionCount: 0, nextBlockHash: '' }); }); it('should respond null if date is too early', async () => { const res = await XRP.getBlockBeforeTime({ chain: 'XRP', network: 'testnet', time: getCloseTime(-1).toISOString() }); expect(res).to.be.null; }); it('should resolve on empty response', async () => { requestStub.callsArgWith(1, null, null); const res = await XRP.getBlockBeforeTime({ chain: 'XRP', network: 'testnet', time }); expect(res).to.be.null; }); it('should throw on invalid time', async () => { try { await XRP.getBlockBeforeTime({ chain: 'XRP', network: 'testnet', time: 'not-a-time' }); throw new Error('should have thrown'); } catch (err: any) { expect(err.message).to.equal('Invalid time value') } }); it('should throw on response error', async () => { requestStub.callsArgWith(1, 'Unresponsive server', validBody); try { await XRP.getBlockBeforeTime({ chain: 'XRP', network: 'testnet', time }); throw new Error('should have thrown'); } catch (err) { expect(err).to.equal('Unresponsive server') } }); it('should return null on error response', async () => { requestStub.callsArgWith(1, null, invalidBody); const res = await XRP.getBlockBeforeTime({ chain: 'XRP', network: 'testnet', time }); expect(res).to.be.null; }); it('should throw on mis-configuration', async () => { requestStub.callsArgWith(1, null, validBody); XRP.config = {}; try { await XRP.getBlockBeforeTime({ chain: 'XRP', network: 'testnet', time }); throw new Error('should have thrown'); } catch (err: any) { expect(err.message).to.equal('Cannot read properties of undefined (reading \'provider\')') } }); }); });