Skip to content
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 Changes to Enable WASM-32 Support #378

Merged
merged 13 commits into from
Jun 14, 2024

Conversation

dagarcia7
Copy link
Collaborator

Summary of Changes

  • Most of the changes needed to support a WASM build revolve around making the code async-friendly. As such, I added a wasm feature flag and added conditional rendering everywhere where async will be needed for WASM.
  • Aside from using the flag to support the async changes needed, I also used it to avoid compiling certain things like CLI files or SQLite store files that aren't needed for WASM.
  • Finally, these changes trickled down to the Cargo.toml file where I tried to organize the dependencies based on whether the wasm feature flag and the target_arch is wasm32 or not.

Once this PR is accepted and merged, I will follow up with another PR to officially add the wasm crate. Once this is checked in, we can work together to publish the wasm code as an NPM package so others can start using it in their web applications.

Copy link
Collaborator

@igamigo igamigo left a comment

Choose a reason for hiding this comment

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

Looks good so far! I didn't take a deep look at the duplicated code (though mostly it looked to be exactly duplicate), but everything else looks OK. Some of this work will probably clash with #367, but this seems like a higher priority and so we can look into merging this first.

Comment on lines 32 to 34
pub mod chain_data;
pub mod note_screener;
pub mod notes;
Copy link
Collaborator

Choose a reason for hiding this comment

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

If these are needed to be exported for the separate crate, we can remove the pub uses from below lines.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I will test this a bit further before merging. I may not need them to be pub after all. Will double check 👍

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Thanks for pointing this out because I don't think I needed them after all 👍

Cargo.toml Outdated
Comment on lines 53 to 63
[target.'cfg(all(not(wasm), not(target_arch = "wasm32")))'.dependencies]
clap = { version = "4.3", features = ["derive"] }
comfy-table = "7.1.0"
figment = { version = "0.10", features = ["toml", "env"] }
lazy_static = "1.4.0"
miden-node-proto = { version = "0.3", default-features = false }
rusqlite = { version = "0.30.0", features = ["vtab", "array", "bundled"] }
rusqlite_migration = { version = "1.0" }
tokio = { version = "1.29", features = ["rt-multi-thread", "net", "macros"] }
tonic = { version = "0.11" }
toml = { version = "0.8" }
Copy link
Contributor

Choose a reason for hiding this comment

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

@mFragaBA we should do something similar in #367 - but instead of making this depend on wasm feature, I'd introduce the executable feature. It could look something like:

executable = ["clap", "comfy-table", "sqlite", "tonic", "etc"]

@bobbinth
Copy link
Contributor

bobbinth commented Jun 7, 2024

Looks great! Thank you! Overall, I agree with the plan:

  1. First, we'll merge #725 in miden-base. For this, @phklive will update the current implementation of winter-maybe-async macro and I'll publish the new crate.
  2. Then, we'll update this PR to remove code duplication. After that we'll merge this PR.
    a. Here, would also be great to get rid of async_trait dependency.
  3. Then, we'll merge #367 to clean up feature organization etc.
    a. For example, rather than having dedicated wasm feature, we might just have an async feature and gate all CLI-related dependencies behind the executable feature.
  4. After these are done, we can add the WASM crate in a separate PR.

@bobbinth
Copy link
Contributor

  • First, we'll merge #725 in miden-base. For this, @phklive will update the current implementation of winter-maybe-async macro and I'll publish the new crate.
  • Then, we'll update this PR to remove code duplication. After that we'll merge this PR.
    a. Here, would also be great to get rid of async_trait dependency.
  • Then, we'll merge #367 to clean up feature organization etc.
    a. For example, rather than having dedicated wasm feature, we might just have an async feature and gate all CLI-related dependencies behind the executable feature.
  • After these are done, we can add the WASM crate in a separate PR.

The first item from this list is now complete. @dagarcia7 - could you start on the second item?

Copy link
Collaborator

@igamigo igamigo left a comment

Choose a reason for hiding this comment

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

Left a small comment, everything else looks good so far! We might want to decide to do something about the miden-node-proto errors and make them compatible, but it doesn't seem too pressing.

Copy link
Contributor

@mFragaBA mFragaBA left a comment

Choose a reason for hiding this comment

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

Great work! Left some comments though mostly are questions

@@ -1,3 +1,5 @@
#![cfg(not(feature = "wasm"))]
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Part of the Github Actions workflow run against this branch is to run the tests against all features, but these integration tests aren't relevant for wasm (and lots of the code breaks because of conditional compilation on the "wasm" feature throughout the codebase) because they require usage of the Sqlite Store. So I added this conditional compile flag at the top level here so none of the downstream test modules will compile.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Does this mean the clippy check with --all-features will not go through this file? If so, we might want to change the CI to not include the wasm feature (and maybe include another step for wasm-exclusive code)

