Skip to content
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

Add support for sourcing chain data from Electrum #486

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 (:

Comment on lines +170 to +190
pub(crate) async fn broadcast(&self, tx: Transaction) {
let electrum_client = Arc::clone(&self.electrum_client);

let txid = tx.compute_txid();
let tx_bytes = tx.encode();

let spawn_fut =
self.runtime.spawn_blocking(move || electrum_client.transaction_broadcast(&tx));

let timeout_fut =
tokio::time::timeout(Duration::from_secs(TX_BROADCAST_TIMEOUT_SECS), spawn_fut);

match timeout_fut.await {
Ok(res) => match res {
Ok(_) => {
log_trace!(self.logger, "Successfully broadcast transaction {}", txid);
},
Err(e) => {
log_error!(self.logger, "Failed to broadcast transaction {}: {}", txid, e);
log_trace!(
self.logger,
Copy link
Contributor

Choose a reason for hiding this comment

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

For the broadcast method in the BitcoindRpcClient, it returns a Result and then logs the errors when called in process_broadcast_queue for ChainSource. Should this do that as well and return a Result and then log them in that method?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Hmm, no, while I agree it's a bit inconsistent I decided to move the logging part also into the chain source-specific module this time around.

Comment on lines +46 to +53
#[test]
fn channel_full_cycle_electrum() {
let (bitcoind, electrsd) = setup_bitcoind_and_electrsd();
let chain_source = TestChainSource::Electrum(&electrsd);
let (node_a, node_b) = setup_two_nodes(&chain_source, false, true, false);
do_channel_full_cycle(node_a, node_b, &bitcoind.client, &electrsd.client, false, true, false);
}

Copy link
Contributor

Choose a reason for hiding this comment

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

would it be good to also add tests in onchain_send_receive for nodes using TestChainSource::Electrum since that functionality depends on the ChainSource used?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Well, essentially every test/operation depends on sourcing chain data. I don't think we want to re-run our entire test suite for all chain sources. The full_cycle test already should cover all fundamental parts of sourcing chain data (syncing onchain wallet, syncing lightning wallet, retrieving fee estimates, broadcasting transactions), so we should be good with just this test for now. That said, we considered making bitcoind RPC the default for all tests (instead of Esplora) before, but that is orthogonal to this PR.

@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 added 12 commits March 19, 2025 13:12
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` 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 an `ElectrumRuntimeClient`.

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 2 times, most recently from 42a499d to 9608e66 Compare March 19, 2025 12:13
@tnull tnull force-pushed the 2025-02-add-electrum-support branch from 9608e66 to 3142c41 Compare March 19, 2025 12:54
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
2 participants