From 76f4969a9840a5918e70b6ffd3a0b1e5b0fd9686 Mon Sep 17 00:00:00 2001 From: Neil Booth Date: Sun, 11 Feb 2018 23:21:30 +0800 Subject: [PATCH] listunspent methods consider mempool receipts - Update docs. Height is 0 for mempool receipts - Implement mempool.get_utxos() and use it - Rename mempool.spends to mempool.potential_spends Closes #365 --- docs/PROTOCOL.rst | 17 ++++++++++------- server/controller.py | 11 ++++++----- server/mempool.py | 29 ++++++++++++++++++++++++----- 3 files changed, 40 insertions(+), 17 deletions(-) diff --git a/docs/PROTOCOL.rst b/docs/PROTOCOL.rst index eb928d7..9e0cf49 100644 --- a/docs/PROTOCOL.rst +++ b/docs/PROTOCOL.rst @@ -272,14 +272,17 @@ Return an ordered list of UTXOs sent to a bitcoin address. **Response** A list of unspent outputs in blockchain order. Each transaction - is a dictionary with keys *height* , *tx_pos*, *tx_height* and + is a dictionary with keys *height* , *tx_pos*, *tx_hash* and *value* keys. *height* is the integer height of the block the - transaction was confirmed in; if unconfirmed then *height* is 0 if - all inputs are confirmed, and -1 otherwise. *tx_hash* the - transaction hash in hexadecimal, *tx_pos* the zero-based index of - the output in the transaction's list of outputs, and *value* its - integer value in minimum coin units (satoshis in the case of - Bitcoin). + transaction was confirmed in, *tx_hash* the transaction hash in + hexadecimal, *tx_pos* the zero-based index of the output in the + transaction's list of outputs, and *value* its integer value in + minimum coin units (satoshis in the case of Bitcoin). + + This function takes the mempool into account. Mempool + transactions paying to the address are included at the end of the + list in an undefined order, each with *tx_height* of zero. Any + output that is spent in the mempool does not appear. **Response Example** diff --git a/server/controller.py b/server/controller.py index 57b80c7..2ecea17 100644 --- a/server/controller.py +++ b/server/controller.py @@ -788,15 +788,16 @@ class Controller(ServerBase): return await self.unconfirmed_history(hashX) 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.''' + '''Return the list of UTXOs of a script hash, including mempool + effects.''' utxos = await self.get_utxos(hashX) - spends = await self.mempool.spends(hashX) + utxos = sorted(utxos) + utxos.extend(self.mempool.get_utxos(hashX)) + spends = await self.mempool.potential_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) + for utxo in utxos if (utxo.tx_hash, utxo.tx_pos) not in spends] async def address_listunspent(self, address): diff --git a/server/mempool.py b/server/mempool.py index 286539c..2cef7f8 100644 --- a/server/mempool.py +++ b/server/mempool.py @@ -305,14 +305,33 @@ class MemPool(util.LoggedClass): item = self.txs.get(hex_hash) if not item or not raw_tx: continue - txin_pairs, txout_pairs, tx_fee, tx_size = item + tx_fee = item[2] tx = deserializer(raw_tx).read_tx() unconfirmed = any(hash_to_str(txin.prev_hash) in self.txs for txin in tx.inputs) result.append((hex_hash, tx_fee, unconfirmed)) return result - async def spends(self, hashX): + def get_utxos(self, hashX): + '''Return an unordered list of UTXO named tuples from mempool + transactions that pay to hashX. + + This does not consider if any other mempool transactions spend + the outputs. + ''' + utxos = [] + # hashXs is a defaultdict, so use get() to query + for hex_hash in self.hashXs.get(hashX, []): + item = self.txs.get(hex_hash) + if not item: + continue + txout_pairs = item[1] + for pos, (hX, value) in enumerate(txout_pairs): + if hX == hashX: + utxos.append(UTXO(-1, pos, hex_hash, 0, value)) + return utxos + + async def potential_spends(self, hashX): '''Return a set of (prev_hash, prev_idx) pairs from mempool transactions that touch hashX. @@ -320,14 +339,14 @@ class MemPool(util.LoggedClass): ''' deserializer = self.coin.DESERIALIZER pairs = await self.raw_transactions(hashX) - spends = set() + result = set() for hex_hash, raw_tx in pairs: if not raw_tx: continue tx = deserializer(raw_tx).read_tx() for txin in tx.inputs: - spends.add((txin.prev_hash, txin.prev_idx)) - return spends + result.add((txin.prev_hash, txin.prev_idx)) + return result def value(self, hashX): '''Return the unconfirmed amount in the mempool for hashX.