Copy link
Contributor

Choose a reason for hiding this comment

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

same thing for fix

Copy link
Contributor

Choose a reason for hiding this comment

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

I think once we remove wasm feature (in a follow-up PR) this shouldn't be an issue. Though, we might have other similar situations (e.g., with conditional async) and we could handle them similar to how we did it in miden-base.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

So is the resolution to leave this for now? To my understanding, the clippy and fix checks should still reach this code for all other features. Agreed that there needs to be an improvement here, though. Reading @bobbinth's message, it sounds like this might be tackled in a follow-up PR.

Copy link
Contributor

Choose a reason for hiding this comment

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

Yeah - doing this in a follow-up PR is fine.

@@ -14,6 +14,7 @@ mod output_note_record;
pub use input_note_record::InputNoteRecord;
pub use output_note_record::OutputNoteRecord;

#[allow(rustdoc::broken_intra_doc_links)]
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Because [SqliteStore](crate::store::sqlite_store::SqliteStore) is compiled out with the "wasm" feature, this code fails the cargo make doc command in the github actions workflows. Added this to suppress the error. Happy to change this is a different route is preferred!

Copy link
Collaborator

Choose a reason for hiding this comment

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

Should we do it like this?

#![cfg_attr(feature = "wasm", allow(rustdoc::broken_intra_doc_links))]

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Yep, this is much better. Thank you!

@@ -574,13 +574,13 @@ impl<N: NodeRpcClient, R: FeltRng, S: Store, A: TransactionAuthenticator> Client
Ok(PartialMmr::from_parts(current_peaks, tracked_nodes, track_latest))
}

#[allow(rustdoc::broken_intra_doc_links)]
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Same story here with the cargo make doc command. In this case, [SyncStateResponse] is not compiled under the "wasm" feature

Copy link
Collaborator

@igamigo igamigo left a comment

Choose a reason for hiding this comment

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

LGTM! Left some final comments but we should be good to merge after addressing them.

@@ -14,6 +14,7 @@ mod output_note_record;
pub use input_note_record::InputNoteRecord;
pub use output_note_record::OutputNoteRecord;

#[allow(rustdoc::broken_intra_doc_links)]
Copy link
Collaborator

Choose a reason for hiding this comment

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

Should we do it like this?

#![cfg_attr(feature = "wasm", allow(rustdoc::broken_intra_doc_links))]

@@ -1,3 +1,5 @@
#![cfg(not(feature = "wasm"))]
Copy link
Collaborator

Choose a reason for hiding this comment

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

Does this mean the clippy check with --all-features will not go through this file? If so, we might want to change the CI to not include the wasm feature (and maybe include another step for wasm-exclusive code)

