Skip to content

Adapt to lightning-block-sync API changes#874

Merged
tnull merged 2 commits intolightningdevkit:mainfrom
jkczyz:2026-04-block-sync-api
Apr 29, 2026
Merged

Adapt to lightning-block-sync API changes#874
tnull merged 2 commits intolightningdevkit:mainfrom
jkczyz:2026-04-block-sync-api

Conversation

@jkczyz
Copy link
Copy Markdown
Contributor

@jkczyz jkczyz commented Apr 14, 2026

Update to use the new HeaderCache type instead of implementing the Cache trait, pass BestBlock instead of BlockHash to synchronize_listeners, and pass HeaderCache by value to SpvClient::new.

Also adapt to BestBlock gaining a previous_blocks field and ChannelManager deserialization returning BestBlock instead of BlockHash.

These changes are from lightningdevkit/rust-lightning#4266.

@ldk-reviews-bot
Copy link
Copy Markdown

ldk-reviews-bot commented Apr 14, 2026

👋 Thanks for assigning @tnull as a reviewer!
I'll wait for their review and will help manage the review process.
Once they submit their review, I'll check if a second reviewer would be helpful.

@jkczyz jkczyz changed the title Adapt to lightning-block-sync API changes Adapt to lightning-block-sync API changes Apr 14, 2026
@ldk-reviews-bot ldk-reviews-bot requested a review from tnull April 14, 2026 04:00
Copy link
Copy Markdown
Collaborator

@tnull tnull left a comment

Choose a reason for hiding this comment

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

Thanks for looking into this, unfortunately bindings are currently broken.

Hmm, so, it seems lightningdevkit/rust-lightning#4266 fundamentally broke what BestBlock used to mean, i.e., being a simple data type for the chain tip. Really not loving the change, but I probably should have spoken up on that PR.

@TheBlueMatt any thoughts on still creating a new type for the 'best-sub-chain' that BestBlock now represents and switching the LDK APIs to that in a follow-up to lightningdevkit/rust-lightning#4266, rather than completely changing BestBlock semantics?

In any case, we need to figure something out as the changes currently broke our bindings, and the changes in BestBlock simply can't be exposed via uniffi as-is. I guess we could duplicate the old BestBlock code in this repo, but I'd really prefer if we could revert BestBlock and just use a new type for the semantics.

@TheBlueMatt
Copy link
Copy Markdown
Contributor

@TheBlueMatt any thoughts on still creating a new type for the 'best-sub-chain' that BestBlock now represents and switching the LDK APIs to that in a follow-up to lightningdevkit/rust-lightning#4266, rather than completely changing BestBlock semantics?

I don't understand this? A BestBlock is the chain tip info, its used (pretty consistently) to indicate where we last synced to and that can/should absolutely include a few recent headers. At least in lightning we don't really have a use for a "best block hash and height" type, if LDK Node has a use for such a type it could of course have an internal type for it, but its not clear to me what the use for that would be?

@tnull
Copy link
Copy Markdown
Collaborator

tnull commented Apr 14, 2026

I don't understand this? A BestBlock is the chain tip info, its used (pretty consistently) to indicate where we last synced to

For one it's a misnomer / unexpected API now. If something is called BestBlock I expect it to represent the information of a (single) block. Something like bdk_chain's CheckPoint that is made up of BlockIds (the equivalent of BestBlock) would be a much more intuitive/expected API and naming scheme, IMO. The previous_blocks field seems like information specific to how our chain syncing logic works, which preferably wouldn't leak into the public API?

It's also unnecessarily inefficient for many cases, as we for example now clone a bunch of BlockHashess when simply checking {ChannelManager, OutputSweeper, etc}::current_best_block.

and that can/should absolutely include a few recent headers.

Thankfully it's just a few hashes, not the full Headers.

At least in lightning we don't really have a use for a "best block hash and height" type, if LDK Node has a use for such a type it could of course have an internal type for it, but its not clear to me what the use for that would be?

Yeah, fair if we don't deem the inefficiency the additional clones fixing, but from my point of view a more intuitive name would still be worth adopting (before the 0.3 release), even if we just move the old BestBlock to LDK Node.

If we think previous_blocks is worth to always have as part of this 'chain check point', it would be nice if LDK could make it somewhat bindings compatible, so that we don't have to introduce yet another awkward From implementation and always have to re-allocate etc.

@tnull tnull added this to the 0.8 milestone Apr 20, 2026
@jkczyz jkczyz force-pushed the 2026-04-block-sync-api branch 2 times, most recently from 3841a65 to 1f99308 Compare April 23, 2026 23:03
@jkczyz
Copy link
Copy Markdown
Contributor Author

jkczyz commented Apr 23, 2026

Discussed offline. We'll @TheBlueMatt will rename upstream's BestBlock to BlockLocator. Here, I've added a BestBlock that is like BlockLocator but without previous_blocks. Also rebased.

@jkczyz
Copy link
Copy Markdown
Contributor Author

jkczyz commented Apr 23, 2026

BTW, we'll probably want to merge this rather than wait on the upstream change since another breaking change (updated in #878) is already upstream.

Copy link
Copy Markdown
Collaborator

@tnull tnull left a comment

Choose a reason for hiding this comment

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

Looks good, but CI is unhappy:

error: use of deprecated associated function `bitcoin::FeeRate::from_sat_per_vb_unchecked`: use from_sat_per_vb_u32 instead
   --> /home/runner/work/ldk-node/ldk-node/target/debug/build/ldk-node-8a4d944ca9f27956/out/ldk_node.uniffi.rs:757:12
    |
757 |     pub fn r#from_sat_per_vb_unchecked(
    |            ^^^^^^^^^^^^^^^^^^^^^^^^^^^
    |
    = note: `-D deprecated` implied by `-D warnings`
    = help: to override `-D warnings` add `#[allow(deprecated)]`

error: could not compile `ldk-node` (lib) due to 1 previous error

@tnull
Copy link
Copy Markdown
Collaborator

tnull commented Apr 27, 2026

CI failure is fixed by #891, would be good to rebase this PR after this lands to have uniffi CI pass before we merge.

@jkczyz jkczyz force-pushed the 2026-04-block-sync-api branch 2 times, most recently from ea17859 to 9450bdd Compare April 27, 2026 17:51
@jkczyz jkczyz requested a review from tnull April 27, 2026 20:20
Copy link
Copy Markdown
Collaborator

@tnull tnull left a comment

Choose a reason for hiding this comment

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

LGTM, one question

Comment thread src/wallet/mod.rs
let mut current_block = Some(checkpoint.clone());
let previous_blocks = std::array::from_fn(|_| {
let child = current_block.take()?;
let parent = child.prev().filter(|cp| cp.height() + 1 == child.height())?;
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Do I understand correctly that this filter is mostly defensive as we expect prev already to return the right parent and we shouldn't have gaps? Maybe it's worth leaving a comment here? (not sure if a debug_assert would be warrented?)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

It can actually have gaps as CheckPoint::from_block_ids doesn't care if they are contiguous. But I was only able to reproduce the behavior with an Esplora backend, which doesn't use current_best_block. Left a comment.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

It can actually have gaps as CheckPoint::from_block_ids doesn't care if they are contiguous. But I was only able to reproduce the behavior with an Esplora backend, which doesn't use current_best_block. Left a comment.

Right, that's what I thought. However, one of the main motivations for the lightning-block-sync refactor was that wanted to enable safe switching between chain sources. Are we positive that users can switch back-and-forth between Esplora / Electrum and bitcoind now, without risking that they don't have the necessary data available to handle reorgs? Or maybe it's fine because BDK doesn't have the same consistency guarantees in general, i.e., don't require to manually unconfirm blocks?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Right, that's what I thought. However, one of the main motivations for the lightning-block-sync refactor was that wanted to enable safe switching between chain sources. Are we positive that users can switch back-and-forth between Esplora / Electrum and bitcoind now, without risking that they don't have the necessary data available to handle reorgs?

AFAICT, Esplora uses 10 or 15 depending on the backend.

https://github.com/bitcoindevkit/rust-esplora-client/blob/603ba6bfb811e379d855413c03bf420f589c2abe/src/blocking.rs#L495-L502

And Electrum uses 8:

https://github.com/bitcoindevkit/bdk/blob/646661fa3d19ac6f2bd7bef58b321372b795433c/crates/electrum/src/bdk_electrum_client.rs#L14

After that they jump back to the genesis block.

So probably enough given it covers ANTI_REORG_DELAY at least. A deeper re-org could be a problem, though.

Or maybe it's fine because BDK doesn't have the same consistency guarantees in general, i.e., don't require to manually unconfirm blocks?

Hmm... isn't the concern more about LDK? We're using Wallet::current_best_block to pass a BlockLocator to lightning_block_sync::init::synchronize_listeners.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

So probably enough given it covers ANTI_REORG_DELAY at least. A deeper re-org could be a problem, though.

Ah, alright, that should be fine then, given we haven't seen a deeper reorg since the early days and we do already assume ANTI_REORG_DELAY in a few other places?

Hmm... isn't the concern more about LDK? We're using Wallet::current_best_block to pass a BlockLocator to lightning_block_sync::init::synchronize_listeners.

Right, but it's mostly about LDK objects, not lightning_block_sync logic overall, no?

jkczyz and others added 2 commits April 28, 2026 11:11
Update to use the new HeaderCache type instead of implementing the
Cache trait, pass BestBlock instead of BlockHash to
synchronize_listeners, and pass HeaderCache by value to SpvClient::new.

Also adapt to BestBlock gaining a previous_blocks field and
ChannelManager deserialization returning BestBlock instead of BlockHash.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
UniFFI cannot represent the fixed-size array that upstream's BestBlock
carries via `previous_blocks`, so NodeStatus.current_best_block was
unusable from Swift, Kotlin, and Python once upstream added that field.
Introduce a small ldk-node BestBlock with just hash and height — the
pieces bindings can handle — and expose it in place of the upstream
type on the public API.

Generated with assistance from Claude Code.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@jkczyz jkczyz force-pushed the 2026-04-block-sync-api branch from 9450bdd to 2ab93c1 Compare April 28, 2026 16:13
@jkczyz jkczyz requested a review from tnull April 28, 2026 16:13
Copy link
Copy Markdown
Collaborator

@tnull tnull left a comment

Choose a reason for hiding this comment

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

LGTM

@tnull tnull merged commit 147247a into lightningdevkit:main Apr 29, 2026
21 checks passed
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.

4 participants