From 9064032684eeb8a6ab34ed8b9dab7dc372e1b48c Mon Sep 17 00:00:00 2001 From: Manav Desai Date: Thu, 20 Jul 2023 23:12:50 +0530 Subject: [PATCH 1/2] feat: Added getblockpeer --- lib/blockchain/chain.js | 47 ++++++++++++++++++++++++++++++++++++++- lib/blockchain/chaindb.js | 12 ++++++++-- lib/client/node.js | 5 ++--- lib/net/pool.js | 34 ++++++++++++++++++++++++++-- lib/node/http.js | 6 +++++ lib/wallet/client.js | 11 +++++++++ lib/wallet/nodeclient.js | 11 +++++++++ lib/wallet/nullclient.js | 11 +++++++++ 8 files changed, 129 insertions(+), 8 deletions(-) diff --git a/lib/blockchain/chain.js b/lib/blockchain/chain.js index 9cd0a312f..5852346e8 100644 --- a/lib/blockchain/chain.js +++ b/lib/blockchain/chain.js @@ -64,6 +64,8 @@ class Chain extends AsyncEmitter { this.orphanMap = new BufferMap(); this.orphanPrev = new BufferMap(); + + this.getPrunedMap = new BufferMap(); } /** @@ -1368,7 +1370,17 @@ class Chain extends AsyncEmitter { } // Do we already have this block? - if (await this.hasEntry(hash)) { + const existingEntry = await this.getEntry(hash); + + if (existingEntry && this.getPrunedMap.has(hash)) { + block = block.toBlock(); + await this.db.updateNeutrinoSave(); + await this.db.save(existingEntry, block, new CoinView()); + await this.db.updateNeutrinoSave(); + return existingEntry; + } + + if (existingEntry) { this.logger.debug('Already have block: %h.', block.hash()); throw new VerifyError(block, 'duplicate', 'duplicate', 0); } @@ -1925,6 +1937,33 @@ class Chain extends AsyncEmitter { return this.db.getBlock(hash); } + async getBlockPeer(hash) { + let block = await this.db.getBlock(hash); + if (block) { + const entry = await this.getEntry(hash); + assert(entry.hash.equals(hash)); + return block; + } else { + this.logger.warning('Block not found, attempting to download'); + + // Ensure hash not height + hash = await this.db.getHash(hash); + + const wait = new Promise((resolve, reject) => { + this.getPrunedMap.set(hash, resolve); + }); + + await this.emitAsync('getprunedblock', hash); + await wait; + block = await this.db.getBlock(hash); + const entry = await this.getEntry(hash); + assert(entry.hash.equals(hash)); + + this.emit('getblockpeer', entry, block); + return block; + } + } + /** * Retrieve a block from the database (not filled with coins). * @param {Hash} block @@ -2616,6 +2655,7 @@ class ChainOptions { this.compression = true; this.spv = false; + this.neutrino = false; this.bip91 = false; this.bip148 = false; this.prune = false; @@ -2662,6 +2702,11 @@ class ChainOptions { this.spv = options.spv; } + if (options.neutrino != null) { + assert(typeof options.neutrino === 'boolean'); + this.neutrino = options.neutrino; + } + if (options.prefix != null) { assert(typeof options.prefix === 'string'); this.prefix = options.prefix; diff --git a/lib/blockchain/chaindb.js b/lib/blockchain/chaindb.js index cb91accaa..cb3c27dea 100644 --- a/lib/blockchain/chaindb.js +++ b/lib/blockchain/chaindb.js @@ -1001,7 +1001,7 @@ class ChainDB { */ async getRawBlock(block) { - if (this.options.spv) + if (this.options.spv && !this.options.neutrino) return null; const hash = await this.getHash(block); @@ -1150,6 +1150,14 @@ class ChainDB { * @returns {Promise} */ + async updateNeutrinoSave () { + if (this.neutrinoSave) { + this.neutrinoSave = false; + } else { + this.neutrinoSave = true; + } + } + async save(entry, block, view) { this.start(); try { @@ -1478,7 +1486,7 @@ class ChainDB { async saveBlock(entry, block, view) { const hash = block.hash(); - if (this.options.spv) + if (this.options.spv && !this.neutrinoSave) return; // Write actual block data. diff --git a/lib/client/node.js b/lib/client/node.js index 50800cac1..693e69222 100644 --- a/lib/client/node.js +++ b/lib/client/node.js @@ -164,9 +164,8 @@ class NodeClient extends Client { * @returns {Promise} */ - getFilter(filter) { - assert(typeof filter === 'string' || typeof filter === 'number'); - return this.get(`/filter/${filter}`); + getBlockPeer(hash) { + return this.call('get block peer', hash); } /** diff --git a/lib/net/pool.js b/lib/net/pool.js index 234b23bc2..a8f2a7a53 100644 --- a/lib/net/pool.js +++ b/lib/net/pool.js @@ -141,6 +141,16 @@ class Pool extends EventEmitter { this.handleBadOrphan('block', err, id); }); + this.chain.on('getprunedblock', async (hash) => { + // Find the first peer with a completed handshake + for (let peer = this.peers.head(); peer; peer = peer.next) { + if (!peer.handshake) + continue; + + await this.getBlock(peer, [hash]); + } + }); + if (this.mempool) { this.mempool.on('tx', (tx) => { this.emit('tx', tx); @@ -2293,7 +2303,7 @@ class Pool extends EventEmitter { const hash = block.hash(); - if (!this.resolveBlock(peer, hash)) { + if (!this.options.neutrino && !this.resolveBlock(peer, hash)) { this.logger.warning( 'Received unrequested block: %h (%s).', block.hash(), peer.hostname()); @@ -2316,6 +2326,14 @@ class Pool extends EventEmitter { } // Block was orphaned. + + const resolve = this.chain.getPrunedMap.get(hash); + if (resolve) { + this.logger.warning('Received pruned block by special request'); + this.chain.getPrunedMap.delete(hash); + resolve(); + } + if (!entry) { if (this.checkpoints) { this.logger.warning( @@ -3690,6 +3708,7 @@ class PoolOptions { this.prefix = null; this.checkpoints = true; this.spv = false; + this.neutrino = false; this.bip37 = false; this.bip157 = false; this.listen = false; @@ -3772,12 +3791,17 @@ class PoolOptions { if (options.spv != null) { assert(typeof options.spv === 'boolean'); - assert(options.spv === this.chain.options.spv); this.spv = options.spv; } else { this.spv = this.chain.options.spv; } + if (options.neutrino != null) { + assert(options.compact !== true); + assert(typeof options.neutrino === 'boolean'); + this.neutrino = options.neutrino; + } + if (options.bip37 != null) { assert(typeof options.bip37 === 'boolean'); this.bip37 = options.bip37; @@ -3953,6 +3977,12 @@ class PoolOptions { this.listen = false; } + if (this.neutrino) { + this.requiredServices |= common.services.NODE_COMPACT_FILTERS; + this.checkpoints = true; + this.compact = false; + } + if (this.selfish) { this.services &= ~common.services.NETWORK; this.bip37 = false; diff --git a/lib/node/http.js b/lib/node/http.js index 8448ec015..c8e8316fb 100644 --- a/lib/node/http.js +++ b/lib/node/http.js @@ -498,6 +498,12 @@ class HTTP extends Server { return null; }); + socket.hook('get block peer', (...args) => { + const valid = new Validator(args); + const hash = valid.hash(0); + return this.chain.getBlockPeer(hash); + }); + socket.hook('estimate fee', (...args) => { const valid = new Validator(args); const blocks = valid.u32(0); diff --git a/lib/wallet/client.js b/lib/wallet/client.js index 768f38e50..b39d5bc33 100644 --- a/lib/wallet/client.js +++ b/lib/wallet/client.js @@ -71,6 +71,17 @@ 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); + } + 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..afcf0d3c2 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; @@ -134,6 +141,10 @@ class NodeClient extends AsyncEmitter { return entry; } + async getBlockFromNode(hash) { + await this.node.chain.getBlockPeer(hash); + } + /** * 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 From da4a6b842cacbb30b158230f56d06fdcf1e033ea Mon Sep 17 00:00:00 2001 From: Manav Desai Date: Wed, 2 Aug 2023 02:05:04 +0530 Subject: [PATCH 2/2] fix: removed dependency on neutrino and removed save block --- lib/blockchain/chain.js | 42 ++++++++++----------------------------- lib/blockchain/chaindb.js | 12 ++--------- 2 files changed, 13 insertions(+), 41 deletions(-) diff --git a/lib/blockchain/chain.js b/lib/blockchain/chain.js index 5852346e8..408e1f01b 100644 --- a/lib/blockchain/chain.js +++ b/lib/blockchain/chain.js @@ -1374,10 +1374,8 @@ class Chain extends AsyncEmitter { if (existingEntry && this.getPrunedMap.has(hash)) { block = block.toBlock(); - await this.db.updateNeutrinoSave(); - await this.db.save(existingEntry, block, new CoinView()); - await this.db.updateNeutrinoSave(); - return existingEntry; + this.emit('getblockpeer', existingEntry, block); + return null; } if (existingEntry) { @@ -1938,30 +1936,18 @@ class Chain extends AsyncEmitter { } async getBlockPeer(hash) { - let block = await this.db.getBlock(hash); - if (block) { - const entry = await this.getEntry(hash); - assert(entry.hash.equals(hash)); - return block; - } else { - this.logger.warning('Block not found, attempting to download'); - - // Ensure hash not height - hash = await this.db.getHash(hash); + this.logger.warning('Block not found, attempting to download'); - const wait = new Promise((resolve, reject) => { - this.getPrunedMap.set(hash, resolve); - }); + // Ensure hash not height + hash = await this.db.getHash(hash); - await this.emitAsync('getprunedblock', hash); - await wait; - block = await this.db.getBlock(hash); - const entry = await this.getEntry(hash); - assert(entry.hash.equals(hash)); + const wait = new Promise((resolve, reject) => { + this.getPrunedMap.set(hash, resolve); + }); - this.emit('getblockpeer', entry, block); - return block; - } + await this.emitAsync('getprunedblock', hash); + await wait; + return; } /** @@ -2655,7 +2641,6 @@ class ChainOptions { this.compression = true; this.spv = false; - this.neutrino = false; this.bip91 = false; this.bip148 = false; this.prune = false; @@ -2702,11 +2687,6 @@ class ChainOptions { this.spv = options.spv; } - if (options.neutrino != null) { - assert(typeof options.neutrino === 'boolean'); - this.neutrino = options.neutrino; - } - if (options.prefix != null) { assert(typeof options.prefix === 'string'); this.prefix = options.prefix; diff --git a/lib/blockchain/chaindb.js b/lib/blockchain/chaindb.js index cb3c27dea..cb91accaa 100644 --- a/lib/blockchain/chaindb.js +++ b/lib/blockchain/chaindb.js @@ -1001,7 +1001,7 @@ class ChainDB { */ async getRawBlock(block) { - if (this.options.spv && !this.options.neutrino) + if (this.options.spv) return null; const hash = await this.getHash(block); @@ -1150,14 +1150,6 @@ class ChainDB { * @returns {Promise} */ - async updateNeutrinoSave () { - if (this.neutrinoSave) { - this.neutrinoSave = false; - } else { - this.neutrinoSave = true; - } - } - async save(entry, block, view) { this.start(); try { @@ -1486,7 +1478,7 @@ class ChainDB { async saveBlock(entry, block, view) { const hash = block.hash(); - if (this.options.spv && !this.neutrinoSave) + if (this.options.spv) return; // Write actual block data.