Comment on lines +47 to +60
#[cfg(feature = "wasm")]
pub trait MissingFieldHelper {
fn missing_field(field_name: &'static str) -> ConversionError;
}

#[cfg(feature = "wasm")]
impl<T: prost::Message> MissingFieldHelper for T {
fn missing_field(field_name: &'static str) -> ConversionError {
ConversionError::MissingFieldInProtobufRepresentation {
entity: type_name::<T>(),
field_name,
}
}
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

Could these be safely removed? It doesn't seem like they would be used but I could be missing something.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Cross-linking the reply in a different comment here #378 (comment) for visibility

Copy link
Collaborator

Choose a reason for hiding this comment

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

I meant the helper trait and the impl, are those used/needed?

Copy link
Collaborator Author

@dagarcia7 dagarcia7 Jun 14, 2024

Choose a reason for hiding this comment

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

Ah, yeah. To get the RPC working in WASM, because I couldn't pull the miden-node-proto crate, I also had to copy the domain files here https://github.com/0xPolygonMiden/miden-node/tree/main/crates/proto/src/domain and the proto definitions here https://github.com/0xPolygonMiden/miden-node/tree/main/crates/proto/proto to my WASM code.

The files in the domain folder are the ones that require the MissingFieldHelper https://github.com/0xPolygonMiden/miden-node/blob/37f2b59bc11b1333334094aa575b7bef7bcc5770/crates/proto/src/domain/accounts.rs#L12. When I create an issue to the miden-node repo to allow me to pull the ConversionError code directly in to WASM, I'll make sure that this is also part of it.

If you're curious as to why I need the domain & proto files in the first place, it's because the ApiClient that is auto-generated via prost-build here https://github.com/0xPolygonMiden/miden-node/blob/37f2b59bc11b1333334094aa575b7bef7bcc5770/crates/proto/src/generated/rpc.rs#L3 isn't compatible with WASM due to its dependencies on tokio. In an effort to get the RPC working for WASM, I copied the proto definitions along with the domain files, and used https://crates.io/crates/tonic-web-wasm-client to generate the ApiClient in a way that is compatible with WASM.

I think this could be either part of the same issue we're discussing above (to make changes to miden-node-proto such that it can be pulled in to WASM) or a separate issue on top of that to see if we can get a WASM-compatible ApiClient generated directly through the miden-node repo that I can pull from WASM.

Comment on lines +22 to +42
pub enum ConversionError {
#[error("Hex error: {0}")]
HexError(#[from] hex::FromHexError),
#[error("SMT leaf error: {0}")]
SmtLeafError(#[from] SmtLeafError),
#[error("SMT proof error: {0}")]
SmtProofError(#[from] SmtProofError),
#[error("Too much data, expected {expected}, got {got}")]
TooMuchData { expected: usize, got: usize },
#[error("Not enough data, expected {expected}, got {got}")]
InsufficientData { expected: usize, got: usize },
#[error("Value is not in the range 0..MODULUS")]
NotAValidFelt,
#[error("Invalid note type value: {0}")]
NoteTypeError(#[from] NoteError),
#[error("Field `{field_name}` required to be filled in protobuf representation of {entity}")]
MissingFieldInProtobufRepresentation {
entity: &'static str,
field_name: &'static str,
},
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

This should be fine for now, but we likely want to tackle this better from the node, we should add a TODO or file an issue to track this.

Copy link
Contributor

Choose a reason for hiding this comment

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

Yeah - let's create an issue to address this.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Yeah, I tried explaining here #378 (comment), but basically this error is brought in from miden-node-proto which currently brings in some dependencies that aren't compatible with the WASM code. And I need the error for the RPC work in the WASM. Completely agree that we can make a fix on the miden-node-proto repo to be able to import that error directly in WASM and remove the code here. I can file an issue against the miden-node-proto crate 👍

Copy link
Contributor

@bobbinth bobbinth left a 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! This is not a super deep review - but I did leave a few comments inline (some of which are for future PRs).

Cargo.toml Outdated
integration = ["testing", "concurrent", "uuid", "assert_cmd"]
std = ["miden-objects/std"]
testing = ["miden-objects/testing", "miden-lib/testing"]
test_utils = ["miden-objects/testing"]
wasm = ["std", "getrandom", "hex", "thiserror", "prost"]
Copy link
Contributor

Choose a reason for hiding this comment

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

Not for this PR: but WASM compilation shouldn't depend on std. What functionality specifically is needed from miden-objects/std for WASM?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

@bobbinth great point. This is the error I get if I don't include it

error[E0599]: the method `as_dyn_error` exists for reference `&SmtLeafError`, but its trait bounds were not satisfied
  --> src/errors.rs:26:20
   |
26 |     SmtLeafError(#[from] SmtLeafError),
   |                    ^^^^ method cannot be called on `&SmtLeafError` due to unsatisfied trait bounds
   |
  ::: /Users/dennisgarcia/.cargo/registry/src/index.crates.io-6f17d22bba15001f/miden-crypto-0.9.3/src/merkle/smt/full/error.rs:14:1
   |
14 | pub enum SmtLeafError {
   | --------------------- doesn't satisfy `SmtLeafError: AsDynError<'_>` or `SmtLeafError: StdError`
   |
   = note: the following trait bounds were not satisfied:
           `SmtLeafError: StdError`
           which is required by `SmtLeafError: AsDynError<'_>`
           `&SmtLeafError: StdError`
           which is required by `&SmtLeafError: AsDynError<'_>`

which is due to that pesky code in src/errors.rs that I'm duplicating from the miden-node-proto module. All the more reason to do a quick follow up to remove that code 👍

Comment on lines 33 to 44
#[cfg(not(feature = "wasm"))]
pub fn get_input_notes(&self, filter: NoteFilter) -> Result<Vec<InputNoteRecord>, ClientError> {
self.store.get_input_notes(filter).map_err(|err| err.into())
}

#[cfg(feature = "wasm")]
pub async fn get_input_notes(
&self,
filter: NoteFilter<'_>,
) -> Result<Vec<InputNoteRecord>, ClientError> {
self.store.get_input_notes(filter).await.map_err(|err| err.into())
}
Copy link
Contributor

Choose a reason for hiding this comment

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

I'm assuming we need to maintain 2 separate versions because of the lifetime? Could we have just a single function which looks like this:

#[maybe_async]
pub fn get_input_notes(
    &self,
    filter: NoteFilter<'_>,
) -> Result<Vec<InputNoteRecord>, ClientError> {
    ...
}

If not, I'd get rid of the lifetime requirement in NoteFilter (I think cloning of Note IDs is fine). But this can also be done in a follow-up PR.

Copy link
Contributor

Choose a reason for hiding this comment

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

I've just checked and clippy doesn't complain about having the lifetime in the non-async version. So I agree, let's keep one implementation for get_input_notes and get_output_notes. Also, you might get an error at get_input_note_with_id_prefix if you do so, but it's related to this comment

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Good call. Tried keeping the lifetime requirement with the maybe-async/maybe-await pattern and it looks like it's compiling and working fine 🎉

Comment on lines 92 to 106
#[cfg(not(feature = "wasm"))]
pub fn get_output_notes(
&self,
filter: NoteFilter,
) -> Result<Vec<OutputNoteRecord>, ClientError> {
self.store.get_output_notes(filter).map_err(|err| err.into())
}

#[cfg(feature = "wasm")]
pub async fn get_output_notes(
&self,
filter: NoteFilter<'_>,
) -> Result<Vec<OutputNoteRecord>, ClientError> {
self.store.get_output_notes(filter).await.map_err(|err| err.into())
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Similar comment as the one above.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Fixed

Comment on lines +31 to +32
#[cfg(feature = "wasm")]
fn main() {}
Copy link
Contributor

Choose a reason for hiding this comment

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

Why do we need this?

Copy link
Contributor

Choose a reason for hiding this comment

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

I think Dennis mentioned it here. I'm not sure, but would splitting (either two independent crates or both in the same workspace) the CLI from the client library might solve the issue, but I think we can tackle it in a separate refactor

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Since I basically compiled out the main function that runs the CLI

#[cfg(not(feature = "wasm"))]
#[tokio::main]
async fn main() -> Result<(), String> {
    tracing_subscriber::fmt::init();
    // read command-line args
    let cli = Cli::parse();
    // execute cli action
    cli.execute().await
}

I needed to replace the main function. So I just added one that does nothing 😅. @mFragaBA's suggestion is a lot cleaner. I'll jot it down as the list of things to follow-up on after this PR 👍

@@ -1,3 +1,5 @@
#![cfg(not(feature = "wasm"))]
Copy link
Contributor

Choose a reason for hiding this comment

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

I think once we remove wasm feature (in a follow-up PR) this shouldn't be an issue. Though, we might have other similar situations (e.g., with conditional async) and we could handle them similar to how we did it in miden-base.

@@ -1,19 +1,22 @@
#![allow(async_fn_in_trait)]
Copy link
Contributor

Choose a reason for hiding this comment

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

Is this attribute still needed?

Copy link
Contributor

Choose a reason for hiding this comment

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

I checked and If we remove this clippy starts complaining:

image

Copy link
Contributor

Choose a reason for hiding this comment

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

Interesting! I'm surprised we didn't run into this issue in miden-base. Could it be due to Rust compiler version differences?

Copy link
Contributor

Choose a reason for hiding this comment

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

hmm, we're both using 1.78 (at least as per the rust-toolchain file) so I don't think so.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Yeah I was also curious about this. Removing the async-trait dependency on the traits works, but yields these warning messages. I suppressed them with this line of code, but also unsure why rust yells at us to begin with.

Comment on lines +22 to +42
pub enum ConversionError {
#[error("Hex error: {0}")]
HexError(#[from] hex::FromHexError),
#[error("SMT leaf error: {0}")]
SmtLeafError(#[from] SmtLeafError),
#[error("SMT proof error: {0}")]
SmtProofError(#[from] SmtProofError),
#[error("Too much data, expected {expected}, got {got}")]
TooMuchData { expected: usize, got: usize },
#[error("Not enough data, expected {expected}, got {got}")]
InsufficientData { expected: usize, got: usize },
#[error("Value is not in the range 0..MODULUS")]
NotAValidFelt,
#[error("Invalid note type value: {0}")]
NoteTypeError(#[from] NoteError),
#[error("Field `{field_name}` required to be filled in protobuf representation of {entity}")]
MissingFieldInProtobufRepresentation {
entity: &'static str,
field_name: &'static str,
},
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Yeah - let's create an issue to address this.

Copy link
Contributor

@mFragaBA mFragaBA left a comment

Choose a reason for hiding this comment

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

LGTM!

@Dominik1999
Copy link
Collaborator

merge?

@igamigo igamigo merged commit d960b02 into 0xPolygonMiden:next Jun 14, 2024
7 checks passed
bobbinth pushed a commit that referenced this pull request Jul 5, 2024
* Adding Changes to Enable WASM-32 Support

* Fixed all conflicts

* Minor cleanup

* Incorporate maybe-async/maybe-await macros

* Feedback

* Added async feature with no dependencies yet. Need miden-base 0.3.1 release

* Reverting miden-client.toml changes

* Feedback

* Cleanup

* Lint

* Fixed more github action errors

* Feedback

---------

Co-authored-by: Dennis Garcia <dennis@demoxlabs.xyz>
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.

5 participants