Repository navigation
Conversation
A wallet sync asks esplora for every unconfirmed transaction it expects and applies the eviction of any esplora does not return. electrs indexes its mempool behind the node, so a sync right after a broadcast evicted the wallet's own transaction and freed its coins for the next selection. Worse, sign_and_broadcast never recorded the transaction at all, so its coins looked unspent until esplora indexed it. EsploraClient now owns the transactions this node broadcast recently. Every broadcast (wallet sends and fee bumps, and the manager's funding, CET, refund and close transactions through Blockchain::send_transaction) goes through EsploraClient::broadcast, which records it. The wallet sync drops evictions of a transaction broadcast less than a minute before the sync started. The eviction stamp is the sync's start time, so the check needs no clock. sign_and_broadcast records the sent transaction as unconfirmed in the wallet and persists it, so its coins are spent at once and stay spent across a restart. Recording forwards the wallet events it produces, so subscribers still hear of each send once, as they did from the sync.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Item 5 of #225. electrs indexes its mempool behind the node. A wallet that broadcasts and then syncs inside that lag reads its own transaction as evicted, frees its coins, and can double-spend them on the next send. Lygos works around it with a 60 s
recent_broadcastsfilter. This PR fixes it in DDK.Changes
ddk/src/chain/broadcasts.rs(privateRecentBroadcasts, 60 s grace), held byEsploraClient. The wallet and the manager share that client, so every broadcast is recorded in one place.EsploraClient::broadcast: records the txid and returns the broadcast time.Blockchain::send_transactiongoes through it, so the manager's funding, CET, refund and close broadcasts are all recorded.command::sync: drops evictions of transactions broadcast within the grace window before applying the update. The comparison uses the sync's own start time, which is whatbdk_esplorastamps evictions with, so it reads no clock.sign_and_broadcastrecords the sent transaction as unconfirmed withapply_unconfirmed_txs_eventsand forwards the events. Before this, DDK's wallet only learned about its own sends from esplora, so its coins looked unspent until electrs indexed the send. On its own, the grace filter changed nothing.Not changed (same exposure as master)
(last_seen, txid). Abump_feein the same second as the previous sync records the replacement with the same timestamp, and bdk usually keeps the original canonical until the next sync. That is when master switched over too, so this is not a regression. Fixing it would mean stamping replacements past their conflicts.Testing
chain::broadcasts::tests::evictions_of_own_broadcasts_wait_out_the_grace: an own eviction inside the window is dropped, one from a sync that started before the broadcast is dropped, one past the window is applied, and a foreign eviction is always applied.wallet::tests::a_sent_transaction_keeps_its_coins_spent_through_an_immediate_sync(regtest): the sent coin is spent right away and stays spent through an immediate sync, and exactly oneTxUnconfirmedevent fires across the send and the sync. With the old code it fails, on both the coin check and the event count.ddklib testswallet::andchain::: 36 passed.enumeration_contractand fourstateless_executionclose and splice tests pass on regtest.cargo fmt --check,cargo clippy -- -D warningsandcargo check -p ddk --no-default-featurespass.bdk_esplora0.22.2 andbdk_chain0.23.3;