-
Notifications
You must be signed in to change notification settings - Fork 40
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
Refactor node from 4 to 1 binary #323
Conversation
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 to me, thank you! I've left a few inline comments. The main idea here is that we should move config parsing and service starting functionality to the node binary (leave only serve
in the libraries). We also should union top-level configs in one single structure in the node binary with optional field for each corresponding service:
struct Config {
rpc: Option<RpcConfig>,
faucet: Option<FaucetConfig>,
....
}
bin/node/Cargo.toml
Outdated
miden-node-rpc = { path = "../../crates/rpc", version = "0.2" } | ||
miden-node-store = { path = "../../crates/store", version = "0.2" } | ||
miden-node-utils = { path = "../../crates/utils", version = "0.2" } | ||
miden-node-faucet = { path = "../../crates/faucet", version = "0.1" } |
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 would probably move these local dependencies to workspace's Cargo.toml
and specify here just workspace = true
. And we don't need to specify versions for them.
crates/block-producer/src/lib.rs
Outdated
// MAIN FUNCTION | ||
// ================================================================================================= | ||
|
||
pub async fn start_block_producer(config_filepath: &Path) -> Result<()> { |
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 should move such functions from libraries to binary and get rid of anyhow
dependency in libraries. server::serve
can return tonic::transport::Error
. We also should process config file once in the node binary, not in each library.
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 you explain where in the binary you would add such functions?
We have 4 for now:
- start_block_producer
- start_rpc
- start_store
- start_faucet
Also to note that the faucet is not a Tonic server but an Actix web server.
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 you explain where in the binary you would add such functions?
I think, commands/start
would be a good place for them.
Also to note that the faucet is not a Tonic server but an Actix web server.
Not a problem, it can return Actix's status messages. The idea is to keep flexible errors in the binary, but return error codes from the library.
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.
Understood. Changed.
crates/faucet/src/config.rs
Outdated
|
||
/// Faucet top-level configuration. | ||
#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Deserialize)] | ||
pub struct FaucetTopLevelConfig { |
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 can be substituted by single node's config structure with optional elements for block-producer, store, faucet and rpc. Config of each service (library) stays in the library.
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.
Agreed
crates/faucet/src/config.rs
Outdated
|
||
/// Faucet top-level configuration. | ||
#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Deserialize)] | ||
pub struct FaucetTopLevelConfig { |
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 can be substituted by single node's config structure with optional elements for block-producer, store, faucet and rpc. Config struct of each service (library) stays in the library.
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.
Done.
crates/block-producer/Cargo.toml
Outdated
path = "src/main.rs" | ||
bench = false | ||
doctest = false | ||
|
||
[features] | ||
tracing-forest = ["miden-node-utils/tracing-forest"] | ||
|
||
[dependencies] | ||
anyhow = { version = "1.0" } |
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 get rid of anyhow
in libraries.
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 you explain further why we should remove it in the libraries and not in the binary?
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.
Using anyhow
or eyre
in libraries is considered as a bad practice, because it's hard to handle different errors by library users. But binary is a different case: we usually just print error message from the binary to the user and don't need to handle it, so anyhow
is suitable there.
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.
Understood.
crates/rpc/src/lib.rs
Outdated
// MAIN FUNCTION | ||
// ================================================================================================= | ||
|
||
pub async fn start_rpc(config_filepath: &Path) -> Result<()> { |
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.
Move this functionality to the binary.
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.
crates/store/src/lib.rs
Outdated
// MAIN FUNCTION | ||
// ================================================================================================= | ||
|
||
pub async fn start_store(config_filepath: &Path) -> Result<()> { |
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 move this functionality to the binary.
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.
crates/faucet/src/handlers.rs
Outdated
let bytes = match created_notes.first() { | ||
Some(note) => { | ||
note_id = note.id(); | ||
note.to_bytes() | ||
}, |
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.
Have you tested this on the client? I think in order to import this you might need an InputNoteRecord
.
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.
Indeed, fixed.
Fixed all requested changes and comments. I have used Now the commands used to run the node are the following: cargo run --release --features testing start node (or faucet, store, block-producer, rpc. You can also pass the --config flag to start to specify a specific config file usually located in bin/node/miden-node.toml) |
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, thanks! I've added several inline comments here.
Cargo.toml
Outdated
@@ -25,6 +26,13 @@ exclude = [".github/"] | |||
[workspace.dependencies] | |||
miden-air = { version = "0.9", default-features = false } | |||
miden-lib = { version = "0.2"} | |||
miden-node-block-producer = { path = "crates/block-producer", version = "0.2" } |
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, it's not needed to specify version here.
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.
removed.
bin/node/src/commands/start.rs
Outdated
|
||
pub async fn start_node(config: NodeConfig) -> Result<()> { | ||
let mut join_set = JoinSet::new(); | ||
let db = Db::setup(config.store.clone().expect("Missing store configuration.")).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.
I would rather not panic here, but raise error with message:
let db = Db::setup(config.store.clone().context("Missing store configuration.")?).await?;
bin/node/src/main.rs
Outdated
.await | ||
}, | ||
StartCommand::Rpc => { | ||
start_rpc(config.rpc.expect("Missing rpc configuration.")).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.
The same as before, I prefer to raise errors, not panics. But for binaries it's not a mandatory.
@@ -56,9 +55,11 @@ pub async fn serve(config: BlockProducerConfig) -> Result<()> { | |||
|
|||
let addr = config | |||
.endpoint | |||
.to_socket_addrs()? | |||
.to_socket_addrs() | |||
.expect("Failed to turn address into socket address.") |
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.
Please, avoid panics in the library code. Consider adding new error enumeration, if this function can return different error types.
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 LGTM in general. I'm a bit confused with some of the error handling changes on component code but once that's handled it should be good to merge. Another thing that we might want to look at in the future is to have top-level commands for each component instead of them being a subcommand of the start
(ie, faucet start
instead of start faucet
; could allow for more component-specific commands in the future), but probably not many good reasons right now to do so.
repository.workspace = true | ||
|
||
[features] | ||
# Makes `make-genesis` subcommand run faster. Is only suitable for testing. |
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.
Let's document (here and elsewhere) that this should be set to match the node's configuration as well
crates/faucet/src/handlers.rs
Outdated
// Instantiate transaction request | ||
let tx_request = client | ||
.lock() | ||
.await | ||
.build_transaction_request(tx_template) | ||
.expect("Failed to build transaction request."); |
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 ideally we would not panic on any of the faucet's code
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.
How do you handle situations like missing config file or others? It is going to panic at one point right?
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 general I think handling error is better overall, although I agree that missing config files would be an anomaly. However what I meant is that in this scenario specifically where we are handling HTTP requests, there should ideally be no Rust panics but rather the server should return the HTTP error code that best describes the situation. In this case the transaction template to transaction request conversion should work fine but it does rely on various validations, etc. that could fail and so the code should ideally not panic.
// Serialize note into bytes | ||
let bytes = match created_notes.first() { | ||
Some(note) => { | ||
note_id = note.id(); | ||
InputNoteRecord::from(note.clone()).to_bytes() | ||
}, | ||
None => { | ||
return Err( | ||
FaucetError::InternalServerError("Failed to generate note.".to_string()).into() | ||
) | ||
}, | ||
}; | ||
|
||
info!("A new note has been created: {}", note_id); |
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 looks good, were you able to test this with the 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.
Yes it works.
.next() | ||
.ok_or(anyhow!("Couldn't resolve server address"))?; | ||
.expect("Failed to resolve address."); |
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'm not sure I agree with changing these errors into expect()
s for these services
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, @polydez requested to remove the anyhow inside the libs. Looking into a better way to handle errors atm.
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'd like to improve error representation here:
- Use
thiserror
for all error enumerations and writeDisplay
representation for each error variant. - Try to keep correct error chain without converting concrete types to
String
. Sometimes,String
is also okay, but not for cases when we have a single error structure/enumeration which caused the error.
I've hilighted some of examples inline.
crates/utils/src/errors.rs
Outdated
ApiInitialisationFailed(String), | ||
ApiServeFailed(String), | ||
AddressResolutionFailed(String), | ||
EndpointToSocketFailed(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.
We can use io::Error
here instead of String
.
crates/utils/src/errors.rs
Outdated
DatabaseConnectionFailed(String), | ||
} | ||
|
||
impl std::fmt::Display for ApiError { |
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.
Please use thiserror
for ApiError
and write display implementation using its macro. We use intensively thiserror
, for example: https://github.com/0xPolygonMiden/miden-node/blob/next/block-producer/src/errors.rs#L16-L47
pub enum ApiError { | ||
ApiInitialisationFailed(String), | ||
ApiServeFailed(String), | ||
AddressResolutionFailed(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.
Currently this String
is used only for error message about address resolution fail. It would be better to not to provide String
here, but output error message for this variant in Display
implementation.
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.
The issue is still here. It would be better to just remove this String
argument.
crates/utils/src/errors.rs
Outdated
@@ -0,0 +1,14 @@ | |||
#[derive(Debug)] | |||
pub enum ApiError { | |||
ApiInitialisationFailed(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.
Here would be better to keep tonic::Error
, don't need to convert it to string.
crates/utils/src/errors.rs
Outdated
#[derive(Debug)] | ||
pub enum ApiError { | ||
ApiInitialisationFailed(String), | ||
ApiServeFailed(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.
The same as above.
bin/node/src/commands/start.rs
Outdated
|
||
pub async fn start_node(config: NodeConfig) -> Result<()> { | ||
let mut join_set = JoinSet::new(); | ||
let db = Db::setup(config.store.clone().context("Missing store configuration.")?).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.
I would rather spawn start_*
functions here. This would help us to reduce/simplify code.
crates/faucet/src/errors.rs
Outdated
|
||
#[derive(Debug, Error)] | ||
pub enum FaucetError { | ||
/// Client has submitted a bad request |
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, these docs are excessive here.
pub enum ApiError { | ||
ApiInitialisationFailed(String), | ||
ApiServeFailed(String), | ||
AddressResolutionFailed(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.
The issue is still here. It would be better to just remove this String
argument.
Which errors are you talking about? All of them or specifically this one? |
Just the pointed error variant. If you look at the code which raises this error, you will see that this string initialized with error message similar to what is implemented in miden-node/crates/store/src/server/mod.rs Line 32 in ebc4c0d
I'd probably set this string with |
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, thanks! I've left a couple of nit comments.
bin/node/src/commands/start.rs
Outdated
// block on all tasks | ||
while let Some(res) = join_set.join_next().await { | ||
// For now, if one of the components fails, crash the node | ||
res.unwrap().unwrap(); |
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, it would be better to raise error here:
res??;
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.
crates/rpc/src/server/mod.rs
Outdated
.map_err(ApiError::EndpointToSocketFailed)? | ||
.next() | ||
.ok_or(config.endpoint.to_string()) | ||
.map_err(|err| ApiError::AddressResolutionFailed(err))?; |
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 don't like such chained conversion here. We firstly convert None
to Err("https://host:port")
and then wrap it with ApiError::AddressResolutionFailed
.
It would be better to just do:
let addr = config
.endpoint
.to_socket_addrs()
.map_err(ApiError::EndpointToSocketFailed)?
.next()
.ok_or_else(|| ApiError::AddressResolutionFailed(config.endpoint.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.
The second disadvantage of current solution is that we format endpoint
as string even if no errors occurred.
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
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 to me, thank you!
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! I know this has already been merged, but I left a few small comments. Most of these can be separate issues which we can address in future PRs.
To install for production use cases, run: | ||
|
||
```sh | ||
cargo install --path node | ||
cargo install --path bin/node | ||
``` | ||
|
||
This will install the executable `miden-node` in your PATH, at `~/.cargo/bin/miden-node`. | ||
|
||
Otherwise, if only to try the node out for testing, run: | ||
|
||
```sh | ||
cargo install --features testing --path node | ||
cargo install --features testing --path bin/node | ||
``` | ||
|
||
Currently, the only difference between the two is how long the `make-genesis` command will take to run (see next subsection). |
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 cargo install --path bin/node
still the right command to use? We can now install directly from crates.io and so I think this could be cargo install miden-node
.
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 would be the right command to use in case you would want to run the software using the cloned repo.
If you would like to use the binary directly installed from crates.io then yes indeed cargo install miden-node
would be the right command.
I believe that both command could / should be documented for completeness ( having multiple ways to download / install the software )
Let me know what is your pov.
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 for cargo install
downloading from crates.io is probably the main use case - but I'm not against also having a section which describes how to deploy a node by cloning the repo. In general, we should probably update the section about deploying the node because we now have multiple ways (installing from crates.io, via docker, via .deb
package, by cloning the repo) and we should describe in a more comprehensive way than we do now.
miden-node-block-producer = { path = "crates/block-producer" } | ||
miden-node-faucet = { path = "crates/faucet" } | ||
miden-node-proto = { path = "crates/proto" } | ||
miden-node-rpc = { path = "crates/rpc" } | ||
miden-node-store = { path = "crates/store" } | ||
miden-node-test-macro = { path = "crates/test-macro" } | ||
miden-node-utils = { path = "crates/utils" } |
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 not include version here as well? For example:
miden-node-block-producer = { path = "crates/block-producer", version = "0.2" }
I think the version is needed for crates published to crates.io (but I'm not 100% sure).
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 believe that the version is inferred from the workspace cargo.toml that has the version number added.
* New working structure * Faucet available but note generation broken stalls on input_note * Fix faucet with new client note handling * Lints + added comments and small refactors * Removed anyhow from librairies + moved start fns to binary * Refactored config location * Removed top level config from libs + removed redundant tests * Added config.rs * Added start_faucet + FaucetConfig to binary * Fixed not serialisation issue * Modified cargo.toml's to include workspace deps * Added optionality to the configuration parameters * Added error handling / removed expect and panic * Improved error handling with thiserror * Fixed docker build and run * Update documentation after refactor * Updated start_node to use start_* * Removed irrelevant comments for errors * Made error printing more expressive using Endpoint * Added in store and block-producer * lint * Fixed nits
* fix: use `OutputNote::metadata` and remove `TODO` (#320) * refactor: store migrations into SQL files * formatting: reduce linebreaks * fix: update SQL file after rebasing * feat: implement database settings storage * feat: compare migrations by hashes * formatting: rustfmt * Improve CI & builds (#325) * Improved workflows * Added spacing * Refactor node from 4 to 1 binary (#323) * New working structure * Faucet available but note generation broken stalls on input_note * Fix faucet with new client note handling * Lints + added comments and small refactors * Removed anyhow from librairies + moved start fns to binary * Refactored config location * Removed top level config from libs + removed redundant tests * Added config.rs * Added start_faucet + FaucetConfig to binary * Fixed not serialisation issue * Modified cargo.toml's to include workspace deps * Added optionality to the configuration parameters * Added error handling / removed expect and panic * Improved error handling with thiserror * Fixed docker build and run * Update documentation after refactor * Updated start_node to use start_* * Removed irrelevant comments for errors * Made error printing more expressive using Endpoint * Added in store and block-producer * lint * Fixed nits * fix: remove table constant * feat: add check for changing of schema version outside migrations * feat: settings in human-readable format, improve logging * docs: address review comments * refactor: use `ANY` datatype in settings table with `STRICT` --------- Co-authored-by: Paul-Henry Kajfasz <[email protected]>
* fix: use `OutputNote::metadata` and remove `TODO` (#320) * refactor: store migrations into SQL files * formatting: reduce linebreaks * fix: update SQL file after rebasing * feat: implement database settings storage * feat: compare migrations by hashes * formatting: rustfmt * Improve CI & builds (#325) * Improved workflows * Added spacing * Refactor node from 4 to 1 binary (#323) * New working structure * Faucet available but note generation broken stalls on input_note * Fix faucet with new client note handling * Lints + added comments and small refactors * Removed anyhow from librairies + moved start fns to binary * Refactored config location * Removed top level config from libs + removed redundant tests * Added config.rs * Added start_faucet + FaucetConfig to binary * Fixed not serialisation issue * Modified cargo.toml's to include workspace deps * Added optionality to the configuration parameters * Added error handling / removed expect and panic * Improved error handling with thiserror * Fixed docker build and run * Update documentation after refactor * Updated start_node to use start_* * Removed irrelevant comments for errors * Made error printing more expressive using Endpoint * Added in store and block-producer * lint * Fixed nits * fix: remove table constant * feat: add check for changing of schema version outside migrations * feat: settings in human-readable format, improve logging * docs: address review comments * refactor: use `ANY` datatype in settings table with `STRICT` --------- Co-authored-by: Paul-Henry Kajfasz <[email protected]>
In this PR I propose the refactor of the node internal structure to enable/disable services using flags and reduce from 4 to 1 binary.
closes: #92 #322