Skip to content

Create an async corepc-client#505

Draft
jamillambert wants to merge 7 commits into
rust-bitcoin:masterfrom
jamillambert:0212-WIP-client-async
Draft

Create an async corepc-client#505
jamillambert wants to merge 7 commits into
rust-bitcoin:masterfrom
jamillambert:0212-WIP-client-async

Conversation

@jamillambert

@jamillambert jamillambert commented Feb 12, 2026

Copy link
Copy Markdown
Collaborator

This PR adds an async corepc-client with the RPCs used by BDK.

The patches are structured to make it easier to see what was changed from sync to async. First the sync version is copied and renamed, then in a separate patch it is changed to async.

@jamillambert jamillambert changed the title WIP: Create an async corepc-client by adding async support to jsonrpc WIP: Create an async corepc-client (adding async support to jsonrpc) Feb 12, 2026
@jamillambert jamillambert force-pushed the 0212-WIP-client-async branch 2 times, most recently from 6981d8f to 6cd8bb2 Compare February 12, 2026 18:53
Comment thread jsonrpc/src/client_async.rs Outdated
Comment thread jsonrpc/Cargo.toml
Comment thread jsonrpc/Cargo.toml
@tcharding

Copy link
Copy Markdown
Member

@apoelstra whats your take man? Would you be willing to have jsonrpc take changes to be able to use bitreq in an async manner? It obviously smashes the dep tree if --all-features are used.

@tcharding

Copy link
Copy Markdown
Member

Holla at me if/when you would like some review mate.

@jamillambert jamillambert force-pushed the 0212-WIP-client-async branch 4 times, most recently from 1c174a6 to 7a79b87 Compare February 20, 2026 15:09
@jamillambert

Copy link
Copy Markdown
Collaborator Author

Rewrote the async client:

  • Removed all the version folders where the RPC macros were defined.
  • Removed macroization from client_async module.
  • Created bdk_client module that implements the the required RPC methods in it using the shared client methods in mod.rs.
    • Supports v25 to v30 by checking the server version and then using the required vtype version before converting into the model type.
    • The bdk_client RPC methods all return the modelled types. This made the error handling difficult since all of the error types for converting into the modelled type are version specific types. I am not sure how to handle this ergonomically. Currently they all return the same Error type defined in the async client.
    • There are a few RPCs that return a single field, e.g. getblock returns a Block and getblockcount a u32. Would it be better to return e.g. Result<u32> instead of Result<GetBlockCount> ?
  • Rewrote the tests to use the new bdk_client rpc calls.

@tcharding thoughts? in particular the error handling of converting to the modelled type. Returning a modelled type vs the inner field e.g. Block or u32. And having a bdk_client module that only implements the RPCs, the idea was to be able to add another one for e.g. LDK with their required RPCs and arguments but sharing the new, call etc. methods.

Comment thread client/src/client_async/bdk_client.rs Outdated
Comment thread client/src/client_async/bdk_client.rs Outdated
Comment thread client/src/client_async/bdk_client.rs Outdated
@tcharding

Copy link
Copy Markdown
Member

Rewrote the async client:

  • Removed all the version folders where the RPC macros were defined.
  • Removed macroization from client_async module.
  • Created bdk_client module that implements the the required RPC methods in it using the shared client methods in mod.rs.

After writing a massive post I realised this solution is not going to work (see bottom for explanation). I think we should inline the bdk module and just document that its designed explicitly with that project in mind.

How about throwing this at the top:

//! Async JSON-RPC client designed explicitly to support BDK.
//!
//! ## Project decisions
//!
//! * Support Core versions 25 to 30.
  • Supports v25 to v30 by checking the server version and then using the required vtype version before converting into the model type.

Troublesome, commented already on the code.

  • The bdk_client RPC methods all return the modelled types. This made the error handling difficult since all of the error types for converting into the modelled type are version specific types. I am not sure how to handle this ergonomically. Currently they all return the same Error type defined in the async client.

Another solution is to have an error type for each function. Then it can have a variant (assuming its an enum) for the exact error returned by into_model.

  • There are a few RPCs that return a single field, e.g. getblock returns a Block and getblockcount a u32. Would it be better to return e.g. Result<u32> instead of Result<GetBlockCount> ?

