From 496e86605401003d63bcf7a90423b1627d915348 Mon Sep 17 00:00:00 2001 From: Manav Desai Date: Thu, 20 Jul 2023 22:57:46 +0530 Subject: [PATCH 1/2] feat: Neutrino Integration --- lib/wallet/client.js | 12 +++++++++++ lib/wallet/nodeclient.js | 19 +++++++++++++++++ lib/wallet/nullclient.js | 11 ++++++++++ lib/wallet/walletdb.js | 44 ++++++++++++++++++++++++++++++++++++++++ 4 files changed, 86 insertions(+) diff --git a/lib/wallet/client.js b/lib/wallet/client.js index 768f38e50..f60eebbb5 100644 --- a/lib/wallet/client.js +++ b/lib/wallet/client.js @@ -71,6 +71,18 @@ class WalletClient extends NodeClient { return super.setFilter(filter.toRaw()); } + /** + * Check filter against wallet key ring + * @param {WalletKey} ring + * @param {Filter} filter + * @returns {Promise} + */ + + async getBlockFromNode(hash) { + // return super.getBlockPeer(hash); + console.log('getBlockFromNode'); + } + async rescan(start) { if (Buffer.isBuffer(start)) start = util.revHex(start); diff --git a/lib/wallet/nodeclient.js b/lib/wallet/nodeclient.js index 9f6c43600..bb5bf098c 100644 --- a/lib/wallet/nodeclient.js +++ b/lib/wallet/nodeclient.js @@ -37,6 +37,13 @@ class NodeClient extends AsyncEmitter { init() { this.node.chain.on('connect', async (entry, block) => { + if (!this.opened || this.node.neutrino) + return; + + await this.emitAsync('block connect', entry, block.txs); + }); + + this.node.chain.on('getblockpeer', async (entry, block) => { if (!this.opened) return; @@ -50,6 +57,13 @@ class NodeClient extends AsyncEmitter { await this.emitAsync('block disconnect', entry); }); + this.node.pool.on('cfilter', async (blockHeight, filter) => { + if (!this.opened) + return; + + await this.emitAsync('cfilter', blockHeight, filter); + }); + this.node.on('tx', (tx) => { if (!this.opened) return; @@ -134,6 +148,11 @@ class NodeClient extends AsyncEmitter { return entry; } + async getBlockFromNode(hash) { + // await this.node.chain.getBlockPeer(hash); + console.log('getBlockFromNode'); + } + /** * Send a transaction. Do not wait for promise. * @param {TX} tx diff --git a/lib/wallet/nullclient.js b/lib/wallet/nullclient.js index 744629d4b..f08ca5d6d 100644 --- a/lib/wallet/nullclient.js +++ b/lib/wallet/nullclient.js @@ -130,6 +130,17 @@ class NullClient extends EventEmitter { this.wdb.emit('reset filter'); } + /** + * Check filter against wallet key ring + * @param {WalletKey} ring + * @param {Filter} filter + * @returns {Promise} + */ + + async getBlockFromNode(hash) { + ; + } + /** * Esimate smart fee. * @param {Number?} blocks diff --git a/lib/wallet/walletdb.js b/lib/wallet/walletdb.js index b1f57ebf0..31ef5e04c 100644 --- a/lib/wallet/walletdb.js +++ b/lib/wallet/walletdb.js @@ -26,6 +26,8 @@ const Outpoint = require('../primitives/outpoint'); const layouts = require('./layout'); const records = require('./records'); const NullClient = require('./nullclient'); +const Script = require('../script/script'); +const Address = require('../primitives/address'); const layout = layouts.wdb; const tlayout = layouts.txdb; @@ -65,10 +67,12 @@ class WalletDB extends EventEmitter { this.state = new ChainState(); this.confirming = false; this.height = 0; + this.filterHeight = 0; this.wallets = new Map(); this.depth = 0; this.rescanning = false; this.filterSent = false; + this.isWitness = false; // Wallet read lock. this.readLock = new MapLock(); @@ -169,6 +173,14 @@ class WalletDB extends EventEmitter { this.emit('error', e); } }); + + this.client.bind('cfilter', async (blockHeight, filter) => { + try { + await this.checkFilter(blockHeight, filter); + } catch (e) { + this.emit('error', e); + } + }); } /** @@ -201,6 +213,9 @@ class WalletDB extends EventEmitter { id: 'primary' }); + const account = await wallet.getAccount(wallet.wid); + this.isWitness = account.witness; + const addr = await wallet.receiveAddress(); this.logger.info( @@ -568,6 +583,35 @@ class WalletDB extends EventEmitter { return this.client.resetFilter(); } + async checkFilter (blockHash, filter) { + this.filterHeight = this.filterHeight + 1; + const gcsKey = blockHash.slice(0, 16); + + const piter = this.db.iterator({ + gte: layout.p.min(), + lte: layout.p.max() + }); + + await piter.each(async (key) => { + const [data] = layout.p.decode(key); + let address = null; + if (data.length === 20) { + if (this.isWitness) + address = Address.fromWitnessPubkeyhash(data); + else + address = Address.fromPubkeyhash(data); + } else if (data.length === 32) { + address = Address.fromWitnessScripthash(data); + } + const script = Script.fromAddress(address); + const match = filter.match(gcsKey, script.toRaw()); + if (match) { + await this.client.getBlockFromNode(blockHash); + return; + } + }); + } + /** * Backup the wallet db. * @param {String} path From 84aeb43df845e7390dd80c535a3354f2c5b535d1 Mon Sep 17 00:00:00 2001 From: Manav Desai Date: Fri, 21 Jul 2023 01:40:28 +0530 Subject: [PATCH 2/2] test: checkFilter --- lib/wallet/walletdb.js | 6 +- test/wallet-neutrino-test.js | 104 +++++++++++++++++++++++++++++++++++ 2 files changed, 109 insertions(+), 1 deletion(-) create mode 100644 test/wallet-neutrino-test.js diff --git a/lib/wallet/walletdb.js b/lib/wallet/walletdb.js index 31ef5e04c..8e652500c 100644 --- a/lib/wallet/walletdb.js +++ b/lib/wallet/walletdb.js @@ -584,6 +584,7 @@ class WalletDB extends EventEmitter { } async checkFilter (blockHash, filter) { + let foundMatch = false; this.filterHeight = this.filterHeight + 1; const gcsKey = blockHash.slice(0, 16); @@ -603,13 +604,16 @@ class WalletDB extends EventEmitter { } else if (data.length === 32) { address = Address.fromWitnessScripthash(data); } + const script = Script.fromAddress(address); const match = filter.match(gcsKey, script.toRaw()); if (match) { - await this.client.getBlockFromNode(blockHash); + foundMatch = true; return; } }); + + return foundMatch; } /** diff --git a/test/wallet-neutrino-test.js b/test/wallet-neutrino-test.js new file mode 100644 index 000000000..55da5a74f --- /dev/null +++ b/test/wallet-neutrino-test.js @@ -0,0 +1,104 @@ +'use strict'; + +const assert = require('bsert'); +const WalletDB = require('../lib/wallet/walletdb'); +const { Network } = require('../lib/protocol'); +const WorkerPool = require('../lib/workers/workerpool'); +const Chain = require('../lib/blockchain/chain'); +const BlockStore = require('../lib/blockstore/level'); +const Miner = require('../lib/mining/miner'); +const CoinView = require('../lib/coins/coinview'); + +const wdb = new WalletDB(); + +const network = Network.get('regtest'); + +const workers = new WorkerPool({ + enabled: true, + size: 2 +}); + +const blocks = new BlockStore({ + memory: true, + network + }); + +const chain = new Chain({ + memory: true, + blocks, + network, + workers +}); + +const miner = new Miner({ + chain, + version: 4, + workers +}); + +let wallet = null; +const addresses = []; +const minedBlocks = []; +const filters = []; + +describe('wallet-neutrino', function() { + before(async () => { + await wdb.open(); + }); + + after(async () => { + await wdb.close(); + }); + + it('should open wallet', async () => { + wallet = await wdb.create(); + }); + + it('should create accounts', async () => { + await wallet.createAccount('foo'); + }); + + it('should generate addresses', async () => { + for (let i = 0; i < 3; i++) { + const key = await wallet.createReceive(0); + const address = key.getAddress(); + addresses.push(address); + } + }); + + it('should create 3 match blocks', async () => { + for (let i = 0; i < 3; i++) { + const addr = addresses[i]; + const block = await miner.mineBlock(null, addr); + minedBlocks.push(block); + } + }); + + it('should create 2 non-match blocks', async () => { + for (let i = 0; i < 2; i++) { + const block = await miner.mineBlock(null, null); + minedBlocks.push(block); + } + }); + + it('should create filters', async () => { + for (let i = 0; i < 5; i++) { + const filter = minedBlocks[i].toBasicFilter(new CoinView()); + filters.push(filter); + } + }); + + it('should match the filters', async () => { + for (let i = 0; i < 3; i++) { + const match = await wdb.checkFilter(minedBlocks[i].hash(), filters[i]); + assert(match); + } + }); + + it('should not match the filters', async () => { + for (let i = 3; i < 5; i++) { + const match = await wdb.checkFilter(minedBlocks[i].hash(), filters[i]); + assert(!match); + } + }); +});