From 346385680eafd18fd2f1cdd2421ed0d07f45dbbd Mon Sep 17 00:00:00 2001 From: Neil Booth Date: Thu, 12 Oct 2017 08:33:18 +0900 Subject: [PATCH] Fix listunspent methods to remove mempool spends Fixes #277 --- server/controller.py | 20 ++++++++++++++------ server/mempool.py | 38 ++++++++++++++++++++++++++++++++------ 2 files changed, 46 insertions(+), 12 deletions(-) diff --git a/server/controller.py b/server/controller.py index 3df855f..15aca74 100644 --- a/server/controller.py +++ b/server/controller.py @@ -795,19 +795,27 @@ class Controller(ServerBase): hashX = self.address_to_hashX(address) raise RPCError('address.get_proof is not yet implemented') + async def hashX_listunspent(self, hashX): + '''Return the list of UTXOs of a script hash. + + We should remove mempool spends from the in-DB UTXOs.''' + utxos = await self.get_utxos(hashX) + spends = await self.mempool.spends(hashX) + + return [{'tx_hash': hash_to_str(utxo.tx_hash), 'tx_pos': utxo.tx_pos, + 'height': utxo.height, 'value': utxo.value} + for utxo in sorted(utxos) + if (utxo.tx_hash, utxo.tx_pos) not in spends] + async def address_listunspent(self, address): '''Return the list of UTXOs of an address.''' hashX = self.address_to_hashX(address) - return [{'tx_hash': hash_to_str(utxo.tx_hash), 'tx_pos': utxo.tx_pos, - 'height': utxo.height, 'value': utxo.value} - for utxo in sorted(await self.get_utxos(hashX))] + return await self.hashX_listunspent(hashX) async def scripthash_listunspent(self, scripthash): '''Return the list of UTXOs of a scripthash.''' hashX = self.scripthash_to_hashX(scripthash) - return [{'tx_hash': hash_to_str(utxo.tx_hash), 'tx_pos': utxo.tx_pos, - 'height': utxo.height, 'value': utxo.value} - for utxo in sorted(await self.get_utxos(hashX))] + return await self.hashX_listunspent(hashX) def block_get_header(self, height): '''The deserialized header at a given height. diff --git a/server/mempool.py b/server/mempool.py index 0a6c27b..45bbba6 100644 --- a/server/mempool.py +++ b/server/mempool.py @@ -260,21 +260,30 @@ class MemPool(util.LoggedClass): return result, deferred - async def transactions(self, hashX): - '''Generate (hex_hash, tx_fee, unconfirmed) tuples for mempool - entries for the hashX. + async def raw_transactions(self, hashX): + '''Returns an iterable of (hex_hash, raw_tx) pairs for all + transactions in the mempool that touch hashX. - unconfirmed is True if any txin is unconfirmed. + raw_tx can be None if the transaction has left the mempool. ''' # hashXs is a defaultdict if hashX not in self.hashXs: return [] - deserializer = self.coin.DESERIALIZER hex_hashes = self.hashXs[hashX] raw_txs = await self.daemon.getrawtransactions(hex_hashes) + return zip(hex_hashes, raw_txs) + + async def transactions(self, hashX): + '''Generate (hex_hash, tx_fee, unconfirmed) tuples for mempool + entries for the hashX. + + unconfirmed is True if any txin is unconfirmed. + ''' + deserializer = self.coin.DESERIALIZER + pairs = await self.raw_transactions(hashX) result = [] - for hex_hash, raw_tx in zip(hex_hashes, raw_txs): + for hex_hash, raw_tx in pairs: item = self.txs.get(hex_hash) if not item or not raw_tx: continue @@ -287,6 +296,23 @@ class MemPool(util.LoggedClass): result.append((hex_hash, tx_fee, unconfirmed)) return result + async def spends(self, hashX): + '''Return a set of (prev_hash, prev_idx) pairs from mempool + transactions that touch hashX. + + None, some or all of these may be spends of the hashX. + ''' + deserializer = self.coin.DESERIALIZER + pairs = await self.raw_transactions(hashX) + spends = set() + for hex_hash, raw_tx in pairs: + if not raw_tx: + continue + tx, tx_hash = deserializer(raw_tx).read_tx() + for txin in tx.inputs: + spends.add((txin.prev_hash, txin.prev_idx)) + return spends + def value(self, hashX): '''Return the unconfirmed amount in the mempool for hashX.