Repository navigation
fix: On-chain send returns before broadcast result #112
Description
Activity
- changed the title
[-]On-chain send returns before backend broadcast result[/-][+]fix: On-chain send returns before broadcast result[/+]on Sep 7, 2026 The reorg recovery finding showed that persisting only the queue event was insufficient: after the wallet committed the new chain state, ordinary sync could no longer reproduce a lost transition.
Implemented in 07c1c19 via PR113: pending reorgs now share the wallet chain's atomic persistence record, with per-account sequence receipts retained until source acknowledgement. This permits normal-sync recovery without replaying an old BDK event and preserves reconfirmation across restart. The change is tested and pushed, not merged; current-head review and rc.67 publication remain outstanding.
The follow-up review found that a reorg-cleanup storage error could return before processing unrelated incoming transaction notifications, even though their wallet update was already persisted and would not emit those events again.
The fix in 2db646f keeps reorg retries under the durable journal while consuming the ordinary event batch before returning a reorg error. Confirmations for transactions with pending reorgs stay behind their reorg event. A regression covers received and confirmed incoming transactions across four reorg failure points and restart without replaying old BDK events.
Implemented in the PR, not merged. Fresh current-commit approval and separate rc.67 publication authorization remain required.
The replacement in #119 uses separate fixed-amount and send-all methods returning
Accepted,RejectedorUnknownwith the locally computed txid. The legacy queued send API remains available. The closed #113 recovery design is historical; transaction journals, RBF recovery, abandonment and reorg redesign are outside this replacement.Backend acceptance is an acknowledgement of this exact transaction, not confirmation or propagation. Refusal or uncertainty never authorizes constructing another payment. Apps persist a bounded attempt guard before dispatch and finish local activity/proof only after acceptance or independent observation of the exact transaction. A lost result can still leave the attempt blocked indefinitely; there is no durable node outcome lookup or exactly-once guarantee across restored backups/devices.
Paired integrations:
Summary
OnchainPayment::send_to_addressandsend_all_to_addressreturn aTxidafter placing the transaction in an asynchronous broadcast queue, before the configured backend attempts the broadcast. Backend rejection and timeout results are only logged, so mobile callers cannot distinguish local transaction submission from backend acceptance.This causes synonymdev/bitkit-ios#717 and synonymdev/bitkit-android#1211: both apps immediately present the returned
Txidas a successful on-chain send.Reproduced against
0.7.0-rc.63(c9a41458a646324c6c8728b4de8329ad4d52ac8d). The same queue-based result contract remains onmain(03ccac798aeb907c6dda367fa4c8af6e3eaa54fa).Reproduction
send_to_address.nLockTime=160623and returns itsTxid.Observed result: the caller receives
Ok(txid)and both mobile apps show success even though the backend dropped the transaction.Root cause
Wallet::send_to_addressinvokes the synchronousBroadcasterInterface, computes the transaction id, and returns it:TransactionBroadcaster::broadcast_transactionsonly enqueues cloned transactions withtry_send.ChainSource::continuously_process_broadcast_queuelater submits each package to Electrum, Esplora, or bitcoind. Those backend results cannot reach the original caller.The Electrum path also misreports backend rejection as success.
spawn_blockingreturns a nestedResult, but the current match treats every successfully joined task as success without inspecting the innertransaction_broadcastresult:Here
Ok(_)includesOk(Err(electrum_rejection))from the joined task.Expected behavior
The on-chain send API exposes an outcome that distinguishes local creation/submission from backend acceptance. A deterministic backend rejection such as
non-finalreaches the caller or an equivalent transaction-keyed event before the caller is expected to present success.Accepted transactions retain the current
Txidbehavior. Asynchronous rebroadcasting for LDK-managed transactions can remain independent of the explicit user-send result.Consumer evidence
sendToAddress/sendAllToAddressand immediately creates sent activity plus the success screen from the returnedTxid.Txid.