From 32cba1bf4aa293bcf49c1849587dc1482ce98050 Mon Sep 17 00:00:00 2001 From: Nodar Chkuaselidze Date: Sun, 25 Nov 2018 03:28:26 +0400 Subject: [PATCH] mempool: reflect spent coins in mempool coinview. Currently coinview does not account for spent coins in the mempool, This does not create problems because we have additional checks in right places which detect double spends, but technically coinview should help you detect double spent in the mempool as well. This way it will be compatible with chain.getCoinView. getSpentView will still return all outputs that are available in the mempool. (This could also return spentView from indexers if available, this method is used by `signrawtransaction`.) --- lib/mempool/mempool.js | 56 ++++++++++++++++++++++++++++++++++++++++-- test/mempool-test.js | 50 +++++++++++++++++++++++++++++++++++++ 2 files changed, 104 insertions(+), 2 deletions(-) diff --git a/lib/mempool/mempool.js b/lib/mempool/mempool.js index 17c4b4af..b365c23c 100644 --- a/lib/mempool/mempool.js +++ b/lib/mempool/mempool.js @@ -502,6 +502,28 @@ class Mempool extends EventEmitter { return Coin.fromTX(entry.tx, index, -1); } + /** + * Check whether coin is still unspent. + * @param {Hash} hash + * @param {Number} index + * @returns {boolean} + */ + + hasCoin(hash, index) { + const entry = this.map.get(hash); + + if (!entry) + return false; + + if (this.isSpent(hash, index)) + return false; + + if (index >= entry.tx.outputs.length) + return false; + + return true; + } + /** * Check to see if a coin has been spent. This differs from * {@link ChainDB#isSpent} in that it actually maintains a @@ -1689,6 +1711,8 @@ class Mempool extends EventEmitter { /** * Get coin viewpoint (lock). + * Note: this does not return + * historical view of coins from the indexers. * @method * @param {TX} tx * @returns {Promise} - Returns {@link CoinView}. @@ -1697,12 +1721,40 @@ class Mempool extends EventEmitter { async getSpentView(tx) { const unlock = await this.locker.lock(); try { - return await this.getCoinView(tx); + return await this._getSpentView(tx); } finally { unlock(); } } + /** + * Get coin viewpoint + * @param {TX} tx + * @returns {Promise} - Returns {@link CoinView} + */ + + async _getSpentView(tx) { + const view = new CoinView(); + + for (const {prevout} of tx.inputs) { + const {hash, index} = prevout; + const tx = this.getTX(hash); + + if (tx) { + if (index < tx.outputs.length) + view.addIndex(tx, index, -1); + continue; + } + + const coin = await this.chain.readCoin(prevout); + + if (coin) + view.addEntry(prevout, coin); + } + + return view; + } + /** * Get coin viewpoint (no lock). * @method @@ -1718,7 +1770,7 @@ class Mempool extends EventEmitter { const tx = this.getTX(hash); if (tx) { - if (index < tx.outputs.length) + if (this.hasCoin(hash, index)) view.addIndex(tx, index, -1); continue; } diff --git a/test/mempool-test.js b/test/mempool-test.js index 992e82e4..1828c215 100644 --- a/test/mempool-test.js +++ b/test/mempool-test.js @@ -177,6 +177,56 @@ describe('Mempool', function() { })); }); + it('should get spend coins and reflect in coinview', async () => { + const wallet = new MemWallet(); + const script = Script.fromAddress(wallet.getAddress()); + const dummyCoin = dummyInput(script, random.randomBytes(32)); + + // spend first output + const mtx1 = new MTX(); + mtx1.addOutput(wallet.getAddress(), 50000); + mtx1.addCoin(dummyCoin); + wallet.sign(mtx1); + + // spend second tx + const tx1 = mtx1.toTX(); + const coin1 = Coin.fromTX(tx1, 0, -1); + const mtx2 = new MTX(); + + mtx2.addOutput(wallet.getAddress(), 10000); + mtx2.addOutput(wallet.getAddress(), 30000); // 10k fee.. + mtx2.addCoin(coin1); + + wallet.sign(mtx2); + + const tx2 = mtx2.toTX(); + + await mempool.addTX(tx1); + + { + const view = await mempool.getCoinView(tx2); + assert(view.hasEntry(coin1)); + } + + await mempool.addTX(tx2); + + // we should not have coins available in the mempool for these txs. + { + const view = await mempool.getCoinView(tx1); + const sview = await mempool.getSpentView(tx1); + + assert(!view.hasEntry(dummyCoin)); + assert(sview.hasEntry(dummyCoin)); + } + + { + const view = await mempool.getCoinView(tx2); + const sview = await mempool.getSpentView(tx2); + assert(!view.hasEntry(coin1)); + assert(sview.hasEntry(coin1)); + } + }); + it('should handle locktime', async () => { const key = KeyRing.generate();