Skip to content

Commit 49d589a

Browse files
committed
protocol change: define order of mempool txs in status of scripthash
Note that this is a soft fork: the server can apply it even for past protocol versions. Previously, with the order being undefined, if an address had multiple mempool transactions touching it, switching between different servers could result in a change in address status simply as a result of these servers ordering mempool txs differently. This would result in the client re-requesting the whole history of the address. ----- D/i | interface.[electrum.blockstream.info:60002] | <-- ('blockchain.scripthash.subscribe', ['660b44502503064f9d5feee48726287c0973e25bc531b4b8a072f57f143d5cd0']) {} (id: 12) D/i | interface.[electrum.blockstream.info:60002] | --> 9da27f9df91e3f860212f65b736fa20a539ba6e3d509f6370367ee7f10a4d5b0 (id: 12) D/i | interface.[electrum.blockstream.info:60002] | <-- ('blockchain.scripthash.get_history', ['660b44502503064f9d5feee48726287c0973e25bc531b4b8a072f57f143d5cd0']) {} (id: 13) D/i | interface.[electrum.blockstream.info:60002] | --> [ {'fee': 200, 'height': 0, 'tx_hash': '3ee6d6e26291ce360127fe039b816470fce6eeea19b5c9d10829a1e4efc2d0c7'}, {'fee': 239, 'height': 0, 'tx_hash': '9e050f09b676b9b0ee26aa02ccee623fae585a85d6a5e24ecedd6f8d6d2d3b1d'}, {'fee': 178, 'height': 0, 'tx_hash': 'fb80adbf8274190418cb3fb0385d82fe9d47a844d9913684fa5fb3d48094b35a'}, {'fee': 200, 'height': 0, 'tx_hash': '713933c50b7c43f606dad5749ea46e3bc6622657e9b13ace9d639697da266e8b'} ] (id: 13) D/i | interface.[testnet.hsmiths.com:53012] | <-- ('blockchain.scripthash.subscribe', ['660b44502503064f9d5feee48726287c0973e25bc531b4b8a072f57f143d5cd0']) {} (id: 12) D/i | interface.[testnet.hsmiths.com:53012] | --> f7ef7237d2d62a3280acae05616200b96ad9dd85fd0473c29152a4a41e05686c (id: 12) D/i | interface.[testnet.hsmiths.com:53012] | <-- ('blockchain.scripthash.get_history', ['660b44502503064f9d5feee48726287c0973e25bc531b4b8a072f57f143d5cd0']) {} (id: 13) D/i | interface.[testnet.hsmiths.com:53012] | --> [ {'tx_hash': '9e050f09b676b9b0ee26aa02ccee623fae585a85d6a5e24ecedd6f8d6d2d3b1d', 'height': 0, 'fee': 239}, {'tx_hash': 'fb80adbf8274190418cb3fb0385d82fe9d47a844d9913684fa5fb3d48094b35a', 'height': 0, 'fee': 178}, {'tx_hash': '3ee6d6e26291ce360127fe039b816470fce6eeea19b5c9d10829a1e4efc2d0c7', 'height': 0, 'fee': 200}, {'tx_hash': '713933c50b7c43f606dad5749ea46e3bc6622657e9b13ace9d639697da266e8b', 'height': 0, 'fee': 200} ] (id: 13)
1 parent 532cacb commit 49d589a

5 files changed

Lines changed: 27 additions & 10 deletions

File tree

docs/protocol-basics.rst

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -153,10 +153,11 @@ block)
153153

154154
* ``height`` is the height of the block it is in.
155155

156-
3. Next, with mempool transactions in any order, append a similar
157-
string for those transactions, but where **height** is ``-1`` if the
158-
transaction has at least one unconfirmed input, and ``0`` if all
159-
inputs are confirmed.
156+
3. Next, with mempool transactions ordered
157+
:ref:`as described here <mempool_tx_order>`,
158+
append a similar string for those transactions,
159+
but where **height** is ``-1`` if the transaction has at least
160+
one unconfirmed input, and ``0`` if all inputs are confirmed.
160161

161162
4. The :dfn:`status` of the script hash is the :func:`sha256` hash of the
162163
full string expressed as a hexadecimal string, or :const:`null` if the

docs/protocol-changes.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,10 @@ Changes
178178
* Breaking change for the version negotiation: we now mandate that
179179
the :func:`server.version` message must be the first message sent.
180180
That is, version negotiation must happen before any other messages.
181+
* The status of a scripthash, :func:`blockchain.scripthash.get_mempool`,
182+
and :func:`blockchain.scripthash.get_history` previously did not define
183+
an order for mempool transactions. We now mandate that these are sorted
184+
by `(-height, tx_hash)`.
181185
* The previously required *height* argument for
182186
:func:`blockchain.transaction.get_merkle` is now optional.
183187
* Optional *mode* argument added to :func:`blockchain.estimatefee`.

docs/protocol-methods.rst

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -369,8 +369,9 @@ hashes>`.
369369

370370
**Result**
371371

372-
A list of mempool transactions in arbitrary order. Each mempool
373-
transaction is a dictionary with the following keys:
372+
A list of mempool transactions, ordered by `(-height, tx_hash)`
373+
(see :ref:`below <mempool_tx_order>`).
374+
Each mempool transaction is a dictionary with the following keys:
374375

375376
* *height*
376377

@@ -384,6 +385,14 @@ hashes>`.
384385

385386
The transaction fee in minimum coin units (satoshis).
386387

388+
389+
.. _mempool_tx_order:
390+
391+
Mempool txs are ordered by `(-height, tx_hash)`, that is,
392+
``0`` height txs come before ``-1`` height txs, and secondarily the
393+
txid (with endianness same as displayed hex) is used to arrive at
394+
a canonical ordering.
395+
387396
**Result Example**
388397

389398
::

electrumx/server/mempool.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -469,12 +469,15 @@ async def potential_spends(self, hashX):
469469
return result
470470

471471
async def transaction_summaries(self, hashX):
472-
'''Return a list of MemPoolTxSummary objects for the hashX.'''
472+
'''Return a list of MemPoolTxSummary objects for the hashX,
473+
sorted as expected by protocol methods.
474+
'''
473475
result = []
474476
for tx_hash in self.hashXs.get(hashX, ()):
475477
tx = self.txs[tx_hash]
476478
has_ui = any(hash in self.txs for hash, idx in tx.prevouts)
477479
result.append(MemPoolTxSummary(tx_hash, tx.fee, has_ui))
480+
result.sort(key=lambda x: (x.has_unconfirmed_inputs, bytes(reversed(x.hash))))
478481
return result
479482

480483
async def unordered_UTXOs(self, hashX):

electrumx/server/session.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1143,7 +1143,7 @@ async def address_status(self, hashX: bytes) -> Optional[str]:
11431143
11441144
Status is a hex string, but must be None if there is no history.
11451145
'''
1146-
# Note history is ordered and mempool unordered in electrum-server
1146+
# Note both confirmed history and mempool history are ordered
11471147
# For mempool, height is -1 if it has unconfirmed inputs, otherwise 0
11481148
db_history, cost = await self.session_mgr.limited_history(hashX)
11491149
mempool = await self.mempool.transaction_summaries(hashX)
@@ -1234,7 +1234,7 @@ async def scripthash_get_balance(self, scripthash):
12341234
return await self.get_balance(hashX)
12351235

12361236
async def unconfirmed_history(self, hashX):
1237-
# Note unconfirmed history is unordered in electrum-server
1237+
# Note both confirmed history and mempool history are ordered
12381238
# height is -1 if it has unconfirmed inputs, otherwise 0
12391239
result = [{'tx_hash': hash_to_hex_str(tx.hash),
12401240
'height': -tx.has_unconfirmed_inputs,
@@ -1244,7 +1244,7 @@ async def unconfirmed_history(self, hashX):
12441244
return result
12451245

12461246
async def confirmed_and_unconfirmed_history(self, hashX):
1247-
# Note history is ordered but unconfirmed is unordered in e-s
1247+
# Note both confirmed history and mempool history are ordered
12481248
history, cost = await self.session_mgr.limited_history(hashX)
12491249
self.bump_cost(cost)
12501250
conf = [{'tx_hash': hash_to_hex_str(tx_hash), 'height': height}

0 commit comments

Comments
 (0)