1
0

synchronizer: get_transaction should discard tx_height as it can change

tx_height comes from the `get_history` RPC, we then call the `get_transaction` RPC.
By the time the `get_transaction` RPC returns, we might have received another
scripthash status update, called `get_history` again, and updated height for the txid.
Synchronizer._get_transaction() should not call adb.receive_tx_callback() with
the old tx_height (but it was doing exactly that).

Patch to trigger, e.g. regtest failures:
(e.g. for tests.regtest.TestLightningAB.test_extract_preimage)
```
diff --git a/electrum/interface.py b/electrum/interface.py
index 8649652b9c..fce7a1c6de 100644
--- a/electrum/interface.py
+++ b/electrum/interface.py
@@ -991,6 +991,7 @@ class Interface(Logger):
         return res

     async def get_transaction(self, tx_hash: str, *, timeout=None) -> str:
+        await asyncio.sleep(3)
         if not is_hash256_str(tx_hash):
             raise Exception(f"{repr(tx_hash)} is not a txid")
         raw = await self.session.send_request('blockchain.transaction.get', [tx_hash], timeout=timeout)

```
This commit is contained in:
SomberNight
2025-05-15 19:31:39 +00:00
parent 61283fe18b
commit b1f0c6e353
2 changed files with 14 additions and 11 deletions

View File

@@ -434,10 +434,12 @@ class AddressSynchronizer(Logger, EventListener):
children |= self.get_depending_transactions(other_hash)
return children
def receive_tx_callback(self, tx: Transaction, *, tx_height: int) -> None:
def receive_tx_callback(self, tx: Transaction, *, tx_height: Optional[int] = None) -> None:
txid = tx.txid()
assert txid is not None
self.add_unverified_or_unconfirmed_tx(txid, tx_height)
if tx_height is not None:
# note: tx_height is only set by the unit tests: to inject a tx into the history
self.add_unverified_or_unconfirmed_tx(txid, tx_height)
self.add_transaction(tx, allow_unrelated=True)
def receive_history_callback(self, addr: str, hist, tx_fees: Dict[str, int]):
@@ -618,7 +620,7 @@ class AddressSynchronizer(Logger, EventListener):
assert self.is_mine(addr), "address needs to be is_mine to be watched"
await self._address_history_changed_events[addr].wait()
def add_unverified_or_unconfirmed_tx(self, tx_hash, tx_height):
def add_unverified_or_unconfirmed_tx(self, tx_hash: str, tx_height: int) -> None:
if self.db.is_in_verified_tx(tx_hash):
if tx_height <= 0:
# tx was previously SPV-verified but now in mempool (probably reorg)
@@ -634,7 +636,7 @@ class AddressSynchronizer(Logger, EventListener):
else:
self.unconfirmed_tx[tx_hash] = tx_height
def remove_unverified_tx(self, tx_hash, tx_height):
def remove_unverified_tx(self, tx_hash: str, tx_height: int) -> None:
with self.lock:
new_height = self.unverified_tx.get(tx_hash)
if new_height == tx_height: