poll_once and reconcile.py's existence check both called
blockchain.transaction.get(txid, verbose=True). Several Electrum server
implementations and versions reject the verbose flag outright
("verbose transactions are currently unsupported"), which would have
meant no confirmations and no reconciliation ever running against such
a server, read as a plain transport error. reconcile.py additionally
decided whether to abandon a transaction - releasing its funds - by
substring-matching the error text ("missing", "not found", ...), which
only works against ElectrumX's specific wording.
Both now ask blockchain.scripthash.get_history for the address that
owns every input of the transaction (a user's own address for a
bet/withdrawal, the pool address for a payout) and look for the txid in
the result: present with height > 0 means confirmed, present with
height <= 0 means still in the mempool, absent means the server
doesn't know it. get_history is a plain, universally-supported Electrum
method, and "not in the list" replaces the old substring-matching
entirely - no more guessing at error wording to decide whether to
release funds. History is cached per scripthash within one pass, since
every "payout" row shares the same pool address.
New app/tx/pending_address.py factors out own_address_for (the
address derivation was previously duplicated informally inside
tx/broadcast.py's signing context) so confirmation.py and reconcile.py
share one definition instead of two that could compute different
addresses for the same row.
tests/unit/test_confirmation.py and test_reconcile.py needed real User
rows and a master-key bootstrap they didn't have before, since address
derivation is now exercised for real rather than assumed. Suite grows
from 217 to 222 tests. BUGS.md moves B-41 to Previously fixed - no
Medium-severity finding remains open.
23 lines
1018 B
Python
23 lines
1018 B
Python
from sqlalchemy.ext.asyncio import AsyncSession
|
|
|
|
from app.db.models import User
|
|
from app.wallet.hd import derive_pool_address, derive_user_address
|
|
|
|
|
|
async def own_address_for(session: AsyncSession, kind: str, user_id: int | None) -> str:
|
|
"""The address that owns every input of a PendingTransaction of this kind —
|
|
a user's own address for a bet/withdrawal, the pool address for a payout.
|
|
All our builders only ever spend one address's UTXOs per tx (see
|
|
tx/broadcast.py:_signing_context, which derives the same address alongside
|
|
the signing key it also needs).
|
|
|
|
Shared by tx/confirmation.py and tx/reconcile.py (B-41): both now check
|
|
blockchain.scripthash.get_history for this address instead of asking
|
|
blockchain.transaction.get for a verbose reply, so the two can't derive
|
|
different addresses for the same row.
|
|
"""
|
|
if kind == "payout":
|
|
return derive_pool_address()
|
|
user = await session.get(User, user_id)
|
|
return derive_user_address(user.derivation_index)
|