From d960b020b834b7a137f8e676492795fe7e1782e1 Mon Sep 17 00:00:00 2001 From: Dennis Garcia Date: Fri, 14 Jun 2024 12:20:46 -0500 Subject: [PATCH] Adding Changes to Enable WASM-32 Support (#378) * 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 --- Cargo.toml | 41 ++++--- src/client/accounts.rs | 51 ++++++-- src/client/mod.rs | 102 +++++++++++++++- src/client/note_screener.rs | 9 +- src/client/notes.rs | 39 +++--- src/client/rpc/mod.rs | 12 +- src/client/rpc/tonic_client.rs | 2 - src/client/store_authenticator.rs | 1 + src/client/sync.rs | 113 +++++++++--------- src/client/transactions/mod.rs | 39 ++++-- .../transactions/transaction_request.rs | 2 +- src/errors.rs | 53 ++++++++ src/lib.rs | 5 +- src/main.rs | 18 +++ src/mock.rs | 2 - src/store/data_store.rs | 51 ++++---- src/store/mod.rs | 32 ++++- src/store/note_record/mod.rs | 9 +- tests/integration/main.rs | 2 + 19 files changed, 426 insertions(+), 157 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 67f8be0a5..44049b3e1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,6 +12,9 @@ edition = "2021" rust-version = "1.78" default-run = "miden" +[lib] +crate-type = ["lib"] + [[bin]] name = "miden" path = "src/main.rs" @@ -22,35 +25,41 @@ path = "tests/integration/main.rs" required-features = ["integration"] [features] +async = ["miden-tx/async"] concurrent = ["miden-lib/concurrent", "miden-objects/concurrent", "miden-tx/concurrent"] -default = ["std"] +default = ["std", "clap", "comfy-table", "figment", "lazy_static", "miden-node-proto", "rusqlite", "rusqlite_migration", "tokio", "tonic", "toml"] integration = ["testing", "concurrent", "uuid", "assert_cmd"] std = ["miden-objects/std"] testing = ["miden-objects/testing", "miden-lib/testing"] test_utils = ["miden-objects/testing"] +wasm = ["std", "async", "getrandom", "hex", "thiserror", "prost"] [dependencies] -async-trait = { version = "0.1" } -clap = { version = "4.3", features = ["derive"] } -comfy-table = "7.1.0" -figment = { version = "0.10", features = ["toml", "env"] } -lazy_static = "1.4.0" -miden-lib = { version = "0.3", default-features = false } -miden-node-proto = { version = "0.3", default-features = false } -miden-tx = { version = "0.3", default-features = false } -miden-objects = { version = "0.3", default-features = false, features = ["serde"] } +assert_cmd = { version = "2.0", optional = true } +clap = { version = "4.3", features = ["derive"], optional = true } +comfy-table = { version = "7.1.0", optional = true } +figment = { version = "0.10", features = ["toml", "env"], optional = true } +getrandom = { version = "0.2", features = ["js"], optional = true } +hex = { version = "0.4", optional = true } +lazy_static = { version = "1.4.0", optional = true } +miden-lib = { version = "0.3.1", default-features = false } +miden-tx = { version = "0.3.1", default-features = false } +miden-node-proto = { version = "0.3", default-features = false, optional = true } +miden-objects = { version = "0.3.1", default-features = false, features = ["serde"] } +prost = { version = "0.12.3", optional = true } rand = { version = "0.8.5" } -rusqlite = { version = "0.30.0", features = ["vtab", "array", "bundled"] } -rusqlite_migration = { version = "1.0" } +rusqlite = { version = "0.30.0", features = ["vtab", "array", "bundled"], optional = true } +rusqlite_migration = { version = "1.0", optional = true } serde = { version = "1.0", features = ["derive"] } serde_json = { version = "1.0", features = ["raw_value"] } -tokio = { version = "1.29", features = ["rt-multi-thread", "net", "macros"] } -tonic = { version = "0.11" } -toml = { version = "0.8" } +thiserror = { version = "1.0", optional = true } +tokio = { version = "1.29", features = ["rt-multi-thread", "net", "macros"], optional = true } +toml = { version = "0.8", optional = true } +tonic = { version = "0.11", optional = true } tracing = { version = "0.1" } tracing-subscriber = { version = "0.3" } uuid = { version = "1.6.1", features = ["serde", "v4"], optional = true } -assert_cmd = { version = "2.0", optional = true } +winter-maybe-async = "0.10.0" [dev-dependencies] # needed for tests to run always with the test utils feature diff --git a/src/client/accounts.rs b/src/client/accounts.rs index eb79a22dc..76f17ac79 100644 --- a/src/client/accounts.rs +++ b/src/client/accounts.rs @@ -9,6 +9,7 @@ use miden_objects::{ Felt, Word, }; use miden_tx::TransactionAuthenticator; +use winter_maybe_async::{maybe_async, maybe_await}; use super::{rpc::NodeRpcClient, Client}; use crate::{errors::ClientError, store::Store}; @@ -31,20 +32,26 @@ impl Client // -------------------------------------------------------------------------------------------- /// Creates a new [Account] based on an [AccountTemplate] and saves it in the store + #[maybe_async] pub fn new_account( &mut self, template: AccountTemplate, ) -> Result<(Account, Word), ClientError> { let account_and_seed = match template { AccountTemplate::BasicWallet { mutable_code, storage_type: storage_mode } => { - self.new_basic_wallet(mutable_code, storage_mode) + maybe_await!(self.new_basic_wallet(mutable_code, storage_mode)) }, AccountTemplate::FungibleFaucet { token_symbol, decimals, max_supply, storage_type: storage_mode, - } => self.new_fungible_faucet(token_symbol, decimals, max_supply, storage_mode), + } => maybe_await!(self.new_fungible_faucet( + token_symbol, + decimals, + max_supply, + storage_mode + )), }?; Ok(account_and_seed) @@ -60,6 +67,7 @@ impl Client /// /// Will panic when trying to import a non-new account without a seed since this functionality /// is not currently implemented + #[maybe_async] pub fn import_account(&mut self, account_data: AccountData) -> Result<(), ClientError> { let account_seed = if !account_data.account.is_new() && account_data.account_seed.is_some() { @@ -74,10 +82,15 @@ impl Client account_data.account_seed }; - self.insert_account(&account_data.account, account_seed, &account_data.auth_secret_key) + maybe_await!(self.insert_account( + &account_data.account, + account_seed, + &account_data.auth_secret_key + )) } /// Creates a new regular account and saves it in the store along with its seed and auth data + #[maybe_async] fn new_basic_wallet( &mut self, mutable_code: bool, @@ -107,10 +120,15 @@ impl Client ) }?; - self.insert_account(&account, Some(seed), &AuthSecretKey::RpoFalcon512(key_pair))?; + maybe_await!(self.insert_account( + &account, + Some(seed), + &AuthSecretKey::RpoFalcon512(key_pair) + ))?; Ok((account, seed)) } + #[maybe_async] fn new_fungible_faucet( &mut self, token_symbol: TokenSymbol, @@ -136,7 +154,11 @@ impl Client auth_scheme, )?; - self.insert_account(&account, Some(seed), &AuthSecretKey::RpoFalcon512(key_pair))?; + maybe_await!(self.insert_account( + &account, + Some(seed), + &AuthSecretKey::RpoFalcon512(key_pair) + ))?; Ok((account, seed)) } @@ -146,6 +168,7 @@ impl Client /// /// If an account is new and no seed is provided, the function errors out because the client /// cannot execute transactions against new accounts for which it does not know the seed. + #[maybe_async] pub fn insert_account( &mut self, account: &Account, @@ -156,8 +179,7 @@ impl Client return Err(ClientError::ImportNewAccountWithoutSeed); } - self.store - .insert_account(account, account_seed, auth_info) + maybe_await!(self.store.insert_account(account, account_seed, auth_info)) .map_err(ClientError::StoreError) } @@ -165,24 +187,26 @@ impl Client // -------------------------------------------------------------------------------------------- /// Returns summary info about the accounts managed by this client. + #[maybe_async] pub fn get_account_stubs(&self) -> Result)>, ClientError> { - self.store.get_account_stubs().map_err(|err| err.into()) + maybe_await!(self.store.get_account_stubs()).map_err(|err| err.into()) } - /// Returns summary info about the specified account. + #[maybe_async] pub fn get_account( &self, account_id: AccountId, ) -> Result<(Account, Option), ClientError> { - self.store.get_account(account_id).map_err(|err| err.into()) + maybe_await!(self.store.get_account(account_id)).map_err(|err| err.into()) } /// Returns summary info about the specified account. + #[maybe_async] pub fn get_account_stub_by_id( &self, account_id: AccountId, ) -> Result<(AccountStub, Option), ClientError> { - self.store.get_account_stub(account_id).map_err(|err| err.into()) + maybe_await!(self.store.get_account_stub(account_id)).map_err(|err| err.into()) } /// Returns an [AuthSecretKey] object utilized to authenticate an account. @@ -191,15 +215,16 @@ impl Client /// /// Returns a [ClientError::StoreError] with a [StoreError::AccountDataNotFound](crate::errors::StoreError::AccountDataNotFound) if the provided ID does /// not correspond to an existing account. + #[maybe_async] pub fn get_account_auth(&self, account_id: AccountId) -> Result { - self.store.get_account_auth(account_id).map_err(|err| err.into()) + maybe_await!(self.store.get_account_auth(account_id)).map_err(|err| err.into()) } } // TESTS // ================================================================================================ -#[cfg(test)] +#[cfg(all(test, not(feature = "wasm")))] pub mod tests { use miden_objects::{ accounts::{Account, AccountData, AccountId, AuthSecretKey}, diff --git a/src/client/mod.rs b/src/client/mod.rs index 7254eaf00..94b7fb6c7 100644 --- a/src/client/mod.rs +++ b/src/client/mod.rs @@ -1,20 +1,36 @@ use alloc::rc::Rc; +#[cfg(feature = "wasm")] +use miden_objects::{ + accounts::AccountId, + crypto::rand::{FeltRng, RpoRandomCoin}, + notes::{NoteExecutionHint, NoteTag, NoteType}, + Felt, NoteError, +}; +#[cfg(not(feature = "wasm"))] use miden_objects::{ crypto::rand::{FeltRng, RpoRandomCoin}, Felt, }; use miden_tx::{TransactionAuthenticator, TransactionExecutor}; +#[cfg(not(feature = "wasm"))] use rand::Rng; +#[cfg(feature = "wasm")] +use rand::{rngs::StdRng, Rng, SeedableRng}; use tracing::info; use crate::store::{data_store::ClientDataStore, Store}; +#[cfg(feature = "wasm")] +use crate::{ + errors::IdPrefixFetchError, + store::{InputNoteRecord, NoteFilter as ClientNoteFilter}, +}; pub mod rpc; use rpc::NodeRpcClient; pub mod accounts; -#[cfg(test)] +#[cfg(all(test, not(feature = "wasm")))] mod chain_data; mod note_screener; mod notes; @@ -89,7 +105,7 @@ impl Client &mut self.rpc_api } - #[cfg(any(test, feature = "test_utils"))] + #[cfg(any(test, feature = "test_utils", feature = "wasm"))] pub fn store(&mut self) -> &S { &self.store } @@ -101,8 +117,90 @@ impl Client /// Gets [RpoRandomCoin] from the client pub fn get_random_coin() -> RpoRandomCoin { // TODO: Initialize coin status once along with the client and persist status for retrieval + #[cfg(not(feature = "wasm"))] let mut rng = rand::thread_rng(); + #[cfg(feature = "wasm")] + let mut rng = StdRng::from_entropy(); let coin_seed: [u64; 4] = rng.gen(); RpoRandomCoin::new(coin_seed.map(Felt::new)) } + +// TODO - move to a more appropriate place. This is duplicated code from cli/mod.rs +// because the cli code is compiled out under the "wasm" feature and +// we need to be able to import this function from the wasm code. +#[cfg(feature = "wasm")] +pub async fn get_input_note_with_id_prefix< + N: NodeRpcClient, + R: FeltRng, + S: Store, + A: TransactionAuthenticator, +>( + client: &mut Client, + note_id_prefix: &str, +) -> Result { + let mut input_note_records = client + .get_input_notes(ClientNoteFilter::All) + .await + .map_err(|err| { + tracing::error!("Error when fetching all notes from the store: {err}"); + IdPrefixFetchError::NoMatch(format!("note ID prefix {note_id_prefix}").to_string()) + })? + .into_iter() + .filter(|note_record| note_record.id().to_hex().starts_with(note_id_prefix)) + .collect::>(); + + if input_note_records.is_empty() { + return Err(IdPrefixFetchError::NoMatch( + format!("note ID prefix {note_id_prefix}").to_string(), + )); + } + if input_note_records.len() > 1 { + let input_note_record_ids = input_note_records + .iter() + .map(|input_note_record| input_note_record.id()) + .collect::>(); + tracing::error!( + "Multiple notes found for the prefix {}: {:?}", + note_id_prefix, + input_note_record_ids + ); + return Err(IdPrefixFetchError::MultipleMatches( + format!("note ID prefix {note_id_prefix}").to_string(), + )); + } + + Ok(input_note_records + .pop() + .expect("input_note_records should always have one element")) +} + +// TODO - move to a more appropriate place. This is duplicated code from cli/utils.rs +// because the cli code is compiled out under the "wasm" feature and +// we need to be able to import this function from the wasm code. +#[cfg(feature = "wasm")] +pub fn build_swap_tag( + note_type: NoteType, + offered_asset_faucet_id: AccountId, + requested_asset_faucet_id: AccountId, +) -> Result { + const SWAP_USE_CASE_ID: u16 = 0; + + // get bits 4..12 from faucet IDs of both assets, these bits will form the tag payload; the + // reason we skip the 4 most significant bits is that these encode metadata of underlying + // faucets and are likely to be the same for many different faucets. + + let offered_asset_id: u64 = offered_asset_faucet_id.into(); + let offered_asset_tag = (offered_asset_id >> 52) as u8; + + let requested_asset_id: u64 = requested_asset_faucet_id.into(); + let requested_asset_tag = (requested_asset_id >> 52) as u8; + + let payload = ((offered_asset_tag as u16) << 8) | (requested_asset_tag as u16); + + let execution = NoteExecutionHint::Local; + match note_type { + NoteType::Public => NoteTag::for_public_use_case(SWAP_USE_CASE_ID, payload, execution), + _ => NoteTag::for_local_use_case(SWAP_USE_CASE_ID, payload), + } +} diff --git a/src/client/note_screener.rs b/src/client/note_screener.rs index c5618cac5..1c723ef52 100644 --- a/src/client/note_screener.rs +++ b/src/client/note_screener.rs @@ -2,6 +2,7 @@ use alloc::{collections::BTreeSet, rc::Rc}; use core::fmt; use miden_objects::{accounts::AccountId, assets::Asset, notes::Note, Word}; +use winter_maybe_async::{maybe_async, maybe_await}; use super::transactions::transaction_request::known_script_roots::{P2ID, P2IDR, SWAP}; use crate::{ @@ -41,17 +42,18 @@ impl NoteScreener { /// Does a fast check for known scripts (P2ID, P2IDR, SWAP). We're currently /// unable to execute notes that are not committed so a slow check for other scripts is currently /// not available. + #[maybe_async] pub fn check_relevance( &self, note: &Note, ) -> Result, ScreenerError> { - let account_ids = BTreeSet::from_iter(self.store.get_account_ids()?); + let account_ids = BTreeSet::from_iter(maybe_await!(self.store.get_account_ids())?); let script_hash = note.script().hash().to_string(); let note_relevance = match script_hash.as_str() { P2ID => Self::check_p2id_relevance(note, &account_ids)?, P2IDR => Self::check_p2idr_relevance(note, &account_ids)?, - SWAP => self.check_swap_relevance(note, &account_ids)?, + SWAP => maybe_await!(self.check_swap_relevance(note, &account_ids))?, _ => self.check_script_relevance(note, &account_ids)?, }; @@ -122,6 +124,7 @@ impl NoteScreener { /// load the account's vaults, or even have a function in the `Store` to do this. /// /// TODO: test/revisit this in the future + #[maybe_async] fn check_swap_relevance( &self, note: &Note, @@ -143,7 +146,7 @@ impl NoteScreener { let mut accounts_with_relevance = Vec::new(); for account_id in account_ids { - let (account, _) = self.store.get_account(*account_id)?; + let (account, _) = maybe_await!(self.store.get_account(*account_id))?; // Check that the account can cover the demanded asset match asset { diff --git a/src/client/notes.rs b/src/client/notes.rs index 4a78c68c3..7c8204929 100644 --- a/src/client/notes.rs +++ b/src/client/notes.rs @@ -6,6 +6,7 @@ use miden_objects::{ }; use miden_tx::{ScriptTarget, TransactionAuthenticator}; use tracing::info; +use winter_maybe_async::{maybe_async, maybe_await}; use super::{note_screener::NoteRelevance, rpc::NodeRpcClient, Client}; use crate::{ @@ -29,25 +30,30 @@ impl Client // -------------------------------------------------------------------------------------------- /// Returns input notes managed by this client. - pub fn get_input_notes(&self, filter: NoteFilter) -> Result, ClientError> { - self.store.get_input_notes(filter).map_err(|err| err.into()) + #[maybe_async] + pub fn get_input_notes( + &self, + filter: NoteFilter<'_>, + ) -> Result, ClientError> { + maybe_await!(self.store.get_input_notes(filter)).map_err(|err| err.into()) } /// Returns input notes that are able to be consumed by the account_id. /// /// If account_id is None then all consumable input notes are returned. + #[maybe_async] pub fn get_consumable_notes( &self, account_id: Option, ) -> Result, ClientError> { - let commited_notes = self.store.get_input_notes(NoteFilter::Committed)?; + let commited_notes = maybe_await!(self.store.get_input_notes(NoteFilter::Committed))?; let note_screener = NoteScreener::new(self.store.clone()); let mut relevant_notes = Vec::new(); for input_note in commited_notes { let account_relevance = - note_screener.check_relevance(&input_note.clone().try_into()?)?; + maybe_await!(note_screener.check_relevance(&input_note.clone().try_into()?))?; if account_relevance.is_empty() { continue; @@ -67,10 +73,9 @@ impl Client } /// Returns the input note with the specified hash. + #[maybe_async] pub fn get_input_note(&self, note_id: NoteId) -> Result { - Ok(self - .store - .get_input_notes(NoteFilter::Unique(note_id))? + Ok(maybe_await!(self.store.get_input_notes(NoteFilter::Unique(note_id)))? .pop() .expect("The vector always has one element for NoteFilter::Unique")) } @@ -79,18 +84,18 @@ impl Client // -------------------------------------------------------------------------------------------- /// Returns output notes managed by this client. + #[maybe_async] pub fn get_output_notes( &self, - filter: NoteFilter, + filter: NoteFilter<'_>, ) -> Result, ClientError> { - self.store.get_output_notes(filter).map_err(|err| err.into()) + maybe_await!(self.store.get_output_notes(filter)).map_err(|err| err.into()) } /// Returns the output note with the specified hash. + #[maybe_async] pub fn get_output_note(&self, note_id: NoteId) -> Result { - Ok(self - .store - .get_output_notes(NoteFilter::Unique(note_id))? + Ok(maybe_await!(self.store.get_output_notes(NoteFilter::Unique(note_id)))? .pop() .expect("The vector always has one element for NoteFilter::Unique")) } @@ -110,12 +115,11 @@ impl Client verify: bool, ) -> Result<(), ClientError> { if !verify { - return self.store.insert_input_note(¬e).map_err(|err| err.into()); + return maybe_await!(self.store.insert_input_note(¬e)).map_err(|err| err.into()); } // Verify that note exists in chain let mut chain_notes = self.rpc_api.get_notes_by_id(&[note.id()]).await?; - if chain_notes.is_empty() { return Err(ClientError::ExistenceVerificationError(note.id())); } @@ -126,7 +130,9 @@ impl Client // If the note exists in the chain and the client is synced to a height equal or // greater than the note's creation block, get MMR and block header data for the // note's block. Additionally create the inclusion proof if none is provided. - let inclusion_proof = if self.get_sync_height()? >= inclusion_details.block_num { + let inclusion_proof = if maybe_await!(self.get_sync_height())? + >= inclusion_details.block_num + { // Add the inclusion proof to the imported note info!("Requesting MMR data for past block num {}", inclusion_details.block_num); let block_header = @@ -165,7 +171,8 @@ impl Client note.details().clone(), None, ); - self.store.insert_input_note(¬e).map_err(|err| err.into()) + + maybe_await!(self.store.insert_input_note(¬e)).map_err(|err| err.into()) } /// Compiles the provided program into a [NoteScript] and checks (to the extent possible) if diff --git a/src/client/rpc/mod.rs b/src/client/rpc/mod.rs index b7e64c1bd..69b80cc95 100644 --- a/src/client/rpc/mod.rs +++ b/src/client/rpc/mod.rs @@ -1,6 +1,10 @@ +#![allow(async_fn_in_trait)] + +#[cfg(not(feature = "wasm"))] +mod tonic_client; + use core::fmt; -use async_trait::async_trait; use miden_objects::{ accounts::{Account, AccountId}, crypto::merkle::{MerklePath, MmrDelta, MmrProof}, @@ -8,12 +12,11 @@ use miden_objects::{ transaction::ProvenTransaction, BlockHeader, Digest, }; +#[cfg(not(feature = "wasm"))] +pub use tonic_client::TonicRpcClient; use crate::errors::NodeRpcClientError; -mod tonic_client; -pub use tonic_client::TonicRpcClient; - // NOTE DETAILS // ================================================================================================ @@ -74,7 +77,6 @@ impl NoteInclusionDetails { /// The implementers are responsible for connecting to the Miden node, handling endpoint /// requests/responses, and translating responses into domain objects relevant for each of the /// endpoints. -#[async_trait] pub trait NodeRpcClient { /// Given a Proven Transaction, send it to the node for it to be included in a future block /// using the `/SubmitProvenTransaction` rpc endpoint diff --git a/src/client/rpc/tonic_client.rs b/src/client/rpc/tonic_client.rs index 41bfa5024..5d0dc0f97 100644 --- a/src/client/rpc/tonic_client.rs +++ b/src/client/rpc/tonic_client.rs @@ -1,6 +1,5 @@ use std::time::Duration; -use async_trait::async_trait; use miden_node_proto::{ errors::ConversionError, generated::{ @@ -68,7 +67,6 @@ impl TonicRpcClient { } } -#[async_trait] impl NodeRpcClient for TonicRpcClient { async fn submit_proven_transaction( &mut self, diff --git a/src/client/store_authenticator.rs b/src/client/store_authenticator.rs index 83c905cd9..f3308a585 100644 --- a/src/client/store_authenticator.rs +++ b/src/client/store_authenticator.rs @@ -48,6 +48,7 @@ impl TransactionAuthenticator for StoreAuthenticator { get_falcon_signature(&k, message, &mut *rng) } } + // HELPER FUNCTIONS // ================================================================================================ diff --git a/src/client/sync.rs b/src/client/sync.rs index 63c4be54b..d42c6351b 100644 --- a/src/client/sync.rs +++ b/src/client/sync.rs @@ -11,6 +11,7 @@ use miden_objects::{ }; use miden_tx::TransactionAuthenticator; use tracing::{info, warn}; +use winter_maybe_async::{maybe_async, maybe_await}; use super::{ rpc::{CommittedNote, NodeRpcClient, NoteDetails}, @@ -158,8 +159,9 @@ impl Client // -------------------------------------------------------------------------------------------- /// Returns the block number of the last state sync block. + #[maybe_async] pub fn get_sync_height(&self) -> Result { - self.store.get_sync_height().map_err(|err| err.into()) + maybe_await!(self.store.get_sync_height()).map_err(|err| err.into()) } /// Returns the list of note tags tracked by the client. @@ -167,13 +169,15 @@ impl Client /// When syncing the state with the node, these tags will be added to the sync request and note-related information will be retrieved for notes that have matching tags. /// /// Note: Tags for accounts that are being tracked by the client are managed automatically by the client and do not need to be added here. That is, notes for managed accounts will be retrieved automatically by the client when syncing. + #[maybe_async] pub fn get_note_tags(&self) -> Result, ClientError> { - self.store.get_note_tags().map_err(|err| err.into()) + maybe_await!(self.store.get_note_tags()).map_err(|err| err.into()) } /// Adds a note tag for the client to track. + #[maybe_async] pub fn add_note_tag(&mut self, tag: NoteTag) -> Result<(), ClientError> { - match self.store.add_note_tag(tag).map_err(|err| err.into()) { + match maybe_await!(self.store.add_note_tag(tag)).map_err(|err| err.into()) { Ok(true) => Ok(()), Ok(false) => { warn!("Tag {} is already being tracked", tag); @@ -184,8 +188,9 @@ impl Client } /// Removes a note tag for the client to track. + #[maybe_async] pub fn remove_note_tag(&mut self, tag: NoteTag) -> Result<(), ClientError> { - match self.store.remove_note_tag(tag)? { + match maybe_await!(self.store.remove_note_tag(tag))? { true => Ok(()), false => { warn!("Tag {} wasn't being tracked", tag); @@ -218,7 +223,7 @@ impl Client /// Attempts to retrieve the genesis block from the store. If not found, /// it requests it from the node and store it. async fn ensure_genesis_in_place(&mut self) -> Result<(), ClientError> { - let genesis = self.store.get_block_header_by_num(0); + let genesis = maybe_await!(self.store.get_block_header_by_num(0)); match genesis { Ok(_) => Ok(()), @@ -236,16 +241,14 @@ impl Client MmrPeaks::new(0, vec![]).expect("Blank MmrPeaks should not fail to instantiate"); // NOTE: If genesis block data ever includes notes in the future, the third parameter in // this `insert_block_header` call may be `true` - self.store.insert_block_header(genesis_block, blank_mmr_peaks, false)?; + maybe_await!(self.store.insert_block_header(genesis_block, blank_mmr_peaks, false))?; Ok(()) } async fn sync_state_once(&mut self) -> Result { - let current_block_num = self.store.get_sync_height()?; + let current_block_num = maybe_await!(self.store.get_sync_height())?; - let accounts: Vec = self - .store - .get_account_stubs()? + let accounts: Vec = maybe_await!(self.store.get_account_stubs())? .into_iter() .map(|(acc_stub, _)| acc_stub) .collect(); @@ -257,14 +260,13 @@ impl Client }) .collect::, _>>()?; - let stored_note_tags: Vec = self.store.get_note_tags()?; + let stored_note_tags: Vec = maybe_await!(self.store.get_note_tags())?; - let uncommited_note_tags: Vec = self - .store - .get_input_notes(NoteFilter::Pending)? - .iter() - .filter_map(|note| note.metadata().map(|metadata| metadata.tag())) - .collect(); + let uncommited_note_tags: Vec = + maybe_await!(self.store.get_input_notes(NoteFilter::Pending))? + .iter() + .filter_map(|note| note.metadata().map(|metadata| metadata.tag())) + .collect(); let note_tags: Vec = [account_note_tags, stored_note_tags, uncommited_note_tags] .concat() @@ -276,12 +278,11 @@ impl Client // To receive information about added nullifiers, we reduce them to the higher 16 bits // Note that besides filtering by nullifier prefixes, the node also filters by block number // (it only returns nullifiers from current_block_num until response.block_header.block_num()) - let nullifiers_tags: Vec = self - .store - .get_unspent_input_note_nullifiers()? - .iter() - .map(|nullifier| (nullifier.inner()[3].as_int() >> FILTER_ID_SHIFT) as u16) - .collect(); + let nullifiers_tags: Vec = + maybe_await!(self.store.get_unspent_input_note_nullifiers())? + .iter() + .map(|nullifier| (nullifier.inner()[3].as_int() >> FILTER_ID_SHIFT) as u16) + .collect(); // Send request let account_ids: Vec = accounts.iter().map(|acc| acc.id()).collect(); @@ -316,14 +317,14 @@ impl Client self.validate_local_account_hashes(&response.account_hash_updates, &offchain_accounts)?; // Derive new nullifiers data - let new_nullifiers = self.get_new_nullifiers(response.nullifiers)?; + let new_nullifiers = maybe_await!(self.get_new_nullifiers(response.nullifiers))?; // Build PartialMmr with current data and apply updates let (new_peaks, new_authentication_nodes) = { - let current_partial_mmr = self.build_current_partial_mmr()?; + let current_partial_mmr = maybe_await!(self.build_current_partial_mmr())?; let (current_block, has_relevant_notes) = - self.store.get_block_header_by_num(current_block_num)?; + maybe_await!(self.store.get_block_header_by_num(current_block_num))?; apply_mmr_changes( current_partial_mmr, @@ -334,7 +335,7 @@ impl Client }; let uncommitted_transactions = - self.store.get_transactions(TransactionFilter::Uncomitted)?; + maybe_await!(self.store.get_transactions(TransactionFilter::Uncomitted))?; let transactions_to_commit = get_transactions_to_commit( &uncommitted_transactions, @@ -364,8 +365,7 @@ impl Client }; // Apply received and computed updates to the store - self.store - .apply_state_sync(state_sync_update) + maybe_await!(self.store.apply_state_sync(state_sync_update)) .map_err(ClientError::StoreError)?; if response.chain_tip == response.block_header.block_num() { @@ -407,19 +407,17 @@ impl Client let mut tracked_input_notes = vec![]; let mut tracked_output_notes_proofs = vec![]; - let pending_input_notes: BTreeMap = self - .store - .get_input_notes(NoteFilter::Pending)? - .into_iter() - .map(|n| (n.id(), n)) - .collect(); + let pending_input_notes: BTreeMap = + maybe_await!(self.store.get_input_notes(NoteFilter::Pending))? + .into_iter() + .map(|n| (n.id(), n)) + .collect(); - let pending_output_notes: BTreeSet = self - .store - .get_output_notes(NoteFilter::Pending)? - .into_iter() - .map(|n| n.id()) - .collect(); + let pending_output_notes: BTreeSet = + maybe_await!(self.store.get_output_notes(NoteFilter::Pending))? + .into_iter() + .map(|n| n.id()) + .collect(); for committed_note in committed_notes { if let Some(note_record) = pending_input_notes.get(committed_note.note_id()) { @@ -535,13 +533,13 @@ impl Client // Find all relevant Input Notes using the note checker for input_note in committed_notes.updated_input_notes() { - if !note_screener.check_relevance(input_note.note())?.is_empty() { + if !maybe_await!(note_screener.check_relevance(input_note.note()))?.is_empty() { return Ok(true); } } for public_input_note in committed_notes.new_public_notes() { - if !note_screener.check_relevance(public_input_note.note())?.is_empty() { + if !maybe_await!(note_screener.check_relevance(public_input_note.note()))?.is_empty() { return Ok(true); } } @@ -555,14 +553,16 @@ impl Client /// /// As part of the syncing process, we add the current block number so we don't need to /// track it here. + #[maybe_async] pub(crate) fn build_current_partial_mmr(&self) -> Result { - let current_block_num = self.store.get_sync_height()?; + let current_block_num = maybe_await!(self.store.get_sync_height())?; - let tracked_nodes = self.store.get_chain_mmr_nodes(ChainMmrNodeFilter::All)?; - let current_peaks = self.store.get_chain_mmr_peaks_by_block_num(current_block_num)?; + let tracked_nodes = maybe_await!(self.store.get_chain_mmr_nodes(ChainMmrNodeFilter::All))?; + let current_peaks = + maybe_await!(self.store.get_chain_mmr_peaks_by_block_num(current_block_num))?; let track_latest = if current_block_num != 0 { - match self.store.get_block_header_by_num(current_block_num - 1) { + match maybe_await!(self.store.get_block_header_by_num(current_block_num - 1)) { Ok((_, previous_block_had_notes)) => Ok(previous_block_had_notes), Err(StoreError::BlockHeaderNotFound(_)) => Ok(false), Err(err) => Err(ClientError::StoreError(err)), @@ -574,13 +574,13 @@ impl Client Ok(PartialMmr::from_parts(current_peaks, tracked_nodes, track_latest)) } + #[cfg_attr(feature = "wasm", allow(rustdoc::broken_intra_doc_links))] /// Extracts information about nullifiers for unspent input notes that the client is tracking /// from the received [SyncStateResponse] + #[maybe_async] fn get_new_nullifiers(&self, new_nullifiers: Vec) -> Result, ClientError> { // Get current unspent nullifiers - let nullifiers = self - .store - .get_unspent_input_note_nullifiers()? + let nullifiers = maybe_await!(self.store.get_unspent_input_note_nullifiers())? .iter() .map(|nullifier| nullifier.inner()) .collect::>(); @@ -646,11 +646,11 @@ impl Client &mut self, block_num: u32, ) -> Result { - let mut current_partial_mmr = self.build_current_partial_mmr()?; + let mut current_partial_mmr = maybe_await!(self.build_current_partial_mmr())?; if current_partial_mmr.is_tracked(block_num as usize) { warn!("Current partial MMR already contains the requested data"); - let (block_header, _) = self.store.get_block_header_by_num(block_num)?; + let (block_header, _) = maybe_await!(self.store.get_block_header_by_num(block_num))?; return Ok(block_header); } @@ -681,9 +681,12 @@ impl Client .map_err(StoreError::MmrError)?; // Insert header and MMR nodes - self.store - .insert_block_header(block_header, current_partial_mmr.peaks(), true)?; - self.store.insert_chain_mmr_nodes(&path_nodes)?; + maybe_await!(self.store.insert_block_header( + block_header, + current_partial_mmr.peaks(), + true + ))?; + maybe_await!(self.store.insert_chain_mmr_nodes(&path_nodes))?; Ok(block_header) } diff --git a/src/client/transactions/mod.rs b/src/client/transactions/mod.rs index 30837b680..ab35bed29 100644 --- a/src/client/transactions/mod.rs +++ b/src/client/transactions/mod.rs @@ -14,8 +14,12 @@ use miden_objects::{ Digest, Felt, Word, }; use miden_tx::{ProvingOptions, ScriptTarget, TransactionAuthenticator, TransactionProver}; +#[cfg(not(feature = "wasm"))] use rand::Rng; +#[cfg(feature = "wasm")] +use rand::{rngs::StdRng, Rng, SeedableRng}; use tracing::info; +use winter_maybe_async::{maybe_async, maybe_await}; use self::transaction_request::{ PaymentTransactionData, SwapTransactionData, TransactionRequest, TransactionTemplate, @@ -45,6 +49,7 @@ pub struct TransactionResult { impl TransactionResult { /// Screens the output notes to store and track the relevant ones, and instantiates a [TransactionResult] + #[maybe_async] pub fn new( transaction: ExecutedTransaction, note_screener: NoteScreener, @@ -53,7 +58,7 @@ impl TransactionResult { let mut relevant_notes = vec![]; for note in notes_from_output(transaction.output_notes()) { - let account_relevance = note_screener.check_relevance(note)?; + let account_relevance = maybe_await!(note_screener.check_relevance(note))?; if !account_relevance.is_empty() { relevant_notes.push(note.clone().into()); @@ -167,11 +172,12 @@ impl Client // -------------------------------------------------------------------------------------------- /// Retrieves tracked transactions, filtered by [TransactionFilter]. + #[maybe_async] pub fn get_transactions( &self, filter: TransactionFilter, ) -> Result, ClientError> { - self.store.get_transactions(filter).map_err(|err| err.into()) + maybe_await!(self.store.get_transactions(filter)).map_err(|err| err.into()) } // TRANSACTION @@ -179,12 +185,13 @@ impl Client /// Compiles a [TransactionTemplate] into a [TransactionRequest] that can be then executed by the /// client + #[maybe_async] pub fn build_transaction_request( &mut self, transaction_template: TransactionTemplate, ) -> Result { let account_id = transaction_template.account_id(); - let account_auth = self.store.get_account_auth(account_id)?; + let account_auth = maybe_await!(self.store.get_account_auth(account_id))?; let (_pk, _sk) = match account_auth { AuthSecretKey::RpoFalcon512(key) => { @@ -224,28 +231,28 @@ impl Client /// - Returns [ClientError::MissingOutputNotes] if the [TransactionRequest] ouput notes are /// not a subset of executor's output notes /// - Returns a [ClientError::TransactionExecutorError] if the execution fails + #[maybe_async] pub fn new_transaction( &mut self, transaction_request: TransactionRequest, ) -> Result { let account_id = transaction_request.account_id(); - self.tx_executor - .load_account(account_id) + maybe_await!(self.tx_executor.load_account(account_id)) .map_err(ClientError::TransactionExecutorError)?; - let block_num = self.store.get_sync_height()?; + let block_num = maybe_await!(self.store.get_sync_height())?; let note_ids = transaction_request.get_input_note_ids(); let output_notes = transaction_request.expected_output_notes().to_vec(); let partial_notes = transaction_request.expected_partial_notes().to_vec(); // Execute the transaction and get the witness - let executed_transaction = self.tx_executor.execute_transaction( + let executed_transaction = maybe_await!(self.tx_executor.execute_transaction( account_id, block_num, ¬e_ids, transaction_request.into(), - )?; + ))?; // Check that the expected output notes matches the transaction outcome. // We comprare authentication hashes where possible since that involves note IDs + metadata @@ -269,7 +276,7 @@ impl Client let screener = NoteScreener::new(self.store.clone()); - TransactionResult::new(executed_transaction, screener, partial_notes) + maybe_await!(TransactionResult::new(executed_transaction, screener, partial_notes)) } /// Proves the specified transaction witness, and returns a [ProvenTransaction] that can be @@ -295,7 +302,7 @@ impl Client info!("Transaction submitted"); // Transaction was proven and submitted to the node correctly, persist note details and update account - self.store.apply_transaction(tx_result)?; + maybe_await!(self.store.apply_transaction(tx_result))?; info!("Transaction stored"); Ok(()) } @@ -321,6 +328,7 @@ impl Client // -------------------------------------------------------------------------------------------- /// Gets [RpoRandomCoin] from the client + #[cfg(not(feature = "wasm"))] fn get_random_coin(&self) -> RpoRandomCoin { // TODO: Initialize coin status once along with the client and persist status for retrieval let mut rng = rand::thread_rng(); @@ -329,6 +337,15 @@ impl Client RpoRandomCoin::new(coin_seed.map(Felt::new)) } + #[cfg(feature = "wasm")] + fn get_random_coin(&self) -> RpoRandomCoin { + // TODO: Initialize coin status once along with the client and persist status for retrieval + let mut rng = StdRng::from_entropy(); + let coin_seed: [u64; 4] = rng.gen(); + + RpoRandomCoin::new(coin_seed.map(Felt::new)) + } + /// Helper to build a [TransactionRequest] for P2ID-type transactions easily. /// /// - auth_info has to be from the executor account @@ -500,7 +517,7 @@ pub(crate) fn prepare_word(word: &Word) -> String { /// Used for: /// - checking the relevance of notes to save them as input notes /// - validate hashes versus expected output notes after a transaction is executed -pub(crate) fn notes_from_output(output_notes: &OutputNotes) -> impl Iterator { +pub fn notes_from_output(output_notes: &OutputNotes) -> impl Iterator { output_notes .iter() .filter(|n| matches!(n, OutputNote::Full(_))) diff --git a/src/client/transactions/transaction_request.rs b/src/client/transactions/transaction_request.rs index e12dedf6f..1f2b1b057 100644 --- a/src/client/transactions/transaction_request.rs +++ b/src/client/transactions/transaction_request.rs @@ -231,7 +231,7 @@ pub mod known_script_roots { pub const SWAP: &str = "0xebbc82ad1688925175599bee2fb56bde649ebb9986fbce957ebee3eb4be5f140"; } -#[cfg(test)] +#[cfg(all(test, not(feature = "wasm")))] mod tests { use miden_lib::notes::{create_p2id_note, create_p2idr_note, create_swap_note}; use miden_objects::{ diff --git a/src/errors.rs b/src/errors.rs index 371a6a69f..85d698c05 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -1,6 +1,11 @@ use core::fmt; +#[cfg(feature = "wasm")] +use std::any::type_name; +#[cfg(not(feature = "wasm"))] use miden_node_proto::errors::ConversionError; +#[cfg(feature = "wasm")] +use miden_objects::crypto::merkle::{SmtLeafError, SmtProofError}; use miden_objects::{ accounts::AccountId, crypto::merkle::MmrError, notes::NoteId, AccountError, AssetError, AssetVaultError, Digest, NoteError, TransactionScriptError, Word, @@ -9,6 +14,50 @@ use miden_tx::{ utils::{DeserializationError, HexParseError}, DataStoreError, TransactionExecutorError, TransactionProverError, }; +#[cfg(feature = "wasm")] +use thiserror::Error; + +#[cfg(feature = "wasm")] +#[derive(Debug, Clone, PartialEq, Error)] +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, + }, +} + +#[cfg(feature = "wasm")] +impl Eq for ConversionError {} + +#[cfg(feature = "wasm")] +pub trait MissingFieldHelper { + fn missing_field(field_name: &'static str) -> ConversionError; +} + +#[cfg(feature = "wasm")] +impl MissingFieldHelper for T { + fn missing_field(field_name: &'static str) -> ConversionError { + ConversionError::MissingFieldInProtobufRepresentation { + entity: type_name::(), + field_name, + } + } +} // CLIENT ERROR // ================================================================================================ @@ -132,6 +181,7 @@ impl From for ClientError { } } +#[cfg(not(feature = "wasm"))] impl From for ClientError { fn from(err: rusqlite::Error) -> Self { Self::StoreError(StoreError::from(err)) @@ -189,11 +239,14 @@ impl From for StoreError { } } +#[cfg(not(feature = "wasm"))] impl From for StoreError { fn from(value: rusqlite_migration::Error) -> Self { StoreError::DatabaseError(value.to_string()) } } + +#[cfg(not(feature = "wasm"))] impl From for StoreError { fn from(value: rusqlite::Error) -> Self { match value { diff --git a/src/lib.rs b/src/lib.rs index 6c9dbf41c..0a16c7eaf 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,12 +1,13 @@ extern crate alloc; pub mod client; +#[cfg(not(feature = "wasm"))] pub mod config; pub mod errors; pub mod store; -#[cfg(any(test, feature = "test_utils"))] +#[cfg(all(any(test, feature = "test_utils"), not(feature = "wasm")))] pub mod mock; -#[cfg(test)] +#[cfg(all(test, not(feature = "wasm")))] mod tests; diff --git a/src/main.rs b/src/main.rs index 719eb6224..7f0fe24ad 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,8 +1,14 @@ +#[cfg(not(feature = "wasm"))] use clap::Parser; +extern crate alloc; +#[cfg(not(feature = "wasm"))] mod cli; + +#[cfg(not(feature = "wasm"))] use cli::Cli; +#[cfg(not(feature = "wasm"))] #[tokio::main] async fn main() -> Result<(), String> { tracing_subscriber::fmt::init(); @@ -12,3 +18,15 @@ async fn main() -> Result<(), String> { // execute cli action cli.execute().await } + +#[cfg(feature = "wasm")] +pub mod client; + +#[cfg(feature = "wasm")] +pub mod store; + +#[cfg(feature = "wasm")] +pub mod errors; + +#[cfg(feature = "wasm")] +fn main() {} diff --git a/src/mock.rs b/src/mock.rs index 4a0e52082..75d4a2747 100644 --- a/src/mock.rs +++ b/src/mock.rs @@ -1,7 +1,6 @@ use alloc::collections::BTreeMap; use std::{env::temp_dir, rc::Rc}; -use async_trait::async_trait; use miden_lib::{transaction::TransactionKernel, AuthScheme}; use miden_node_proto::generated::{ account::AccountId as ProtoAccountId, @@ -99,7 +98,6 @@ impl MockRpcApi { } } -#[async_trait] impl NodeRpcClient for MockRpcApi { /// Executes the specified sync state request and returns the response. async fn sync_state( diff --git a/src/store/data_store.rs b/src/store/data_store.rs index 2baf1a3ec..4dc1db443 100644 --- a/src/store/data_store.rs +++ b/src/store/data_store.rs @@ -12,6 +12,7 @@ use miden_objects::{ BlockHeader, }; use miden_tx::{DataStore, DataStoreError, TransactionInputs}; +use winter_maybe_async::{maybe_async, maybe_await}; use super::{ChainMmrNodeFilter, InputNoteRecord, NoteFilter, NoteStatus, Store}; use crate::errors::{ClientError, StoreError}; @@ -32,18 +33,18 @@ impl ClientDataStore { } impl DataStore for ClientDataStore { + #[maybe_async] fn get_transaction_inputs( &self, account_id: AccountId, block_num: u32, notes: &[NoteId], ) -> Result { - let input_note_records: BTreeMap = self - .store - .get_input_notes(NoteFilter::List(notes))? - .into_iter() - .map(|note_record| (note_record.id(), note_record)) - .collect(); + let input_note_records: BTreeMap = + maybe_await!(self.store.get_input_notes(NoteFilter::List(notes)))? + .into_iter() + .map(|note_record| (note_record.id(), note_record)) + .collect(); // First validate that all notes were found and can be consumed for note_id in notes { @@ -68,10 +69,11 @@ impl DataStore for ClientDataStore { } // Construct Account - let (account, seed) = self.store.get_account(account_id)?; + let (account, seed) = maybe_await!(self.store.get_account(account_id))?; // Get header data - let (block_header, _had_notes) = self.store.get_block_header_by_num(block_num)?; + let (block_header, _had_notes) = + maybe_await!(self.store.get_block_header_by_num(block_num))?; let mut list_of_notes = vec![]; let mut notes_blocks: Vec = vec![]; @@ -90,16 +92,18 @@ impl DataStore for ClientDataStore { } } - let notes_blocks: Vec = self - .store - .get_block_headers(¬es_blocks)? - .iter() - .map(|(header, _has_notes)| *header) - .collect(); - - let partial_mmr = - build_partial_mmr_with_paths(self.store.as_ref(), block_num, ¬es_blocks)?; - let chain_mmr = ChainMmr::new(partial_mmr, notes_blocks) + let notes_blocks: Vec = + maybe_await!(self.store.get_block_headers(¬es_blocks))? + .iter() + .map(|(header, _has_notes)| *header) + .collect(); + + let partial_mmr = maybe_await!(build_partial_mmr_with_paths( + self.store.as_ref(), + block_num, + ¬es_blocks + )); + let chain_mmr = ChainMmr::new(partial_mmr?, notes_blocks) .map_err(|err| DataStoreError::InternalError(err.to_string()))?; let input_notes = @@ -109,8 +113,9 @@ impl DataStore for ClientDataStore { .map_err(DataStoreError::InvalidTransactionInput) } + #[maybe_async] fn get_account_code(&self, account_id: AccountId) -> Result { - let (account, _seed) = self.store.get_account(account_id)?; + let (account, _seed) = maybe_await!(self.store.get_account(account_id))?; let module_ast = account.code().module().clone(); Ok(module_ast) @@ -122,13 +127,14 @@ impl DataStore for ClientDataStore { /// /// `authenticated_blocks` cannot contain `forest`. For authenticating the last block we have, /// the kernel extends the MMR which is why it's not needed here. +#[maybe_async] fn build_partial_mmr_with_paths( store: &S, forest: u32, authenticated_blocks: &[BlockHeader], ) -> Result { let mut partial_mmr: PartialMmr = { - let current_peaks = store.get_chain_mmr_peaks_by_block_num(forest)?; + let current_peaks = maybe_await!(store.get_chain_mmr_peaks_by_block_num(forest))?; PartialMmr::from_peaks(current_peaks) }; @@ -136,7 +142,7 @@ fn build_partial_mmr_with_paths( let block_nums: Vec = authenticated_blocks.iter().map(|b| b.block_num()).collect(); let authentication_paths = - get_authentication_path_for_blocks(store, &block_nums, partial_mmr.forest())?; + maybe_await!(get_authentication_path_for_blocks(store, &block_nums, partial_mmr.forest()))?; for (header, path) in authenticated_blocks.iter().zip(authentication_paths.iter()) { partial_mmr @@ -151,6 +157,7 @@ fn build_partial_mmr_with_paths( /// constructs the path for each of them. /// /// This method assumes `block_nums` cannot contain `forest`. +#[maybe_async] pub fn get_authentication_path_for_blocks( store: &S, block_nums: &[u32], @@ -174,7 +181,7 @@ pub fn get_authentication_path_for_blocks( let node_indices: Vec = node_indices.into_iter().collect(); let filter = ChainMmrNodeFilter::List(&node_indices); - let mmr_nodes = store.get_chain_mmr_nodes(filter)?; + let mmr_nodes = maybe_await!(store.get_chain_mmr_nodes(filter))?; // Construct authentication paths let mut authentication_paths = vec![]; diff --git a/src/store/mod.rs b/src/store/mod.rs index 45684b5cb..676f25f40 100644 --- a/src/store/mod.rs +++ b/src/store/mod.rs @@ -1,5 +1,6 @@ use alloc::collections::BTreeMap; +#[cfg(not(feature = "wasm"))] use clap::error::Result; use miden_objects::{ accounts::{Account, AccountId, AccountStub, AuthSecretKey}, @@ -7,6 +8,7 @@ use miden_objects::{ notes::{NoteId, NoteTag, Nullifier}, BlockHeader, Digest, Word, }; +use winter_maybe_async::{maybe_async, maybe_await}; use crate::{ client::{ @@ -17,6 +19,7 @@ use crate::{ }; pub mod data_store; +#[cfg(not(feature = "wasm"))] pub mod sqlite_store; mod note_record; @@ -40,6 +43,7 @@ pub trait Store { // -------------------------------------------------------------------------------------------- /// Retrieves stored transactions, filtered by [TransactionFilter]. + #[maybe_async] fn get_transactions( &self, filter: TransactionFilter, @@ -52,6 +56,7 @@ pub trait Store { /// - Applying the resulting [AccountDelta](miden_objects::accounts::AccountDelta) and storing the new [Account] state /// - Storing new notes and payback note details as a result of the transaction execution /// - Inserting the transaction into the store to track + #[maybe_async] fn apply_transaction(&self, tx_result: TransactionResult) -> Result<(), StoreError>; // NOTES @@ -62,6 +67,7 @@ pub trait Store { /// # Errors /// /// Returns a [StoreError::NoteNotFound] if the filter is [NoteFilter::Unique] and there is no Note with the provided ID + #[maybe_async] fn get_input_notes(&self, filter: NoteFilter) -> Result, StoreError>; /// Retrieves the output notes from the store @@ -69,14 +75,15 @@ pub trait Store { /// # Errors /// /// Returns a [StoreError::NoteNotFound] if the filter is [NoteFilter::Unique] and there is no Note with the provided ID + #[maybe_async] fn get_output_notes(&self, filter: NoteFilter) -> Result, StoreError>; /// Returns the nullifiers of all unspent input notes /// /// The default implementation of this method uses [Store::get_input_notes]. + #[maybe_async] fn get_unspent_input_note_nullifiers(&self) -> Result, StoreError> { - let nullifiers = self - .get_input_notes(NoteFilter::Committed)? + let nullifiers = maybe_await!(self.get_input_notes(NoteFilter::Committed))? .iter() .map(|input_note| Ok(Nullifier::from(Digest::try_from(input_note.nullifier())?))) .collect::, _>>(); @@ -85,6 +92,7 @@ pub trait Store { } /// Inserts the provided input note into the database + #[maybe_async] fn insert_input_note(&self, note: &InputNoteRecord) -> Result<(), StoreError>; // CHAIN DATA @@ -97,6 +105,7 @@ pub trait Store { /// /// For each block header an additional boolean value is returned representing whether the block /// contains notes relevant to the client. + #[maybe_async] fn get_block_headers( &self, block_numbers: &[u32], @@ -109,11 +118,12 @@ pub trait Store { /// /// # Errors /// Returns a [StoreError::BlockHeaderNotFound] if the block was not found. + #[maybe_async] fn get_block_header_by_num( &self, block_number: u32, ) -> Result<(BlockHeader, bool), StoreError> { - self.get_block_headers(&[block_number]) + maybe_await!(self.get_block_headers(&[block_number])) .map(|block_headers_list| block_headers_list.first().cloned()) .and_then(|block_header| { block_header.ok_or(StoreError::BlockHeaderNotFound(block_number)) @@ -121,9 +131,11 @@ pub trait Store { } /// Retrieves a list of [BlockHeader] that include relevant notes to the client. + #[maybe_async] fn get_tracked_block_headers(&self) -> Result, StoreError>; /// Retrieves all MMR authentication nodes based on [ChainMmrNodeFilter]. + #[maybe_async] fn get_chain_mmr_nodes( &self, filter: ChainMmrNodeFilter, @@ -132,17 +144,20 @@ pub trait Store { /// Inserts MMR authentication nodes. /// /// In the case where the [InOrderIndex] already exists on the table, the insertion is ignored + #[maybe_async] fn insert_chain_mmr_nodes(&self, nodes: &[(InOrderIndex, Digest)]) -> Result<(), StoreError>; /// Returns peaks information from the blockchain by a specific block number. /// /// If there is no chain MMR info stored for the provided block returns an empty [MmrPeaks] + #[maybe_async] fn get_chain_mmr_peaks_by_block_num(&self, block_num: u32) -> Result; /// Inserts a block header into the store, alongside peaks information at the block's height. /// /// `has_client_notes` describes whether the block has relevant notes to the client; this means /// the client might want to authenticate merkle paths based on this value. + #[maybe_async] fn insert_block_header( &self, block_header: BlockHeader, @@ -154,12 +169,14 @@ pub trait Store { // -------------------------------------------------------------------------------------------- /// Returns the account IDs of all accounts stored in the database + #[maybe_async] fn get_account_ids(&self) -> Result, StoreError>; /// Returns a list of [AccountStub] of all accounts stored in the database along with the seeds /// used to create them. /// /// Said accounts' state is the state after the last performed sync. + #[maybe_async] fn get_account_stubs(&self) -> Result)>, StoreError>; /// Retrieves an [AccountStub] object for the specified [AccountId] along with the seed @@ -170,6 +187,7 @@ pub trait Store { /// /// # Errors /// Returns a `StoreError::AccountDataNotFound` if there is no account for the provided ID + #[maybe_async] fn get_account_stub( &self, account_id: AccountId, @@ -185,6 +203,7 @@ pub trait Store { /// # Errors /// /// Returns a `StoreError::AccountDataNotFound` if there is no account for the provided ID + #[maybe_async] fn get_account(&self, account_id: AccountId) -> Result<(Account, Option), StoreError>; /// Retrieves an account's [AuthSecretKey], utilized to authenticate the account. @@ -192,6 +211,7 @@ pub trait Store { /// # Errors /// /// Returns a `StoreError::AccountDataNotFound` if there is no account for the provided ID + #[maybe_async] fn get_account_auth(&self, account_id: AccountId) -> Result; /// Retrieves an account's [AuthSecretKey] by pub key, utilized to authenticate the account. @@ -203,6 +223,7 @@ pub trait Store { fn get_account_auth_by_pub_key(&self, pub_key: Word) -> Result; /// Inserts an [Account] along with the seed used to create it and its [AuthSecretKey] + #[maybe_async] fn insert_account( &self, account: &Account, @@ -214,20 +235,24 @@ pub trait Store { // -------------------------------------------------------------------------------------------- /// Returns the note tags that the client is interested in. + #[maybe_async] fn get_note_tags(&self) -> Result, StoreError>; /// Adds a note tag to the list of tags that the client is interested in. /// /// If the tag was already being tracked, returns false since no new tags were actually added. Otherwise true. + #[maybe_async] fn add_note_tag(&self, tag: NoteTag) -> Result; /// Removes a note tag from the list of tags that the client is interested in. /// /// If the tag was not present in the store returns false since no tag was actually removed. /// Otherwise returns true. + #[maybe_async] fn remove_note_tag(&self, tag: NoteTag) -> Result; /// Returns the block number of the last state sync block. + #[maybe_async] fn get_sync_height(&self) -> Result; /// Applies the state sync update to the store. An update involves: @@ -238,6 +263,7 @@ pub trait Store { /// - Updating transactions in the store, marking as `committed` the ones provided with /// `committed_transactions` /// - Storing new MMR authentication nodes + #[maybe_async] fn apply_state_sync(&self, state_sync_update: StateSyncUpdate) -> Result<(), StoreError>; } diff --git a/src/store/note_record/mod.rs b/src/store/note_record/mod.rs index b39ae4d85..d6eb920ad 100644 --- a/src/store/note_record/mod.rs +++ b/src/store/note_record/mod.rs @@ -14,6 +14,7 @@ mod output_note_record; pub use input_note_record::InputNoteRecord; pub use output_note_record::OutputNoteRecord; +#[cfg_attr(feature = "wasm", allow(rustdoc::broken_intra_doc_links))] /// This module defines common structs to be used within the [Store](crate::store::Store) for notes /// that are available to be consumed ([InputNoteRecord]) and notes that have been produced as a /// result of executing a transaction ([OutputNoteRecord]). @@ -154,12 +155,12 @@ impl NoteRecordDetails { impl Serializable for NoteRecordDetails { fn write_into(&self, target: &mut W) { let nullifier_bytes = self.nullifier.as_bytes(); - target.write_usize(nullifier_bytes.len()); + target.write_u64(nullifier_bytes.len() as u64); target.write_bytes(nullifier_bytes); self.script().write_into(target); - target.write_usize(self.inputs.len()); + target.write_u64(self.inputs.len() as u64); target.write_many(self.inputs()); self.serial_num().write_into(target); @@ -168,14 +169,14 @@ impl Serializable for NoteRecordDetails { impl Deserializable for NoteRecordDetails { fn read_from(source: &mut R) -> Result { - let nullifier_len = usize::read_from(source)?; + let nullifier_len = u64::read_from(source)? as usize; let nullifier_bytes = source.read_vec(nullifier_len)?; let nullifier = String::from_utf8(nullifier_bytes).expect("Nullifier String bytes should be readable."); let script = NoteScript::read_from(source)?; - let inputs_len = source.read_usize()?; + let inputs_len = source.read_u64()? as usize; let inputs = source.read_many::(inputs_len)?; let serial_num = Word::read_from(source)?; diff --git a/tests/integration/main.rs b/tests/integration/main.rs index 794364a04..eeca7725e 100644 --- a/tests/integration/main.rs +++ b/tests/integration/main.rs @@ -1,3 +1,5 @@ +#![cfg(not(feature = "wasm"))] + use miden_client::{ client::{ accounts::AccountTemplate,