From 2083050eb2218a47337a00076a6daba4b4bc5184 Mon Sep 17 00:00:00 2001 From: Matthew Zipkin Date: Fri, 25 Mar 2022 11:16:47 -0400 Subject: [PATCH] pool/wallet/spvnode: test imported names are added to filter and sent --- lib/net/pool.js | 9 ++++ lib/wallet/nodeclient.js | 4 ++ test/node-spv-test.js | 93 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 106 insertions(+) create mode 100644 test/node-spv-test.js diff --git a/lib/net/pool.js b/lib/net/pool.js index a280b3c3..b097b114 100644 --- a/lib/net/pool.js +++ b/lib/net/pool.js @@ -3661,6 +3661,15 @@ class Pool extends EventEmitter { this.watch(outpoint.encode()); } + /** + * Add a nameHash to the bloom filter (SPV-only). + * @param {Hash} nameHash + */ + + watchName(nameHash) { + this.watch(nameHash); + } + /** * Send `getblocks` to peer after building * locator and resolving orphan root. diff --git a/lib/wallet/nodeclient.js b/lib/wallet/nodeclient.js index 06d61e6e..7144c307 100644 --- a/lib/wallet/nodeclient.js +++ b/lib/wallet/nodeclient.js @@ -172,6 +172,10 @@ class NodeClient extends AsyncEmitter { */ async addFilter(data) { + // `data` is ignored because pool.spvFilter === walletDB.filter + // and therefore is already updated. + // Argument is kept here to be consistent with API in + // wallet/client.js (hs-client NodeClient) and wallet/nullclient.js this.node.pool.queueFilterLoad(); } diff --git a/test/node-spv-test.js b/test/node-spv-test.js new file mode 100644 index 00000000..d925ff54 --- /dev/null +++ b/test/node-spv-test.js @@ -0,0 +1,93 @@ +'use strict'; + +const assert = require('bsert'); +const SPVNode = require('../lib/node/spvnode'); +const rules = require('../lib/covenants/rules'); + +describe('SPV Node', function() { + describe('Filter update', function() { + const node = new SPVNode({ + memory: true, + network: 'regtest', + plugins: [require('../lib/wallet/plugin')] + }); + + const pool = node.pool; + const {wdb} = node.require('walletdb'); + let wallet; + + const name1 = 'control'; // never add + const hash1 = rules.hashName(name1); + const name2 = 'lettuce'; + const hash2 = rules.hashName(name2); + const name3 = 'tomato'; + const hash3 = rules.hashName(name3); + + // This function normally calls + // peer.sendFilterLoad(this.spvFilter) + // for each peer in pool. + // This stub hands us the filter directly without any p2p connections. + pool.sendFilterLoad = () => { + pool.emit('filter load', pool.spvFilter); + }; + + before(async () => { + await node.open(); + wallet = await wdb.get('primary'); + }); + + after(async () => { + await node.close(); + }); + + it('should test false for all names', () => { + assert(!wdb.filter.test(hash1)); + assert(!wdb.filter.test(hash2)); + assert(!wdb.filter.test(hash3)); + assert(!pool.spvFilter.test(hash1)); + assert(!pool.spvFilter.test(hash2)); + assert(!pool.spvFilter.test(hash3)); + }); + + it('should import name (wallet) and update filter', async () => { + const waiter = new Promise((resolve) => { + pool.once('filter load', (filter) => { + resolve(filter); + }); + }); + await wallet.importName(name2); + + // WalletDB filter has added name + assert(!wdb.filter.test(hash1)); + assert(wdb.filter.test(hash2)); + assert(!wdb.filter.test(hash3)); + + // Filter sent from pool has also added name + const filter = await waiter; + assert(!filter.test(hash1)); + assert(filter.test(hash2)); + assert(!filter.test(hash3)); + }); + + it('should watch name (pool) and update filter again', async () => { + const waiter = new Promise((resolve) => { + pool.once('filter load', (filter) => { + resolve(filter); + }); + }); + await pool.watchName(hash3); + + // Filter sent from pool has added name + const filter = await waiter; + assert(!filter.test(hash1)); + assert(filter.test(hash2)); + assert(filter.test(hash3)); + + // ...so has walletDB filter + // (seems backwards, but the filters are the same literal object) + assert(!wdb.filter.test(hash1)); + assert(wdb.filter.test(hash2)); + assert(wdb.filter.test(hash3)); + }); + }); +});