Agreed that a wrapper type is not useful to returen (eg GetBlockCount). At first blush I think you are correct and returning the inner type is better (its a u64 in this case). After upgrade to bitcoin v0.33 we may have better concrete types to return if needed but your argument stands.

  • Rewrote the tests to use the new bdk_client rpc calls.

Nice.

@tcharding thoughts? in particular the error handling of converting to the modelled type. Returning a modelled type vs the inner field e.g. Block or u32. And having a bdk_client module that only implements the RPCs, the idea was to be able to add another one for e.g. LDK with their required RPCs and arguments but sharing the new, call etc. methods.

I don't think separation by project module is not going to work because the modules will conflict with each other. And if we feature gate the features will not be additive. We could try some macro stuff but I think it defeats the purpose which is to write a simple clean client that others can fork if they need to.

@apoelstra

Copy link
Copy Markdown
Member

@apoelstra whats your take man? Would you be willing to have jsonrpc take changes to be able to use bitreq in an async manner? It obviously smashes the dep tree if --all-features are used.

Would this then tie all users of the crate to tokio? Why can't it be executor-agnostic?

@jamillambert jamillambert force-pushed the 0212-WIP-client-async branch 3 times, most recently from fd21983 to 8448aab Compare February 25, 2026 19:10
@jamillambert

Copy link
Copy Markdown
Collaborator Author

@apoelstra whats your take man? Would you be willing to have jsonrpc take changes to be able to use bitreq in an async manner? It obviously smashes the dep tree if --all-features are used.

Would this then tie all users of the crate to tokio? Why can't it be executor-agnostic?

The changes to jsonrpc to make it support async is executor-agnostic. It is only the use of bitreq and async that requires tokio in the current state of this PR.

tcharding added a commit that referenced this pull request Apr 23, 2026
0aa7803 Add async support to jsonrpc (Jamil Lambert, PhD)
7302ebd Change Debug impl to local Client (Jamil Lambert, PhD)
7d556f9 Remove fuzz related sections (Jamil Lambert, PhD)
e62b044 Copy jsonrpc client and bitreq_http for async. (Jamil Lambert, PhD)
c16f4b5 Round timeouts to whole seconds (Jamil Lambert, PhD)

Pull request description:

  Pulled out of #505 and polished.

  The aim is to add async support without breaking existing sync downstream. This includes keeping the reexports of the current sync `Client` at the crate root.

ACKs for top commit:
  apoelstra:
    utACK 0aa7803

Tree-SHA512: d0a096181668f7656ed3ba8f7d3cf77560a39c6eec2c6ff410627e35ae7d798ef6abe6cd0a467317383a6091c403aded977d095a43f2090a5e359f305e0bf12a
@jamillambert jamillambert force-pushed the 0212-WIP-client-async branch from 84d501b to b27af91 Compare June 10, 2026 10:48
@jamillambert jamillambert changed the title WIP: Create an async corepc-client (adding async support to jsonrpc) WIP: Create an async corepc-client Jun 10, 2026
@jamillambert

Copy link
Copy Markdown
Collaborator Author

Reviving this PR.

I rebased on master now that jsonrpc has async support. The commit sequence is recreated to be simpler. Still a work in progress.

@jamillambert jamillambert force-pushed the 0212-WIP-client-async branch from b27af91 to 021baf0 Compare June 10, 2026 12:04
@jamillambert jamillambert force-pushed the 0212-WIP-client-async branch 3 times, most recently from 1674e53 to 035940d Compare June 11, 2026 13:37
@jamillambert jamillambert changed the title WIP: Create an async corepc-client Create an async corepc-client Jun 11, 2026
@jamillambert jamillambert force-pushed the 0212-WIP-client-async branch from 035940d to 854ac31 Compare June 11, 2026 14:36
There is a lint error: this expression creates a reference which is
immediately dereferenced by the compiler.

Fix it as suggested by the compiler.
@jamillambert jamillambert force-pushed the 0212-WIP-client-async branch from 854ac31 to aa1e80d Compare June 11, 2026 14:43
@jamillambert

Copy link
Copy Markdown
Collaborator Author

@tcharding I think this is ready for preliminary review.

