fcoin/test/indexer-test.js
Braydon Fuller ed06c2184d
indexer: check that blocks are connected
There was a rare case that a block could be incorrectly added to
the indexer if the indexer was disabled during a reorg to a height
that matched the height that was expected, and the `sync` method
for the indexer wasn't called that would detect the reorg.
2019-05-15 12:11:37 -07:00

1205 lines
32 KiB
JavaScript

/* eslint-env mocha */
/* eslint prefer-arrow-callback: "off" */
'use strict';
const assert = require('./util/assert');
const reorg = require('./util/reorg');
const Script = require('../lib/script/script');
const Opcode = require('../lib/script/opcode');
const Address = require('../lib/primitives/address');
const Block = require('../lib/primitives/block');
const Chain = require('../lib/blockchain/chain');
const WorkerPool = require('../lib/workers/workerpool');
const Miner = require('../lib/mining/miner');
const MemWallet = require('./util/memwallet');
const TXIndexer = require('../lib/indexer/txindexer');
const AddrIndexer = require('../lib/indexer/addrindexer');
const BlockStore = require('../lib/blockstore/level');
const FullNode = require('../lib/node/fullnode');
const SPVNode = require('../lib/node/spvnode');
const Network = require('../lib/protocol/network');
const network = Network.get('regtest');
const {NodeClient, WalletClient} = require('bclient');
const {forValue, testdir, rimraf} = require('./util/common');
const ports = {
p2p: 49331,
node: 49332,
wallet: 49333
};
const vectors = [
// Secret for the public key vectors:
// cVDJUtDjdaM25yNVVDLLX3hcHUfth4c7tY3rSc4hy9e8ibtCuj6G
{
addr: 'bcrt1qngw83fg8dz0k749cg7k3emc7v98wy0c7azaa6h',
amount: 19.99,
label: 'p2wpkh'
},
{
addr: 'muZpTpBYhxmRFuCjLc7C6BBDF32C8XVJUi',
amount: 1.99,
label: 'p2pkh'
},
// Secrets for 1 of 2 multisig vectors:
// cVDJUtDjdaM25yNVVDLLX3hcHUfth4c7tY3rSc4hy9e8ibtCuj6G
// 93KCDD4LdP4BDTNBXrvKUCVES2jo9dAKKvhyWpNEMstuxDauHty
{
addr: 'bcrt1q2nj8e2nhmsa4hl9qw3xas7l5n2547h5uhlj47nc3pqfxaeq5rtjs9g328g',
amount: 0.99,
label: 'p2wsh'
},
{
addr: '2Muy8nSQaMsMFAZwPyiXSEMTVFJv9iYuhwT',
amount: 0.11,
label: 'p2sh'
}
];
const workers = new WorkerPool({
enabled: true
});
const blocks = new BlockStore({
memory: true,
network
});
const chain = new Chain({
memory: true,
network,
workers,
blocks
});
const miner = new Miner({
chain,
version: 4,
workers
});
const cpu = miner.cpu;
const wallet = new MemWallet({
network
});
const txindexer = new TXIndexer({
memory: true,
network,
chain,
blocks
});
const addrindexer = new AddrIndexer({
memory: true,
network,
chain,
blocks
});
describe('Indexer', function() {
this.timeout(120000);
before(async () => {
await blocks.open();
await chain.open();
await miner.open();
await txindexer.open();
await addrindexer.open();
});
after(async () => {
await blocks.close();
await chain.close();
await miner.close();
await txindexer.close();
await addrindexer.close();
});
describe('Unit', function() {
it('should connect block', async () => {
const indexer = new AddrIndexer({
blocks: {},
chain: {}
});
indexer.height = 9;
indexer.getBlockMeta = (height) => {
return {
hash: Buffer.alloc(32, 0x00),
height: height
};
};
let called = false;
indexer._addBlock = async () => {
called = true;
};
const meta = {height: 10};
const block = {prevBlock: Buffer.alloc(32, 0x00)};
const view = {};
const connected = await indexer._syncBlock(meta, block, view);
assert.equal(connected, true);
assert.equal(called, true);
});
it('should not connect block', async () => {
const indexer = new AddrIndexer({
blocks: {},
chain: {}
});
indexer.height = 9;
indexer.getBlockMeta = (height) => {
return {
hash: Buffer.alloc(32, 0x02),
height: height
};
};
let called = false;
indexer._addBlock = async () => {
called = true;
};
const meta = {height: 10};
const block = {prevBlock: Buffer.alloc(32, 0x01)};
const view = {};
const connected = await indexer._syncBlock(meta, block, view);
assert.equal(connected, false);
assert.equal(called, false);
});
it('should disconnect block', async () => {
const indexer = new AddrIndexer({
blocks: {},
chain: {}
});
indexer.height = 9;
indexer.getBlockMeta = (height) => {
return {
hash: Buffer.alloc(32, 0x00),
height: height
};
};
let called = false;
indexer._removeBlock = async () => {
called = true;
};
const meta = {height: 9};
const block = {hash: () => Buffer.alloc(32, 0x00)};
const view = {};
const connected = await indexer._syncBlock(meta, block, view);
assert.equal(connected, true);
assert.equal(called, true);
});
it('should not disconnect block', async () => {
const indexer = new AddrIndexer({
blocks: {},
chain: {}
});
indexer.height = 9;
indexer.getBlockMeta = (height) => {
return {
hash: Buffer.alloc(32, 0x01),
height: height
};
};
let called = false;
indexer._removeBlock = async () => {
called = true;
};
const meta = {height: 9};
const block = {hash: () => Buffer.alloc(32, 0x02)};
const view = {};
const connected = await indexer._syncBlock(meta, block, view);
assert.equal(connected, false);
assert.equal(called, false);
});
it('should not index transaction w/ invalid address', async () => {
const indexer = new AddrIndexer({
blocks: {},
chain: {}
});
const ops = [];
indexer.put = (key, value) => ops.push([key, value]);
indexer.del = (key, value) => ops.push([key, value]);
// Create a witness program version 1 with
// 40 byte data push.
const script = new Script();
script.push(Opcode.fromSmall(1));
script.push(Opcode.fromData(Buffer.alloc(40)));
script.compile();
const addr = Address.fromScript(script);
const tx = {
getAddresses: () => [addr],
hash: () => Buffer.alloc(32)
};
const entry = {height: 323549};
const block = {txs: [tx]};
const view = {};
indexer.indexBlock(entry, block, view);
indexer.unindexBlock(entry, block, view);
assert.equal(ops.length, 0);
});
it('should index transaction w/ valid address', async () => {
const indexer = new AddrIndexer({
blocks: {},
chain: {}
});
const ops = [];
indexer.put = (key, value) => ops.push([key, value]);
indexer.del = (key, value) => ops.push([key, value]);
// Create a witness program version 0 with
// 20 byte data push.
const script = new Script();
script.push(Opcode.fromSmall(0));
script.push(Opcode.fromData(Buffer.alloc(20)));
script.compile();
const addr = Address.fromScript(script);
const tx = {
getAddresses: () => [addr],
hash: () => Buffer.alloc(32)
};
const entry = {height: 323549};
const block = {txs: [tx]};
const view = {};
indexer.indexBlock(entry, block, view);
indexer.unindexBlock(entry, block, view);
assert.equal(ops.length, 6);
});
it('should error with limits', async () => {
const indexer = new AddrIndexer({
blocks: {},
chain: {},
maxTxs: 10
});
await assert.asyncThrows(async () => {
await indexer.getHashesByAddress(vectors[0].addr, {limit: 11});
}, 'Limit above max');
});
});
describe('Index 10 blocks', function() {
let addr = null;
before(async () => {
miner.addresses.length = 0;
miner.addAddress(wallet.getReceive());
addr = miner.getAddress();
for (let i = 0; i < 10; i++) {
const block = await cpu.mineBlock();
assert(block);
assert(await chain.add(block));
}
assert.strictEqual(chain.height, 10);
assert.strictEqual(txindexer.height, 10);
assert.strictEqual(addrindexer.height, 10);
});
it('should get txs by address', async () => {
const hashes = await addrindexer.getHashesByAddress(miner.getAddress());
assert.strictEqual(hashes.length, 10);
});
it('should get txs by address (limit)', async () => {
const hashes = await addrindexer.getHashesByAddress(addr, {limit: 1});
assert.strictEqual(hashes.length, 1);
});
it('should get txs by address (reverse)', async () => {
const hashes = await addrindexer.getHashesByAddress(
addr, {reverse: false});
assert.strictEqual(hashes.length, 10);
const reversed = await addrindexer.getHashesByAddress(
addr, {reverse: true});
assert.strictEqual(reversed.length, 10);
for (let i = 0; i < 10; i++)
assert.deepEqual(hashes[i], reversed[9 - i]);
});
it('should get txs by address after txid', async () => {
const hashes = await addrindexer.getHashesByAddress(addr, {limit: 5});
assert.strictEqual(hashes.length, 5);
const txid = hashes[4];
const next = await addrindexer.getHashesByAddress(
addr, {after: txid, limit: 5});
assert.strictEqual(next.length, 5);
const all = await addrindexer.getHashesByAddress(addr);
assert.strictEqual(all.length, 10);
assert.deepEqual(hashes.concat(next), all);
});
it('should get txs by address after txid (reverse)', async () => {
const hashes = await addrindexer.getHashesByAddress(
addr, {limit: 5, reverse: true});
assert.strictEqual(hashes.length, 5);
const txid = hashes[4];
const next = await addrindexer.getHashesByAddress(
addr, {after: txid, limit: 5, reverse: true});
assert.strictEqual(next.length, 5);
const all = await addrindexer.getHashesByAddress(
addr, {reverse: true});
assert.strictEqual(all.length, 10);
assert.deepEqual(hashes.concat(next), all);
});
it('should get tx and meta', async () => {
const hashes = await addrindexer.getHashesByAddress(addr, {limit: 1});
assert.equal(hashes.length, 1);
const hash = hashes[0];
const tx = await txindexer.getTX(hash);
const meta = await txindexer.getMeta(hash);
assert(meta.height);
assert(meta.block);
assert(meta.time);
assert.deepEqual(meta.tx, tx);
});
it('should get null if not found for tx and meta', async () => {
const hash = Buffer.alloc(32);
const tx = await txindexer.getTX(hash);
const meta = await txindexer.getMeta(hash);
assert.strictEqual(tx, null);
assert.strictEqual(meta, null);
});
it('should get unspendable genesis tx', async () => {
const block = Block.fromRaw(Buffer.from(network.genesisBlock, 'hex'));
const hash = block.txs[0].hash();
const tx = await txindexer.getTX(hash);
const meta = await txindexer.getMeta(hash);
assert(meta);
assert.equal(meta.height, 0);
assert(meta.block);
assert(meta.time);
assert.deepEqual(meta.tx, tx);
});
});
describe('Reorg and rescan', function() {
it('should rescan and reindex 10 missed blocks', async () => {
for (let i = 0; i < 10; i++) {
const block = await cpu.mineBlock();
assert(block);
assert(await chain.add(block));
}
assert.strictEqual(chain.height, 20);
assert.strictEqual(txindexer.height, 20);
assert.strictEqual(addrindexer.height, 20);
const hashes = await addrindexer.getHashesByAddress(miner.getAddress());
assert.strictEqual(hashes.length, 20);
for (const hash of hashes) {
const meta = await txindexer.getMeta(hash);
assert.bufferEqual(meta.tx.hash(), hash);
}
});
it('should handle indexing a reorg', async () => {
await reorg(chain, cpu, 10);
assert.strictEqual(txindexer.height, 31);
assert.strictEqual(addrindexer.height, 31);
const hashes = await addrindexer.getHashesByAddress(miner.getAddress());
assert.strictEqual(hashes.length, 31);
for (const hash of hashes) {
const meta = await txindexer.getMeta(hash);
assert.bufferEqual(meta.tx.hash(), hash);
}
});
describe('Integration', function() {
const prefix = testdir('indexer');
beforeEach(async () => {
await rimraf(prefix);
});
after(async () => {
await rimraf(prefix);
});
it('will enable indexes retroactively', async () => {
let node, nclient = null;
try {
node = new FullNode({
prefix: prefix,
network: 'regtest',
apiKey: 'foo',
memory: false,
indexTX: false,
indexAddress: false,
port: ports.p2p,
httpPort: ports.node
});
await node.ensure();
await node.open();
nclient = new NodeClient({
port: ports.node,
apiKey: 'foo',
timeout: 120000
});
await nclient.open();
const blocks = await nclient.execute(
'generatetoaddress', [150, vectors[0].addr]);
assert.equal(blocks.length, 150);
await forValue(node.chain, 'height', 150);
const info = await nclient.request('GET', '/');
assert.equal(info.chain.height, 150);
assert.equal(info.indexes.addr.enabled, false);
assert.equal(info.indexes.addr.height, 0);
assert.equal(info.indexes.tx.enabled, false);
assert.equal(info.indexes.tx.height, 0);
} finally {
if (nclient)
await nclient.close();
if (node)
await node.close();
}
try {
node = new FullNode({
prefix: prefix,
network: 'regtest',
memory: false,
indexTX: true,
indexAddress: false,
port: ports.p2p,
httpPort: ports.node
});
await node.ensure();
await node.open();
assert(node.txindex);
assert.equal(node.txindex.height, 0);
node.startSync();
await forValue(node.txindex, 'height', 150);
} finally {
if (node)
await node.close();
}
});
it('will sync if disabled during reorganization', async () => {
let node, nclient, wclient = null;
try {
// Generate initial set of blocks that are are spending
// coins and therefore data in undo blocks.
node = new FullNode({
prefix: prefix,
network: 'regtest',
apiKey: 'foo',
memory: false,
indexTX: true,
indexAddress: false,
port: ports.p2p,
httpPort: ports.node,
plugins: [require('../lib/wallet/plugin')],
env: {
'BCOIN_WALLET_HTTP_PORT': ports.wallet.toString()
},
logLevel: 'none'
});
await node.ensure();
await node.open();
nclient = new NodeClient({
port: ports.node,
apiKey: 'foo',
timeout: 120000
});
await nclient.open();
wclient = new WalletClient({
port: ports.wallet,
apiKey: 'foo',
timeout: 120000
});
await wclient.open();
const coinbase = await wclient.execute(
'getnewaddress', ['default']);
const blocks = await nclient.execute(
'generatetoaddress', [150, coinbase]);
assert.equal(blocks.length, 150);
for (let i = 0; i < 10; i++) {
for (const v of vectors)
await wclient.execute('sendtoaddress', [v.addr, v.amount]);
const blocks = await nclient.execute(
'generatetoaddress', [1, coinbase]);
assert.equal(blocks.length, 1);
}
await forValue(node.chain, 'height', 160);
await forValue(node.txindex, 'height', 160);
} finally {
if (wclient)
await wclient.close();
if (nclient)
await nclient.close();
if (node)
await node.close();
}
try {
// Now create a reorganization in the chain while
// the indexer is disabled.
node = new FullNode({
prefix: prefix,
network: 'regtest',
apiKey: 'foo',
memory: false,
indexTX: false,
indexAddress: false,
port: ports.p2p,
httpPort: ports.node,
logLevel: 'none'
});
await node.ensure();
await node.open();
nclient = new NodeClient({
port: ports.node,
apiKey: 'foo',
timeout: 120000
});
await nclient.open();
for (let i = 0; i < 10; i++) {
const hash = await nclient.execute('getbestblockhash');
await nclient.execute('invalidateblock', [hash]);
}
await forValue(node.chain, 'height', 150);
const blocks = await nclient.execute(
'generatetoaddress', [20, vectors[0].addr]);
assert.equal(blocks.length, 20);
await forValue(node.chain, 'height', 170);
} finally {
if (nclient)
await nclient.close();
if (node)
await node.close();
}
try {
// Now turn the indexer back on and check that it
// is able to disconnect blocks and add the new blocks.
node = new FullNode({
prefix: prefix,
network: 'regtest',
apiKey: 'foo',
memory: false,
indexTX: true,
indexAddress: false,
port: ports.p2p,
httpPort: ports.node,
logLevel: 'none'
});
await node.ensure();
await node.open();
assert(node.txindex);
assert.equal(node.txindex.height, 160);
node.txindex.sync();
await forValue(node.txindex, 'height', 170, 5000);
} finally {
if (node)
await node.close();
}
});
it('will reset indexes', async () => {
let node, nclient = null;
try {
node = new FullNode({
prefix: prefix,
network: 'regtest',
apiKey: 'foo',
memory: false,
indexTX: true,
indexAddress: false,
port: ports.p2p,
httpPort: ports.node,
logLevel: 'none'
});
await node.ensure();
await node.open();
nclient = new NodeClient({
port: ports.node,
apiKey: 'foo',
timeout: 120000
});
await nclient.open();
const blocks = await nclient.execute(
'generatetoaddress', [150, vectors[0].addr]);
assert.equal(blocks.length, 150);
await forValue(node.txindex, 'height', 150);
await node.chain.reset(0);
await forValue(node.txindex, 'height', 1);
} finally {
if (nclient)
await nclient.close();
if (node)
await node.close();
}
});
it('will not index if pruned', async () => {
let err = null;
try {
new FullNode({
prefix: prefix,
network: 'regtest',
apiKey: 'foo',
memory: false,
prune: true,
indexTX: true,
indexAddress: true,
port: ports.p2p,
httpPort: ports.node
});
} catch (e) {
err = e;
}
assert(err);
assert.equal(err.message, 'Can not index while pruned.');
});
it('will not index if spv', async () => {
const node = new SPVNode({
prefix: prefix,
network: 'regtest',
apiKey: 'foo',
memory: false,
indexTX: true,
indexAddress: true,
port: ports.p2p,
httpPort: ports.node
});
assert.equal(node.txindex, undefined);
assert.equal(node.addrindex, undefined);
});
});
});
describe('HTTP', function() {
this.timeout(120000);
let node, nclient, wclient = null;
const confirmed = [];
const unconfirmed = [];
function sanitize(txs) {
return txs.map((tx) => {
// Remove mtime from the results for deep
// comparisons as it can be variable.
delete tx.mtime;
return tx;
});
}
before(async () => {
this.timeout(120000);
// Setup a testing node with txindex and addrindex
// both enabled.
node = new FullNode({
network: 'regtest',
apiKey: 'foo',
walletAuth: true,
memory: true,
workers: true,
indexTX: true,
indexAddress: true,
port: ports.p2p,
httpPort: ports.node,
plugins: [require('../lib/wallet/plugin')],
env: {
'BCOIN_WALLET_HTTP_PORT': ports.wallet.toString()
}
});
await node.open();
// Setup the node client to make calls to the node
// to generate blocks and other tasks.
nclient = new NodeClient({
port: ports.node,
apiKey: 'foo',
timeout: 120000
});
await nclient.open();
// Setup a test wallet to generate transactions for
// testing various scenarios.
wclient = new WalletClient({
port: ports.wallet,
apiKey: 'foo',
timeout: 120000
});
await wclient.open();
// Generate initial set of transactions and
// send the coinbase to alice.
const coinbase = await wclient.execute(
'getnewaddress', ['default']);
const blocks = await nclient.execute(
'generatetoaddress', [150, coinbase]);
assert.equal(blocks.length, 150);
// Send to the vector addresses for several blocks.
for (let i = 0; i < 10; i++) {
for (const v of vectors) {
const txid = await wclient.execute(
'sendtoaddress', [v.addr, v.amount]);
confirmed.push(txid);
}
const blocks = await nclient.execute(
'generatetoaddress', [1, coinbase]);
assert.equal(blocks.length, 1);
}
await forValue(node.chain, 'height', 160);
// Send unconfirmed to the vector addresses.
for (let i = 0; i < 5; i++) {
for (const v of vectors) {
const txid = await wclient.execute(
'sendtoaddress', [v.addr, v.amount]);
unconfirmed.push(txid);
}
}
await forValue(node.mempool.map, 'size', 20);
});
after(async () => {
await nclient.close();
await wclient.close();
await node.close();
});
for (const v of vectors) {
it(`txs by ${v.label} addr`, async () => {
const res = await nclient.request(
'GET', `/tx/address/${v.addr}`, {});
assert.equal(res.length, 15);
for (let i = 0; i < 10; i++)
assert(confirmed.includes(res[i].hash));
for (let i = 10; i < 15; i++)
assert(unconfirmed.includes(res[i].hash));
});
it(`txs by ${v.label} addr (limit)`, async () => {
const res = await nclient.request(
'GET', `/tx/address/${v.addr}`, {limit: 3});
assert.equal(res.length, 3);
for (const tx of res)
assert(confirmed.includes(tx.hash));
});
it(`txs by ${v.label} addr (limit w/ unconf)`, async () => {
const res = await nclient.request(
'GET', `/tx/address/${v.addr}`, {limit: 11});
assert.equal(res.length, 11);
for (let i = 0; i < 10; i++)
assert(confirmed.includes(res[i].hash));
for (let i = 10; i < 11; i++)
assert(unconfirmed.includes(res[i].hash));
});
it(`txs by ${v.label} addr (reverse)`, async () => {
const asc = await nclient.request(
'GET', `/tx/address/${v.addr}`, {reverse: false});
assert.equal(asc.length, 15);
const dsc = await nclient.request(
'GET', `/tx/address/${v.addr}`, {reverse: true});
assert.equal(dsc.length, 15);
for (let i = 0; i < 10; i++)
assert(confirmed.includes(asc[i].hash));
for (let i = 10; i < 15; i++)
assert(unconfirmed.includes(asc[i].hash));
// Check the the results are reverse
// of each other.
for (let i = 0; i < dsc.length; i++) {
const atx = asc[i];
const dtx = dsc[dsc.length - i - 1];
assert.equal(atx.hash, dtx.hash);
}
});
it(`txs by ${v.label} addr (after)`, async () => {
const one = await nclient.request(
'GET', `/tx/address/${v.addr}`, {limit: 3});
assert.strictEqual(one.length, 3);
for (let i = 0; i < 3; i++)
assert(confirmed.includes(one[i].hash));
// The after hash is within the
// confirmed transactions.
const hash = one[2].hash;
const two = await nclient.request(
'GET', `/tx/address/${v.addr}`, {after: hash, limit: 3});
assert.strictEqual(one.length, 3);
const all = await nclient.request(
'GET', `/tx/address/${v.addr}`, {limit: 6});
assert.strictEqual(one.length, 3);
assert.deepEqual(sanitize(one.concat(two)), sanitize(all));
});
it(`txs by ${v.label} addr (after w/ unconf)`, async () => {
const one = await nclient.request(
'GET', `/tx/address/${v.addr}`, {limit: 11});
assert.strictEqual(one.length, 11);
for (let i = 0; i < 10; i++)
assert(confirmed.includes(one[i].hash));
for (let i = 10; i < 11; i++)
assert(unconfirmed.includes(one[i].hash));
// The after hash is within the
// unconfirmed transactions.
const hash = one[10].hash;
const two = await nclient.request(
'GET', `/tx/address/${v.addr}`, {after: hash, limit: 1});
assert.strictEqual(two.length, 1);
assert(unconfirmed.includes(two[0].hash));
const all = await nclient.request(
'GET', `/tx/address/${v.addr}`, {limit: 12});
assert.strictEqual(all.length, 12);
assert.deepEqual(sanitize(one.concat(two)), sanitize(all));
});
it(`txs by ${v.label} addr (after w/ unconf 2)`, async () => {
const one = await nclient.request(
'GET', `/tx/address/${v.addr}`, {limit: 12});
assert.strictEqual(one.length, 12);
for (let i = 0; i < 10; i++)
assert(confirmed.includes(one[i].hash));
for (let i = 10; i < 12; i++)
assert(unconfirmed.includes(one[i].hash));
const hash = one[11].hash;
const two = await nclient.request(
'GET', `/tx/address/${v.addr}`, {after: hash, limit: 10});
assert.strictEqual(two.length, 3);
for (let i = 0; i < 3; i++)
assert(unconfirmed.includes(two[i].hash));
const all = await nclient.request(
'GET', `/tx/address/${v.addr}`, {limit: 100});
assert.strictEqual(all.length, 15);
assert.deepEqual(sanitize(one.concat(two)), sanitize(all));
});
it(`txs by ${v.label} addr (after w/ unconf 3)`, async () => {
const one = await nclient.request(
'GET', `/tx/address/${v.addr}`, {limit: 13});
assert.strictEqual(one.length, 13);
for (let i = 0; i < 10; i++)
assert(confirmed.includes(one[i].hash));
for (let i = 10; i < 13; i++)
assert(unconfirmed.includes(one[i].hash));
const hash = one[12].hash;
const two = await nclient.request(
'GET', `/tx/address/${v.addr}`, {after: hash, limit: 1});
assert.strictEqual(two.length, 1);
assert(unconfirmed.includes(two[0].hash));
const all = await nclient.request(
'GET', `/tx/address/${v.addr}`, {limit: 14});
assert.strictEqual(all.length, 14);
assert.deepEqual(sanitize(one.concat(two)), sanitize(all));
});
it(`txs by ${v.label} addr (after, reverse)`, async () => {
const one = await nclient.request(
'GET', `/tx/address/${v.addr}`,
{limit: 8, reverse: true});
assert.strictEqual(one.length, 8);
for (let i = 0; i < 5; i++)
assert(unconfirmed.includes(one[i].hash));
for (let i = 5; i < 8; i++)
assert(confirmed.includes(one[i].hash));
// The after hash is within the
// confirmed transactions.
const hash = one[7].hash;
const two = await nclient.request(
'GET', `/tx/address/${v.addr}`,
{after: hash, limit: 3, reverse: true});
assert.strictEqual(two.length, 3);
for (let i = 0; i < 3; i++)
assert(confirmed.includes(two[i].hash));
const all = await nclient.request(
'GET', `/tx/address/${v.addr}`,
{limit: 11, reverse: true});
assert.strictEqual(all.length, 11);
for (let i = 0; i < 5; i++)
assert(unconfirmed.includes(all[i].hash));
for (let i = 5; i < 11; i++)
assert(confirmed.includes(all[i].hash));
assert.deepEqual(sanitize(one.concat(two)), sanitize(all));
});
it(`txs by ${v.label} addr (after, reverse w/ unconf)`, async () => {
const one = await nclient.request(
'GET', `/tx/address/${v.addr}`,
{limit: 5, reverse: true});
assert.strictEqual(one.length, 5);
for (let i = 0; i < 5; i++)
assert(unconfirmed.includes(one[i].hash));
// The after hash is within the
// unconfirmed transactions.
const hash = one[4].hash;
const two = await nclient.request(
'GET', `/tx/address/${v.addr}`,
{after: hash, limit: 3, reverse: true});
assert.strictEqual(two.length, 3);
for (let i = 0; i < 3; i++)
assert(confirmed.includes(two[i].hash));
const all = await nclient.request(
'GET', `/tx/address/${v.addr}`,
{limit: 8, reverse: true});
assert.strictEqual(all.length, 8);
for (let i = 0; i < 5; i++)
assert(unconfirmed.includes(all[i].hash));
for (let i = 5; i < 8; i++)
assert(confirmed.includes(all[i].hash));
assert.deepEqual(sanitize(one.concat(two)), sanitize(all));
});
it(`txs by ${v.label} addr (after, reverse w/ unconf 2)`, async () => {
const one = await nclient.request(
'GET', `/tx/address/${v.addr}`,
{limit: 3, reverse: true});
assert.strictEqual(one.length, 3);
for (let i = 0; i < 3; i++)
assert(unconfirmed.includes(one[i].hash));
const hash = one[2].hash;
const two = await nclient.request(
'GET', `/tx/address/${v.addr}`,
{after: hash, limit: 1, reverse: true});
assert.strictEqual(two.length, 1);
assert(unconfirmed.includes(two[0].hash));
const all = await nclient.request(
'GET', `/tx/address/${v.addr}`,
{limit: 4, reverse: true});
assert.strictEqual(all.length, 4);
for (let i = 0; i < 4; i++)
assert(unconfirmed.includes(all[i].hash));
assert.deepEqual(sanitize(one.concat(two)), sanitize(all));
});
}
describe('Errors', function() {
it('will give error if limit is exceeded', async () => {
await assert.asyncThrows(async () => {
await nclient.request(
'GET', `/tx/address/${vectors[0].addr}`, {limit: 101});
}, 'Limit above max');
});
it('will give error with invalid after hash', async () => {
await assert.asyncThrows(async () => {
await nclient.request(
'GET', `/tx/address/${vectors[0].addr}`, {after: 'deadbeef'});
});
});
it('will give error with invalid reverse', async () => {
await assert.asyncThrows(async () => {
await nclient.request(
'GET', `/tx/address/${vectors[0].addr}`, {reverse: 'sure'});
});
});
});
});
});