Skip to content

Wrap LightClient in typestate for guided API#146

Merged
rustaceanrob merged 1 commit intobitcoindevkit:masterfrom
rustaceanrob:26-2-24-typestate
Mar 6, 2026
Merged

Wrap LightClient in typestate for guided API#146
rustaceanrob merged 1 commit intobitcoindevkit:masterfrom
rustaceanrob:26-2-24-typestate

Conversation

@rustaceanrob
Copy link
Copy Markdown
Collaborator

@rustaceanrob rustaceanrob commented Feb 24, 2026

I made an attempt in the base client to use typestate for running the node here. While I think the pattern is ergonomic, it imposes too many restrictions on the Client ownership model. The pattern does seem appropriate here, however, where we are taking a more opinionated approach as to how the user interacts with the light client. This API abstracts the Node entirely for users that have a tokio environment present.

I divided the stages of the LightClient into Idle, Subscribed, Active. The differentiation between Idle and Subscribed can be seen in the example. This allows the user to set up the necessary logging tasks required for their app. Then they may call start to execute the task, and finally make requests thereafter.

ref: 2140-dev/kyoto#527
ref: https://cliffle.com/blog/rust-typestate/

cc @thunderbiscuit

@rustaceanrob rustaceanrob changed the title Wrap `LightClient in typestate for guided API Wrap LightClient in typestate for guided API Feb 24, 2026
@rustaceanrob rustaceanrob marked this pull request as draft February 24, 2026 14:59
@rustaceanrob rustaceanrob marked this pull request as ready for review February 27, 2026 10:46
@rustaceanrob rustaceanrob force-pushed the 26-2-24-typestate branch 3 times, most recently from 553160d to bdbfeeb Compare February 28, 2026 10:34
Copy link
Copy Markdown
Member

@thunderbiscuit thunderbiscuit left a comment

Choose a reason for hiding this comment

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

Concept ACK. I got it working on the bindings side, see bitcoindevkit/bdk-ffi#965.

I'm not sure the code is where I'd like it to be there but at least it compiles and works for now. The one odd thing is that I had to use start_managed, so in the end it's the same as if I had created a wrapper on the old API and kept the node inside the wrapper and not exposed it to the user.

@rustaceanrob
Copy link
Copy Markdown
Collaborator Author

Yes, admittedly this won't change the situation much for the bindings, however bdk-cli would get to try single client style. I'm not sure this fully closes #148, but I think this is a big improvement for the Rust users.

I made an attempt in the base client to use typestate for running the
node [here](2140-dev/kyoto#535). While I think
the pattern is ergonomic, it imposes too many restrictions on the
`Client` ownership model. The pattern does seem appropriate here,
however, where we are taking a more opinionated approach as to how the
user interacts with the light client. This API abstracts the `Node`
entirely for users that have a `tokio` environment present.

I divided the stages of the `LightClient` into `Idle`, `Subscribed`,
`Active`. The differentiation between `Idle` and `Subscribed` can be
seen in the example. This allows the user to set up the necessary
logging tasks required for their app. Then they may call `start` to
execute the task, and finally make requests thereafter.
@rustaceanrob rustaceanrob merged commit 539fef2 into bitcoindevkit:master Mar 6, 2026
5 checks passed
@rustaceanrob rustaceanrob deleted the 26-2-24-typestate branch March 6, 2026 12:58
@thunderbiscuit
Copy link
Copy Markdown
Member

thunderbiscuit commented Mar 6, 2026

One thing I'll look into is how uniffi can allow Rust to actually hand over async code directly to the target languages, and so it's (I think) at least in theory possible to use the asynchronous code directly in Kotlin and not require tokio at all. This would not necessarily be a huge gain, but maybe a small optimization because it would allow the node to not use a full OS thread for its work, and instead be jointly given to a dispatcher (on Kotlin) and so interleave its work with the loggers for example. I assume something similarly doable for Swift. If that was even possible you'd also save the size of shipping Tokio with the lib? Not sure if that's big or not.

@rustaceanrob
Copy link
Copy Markdown
Collaborator Author

rustaceanrob commented Mar 6, 2026

Kyoto internally uses tokio::task::spawn which is runtime-dependent, so I don't think removing tokio will be a possibility. We would run into the same panic that was recently patched.

@thunderbiscuit
Copy link
Copy Markdown
Member

Yeah it's not possible with kyoto and bdk-kyoto as is, but I wanted to see what the requirements are for this type of thing (I know it's currently supported by uniffi). What you need is a runtime-agnostic library on the Rust side, but honestly this sounds it would have very low usefulness because it's not like there are a lot of other options other than Tokio in Rust, and so the only value would be for bindings at the cost of (I assume?) significant increase in complexity.

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.

2 participants