The last patch is a new idea to make the async client universal by not having any inherent RPC methods. Users instead add an extension trait to implement the RPCs that they need in their own codebase. The RPCs for BDK have already been done in this crate and can be used as a template for others. Users don't need to fork the client and can instead depend on the published version which is better for any fixes or security updates. It can also stay in this repo.

The docs will need to be improved, I'm leaving it for now due to this possible change in structure.

The CI failure for v31 needs the related PR and then I'll see if any of the async client tests need to be updated.

Comment thread client/Cargo.toml Outdated
Comment thread client/src/client_async/mod.rs Outdated
Comment thread client/src/client_async/mod.rs Outdated
Comment thread client/src/client_async/mod.rs Outdated
Comment thread client/src/client_async/error.rs Outdated
GetBlockVerboseOneError as GetBlockVerboseOneErrorV29,
};

pub use crate::error::{Error, UnexpectedServerVersionError};

@tcharding tcharding Jun 12, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

For a production client all this error stuff could be better I rekon.

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.

I made the errors simpler and better.

Comment on lines +13 to +17
/// This trait exposes the Bitcoin Core RPC methods available on [`Client`]. Downstream users
/// can define their own extension traits with additional methods without risk of
/// name collision with the methods defined here.
// `async fn` in traits produces non-`Send` futures by default; we suppress this lint because
// `BitcoinRpcs` is intended only for use with concrete types (never `dyn BitcoinRpcs`).

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

We can just put the subset of RPC methods we want directly on the Client type. Downstream can still define extension traits and name collisions can still be avoided by calling their trait methods using trait syntax or whatever its called.

Does BDK use the verbose methods? At first blush I don't like them, bit ugly for a 'bare bones client'.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I rekon we add an example that adds an extension trait and a single method, then that can be used as documentation and to link LLMs too.

@jamillambert jamillambert Jun 12, 2026

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.

The reason for this change was to separate Client from the BDK RPCs. Otherwise we have a BDK client which can be modified for other projects, rather than the way it is here where it is a universal client with a trait premade for BDK.

So I still prefer to have no RPCs at all in the Client, and keep the full set of BDK RPCs in the prewritten trait.

I also think that having extra RPCs in this trait is a good idea rather than just one example and force everyone to write their own. If we put in all that BDK need it works out of the box for them, and if a few can be added to fill the needs of LDK and possibly there is only a few changes needed to this set to support multiple users. I understand the concern that supporting multiple projects will cause conflicts in the RPC methods if they are all together. But if we can support multiple projects without conflicts why not? i.e. The policy is we don't support adding downstream RPC requirements to the Client you need to add the trait to your own codebase, but on an individual basis we can consider if only a small change is required to support whole project then why not.

EDIT: AI says LDK only need getblockchaininfo and gettxout added to the trait so that is supports them too.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I agree with all that about supporting bdk and ldk. I rekon we should do so as methods on the client still though not a trait. We can chat about it on the phone tomorrow though.

In preparation for adding bitreq_http_async feature to jsonrpc move
the sync version to the client-sync feature so it is not always on
with jsonrpc.
These will both be used by the async client. Move them out of sync
client in preparation.

Edit the logging function so it doesn't panic.
Create a new folder for the upcoming async client and copy in the
existing client_sync code.

Code copy only to make the next patch easier to review.
Edit the copy of the sync client created in the previous commit to be
async. Update the readme and cargo.toml files.

Replace macros with functions. There is only one async client so the
macros are not needed anymore.

Create a new module for the bdk client that has the required RPCs in it
that return either the rust-bitcoin type or non-version specific model
types.
Use trait methods instead of inherent methods to define the RPCs to
better allow downstream clients that define their own RPCs. This helps
when different implementations of the ones defined in the client are
required.

Move the error module to the crate root. Create a new `IntoModelError`
error type that carries the RPC method name as context and the original
conversion error as a boxed `source`. Use this error type in both sync
and async clients.

Assisted-by: GPT-5.4
@jamillambert jamillambert force-pushed the 0212-WIP-client-async branch from aa1e80d to 436d488 Compare June 15, 2026 16:54
Add these two RPCs to the async client Trait and
tests.

Assisted-by: GPT-5.4
@jamillambert jamillambert force-pushed the 0212-WIP-client-async branch from 436d488 to 2750000 Compare June 15, 2026 18:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants