Skip to content

Follow reorgs with bitcoind REST - #1022

Open
tnull wants to merge 2 commits into
lightningdevkit:mainfrom
tnull:2026-08-fix-bitcoind-rest-reorg
Open

Follow reorgs with bitcoind REST#1022
tnull wants to merge 2 commits into
lightningdevkit:mainfrom
tnull:2026-08-fix-bitcoind-rest-reorg

Conversation

@tnull

@tnull tnull commented Aug 5, 2026

Copy link
Copy Markdown
Collaborator

Bitcoin Core's REST headers endpoint cannot serve headers from a stale branch. This prevented REST-backed nodes from finding a common ancestor and following the replacement chain after a reorg.

In #1006 we enabled bitcoind REST chain source support in CI, which exposed this failure.

Co-Authored-By: HAL 9000

Bitcoin Core's REST headers endpoint cannot serve headers from a stale
branch. This prevented REST-backed nodes from finding a common ancestor
and following the replacement chain after a reorg.

In lightningdevkit#1006 we enabled bitcoind REST chain source support in CI, which
exposed this failure.

Co-Authored-By: HAL 9000
@tnull
tnull requested a review from joostjager August 5, 2026 13:27
@ldk-reviews-bot

ldk-reviews-bot commented Aug 5, 2026

Copy link
Copy Markdown

👋 Thanks for assigning @joostjager 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.

@tnull tnull added this to the 0.8 milestone Aug 5, 2026
@TheBlueMatt

Copy link
Copy Markdown
Contributor

Hmm, that's somewhat surprising. Can you file an issue on https://github.com/bitcoin/bitcoin as well?

@tnull

tnull commented Aug 5, 2026

Copy link
Copy Markdown
Collaborator Author

Hmm, that's somewhat surprising. Can you file an issue on https://github.com/bitcoin/bitcoin as well?

Will do.

@joostjager joostjager left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I wonder why we need a chain backend to find a common ancestor. What happens if someone switches to a different backend that doesn't know about the stale branch at all?

Comment thread src/chain/bitcoind.rs Outdated
BitcoindClient::Rest { rest_client, rpc_client, .. } => {
match rest_client.get_header(header_hash, height_hint).await {
Err(e) if e.kind() == BlockSourceErrorKind::Persistent => {
rpc_client.get_header(header_hash, height_hint).await

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

It looks a bit weird that we have to fallback to the other interface. From history it seems we need rest because rpc is too slow for large sync ops?

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.

REST was added both for its unauthenticated read-only interface and for more efficient/cacheable binary block retrieval; it isn’t simply that RPC is unusably slow.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Is the unauth interface an advantage if you also may need rpc?

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.

It is to enable easier caching, but see your point.

@tnull

tnull commented Aug 6, 2026

Copy link
Copy Markdown
Collaborator Author

I wonder why we need a chain backend to find a common ancestor. What happens if someone switches to a different backend that doesn't know about the stale branch at all?

Good question. We mostly need the backend to resolve the stale branch here because we accidentally stopped retaining the header cache in #874 (though note after restart that cache is also empty, so the fallback still makes sense anyways). Before that change / lightningdevkit/rust-lightning#4266, BitcoindChainSource kept one cache across polls. When SpvClient changed to own its HeaderCache, we started constructing it with a fresh cache every time and discarded the cache returned by initial synchronization.

I’ll see to restore cache reuse by retaining the SpvClient across polls.

Switching backends happens across a restart. In that case, initial synchronization uses the recent hashes stored in BlockLocator and tries them until the new backend recognizes a common ancestor, so the new backend need not know the stale tip. This is bounded by the stored history, currently 12 blocks. Also cf. #963.

Retain the cache populated during initial listener synchronization so
subsequent polls can find headers for stale forks when REST no longer
serves them.

Use weak listener handles to avoid creating an ownership cycle through
the chain source.

Co-Authored-By: HAL 9000
@tnull

tnull commented Aug 6, 2026

Copy link
Copy Markdown
Collaborator Author

Now updated to preserve the SpvClient across polls.

@tnull
tnull requested a review from joostjager August 6, 2026 11:33
Comment thread src/chain/bitcoind.rs
result => result,
}
BitcoindClient::Rest { rest_client, .. } => {
rest_client.get_header(header_hash, height_hint).await

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

In your comment, you said "though note after restart that cache is also empty, so the fallback still makes sense anyways", but you remove it anyway?

Or is the shared and initialized spv client all that is needed?

Comment thread src/chain/bitcoind.rs
};
*spv_client_lock = Some(self.new_spv_client(
chain_tip,
HeaderCache::new(),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Is it possible to hit this? And how is a stale branch walked backwards in that case?

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