From b4be8574e55a18ed9e8afe66ec98cc6e8e1474c8 Mon Sep 17 00:00:00 2001 From: Braydon Fuller Date: Thu, 18 Apr 2019 10:12:04 -0700 Subject: [PATCH] test: improve addrindex tests - Vectors for p2wsh and p2sh. - Edge case for witness program. - Improve determinism by not comparing mtime. - Various edge and error cases. --- lib/indexer/addrindexer.js | 30 +++- lib/mempool/addrindexer.js | 235 ++++++++++++++++++++++++++ lib/mempool/mempool.js | 213 +----------------------- test/indexer-test.js | 333 ++++++++++++++++++++++++++++++------- test/mempool-test.js | 36 ++++ test/util/assert.js | 7 +- 6 files changed, 579 insertions(+), 275 deletions(-) create mode 100644 lib/mempool/addrindexer.js diff --git a/lib/indexer/addrindexer.js b/lib/indexer/addrindexer.js index 6f463c07..192581ef 100644 --- a/lib/indexer/addrindexer.js +++ b/lib/indexer/addrindexer.js @@ -138,8 +138,7 @@ class AddrIndexer extends Indexer { const hash = tx.hash(); const count = new Count(height, i); - this.put(layout.C.encode(height, i), hash); - this.put(layout.c.encode(hash), count.toRaw()); + let hasAddress = false; for (const addr of tx.getAddresses(view)) { const prefix = addr.getPrefix(); @@ -150,6 +149,13 @@ class AddrIndexer extends Indexer { const addrHash = addr.getHash(); this.put(layout.A.encode(prefix, addrHash, height, i), null); + + hasAddress = true; + } + + if (hasAddress) { + this.put(layout.C.encode(height, i), hash); + this.put(layout.c.encode(hash), count.toRaw()); } } } @@ -169,8 +175,7 @@ class AddrIndexer extends Indexer { const tx = block.txs[i]; const hash = tx.hash(); - this.del(layout.C.encode(height, i)); - this.del(layout.c.encode(hash)); + let hasAddress = false; for (const addr of tx.getAddresses(view)) { const prefix = addr.getPrefix(); @@ -181,6 +186,13 @@ class AddrIndexer extends Indexer { const addrHash = addr.getHash(); this.del(layout.A.encode(prefix, addrHash, height, i)); + + hasAddress = true; + } + + if (hasAddress) { + this.del(layout.C.encode(height, i)); + this.del(layout.c.encode(hash)); } } } @@ -222,13 +234,19 @@ class AddrIndexer extends Indexer { } }; + // Determine if the hash -> height + index mapping exists. const hasAfter = (after && await this.db.has(layout.c.encode(after))); - const skip = (after && !hasAfter && !reverse); + // Check to see if results should be skipped because + // the after hash is expected to be within a following + // mempool query. + const skip = (after && !hasAfter && !reverse); if (skip) return []; if (after && hasAfter) { + // Give results starting from after + // the tx hash for the address. const raw = await this.db.get(layout.c.encode(after)); const count = Count.fromRaw(raw); const {height, index} = count; @@ -241,6 +259,8 @@ class AddrIndexer extends Indexer { opts.lt = layout.A.max(prefix, hash, height, index); } } else { + // Give earliest or latest results + // for the address. opts.gte = layout.A.min(prefix, hash); opts.lte = layout.A.max(prefix, hash); } diff --git a/lib/mempool/addrindexer.js b/lib/mempool/addrindexer.js new file mode 100644 index 00000000..e28353b6 --- /dev/null +++ b/lib/mempool/addrindexer.js @@ -0,0 +1,235 @@ +/*! + * mempool.js - mempool for bcoin + * Copyright (c) 2018-2019, the bcoin developers (MIT License). + * Copyright (c) 2014-2017, Christopher Jeffrey (MIT License). + * https://github.com/bcoin-org/bcoin + */ + +'use strict'; + +const assert = require('bsert'); +const {BufferMap} = require('buffer-map'); +const TXMeta = require('../primitives/txmeta'); + +/** + * Address Indexer + * @ignore + */ + +class AddrIndexer { + /** + * Create TX address index. + * @constructor + */ + + constructor() { + // Map of addr->entries. + this.index = new BufferMap(); + + // Map of txid->addrs. + this.map = new BufferMap(); + } + + reset() { + this.index.clear(); + this.map.clear(); + } + + getKey(addr) { + const prefix = addr.getPrefix(); + + if (prefix < 0) + return null; + + const raw = Buffer.allocUnsafe(1); + raw.writeUInt8(prefix); + + return Buffer.concat([raw, addr.getHash()]); + } + + /** + * Get transactions by address. + * @param {Address} addr + * @param {Object} options + * @param {Number} options.limit + * @param {Number} options.reverse + * @param {Buffer} options.after + */ + + get(addr, options = {}) { + const values = this.getEntries(addr, options); + + const out = []; + + for (const entry of values) + out.push(entry.tx); + + return out; + } + + /** + * Get transaction meta by address. + * @param {Address} addr + * @param {Object} options + * @param {Number} options.limit + * @param {Number} options.reverse + * @param {Buffer} options.after + */ + + getMeta(addr, options = {}) { + const values = this.getEntries(addr, options); + + const out = []; + + for (const entry of values) { + const meta = TXMeta.fromTX(entry.tx); + meta.mtime = entry.time; + out.push(meta); + } + + return out; + } + + /** + * Get entries by address. + * @param {Address} addr + * @param {Object} options + * @param {Number} options.limit + * @param {Number} options.reverse + * @param {Buffer} options.after + */ + + getEntries(addr, options = {}) { + const {limit, reverse, after} = options; + const key = this.getKey(addr); + + if (!key) + return []; + + const items = this.index.get(key); + + if (!items) + return []; + + let values = []; + + // Check to see if results should be skipped because + // the after hash is expected to be within a following + // confirmed query. + const skip = (after && !items.has(after) && reverse); + + if (skip) + return values; + + if (after && items.has(after)) { + // Give results starting from after + // the tx hash for the address. + let index = 0; + + for (const k of items.keys()) { + if (k.compare(after) === 0) + break; + index += 1; + } + + values = Array.from(items.values()); + + let start = index + 1; + let end = values.length; + + if (end - start > limit) + end = start + limit; + + if (reverse) { + start = 0; + end = index; + + if (end > limit) + start = end - limit; + } + + values = values.slice(start, end); + } else { + // Give earliest or latest results + // for the address. + values = Array.from(items.values()); + + if (values.length > limit) { + let start = 0; + let end = limit; + + if (reverse) { + start = values.length - limit; + end = values.length; + } + + values = values.slice(start, end); + } + } + + if (reverse) + values.reverse(); + + return values; + } + + insert(entry, view) { + const tx = entry.tx; + const hash = tx.hash(); + const addrs = tx.getAddresses(view); + + if (addrs.length === 0) + return; + + for (const addr of addrs) { + const key = this.getKey(addr); + + if (!key) + continue; + + let items = this.index.get(key); + + if (!items) { + items = new BufferMap(); + this.index.set(key, items); + } + + assert(!items.has(hash)); + items.set(hash, entry); + } + + this.map.set(hash, addrs); + } + + remove(hash) { + const addrs = this.map.get(hash); + + if (!addrs) + return; + + for (const addr of addrs) { + const key = this.getKey(addr); + + if (!key) + continue; + + const items = this.index.get(key); + + assert(items); + assert(items.has(hash)); + + items.delete(hash); + + if (items.size === 0) + this.index.delete(key); + } + + this.map.delete(hash); + } +} + +/* + * Expose + */ + +module.exports = AddrIndexer; diff --git a/lib/mempool/mempool.js b/lib/mempool/mempool.js index 7410a5d0..5492a9c1 100644 --- a/lib/mempool/mempool.js +++ b/lib/mempool/mempool.js @@ -27,6 +27,7 @@ const TXMeta = require('../primitives/txmeta'); const MempoolEntry = require('./mempoolentry'); const Network = require('../protocol/network'); const layout = require('./layout'); +const AddrIndexer = require('./addrindexer'); const Fees = require('./fees'); const CoinView = require('../coins/coinview'); @@ -72,7 +73,7 @@ class Mempool extends EventEmitter { this.spents = new BufferMap(); this.rejects = new RollingFilter(120000, 0.000001); - this.addrindex = new AddrIndex(); + this.addrindex = new AddrIndexer(); } /** @@ -2118,216 +2119,6 @@ class MempoolOptions { } } -/** - * Address Index - * @ignore - */ - -class AddrIndex { - /** - * Create TX address index. - * @constructor - */ - - constructor() { - // Map of addr->entries. - this.index = new BufferMap(); - - // Map of txid->addrs. - this.map = new BufferMap(); - } - - reset() { - this.index.clear(); - this.map.clear(); - } - - getKey(addr) { - const prefix = addr.getPrefix(); - - if (prefix < 0) - return null; - - const raw = Buffer.allocUnsafe(1); - raw.writeUInt8(prefix); - - return Buffer.concat([raw, addr.getHash()]); - } - - /** - * Get transactions by address. - * @param {Address} addr - * @param {Object} options - * @param {Number} options.limit - * @param {Number} options.reverse - * @param {Buffer} options.after - */ - - get(addr, options = {}) { - const values = this.getEntries(addr, options); - - const out = []; - - for (const entry of values) - out.push(entry.tx); - - return out; - } - - /** - * Get transaction meta by address. - * @param {Address} addr - * @param {Object} options - * @param {Number} options.limit - * @param {Number} options.reverse - * @param {Buffer} options.after - */ - - getMeta(addr, options = {}) { - const values = this.getEntries(addr, options); - - const out = []; - - for (const entry of values) { - const meta = TXMeta.fromTX(entry.tx); - meta.mtime = entry.time; - out.push(meta); - } - - return out; - } - - /** - * Get entries by address. - * @param {Address} addr - * @param {Object} options - * @param {Number} options.limit - * @param {Number} options.reverse - * @param {Buffer} options.after - */ - - getEntries(addr, options = {}) { - const {limit, reverse, after} = options; - const key = this.getKey(addr); - - if (!key) - return []; - - const items = this.index.get(key); - - if (!items) - return []; - - let values = []; - - const skip = (after && !items.has(after) && reverse); - - if (skip) - return values; - - if (after && items.has(after)) { - let index = 0; - - for (const k of items.keys()) { - if (k.compare(after) === 0) - break; - index += 1; - } - - values = Array.from(items.values()); - - let start = index + 1; - let end = values.length; - - if (end - start > limit) - end = start + limit; - - if (reverse) { - start = 0; - end = index; - - if (end - start > limit) - start = end - limit; - } - - values = values.slice(start, end); - } else { - values = Array.from(items.values()); - - if (values.length > limit) { - let start = 0; - let end = limit; - - if (reverse) { - start = values.length - limit; - end = values.length; - } - - values = values.slice(start, end); - } - } - - if (reverse) - values.reverse(); - - return values; - } - - insert(entry, view) { - const tx = entry.tx; - const hash = tx.hash(); - const addrs = tx.getAddresses(view); - - if (addrs.length === 0) - return; - - for (const addr of addrs) { - const key = this.getKey(addr); - - if (!key) - continue; - - let items = this.index.get(key); - - if (!items) { - items = new BufferMap(); - this.index.set(key, items); - } - - assert(!items.has(hash)); - items.set(hash, entry); - } - - this.map.set(hash, addrs); - } - - remove(hash) { - const addrs = this.map.get(hash); - - if (!addrs) - return; - - for (const addr of addrs) { - const key = this.getKey(addr); - - if (!key) - continue; - - const items = this.index.get(key); - - assert(items); - assert(items.has(hash)); - - items.delete(hash); - - if (items.size === 0) - this.index.delete(key); - } - - this.map.delete(hash); - } -} - /** * Orphan * @ignore diff --git a/test/indexer-test.js b/test/indexer-test.js index e2600302..7249a160 100644 --- a/test/indexer-test.js +++ b/test/indexer-test.js @@ -5,6 +5,9 @@ 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 Chain = require('../lib/blockchain/chain'); const WorkerPool = require('../lib/workers/workerpool'); const Miner = require('../lib/mining/miner'); @@ -18,6 +21,34 @@ const network = Network.get('regtest'); const {NodeClient, WalletClient} = require('bclient'); const {forValue} = require('./util/common'); +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 }); @@ -79,7 +110,89 @@ describe('Indexer', function() { await addrindexer.close(); }); - describe('index 10 blocks', function() { + describe('Unit', function() { + 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 () => { @@ -189,7 +302,7 @@ describe('Indexer', function() { }); }); - describe('rescan and reorg', function() { + describe('Rescan and reorg', function() { it('should rescan and reindex 10 missed blocks', async () => { for (let i = 0; i < 10; i++) { const block = await cpu.mineBlock(); @@ -226,26 +339,11 @@ describe('Indexer', function() { }); }); - describe('http', function() { + describe('HTTP', function() { this.timeout(120000); let node, nclient, wclient = null; - const vectors = [ - // Secret for the vectors: - // cVDJUtDjdaM25yNVVDLLX3hcHUfth4c7tY3rSc4hy9e8ibtCuj6G - { - addr: 'bcrt1qngw83fg8dz0k749cg7k3emc7v98wy0c7azaa6h', - amount: 19.99, - label: 'p2wpkh' - }, - { - addr: 'muZpTpBYhxmRFuCjLc7C6BBDF32C8XVJUi', - amount: 1.99, - label: 'p2pkh' - } - ]; - const confirmed = []; const unconfirmed = []; @@ -255,6 +353,15 @@ describe('Indexer', function() { wallet: 49333 }; + 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); @@ -326,7 +433,7 @@ describe('Indexer', function() { await forValue(node.chain, 'height', 160); // Send unconfirmed to the vector addresses. - for (let i = 0; i < 3; i++) { + for (let i = 0; i < 5; i++) { for (const v of vectors) { const txid = await wclient.execute( 'sendtoaddress', [v.addr, v.amount]); @@ -335,7 +442,7 @@ describe('Indexer', function() { } } - await forValue(node.mempool.map, 'size', 6); + await forValue(node.mempool.map, 'size', 20); }); after(async () => { @@ -345,20 +452,20 @@ describe('Indexer', function() { }); for (const v of vectors) { - it(`txs by ${v.label} address`, async () => { + it(`txs by ${v.label} addr`, async () => { const res = await nclient.request( 'GET', `/tx/address/${v.addr}`, {}); - assert.equal(res.length, 13); + assert.equal(res.length, 15); for (let i = 0; i < 10; i++) assert(confirmed.includes(res[i].hash)); - for (let i = 10; i < 13; i++) + for (let i = 10; i < 15; i++) assert(unconfirmed.includes(res[i].hash)); }); - it(`txs by ${v.label} address (limit)`, async () => { + it(`txs by ${v.label} addr (limit)`, async () => { const res = await nclient.request( 'GET', `/tx/address/${v.addr}`, {limit: 3}); @@ -368,7 +475,7 @@ describe('Indexer', function() { assert(confirmed.includes(tx.hash)); }); - it(`txs by ${v.label} address (limit w/ unconf)`, async () => { + it(`txs by ${v.label} addr (limit w/ unconf)`, async () => { const res = await nclient.request( 'GET', `/tx/address/${v.addr}`, {limit: 11}); @@ -381,21 +488,21 @@ describe('Indexer', function() { assert(unconfirmed.includes(res[i].hash)); }); - it(`txs by ${v.label} address (reverse)`, async () => { + it(`txs by ${v.label} addr (reverse)`, async () => { const asc = await nclient.request( 'GET', `/tx/address/${v.addr}`, {reverse: false}); - assert.equal(asc.length, 13); + assert.equal(asc.length, 15); const dsc = await nclient.request( 'GET', `/tx/address/${v.addr}`, {reverse: true}); - assert.equal(asc.length, 13); + assert.equal(dsc.length, 15); for (let i = 0; i < 10; i++) assert(confirmed.includes(asc[i].hash)); - for (let i = 10; i < 13; i++) + for (let i = 10; i < 15; i++) assert(unconfirmed.includes(asc[i].hash)); // Check the the results are reverse @@ -407,11 +514,16 @@ describe('Indexer', function() { } }); - it(`txs by ${v.label} address (after)`, async () => { + 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( @@ -422,10 +534,10 @@ describe('Indexer', function() { 'GET', `/tx/address/${v.addr}`, {limit: 6}); assert.strictEqual(one.length, 3); - assert.deepEqual(one.concat(two), all); + assert.deepEqual(sanitize(one.concat(two)), sanitize(all)); }); - it(`txs by ${v.label} address (after w/ unconf)`, async () => { + 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); @@ -449,24 +561,113 @@ describe('Indexer', function() { 'GET', `/tx/address/${v.addr}`, {limit: 12}); assert.strictEqual(all.length, 12); - assert.deepEqual(one.concat(two), all); + assert.deepEqual(sanitize(one.concat(two)), sanitize(all)); }); - it(`txs by ${v.label} address (after, reverse)`, async () => { + 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 < 3; i++) + for (let i = 0; i < 5; i++) assert(unconfirmed.includes(one[i].hash)); - for (let i = 3; i < 5; i++) - assert(confirmed.includes(one[i].hash)); - // The after hash is within the - // confirmed transactions. + // unconfirmed transactions. const hash = one[4].hash; const two = await nclient.request( @@ -474,6 +675,7 @@ describe('Indexer', function() { {after: hash, limit: 3, reverse: true}); assert.strictEqual(two.length, 3); + for (let i = 0; i < 3; i++) assert(confirmed.includes(two[i].hash)); @@ -483,16 +685,16 @@ describe('Indexer', function() { assert.strictEqual(all.length, 8); - for (let i = 0; i < 3; i++) + for (let i = 0; i < 5; i++) assert(unconfirmed.includes(all[i].hash)); - for (let i = 3; i < 8; i++) + for (let i = 5; i < 8; i++) assert(confirmed.includes(all[i].hash)); - assert.deepEqual(one.concat(two), all); + assert.deepEqual(sanitize(one.concat(two)), sanitize(all)); }); - it(`txs by ${v.label} address (after, reverse w/ unconf)`, async () => { + 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}); @@ -501,32 +703,49 @@ describe('Indexer', function() { for (let i = 0; i < 3; i++) assert(unconfirmed.includes(one[i].hash)); - // The after hash is within the - // unconfirmed transactions. const hash = one[2].hash; const two = await nclient.request( 'GET', `/tx/address/${v.addr}`, - {after: hash, limit: 3, reverse: true}); + {after: hash, limit: 1, reverse: true}); - assert.strictEqual(two.length, 3); - for (let i = 0; i < 3; i++) - assert(confirmed.includes(two[i].hash)); + assert.strictEqual(two.length, 1); + assert(unconfirmed.includes(two[0].hash)); const all = await nclient.request( 'GET', `/tx/address/${v.addr}`, - {limit: 6, reverse: true}); + {limit: 4, reverse: true}); - assert.strictEqual(all.length, 6); + assert.strictEqual(all.length, 4); - for (let i = 0; i < 3; i++) + for (let i = 0; i < 4; i++) assert(unconfirmed.includes(all[i].hash)); - for (let i = 3; i < 6; i++) - assert(confirmed.includes(all[i].hash)); - - assert.deepEqual(one.concat(two), all); + 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'}); + }); + }); + }); }); }); diff --git a/test/mempool-test.js b/test/mempool-test.js index 0f02c023..dbe19b37 100644 --- a/test/mempool-test.js +++ b/test/mempool-test.js @@ -9,6 +9,7 @@ const common = require('../lib/blockchain/common'); const Block = require('../lib/primitives/block'); const MempoolEntry = require('../lib/mempool/mempoolentry'); const Mempool = require('../lib/mempool/mempool'); +const AddrIndexer = require('../lib/mempool/addrindexer'); const WorkerPool = require('../lib/workers/workerpool'); const Chain = require('../lib/blockchain/chain'); const MTX = require('../lib/primitives/mtx'); @@ -18,6 +19,7 @@ const Address = require('../lib/primitives/address'); const Outpoint = require('../lib/primitives/outpoint'); const Input = require('../lib/primitives/input'); const Script = require('../lib/script/script'); +const Opcode = require('../lib/script/opcode'); const opcodes = Script.opcodes; const Witness = require('../lib/script/witness'); const MemWallet = require('./util/memwallet'); @@ -780,6 +782,40 @@ describe('Mempool', function() { }); }); + describe('AddrIndexer', function () { + it('will not get key for witness program v1', function() { + const addrindex = new AddrIndexer(); + + // 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 key = addrindex.getKey(addr); + + assert.strictEqual(key, null); + }); + + it('will get key for witness program v0', function() { + const addrindex = new AddrIndexer(); + + // Create a witness program version 0 with + // 32 byte data push. + const script = new Script(); + script.push(Opcode.fromSmall(0)); + script.push(Opcode.fromData(Buffer.alloc(32))); + script.compile(); + const addr = Address.fromScript(script); + + const key = addrindex.getKey(addr); + + assert.bufferEqual(key, Buffer.from('0a' + '00'.repeat(32), 'hex')); + }); + }); + describe('Mempool persistent cache', function () { const workers = new WorkerPool({ enabled: true diff --git a/test/util/assert.js b/test/util/assert.js index e1bdcb3e..772a5270 100644 --- a/test/util/assert.js +++ b/test/util/assert.js @@ -115,8 +115,11 @@ assert.asyncThrows = async function asyncThrows(func, expectedError) { } catch (e) { err = e; } - const re = new RegExp('^' + expectedError); - assert(re.test(err.message)); + assert(err, 'Expected error.'); + if (expectedError) { + const re = new RegExp('^' + expectedError); + assert(re.test(err.message), err.message); + } }; function _isString(value, message, stackStartFunction) {