From 11d70432b3f1fd13875124dd0871a262adb7232c Mon Sep 17 00:00:00 2001 From: Matthew Zipkin Date: Tue, 26 Mar 2019 10:17:10 -0700 Subject: [PATCH 1/2] node-http: require filter to scan --- lib/node/http.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/node/http.js b/lib/node/http.js index 98279d96..dc25f8ff 100644 --- a/lib/node/http.js +++ b/lib/node/http.js @@ -655,6 +655,9 @@ class HTTP extends Server { */ async scan(socket, start) { + if (!socket.filter) + return null; + await this.node.scan(start, socket.filter, (entry, txs) => { const block = entry.toRaw(); const raw = []; From 2980b4e7394716a8faa72947cef1793735efe061 Mon Sep 17 00:00:00 2001 From: Matthew Zipkin Date: Tue, 26 Mar 2019 11:07:00 -0700 Subject: [PATCH 2/2] http-test: socket rescan --- test/http-test.js | 52 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/test/http-test.js b/test/http-test.js index a7a8214d..a5b85803 100644 --- a/test/http-test.js +++ b/test/http-test.js @@ -3,6 +3,7 @@ 'use strict'; +const {BloomFilter} = require('bfilter'); const assert = require('./util/assert'); const consensus = require('../lib/protocol/consensus'); const Address = require('../lib/primitives/address'); @@ -10,6 +11,7 @@ const Script = require('../lib/script/script'); const Outpoint = require('../lib/primitives/outpoint'); const MTX = require('../lib/primitives/mtx'); const FullNode = require('../lib/node/fullnode'); +const ChainEntry = require('../lib/blockchain/chainentry'); const pkg = require('../lib/pkg'); if (process.browser) @@ -52,6 +54,7 @@ const {wdb} = node.require('walletdb'); let addr = null; let hash = null; +let blocks = null; describe('HTTP', function() { this.timeout(15000); @@ -338,6 +341,55 @@ describe('HTTP', function() { } }); + it('should generate 10 blocks from RPC call', async () => { + blocks = await nclient.execute( + 'generatetoaddress', + [10, addr.toString('regtest')] + ); + assert.strictEqual(blocks.length, 10); + }); + + it('should initiate rescan from socket without a bloom filter', async () => { + // Rescan from height 5. Without a filter loaded = no response, but no error + const response = await nclient.call('rescan', 5); + assert.strictEqual(null, response); + }); + + it('should initiate rescan from socket WITH a bloom filter', async () => { + // Create an SPV-standard Bloom filter and add one of our wallet addresses + const filter = BloomFilter.fromRate(20000, 0.001, BloomFilter.flags.ALL); + const walletAddr = addr.toString('regtest'); + filter.add(walletAddr, 'ascii'); + + // Send Bloom filter to server + await nclient.call('set filter', filter.filter); + + // `rescan` commands the node server to check blocks against a bloom filter. + // When the server matches a transaction in a block to the filter, it + // sends a socket call BACK to the client with the ChainEntry of the block, + // and an array of matched transactions. Because of this callback, the + // CLIENT MUST have a `block rescan` hook in place or the server will throw. + const matchingBlocks = []; + nclient.hook('block rescan', (entry, txs) => { + // Coinbase transactions were mined to our watch address, matching filter. + assert.strictEqual(txs.length, 1); + const cbtx = MTX.fromRaw(txs[0]); + assert.strictEqual( + cbtx.outputs[0].getAddress().toString('regtest'), + walletAddr + ); + + // Blocks are returned as raw ChainEntry + matchingBlocks.push( + ChainEntry.fromRaw(entry).rhash().toString('hex') + ); + }); + + // Rescan from height 5 -- should return blocks 5 through 10, inclusive. + await nclient.call('rescan', 5); + assert.deepStrictEqual(matchingBlocks, blocks.slice(4)); + }); + it('should cleanup', async () => { consensus.COINBASE_MATURITY = 100; await wallet.close();