-
Notifications
You must be signed in to change notification settings - Fork 37
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
Adding client builder for simple initialization of the Rust client #741
Conversation
crates/rust-client/src/tests.rs
Outdated
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is it ok to use #[tokio::test]
here? And is it ok that this test requires an internet connection to ping the testnet?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
An integration test is probably better suited for tests that require syncing against the node. Integration tests are executed in the CI against a locally running node running the next
branch. The builder itself may also probably be successfully tested with unit tests as well.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the PR! I think a builder could be useful as the client's instantiation API becomes more complex. I had made some comments a while ago about potentially introducing one, but it's probably a very stale comment.
In general we might not be able to provide "good" defaults (for the context of the tutorials, at least), because we need to be generic enough that the Client
can be compiled for no_std
environments, so not entirely sure if this would provide a ton of value for your use-case, but I still think it probably makes for a good addition.
use miden_objects::crypto::rand::RpoRandomCoin; | ||
use rand::Rng; | ||
|
||
pub struct ClientBuilder { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should add doc comments to the struct and the fields.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
With this, we might have to update library.md
to reflect these changes as well.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added doc comments. Let me know if this is the correct format.
crates/rust-client/src/builder.rs
Outdated
} | ||
|
||
/// Sets the RPC endpoint via a URL. | ||
pub fn with_rpc(mut self, url: &str) -> Self { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could this not provide a generic dyn NodeRpcClient + Send
? Because otheriwse if we hardcode it to a TonicRpcClient
, then the std
feature becomes a requirement and this means we cannot compile the client for wasm32
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What about putting it behind a tonic feature flag? This would mean we could still compile for wasm32
.
#[cfg(feature = "tonic")]
pub fn with_rpc(mut self, url: &str) -> Self {
...
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we could add a ClientBuilder
block within the TonicRpcClient
that contains this method and we could avoid having the #[cfg(feature="std")]
guard for this mod.
Could look something like this:
// (on rpc/tonic_client/mod.rs):
impl ClientBuilder {
#[must_use]
pub fn with_tonic_rpc(mut self, url:&Endpoint, timeout: u64) -> Self {
let tonic_client = TonicRpcClient::new(..,);
self.with_rpc_client(Box::new(tonic_client))
}
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe the same could be done for SQLite store. I think this might be a bit of an antipattern in Rust but it should work well here for making the builder available but also making it more ergonomic if certain features are turned on.
crates/rust-client/src/builder.rs
Outdated
} | ||
|
||
/// Optionally set a custom store path. | ||
pub fn with_store_path(mut self, path: &str) -> Self { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Similar to the previous comment, we should provide the store generically instead of always building a SqliteStore
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We probably want to do the same for all components that were not defined here as well (like the RNG component)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the more generic method is with_store()
right? I think one of the intentions of this method was it to make it configuring sqlite store as easy as possible. So, maybe the way to go there is to rename this into something like with_sqlite_store()
so that it is clear that this method is intended to work specifically with sqlite store.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That makes sense. Will modify.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah yes, true. Agree with the rename. I'd also move it to the SQLite mod but maybe we could refactor it separately
crates/rust-client/src/tests.rs
Outdated
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
An integration test is probably better suited for tests that require syncing against the node. Integration tests are executed in the CI against a locally running node running the next
branch. The builder itself may also probably be successfully tested with unit tests as well.
After adding Compiled successfully when I ran this: cargo build --manifest-path=crates/rust-client/Cargo.toml --target wasm32-unknown-unknown --no-default-features |
Testing again locally with my miden dApp testing repo, this PR makes client instantiation much cleaner! Now it is possible to do this: let mut client = Client::initialize()
.with_rpc("https://rpc.testnet.miden.io:443")
.in_debug_mode(true)
.build()
.await?; Instead of this: https://github.com/partylikeits1983/miden-tx/blob/ad0e378687ce950078b0bccd62ca030ac0ff09fb/src/bin/ephemeral_notes.rs#L26-L53 |
crates/rust-client/src/builder.rs
Outdated
// Determine the scheme and strip it from the URL. | ||
let (scheme, rest) = if let Some(stripped) = url.strip_prefix("https://") { | ||
("https", stripped) | ||
} else if let Some(stripped) = url.strip_prefix("http://") { | ||
("http", stripped) | ||
} else { | ||
("https", url) | ||
}; | ||
|
||
// Attempt to find a colon indicating a port. | ||
let (host, port) = if let Some(colon_index) = rest.find(':') { | ||
// Split the host and port. | ||
let host = &rest[..colon_index]; | ||
let port_str = &rest[colon_index + 1..]; | ||
// Try parsing the port. If it fails, use None. | ||
let port = port_str.parse::<u16>().ok(); | ||
(host.to_string(), port) | ||
} else { | ||
// No colon found, so use the entire string as the host and no port. | ||
(rest.to_string(), None) | ||
}; | ||
|
||
// Create the endpoint using the parsed scheme, host, and port. | ||
let endpoint = Endpoint::new(scheme.to_string(), host, port); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It might be possible to replace this with Endpoint::try_from(&str)
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think so too, this function could even receive a built Endpoint
object. This way the user can use the known endpoints (like Endpoint::testnet()
).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes it does! Will fix.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed!
crates/rust-client/src/builder.rs
Outdated
// Determine the scheme and strip it from the URL. | ||
let (scheme, rest) = if let Some(stripped) = url.strip_prefix("https://") { | ||
("https", stripped) | ||
} else if let Some(stripped) = url.strip_prefix("http://") { | ||
("http", stripped) | ||
} else { | ||
("https", url) | ||
}; | ||
|
||
// Attempt to find a colon indicating a port. | ||
let (host, port) = if let Some(colon_index) = rest.find(':') { | ||
// Split the host and port. | ||
let host = &rest[..colon_index]; | ||
let port_str = &rest[colon_index + 1..]; | ||
// Try parsing the port. If it fails, use None. | ||
let port = port_str.parse::<u16>().ok(); | ||
(host.to_string(), port) | ||
} else { | ||
// No colon found, so use the entire string as the host and no port. | ||
(rest.to_string(), None) | ||
}; | ||
|
||
// Create the endpoint using the parsed scheme, host, and port. | ||
let endpoint = Endpoint::new(scheme.to_string(), host, port); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think so too, this function could even receive a built Endpoint
object. This way the user can use the known endpoints (like Endpoint::testnet()
).
crates/rust-client/src/builder.rs
Outdated
self.rpc_api = Some(Box::new(TonicRpcClient::new(endpoint, self.timeout_ms))); | ||
self |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit
self.rpc_api = Some(Box::new(TonicRpcClient::new(endpoint, self.timeout_ms))); | |
self | |
self.with_rpc_api(Box::new(TonicRpcClient::new(endpoint, self.timeout_ms)))); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think so too, this function could even receive a built Endpoint object. This way the user can use the known endpoints (like Endpoint::testnet()).
The only thing about doing this is that it might make initializing the client take one extra line of code in a Miden app.
Would it mean doing this?
let endpoint = Endpoint::testnet();
let mut client = Client::initialize()
.with_rpc(endpoint)
.in_debug_mode(true)
.build()
.await?;
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You could create the endpoint inside the function call. Like this:
let mut client = Client::initialize()
.with_rpc(Endpoint::testnet())
.in_debug_mode(true)
.build()
.await?;
And if the user wants a custom endpoint they can build it from a string like the old interface:
let mut client = Client::initialize()
.with_rpc(Endpoint::try_from("https://test:123")?)
.in_debug_mode(true)
.build()
.await?;
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok this makes sense. Will change.
crates/rust-client/src/builder.rs
Outdated
/// Optionally set a custom timeout (in ms). | ||
pub fn with_timeout(mut self, timeout_ms: u64) -> Self { | ||
self.timeout_ms = timeout_ms; | ||
self | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We would need to specify that this function should be called before with_rpc
(if not, the parameter would be ignored). I think it would be even better if we move the timeout_ms
parameter to the with_rpc
function. The parameter could be optional if we want to allow for a default timeout.
crates/rust-client/src/builder.rs
Outdated
// Set up the store. | ||
// If the user provided a store, use it. | ||
// Otherwise, if the `sqlite` feature is enabled, build one from the store_path. | ||
// If not, return an error. | ||
let arc_store: Arc<dyn Store> = { | ||
#[cfg(feature = "sqlite")] | ||
{ | ||
if let Some(store) = self.store { | ||
store | ||
} else { | ||
let store = SqliteStore::new(self.store_path.into()) | ||
.await | ||
.map_err(ClientError::StoreError)?; | ||
Arc::new(store) | ||
} | ||
} | ||
#[cfg(not(feature = "sqlite"))] | ||
{ | ||
self.store.ok_or_else(|| { | ||
ClientError::ClientInitializationError( | ||
"No store provided and the sqlite feature is disabled.".into(), | ||
) | ||
})? | ||
} | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the store could be instantiated in with_store_path
. Similar to what we do with the rpc.
crates/rust-client/src/builder.rs
Outdated
}; | ||
|
||
// Create the authenticator. | ||
let authenticator = StoreAuthenticator::new_with_rng(arc_store.clone(), rng.clone()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
After merging #718, the authenticator now needs a KeyStore
to be built, this keystore would need to be created externally and not by the builder (because the user of the client needs the keystore to add new keys).
We can probably add a with_keystore
function to the builder and use the provided keystore here. It should fail if no keystore is defined as there's no clear default implementation (one is std and the other no-std and they don't share parameters).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nevermind, this PR is going to main
so those changes aren't relevant here. We will need to merge these changes to next
when this gets merged so we can add this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@tomyrd I can rebase this to the next
branch if it makes things easier. Would be useful to have this feature on 0.7
, but if it's too complicated can rebase to next
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks! But I also think it's best for 0.7, it won't be difficult to bring these changes to next
once they get merged. I was just mentioning it so we don't forget.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just saw this. @igamigo told me to rebase to next
. Not an urgent change or anything. Rebasing to next
means the PR can be ironed out a bit more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Perfect! Let me know if you have questions about the new authenticator
crates/rust-client/src/lib.rs
Outdated
|
||
// BUILDER ENTRY POINT (OPTIONAL HELPER) | ||
// -------------------------------------------------------------------------------------------- | ||
|
||
impl Client<RpoRandomCoin> { | ||
pub fn initialize() -> ClientBuilder { | ||
ClientBuilder::new() | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we want this helper? I think it may hide the fact that we are using an underlying ClientBuilder
and not a Client
to initialize. We may want them to be separate (like the TransactionRequest
and TransactionRequestBuilder
).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree, is there any benefit to this helper?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see now, definitely makes sense to remove it.
8fe9f66
to
7c9ae64
Compare
Rebased this PR from Also I made the entire client builder abstraction to only compile when |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks great! Thank you for doing this. I left one small comment above.
Also, what needs to be done to make the CI green?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks! Leaving some more comments for now. As @bobbinth mentioned, the CI checks should be reviewed. Upon a quick look, errors seem quite simple. You can check them locally using the make targets (make clippy
, make clippy-wasm
, make fix
for applying superficial fixes, etc.). Feel free to let me know if you have any questions about any of this
/// An optional store provided by the user. | ||
store: Option<Arc<dyn Store>>, | ||
/// An optional RNG provided by the user. | ||
rng: Option<RpoRandomCoin>, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could this be generic as well? There might not be a lot of value in forcing an RpoRandomCoin
, but I could be wrong.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would this mean that when initializing the client the user will have to specify type annotations for the client builder?
I implemented a basic version making RpoRandomCoin
generic, but it requires that I specify type annotations when initializing the client.
This is how it looks:
let mut client = ClientBuilder::<RpoRandomCoin>::new()
.with_rpc(Endpoint::try_from("https://rpc.testnet.miden.io:443").unwrap())
.with_timeout(10_000)
.with_sqlite_store("store.sqlite3")
.in_debug_mode(true)
.build()
.await?;
Maybe there is a way to make RpoRandomCoin
generic and not have to specify the type annotation?
crates/rust-client/src/builder.rs
Outdated
@@ -0,0 +1,155 @@ | |||
#![cfg(feature = "std")] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Usually this would be in the mod declaration (ie, in lib.rs
).
EDIT: Just saw you added this there, so this can be removed.
crates/rust-client/src/builder.rs
Outdated
} | ||
|
||
/// Sets the RPC client directly (for custom NodeRpcClient implementations). | ||
pub fn with_rpc_client(mut self, client: Box<dyn NodeRpcClient + Send>) -> Self { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: We could add #[must_use]
to these functions.
crates/rust-client/src/lib.rs
Outdated
|
||
// BUILDER ENTRY POINT (OPTIONAL HELPER) | ||
// -------------------------------------------------------------------------------------------- | ||
|
||
impl Client<RpoRandomCoin> { | ||
pub fn initialize() -> ClientBuilder { | ||
ClientBuilder::new() | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree, is there any benefit to this helper?
crates/rust-client/src/builder.rs
Outdated
} | ||
|
||
/// Sets the RPC endpoint via a URL. | ||
pub fn with_rpc(mut self, url: &str) -> Self { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we could add a ClientBuilder
block within the TonicRpcClient
that contains this method and we could avoid having the #[cfg(feature="std")]
guard for this mod.
Could look something like this:
// (on rpc/tonic_client/mod.rs):
impl ClientBuilder {
#[must_use]
pub fn with_tonic_rpc(mut self, url:&Endpoint, timeout: u64) -> Self {
let tonic_client = TonicRpcClient::new(..,);
self.with_rpc_client(Box::new(tonic_client))
}
}
crates/rust-client/src/builder.rs
Outdated
} | ||
|
||
/// Sets the RPC endpoint via a URL. | ||
pub fn with_rpc(mut self, url: &str) -> Self { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe the same could be done for SQLite store. I think this might be a bit of an antipattern in Rust but it should work well here for making the builder available but also making it more ergonomic if certain features are turned on.
Addressed several comments from above.
|
5a97e01
to
025efcf
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good! Thank you! It seems like the integration tests are not passing - but maybe that's something can be fixed rebasing from next
?
Yes, I think this may be the case. I'd also see if this could be addressed before merging. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM! Left a small comment to be addressed before merging.
crates/rust-client/src/builder.rs
Outdated
None => FilesystemKeyStore::new(temp_dir()) | ||
.map_err(|err| ClientError::ClientInitializationError(err.to_string()))?, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we fail if the keystore is not set? If not, I wouldn't use the temp_dir
to store the keys, it might get cleared up.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah yes that is true. What would be a good reasonable default? I think I was following the cli tests as a guide when writing this, that's why I use temp_dir()
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In the CLI we create a "keystore" dir on the current directory and use that.
But in this case, I feel like we should have it so that the user always provides the keystore themselves. My reasoning is that if we create the keystore in the builder, then the user won't have a reference to it. This means that when adding new accounts, the user won't be able to add the authentication keys to the keystore.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would this make sense as a standard use example? This creates a /keystore
directory.
let keystore = FilesystemKeyStore::new("keystore".into()).unwrap();
let mut client = ClientBuilder::new()
.with_rpc(Endpoint::try_from("https://rpc.testnet.miden.io:443").unwrap())
.with_timeout(10_000)
.with_keystore(keystore)
.with_sqlite_store("store.sqlite3")
.in_debug_mode(true)
.build()
.await?;
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This way the user has reference to the keystore.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If we make it so that the with_keystore()
method takes a string, then we would have to change it so that the builder also returns the keystore it created internally, something like:
let (mut client, mut keystore) = ClientBuilder::new()
.with_rpc(Endpoint::try_from("https://rpc.testnet.miden.io:443").unwrap())
.with_timeout(10_000)
.with_keystore("keystore")
.with_sqlite_store("store.sqlite3")
.in_debug_mode(true)
.build()
.await?;
I'm open to either one but I think I prefer the first one.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think one of the goals here is to make assumptions about a reasonable set of defaults while also keeping some flexibility. So, I'd probably go with more specialized methods that make ergonomics a bit better. For example:
let mut client = ClientBuilder::new()
.with_tonic_rpc("https://rpc.testnet.miden.io:443")
.with_timeout(10_000)
.with_filesystem_keystore("keystore")
.with_sqlite_store("store.sqlite3")
.in_debug_mode(true)
.build()
.await?;
Ff the user wanted to customize things beyond these defaults, they could also do something like:
let mut client = ClientBuilder::new()
.with_rpc(my_rpc)
.with_keystore(my_keystore)
.with_store(my_store)
.in_debug_mode(true)
.build()
.await?;
The only thing which I think doesn't fit this pattern would be the random coin. But maybe we keep it as is for now and make it more flexible in another PR.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Overall looks good, I think that after addressing https://github.com/0xPolygonMiden/miden-client/pull/741/files#r1962738199 and https://github.com/0xPolygonMiden/miden-client/pull/741/files#r1960564185 this can be ready to merge.
025efcf
to
98ab566
Compare
Addressed several comments from above.
The following is possible: let mut client = ClientBuilder::new()
.with_tonic_rpc("https://rpc.testnet.miden.io:443")
.with_timeout(10_000)
.with_filesystem_keystore("keystore")
.with_sqlite_store("store.sqlite3")
.in_debug_mode(true)
.build()
.await?; The |
5eb3859
to
f686789
Compare
Force pushed to keep everything in a single commit & to fix CI/CD issues. |
@igamigo Regarding this: https://github.com/0xPolygonMiden/miden-client/pull/741/files#r1962738199 Would it be possible to address this in a separate PR? I tried implementing it but I believe it could get a bit complicated. Also not 100% sure how to make let mut client = ClientBuilder::<RpoRandomCoin>::new(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good! Thank you! I left a coupe of optional comments inline.
Beyond these, I think we can make a couple of improvements in the future:
- Make handling of
KeyStore
more generic (so that we could use non-FilesystemKeyStore
s as well). - Make handling of
Rng
more generic (so that we could use non-RpoRandomCoin
as well).
But I would do it in a separate PR - so, let's create an issue for this.
crates/rust-client/src/builder.rs
Outdated
pub fn with_rpc(mut self, endpoint: Endpoint) -> Self { | ||
self.rpc_endpoint = Some(endpoint); | ||
self | ||
} | ||
|
||
/// Sets a custom RPC client directly. | ||
/// | ||
/// This method overrides any previously set RPC endpoint. | ||
#[must_use] | ||
pub fn with_rpc_client(mut self, client: Box<dyn NodeRpcClient + Send>) -> Self { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: I would maybe rename the current with_rpc_client()
into just with_rpc()
, and the current with_rpc()
into with_tonic_rpc()
.
Also, with_tonic_rpc()
could probably take a string rather than Endpoint
as an argument - or is there a reason not to do this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also, with_tonic_rpc() could probably take a string rather than Endpoint as an argument - or is there a reason not to do this?
The reason why the with_rpc()
function takes an Endpoint
input was because I was following @tomyrd suggestion here:
I think so too, this function could even receive a built Endpoint object. This way the user can use the known endpoints (like Endpoint::testnet())
#741 (comment)
I do like the idea of just passing in the RPC url string as it is cleaner, but maybe it is useful to be able to initialize the endpoing like: Endpoint::testnet()
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yep, makes sense. I'm fine with either approach.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
btw, another option is to use something like AsRef<str>
or maybe Into<String>
- e.g.:
pub fn with_tonic_rpc(mut self, url: impl Into<String>) -> Self {
...
}
Then, if we have From<Endpoint> for String
implemented, we'd be able to pass it into the method as well. We would need to change the internal field type of rpc_endpoint
to String
, but I don't think this should be a problem.
Sure! I think we could make the RNG a trait object. In any case, because the RNG component is necessary, it could be an argument of the BTW the EDIT: Also, seems like you should rebase from the latest |
f686789
to
8752e9b
Compare
There are a few outstanding issues that I can address in a separate PR:
|
Opened an issue for a separate PR to improve the |
crates/rust-client/src/builder.rs
Outdated
pub fn with_rpc(mut self, endpoint: Endpoint) -> Self { | ||
self.rpc_endpoint = Some(endpoint); | ||
self | ||
} | ||
|
||
/// Sets a custom RPC client directly. | ||
/// | ||
/// This method overrides any previously set RPC endpoint. | ||
#[must_use] | ||
pub fn with_tonic_rpc(mut self, client: Box<dyn NodeRpcClient + Send>) -> Self { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the naming here is reversed. It should be:
pub fn with_rpc(mut self, client: Box<dyn NodeRpcClient + Send>) -> Self {
...
}
pub fn with_tonic_rpc(mut self, endpoint: Endpoint) -> Self {
...
}
The idea is that with_tonic_rpc()
is more specialized - i.e., we know that we'll be using the TonicRpcClient
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That makes sense. I changed the naming of the functions.
8752e9b
to
2784861
Compare
Unused dependencies check was fixed on #761 so we can merge this safely |
Adding client builder for simple initialization of the Rust client
Overview
This PR introduces a new client builder to the Rust client, streamlining the initialization process and reduces the amount of boilerplate code when getting started with a Miden dApp. With the new client builder, setting up a Miden dApp in Rust becomes significantly easier. Personally, this was one of the features I really wanted to have as I was building the tutorial code examples.
By integrating this feature, we lower the barrier to entry and reduce the lines of code needed to deploy and interact with smart contracts and notes. For context, our Miden dApp tutorials currently showcase the older, more verbose approach where the
initialize_client()
function is used in each code example.This improvement was discussed internally in this issue.
What This Solves
initialize_client()
function in the tutorial docs.Usage
Initializing the client is now as simple as:
Tests
I added a simple test of instantiating the client using the new client builder. I also tested the new client builder in a separate repository.
Future Work
There may be further improvements in the instantiation process. However, the primary goal here is to reduce the number of lines and the complexity required to get started with building on Miden.
Please let me know if I can make any improvements.