interface: get_history: enforce order of mempool txs
What changed, and why it matters
This commit adds validation to Electrum's server communication layer. It now rejects invalid block heights (below -1) and, for newer server protocol versions, requires that unconfirmed ('mempool') transactions be returned in a specific predictable order. The goal is to expose servers that don't follow the protocol correctly, which helps Electrum detect misbehaving or malicious servers rather than silently accepting inconsistent data.
Treat this as a defensive hardening commit. Users and downstream packagers should upgrade to a version containing this patch. Server operators should ensure their implementations return mempool transactions in the canonical order expected by protocol version 1.6+ and never return heights below -1.
Security signals we found
Input validation added for block height lower bound
Protocol compliance enforcement for mempool transaction ordering
RequestCorrupted exception raised on server-side anomalies
Defensive hardening against malicious or buggy ElectrumX/Electrum server responses
Evidence from the diff
In electrum/interface.py’s get_history response parsing, two new checks are added: (1) height values less than -1 are rejected as invalid; (2) for protocol version 1.6+, mempool transactions (height <= 0) must be sorted by descending height and then by tx_hash bytes. If ordering is wrong, a RequestCorrupted exception is raised. This surfaces non-compliant servers and prevents downstream code from operating on unordered or malformed history data.
Changed components
electrum/interface.pyInterface.get_historyElectrum server protocol handlingInspect captured patch +7 / −0
diff --git a/electrum/interface.py b/electrum/interface.py
index 1e19b8d..b9827cf 100644
--- a/electrum/interface.py
+++ b/electrum/interface.py
@@ -1455,6 +1455,8 @@ class Interface(Logger):
height = assert_dict_contains_field(tx_item, field_name='height')
assert_dict_contains_field(tx_item, field_name='tx_hash')
assert_integer(height)
+ if height < -1:
+ raise RequestCorrupted(f'{height!r} is not a valid block height')
assert_hash256_str(tx_item['tx_hash'])
if height in (-1, 0):
assert_dict_contains_field(tx_item, field_name='fee')
@@ -1465,6 +1467,11 @@ class Interface(Logger):
if height < prev_height:
raise RequestCorrupted(f'heights of confirmed txs must be in increasing order')
prev_height = height
+ if self.active_protocol_tuple >= (1, 6):
+ # enforce order of mempool txs
+ mempool_txs = [tx_item for tx_item in res if tx_item['height'] <= 0]
+ if mempool_txs != sorted(mempool_txs, key=lambda x: (-x['height'], bytes.fromhex(x['tx_hash']))):
+ raise RequestCorrupted(f'mempool txs not in canonical order')
hashes = set(map(lambda item: item['tx_hash'], res))
if len(hashes) != len(res):
# Either server is sending garbage... or maybe if server is race-prone
Why this scored 47/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.