Skip to content

Add support for sourcing chain data from Electrum #486

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 13 commits into
base: main
Choose a base branch
from

Conversation

tnull
Copy link
Collaborator

@tnull tnull commented Mar 17, 2025

Closes #196.

So far, we support Esplora and Bitcoind RPC chain sources. In this PR we add Electrum support based on the blocking rust-electrum-client, and it's bdk_electrum and lightning-transaction-sync counterparts.

Due to the blocking nature of rust-electrum-client and as it directly connects to the server upon Client::new (see bitcoindevkit/rust-electrum-client#166), we ended up wrapping the runtime-specific behavior in an ElectrumRuntimeClient object that is initialized and dropped in Node::start and stop, respectively.

One thing missing that we still need to consider is how we'd reestablish connections to the remote after they have been lost for one reason or another. IMO, that behavior should live in rust-electrum-client to avoid all users having to duplicate it, so it's pending resolution of bitcoindevkit/rust-electrum-client#165

As we did with bitcoind-RPC, Electrum support is tested by adding another full_cycle integration test.

@tnull tnull added this to the 0.5 milestone Mar 17, 2025
@tnull tnull force-pushed the 2025-02-add-electrum-support branch from b386ee7 to 9352b2e Compare March 17, 2025 14:17
@tnull tnull requested a review from jkczyz March 17, 2025 14:51
@tnull tnull force-pushed the 2025-02-add-electrum-support branch 3 times, most recently from 568d10d to 678cf91 Compare March 18, 2025 12:50
Copy link
Contributor

@elnosh elnosh left a comment

Choose a reason for hiding this comment

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

I'm familiarizing myself with the codebase but I tried running a node locally with the changes here pointing to an electrum server and basic operations are working. I just have a couple of questions, if I may, to get a better understanding.

Not strictly related to the PR but maybe I can sneak it in, why is it that for bitcoin core rpc and electrum they are in their separate bitcoind_rpc.rs and electrum.rs files but for esplora it is all in the mod.rs? I see that that the EsploraAsyncClient is directly used without a wrapper but wanted to see what were the reasons for it (:

@tnull
Copy link
Collaborator Author

tnull commented Mar 19, 2025

Not strictly related to the PR but maybe I can sneak it in, why is it that for bitcoin core rpc and electrum they are in their separate bitcoind_rpc.rs and electrum.rs files but for esplora it is all in the mod.rs?

Hmm, for one it has historic reasons, but also bitcoind RPC and electrum both ended up needing additional helper objects/types that made sense to split out to dedicated sub-modules. While we could eventually refactor the Esplora part so more code lives in a dedicated esplora.rs file, there is no strong necessity for it right now I think.

I see that that the EsploraAsyncClient is directly used without a wrapper but wanted to see what were the reasons for it (:

Right, that's essentially the main reason why it still lives in mod.rs. For electrum we ended up needing a wrapper that is created on runtime, and for bitcoind RPC we implemented a specific wrapper around RpcClient that handles the JSON parsing, essentially.

@tnull tnull force-pushed the 2025-02-add-electrum-support branch 3 times, most recently from 9608e66 to 3142c41 Compare March 19, 2025 12:54
@tnull tnull force-pushed the 2025-02-add-electrum-support branch 4 times, most recently from 466ab9f to e39b1f2 Compare March 27, 2025 08:47
@tnull
Copy link
Collaborator Author

tnull commented Mar 27, 2025

Blocked on rust-bitcoin/corepc#111 for now.

@tnull tnull force-pushed the 2025-02-add-electrum-support branch 2 times, most recently from 0d401fe to dda6bc7 Compare March 27, 2025 10:50
@jkczyz jkczyz removed their request for review March 27, 2025 18:42
@tnull tnull requested a review from joostjager March 27, 2025 19:04
@tnull tnull force-pushed the 2025-02-add-electrum-support branch from dda6bc7 to edb4b10 Compare March 28, 2025 08:55
@tnull
Copy link
Collaborator Author

tnull commented Mar 28, 2025

Rebased on main after #508 landed.

Copy link
Contributor

@joostjager joostjager left a comment

Choose a reason for hiding this comment

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

The question that I asked myself most during review is whether the way this PR deals with the runtime and start/stop is really the best option for now.

@tnull tnull force-pushed the 2025-02-add-electrum-support branch from edb4b10 to 97f4bd9 Compare March 28, 2025 16:21
@tnull tnull force-pushed the 2025-02-add-electrum-support branch 3 times, most recently from 96ee279 to cb5dc5b Compare April 3, 2025 12:51
@tnull
Copy link
Collaborator Author

tnull commented Apr 3, 2025

Squashed fixups without further changes.

Copy link
Contributor

@joostjager joostjager left a comment

Choose a reason for hiding this comment

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

Nearly there. Just the one question about the double timeouts.


let spawn_fut = self.runtime.spawn_blocking(move || electrum_client.batch_call(&batch));

let timeout_fut = tokio::time::timeout(
Copy link
Contributor

Choose a reason for hiding this comment

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

I see you also set retries now. I saw that it was 1 previously (the default). Did the earlier test with the wifi disconnect pass because that one retry happened after you already reconnected wifi?

If I understand this correctly, setting the timeout on the electrum client itself is essential to not end up with a potential graveyard of stuck threads? This might be important to add as a comment.

Also curious to hear why you think that retrying over and over again when a previous request is still stuck is a good idea? It will eventually exhaust the thread pool, right? And then stop independent processes within LDK too?

The original reason for the double timeout is because esplora-client turned out to be not trustworthy. But, as suggested above, isn't electrum different because it isn't an async library and it is unlikely for the timeout to fail? Looking at the sources, it seems that the timeout is set on a pretty low level for all calls.

.. we need to bump the version, while making sure `electrsd` and CLN CI
are using the same version. As the `blockstream/bitcoind` version hasn't
reached v28 yet, we opt for v27.2 everywhere for now.
@tnull tnull force-pushed the 2025-02-add-electrum-support branch from cb5dc5b to db756c6 Compare April 4, 2025 08:21
@tnull
Copy link
Collaborator Author

tnull commented Apr 4, 2025

The corerpc-node just shipped the needed fix as part of the 0.7 minor release, now we just need RCasatta/electrsd#100 to land as part of an electrsd release to go forward with this.

@tnull tnull mentioned this pull request Apr 7, 2025
Copy link
Contributor

@enigbe enigbe left a comment

Choose a reason for hiding this comment

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

I have taken another look at this PR and now understand why you apply pending filter entries to the pending transaction and output vectors. The refactor that introduced ElectrumRuntimeStatus made it easier to reason about it as a state machine with the attendant events, actions, and state or internal transitions.

state machine

From the snippet below, when we start the Node with the tokio runtime, it indicates that other background tasks do not get started before the ChainSource has started:

pub fn start_with_runtime(&self, runtime: Arc<tokio::runtime::Runtime>) -> Result<(), Error> {

        ... 

        // Start up any runtime-dependant chain sources (e.g. Electrum)

        self.chain_source.start(Arc::clone(&runtime)).map_err(|e| {

            log_error!(self.logger, "Failed to start chain syncing: {}", e);

            e

        })?;
        ...
       // Other background tasks

This suggests that it is unlikely that Confirmables would register transactions and outputs prior the chain source starting and, in extension, prior to the runtime client being available (ElectrumRuntimeStatus::Started). Are there scenarios I am missing where this could happen? I'd appreciate any further clarification.

Otherwise, the PR looks good to me. Reviewing this has been quite educational. Thank you!

@tnull
Copy link
Collaborator Author

tnull commented Apr 10, 2025

I have taken another look at this PR and now understand why you apply pending filter entries to the pending transaction and output vectors.

Thank you for the review!

This suggests that it is unlikely that Confirmables would register transactions and outputs prior the chain source starting and, in extension, prior to the runtime client being available (ElectrumRuntimeStatus::Started). Are there scenarios I am missing where this could happen? I'd appreciate any further clarification.

Unfortunately, the ChannelMonitors register the transactions/outputs they're interested in when ChainMonitor::watch_channel is called. See:
https://github.com/lightningdevkit/rust-lightning/blob/85185d863871fefa5243fa9e38f3037b5db3eac6/lightning/src/chain/chainmonitor.rs#L790

and

https://github.com/lightningdevkit/rust-lightning/blob/85185d863871fefa5243fa9e38f3037b5db3eac6/lightning/src/chain/channelmonitor.rs#L1776

In turn we currently call this upon initialization:

chain_monitor.watch_channel(funding_outpoint, channel_monitor).map_err(|e| {

not at runtime/during Node::start. This is one of the reasons we decided to soon refactor this so that we'd intialize most LDK objects only at start, not in Builder::build. Another (actually even more important) reason to do this is that we'd then be able to first retrieve the current best block before initializing the ChannelManager and other objects for the first time, avoiding the currently necessary resyncing from genesis (cf. #415).

@tnull tnull force-pushed the 2025-02-add-electrum-support branch 3 times, most recently from 37d667c to 24d0874 Compare April 15, 2025 10:01
@tnull tnull requested a review from joostjager April 15, 2025 12:24
tnull added 12 commits April 15, 2025 18:14
We upgrade our tests to use `electrum-client` v0.22 and `electrsd` v0.31
to ensure compatibility with `bdk_electrum` were about to start using.
By default `rustls`, our TLS implementation of choice, uses `aws-lc-rs`
which requires `bindgen` on some platforms. To allow building with
`aws-lc-rs` on Android, we here install the `bindgen-cli` tool before
running the bindings generation script in CI.
We here setup the basic API and structure for the
`ChainSource::Electrum`.

Currently, we won't have a `Runtime` available when initializing
`ChainSource::Electrum` in `Builder::build`. We therefore isolate any
runtime-specific behavior into an `ElectrumRuntimeClient`.

This might change in the future, but for now we do need this workaround.
Currently, we won't have a `Runtime` available when initializing
`ChainSource::Electrum`. We therefore isolate any runtime-specific
behavior into the `ElectrumRuntimeStatus`.

Here, we implement `Filter` for `ElectrumRuntimeClient`, but we need to
cache the registrations as they might happen prior to
`ElectrumRuntimeClient` becoming available.
.. as we do with `BitcoindRpc`, we now test `Electrum` support by
running the `full_cycle` test in CI.
@tnull tnull force-pushed the 2025-02-add-electrum-support branch from 24d0874 to 5cdd2e3 Compare April 15, 2025 16:14
@tnull
Copy link
Collaborator Author

tnull commented Apr 15, 2025

Squashed fixups without further changes.

Copy link
Contributor

@joostjager joostjager left a comment

Choose a reason for hiding this comment

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

LGTM!

Still interested to see if conversion to a more object-oriented style for the backends is straight-forward or not.

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.

Add Electrum support
4 participants