Skip to content

Comments

Encapsulate Client functionality with typestate#535

Open
rustaceanrob wants to merge 2 commits into2140-dev:masterfrom
rustaceanrob:26-2-24-typestate
Open

Encapsulate Client functionality with typestate#535
rustaceanrob wants to merge 2 commits into2140-dev:masterfrom
rustaceanrob:26-2-24-typestate

Conversation

@rustaceanrob
Copy link
Collaborator

@rustaceanrob rustaceanrob commented Feb 24, 2026

Take two of #527, which is more flexible but leaves Node exposed for those that need it.

Similar to #534, however I think the Node may ultimately remain in the public interface. This is due to the original reason for separation in the first place, as this crate does not make an assumption of how the Node::run future will be executed. For instance, LDK node has a unique way of spawning tasks.

I do think the typestate can simplify the use cases for the majority of users though. In the usual case, the user can get a Client that can be subscribed to, ran, and published events to.

cc @thunderbiscuit @nyonson

First step in cleaning up the `Node`/`Client` relationship. This
abstracts running a `task`, which will require some new methods to run
with more flexibility (bring your own runtime or use an OS thread). The
panic introduced will be impossible to hit in a later commit where I
will introduce sealed traits.
Uses the sealed trait hack to only allow for compiler enforced state
transitions. The flow is `Idle -> Subscribed -> Actice`, where a user
decides what to do with the events, runs the node, then gets access to
all the usual client features.

I decided to scrap the `Requester` and instead encapsulate the event
receivers with a newtype.

The only downside I see with this is `&mut self` doesn't seem to be
possible, as each impl block is defined for it's `State` type. I need to
fiddle with that to see if we can't remove the need to return the client
each state transition.
Copy link

@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.

Took me a bit of time to wrap my head around the code because I haven't played with this library directly, but after some poking around I think I understand it well. It's surprising how little change is required here to make this work well for both use cases.

I think maybe a little more documentation on how the Subscribed/Active distinction is useful in practice would be good, in case there is something users might want to do in between transitioning between these two states. I read your line 66 in client.rs but I'm still not sure when I'd use the client in that state.

The exact shape this will take in the bindings I haven't really dug into. We know generics can't be used, so I suspect the pattern will turn into a slightly more "crude" version of itself here, but that's a problem for another repo! Thanks for thinking about #527 and working on a cool solution.

@rustaceanrob
Copy link
Collaborator Author

rustaceanrob commented Feb 24, 2026

I'm not sure when I'd use the client in that state

The test has an example where the logging is setup before we actually spin up the node. You can imagine this being potentially more complex (i.e. set up logging to a file on disk). We'd only want to run the node in the case something like that succeeds. I can add some documentation for rationale, probably after this PR, maybe with a code example.

slightly more "crude" version of itself here

I am imagining the builder makes a "Node" which you can call start on which gives you a "Client". I've cleared it with Andreas and this is basically what he does already.

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