Skip to content

Cbf fix block fetch#34

Open
randomlogin wants to merge 5 commits into
febyeji:cbf-chain-source-cleanupfrom
randomlogin:cbf-fix-block-fetch
Open

Cbf fix block fetch#34
randomlogin wants to merge 5 commits into
febyeji:cbf-chain-source-cleanupfrom
randomlogin:cbf-fix-block-fetch

Conversation

@randomlogin

Copy link
Copy Markdown
Collaborator

This PR fixes several bugs:

  1. Previously we did not understand whether we really reached the tip: for example on restart of the node the tip from kyoto would be 0, so sync_wallets would incorrectly return without really waiting for the sync. Right now we only are synced when we have received FiltersSynced kyoto event.

  2. Wallet persistence sometime deadlock which affected our setup. While it will be fixed in a more general way in future (Wallet mutex held across Runtime::block_on(persist_async) can deadlock the entire node runtime lightningdevkit/ldk-node#978 and Avoid wallet persistence deadlocks lightningdevkit/ldk-node#980) I decided to leave it here in place to distinguish tests failing because of that and other failing ones.

  3. Added lookahead addresses to list_revealed_scripts in wallet and changed its name to list_watched_scripts, as on a fresh run I encountered missed payments during sync because of that.

  4. Added timeout to block fetch (same for the fee fetch and just block fetch for block data).

randomlogin and others added 5 commits July 13, 2026 19:07
Previously we did not track the `FiltersSynced` kyoto event, so we could
not tell when we had applied all blocks up to the tip. For example, when
we stop and restart the node, kyoto's tip is 0 at the instant of start
(it does not persist its chain), so our applied height trivially matches
kyoto's tip and we would falsely conclude we had reached it. That is only
actually true once we have received `FiltersSynced`.
`block_connected` applied the block and then called `block_on(persist_async)`
while still holding the wallet mutex (`self.inner`) — the old `persist_async`
was a method on the locked wallet, so the lock was structurally required.

`Runtime::block_on` parks the current worker via `block_in_place` while driving
the persist future. Holding the blocking (std) wallet lock across that park means
any other task touching the wallet blocks the thread for the whole persist I/O;
on a single-worker runtime that can wedge the block-application pipeline, and it
widens the window for the documented `block_on` I/O-starvation deadlock.

Apply the block under the lock, `take_staged()` the changeset, drop the lock,
then persist the owned changeset via `AsyncWalletPersister::persist`. We still
block on the persist before returning (block N durable before N+1 is applied);
a reader observing applied-but-unflushed state is safe because a crash reverts
to the last persisted checkpoint.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Now the function is called `list_watched_scripts`.
Previously stalled fetch would hang indefinitely.
Comment thread src/chain/cbf.rs
}
self.chain_listener.block_connected(&ib.block, ib.height);
self.next_height += 1;
self.mark_syncing();

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we mark the state as syncing as soon as a new header/filter is observed, before awaiting the block fetch? As written, synced_to_tip remains true while a matched block is being fetched, so sync_wallets() can still return before that known block is applied.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What if block fetch fails in the end? We would not apply it all, but sync_wallets() would have returned.

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we're concerned about the same case. My suggestion is to set synced_to_tip to false as soon as an IndexedFilter is received, before awaiting request_block(), rather than only after ConnectFull has been applied. Then sync_wallets() will wait while the fetch is pending. If the fetch ultimately fails, the existing ChainOp::Failed(Error::TxSyncFailed) path will return an error; if it succeeds, synced_to_tip will become true again only after the block has been applied and Synced is processed.

Please let me know if I am missing the point!

Comment thread src/wallet/mod.rs
// now-durable delta or has already been flushed and cleared by a concurrent writer —
// we never drop unpersisted changes.
Ok(_) => {
let _ = self.inner.lock().expect("lock").take_staged();

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we drop this workaround and depend on lightningdevkit#980 instead? Once the wallet lock is released, another operation can add to the staged changeset, and this unconditional take_staged() may clear changes that were not persisted by this call.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah, i'll try to cherry-pick upstream solution

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants