Skip to content

Commit

Permalink
Adding Changes to Enable WASM-32 Support (#378)
Browse files Browse the repository at this point in the history
* 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>
  • Loading branch information
dagarcia7 and Dennis Garcia authored Jun 14, 2024
1 parent 058b217 commit d960b02
Show file tree
Hide file tree
Showing 19 changed files with 426 additions and 157 deletions.
41 changes: 25 additions & 16 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ edition = "2021"
rust-version = "1.78"
default-run = "miden"

[lib]
crate-type = ["lib"]

[[bin]]
name = "miden"
path = "src/main.rs"
Expand All @@ -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
Expand Down
51 changes: 38 additions & 13 deletions src/client/accounts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand All @@ -31,20 +32,26 @@ impl<N: NodeRpcClient, R: FeltRng, S: Store, A: TransactionAuthenticator> 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)
Expand All @@ -60,6 +67,7 @@ impl<N: NodeRpcClient, R: FeltRng, S: Store, A: TransactionAuthenticator> 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()
{
Expand All @@ -74,10 +82,15 @@ impl<N: NodeRpcClient, R: FeltRng, S: Store, A: TransactionAuthenticator> 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,
Expand Down Expand Up @@ -107,10 +120,15 @@ impl<N: NodeRpcClient, R: FeltRng, S: Store, A: TransactionAuthenticator> 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,
Expand All @@ -136,7 +154,11 @@ impl<N: NodeRpcClient, R: FeltRng, S: Store, A: TransactionAuthenticator> 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))
}

Expand All @@ -146,6 +168,7 @@ impl<N: NodeRpcClient, R: FeltRng, S: Store, A: TransactionAuthenticator> 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,
Expand All @@ -156,33 +179,34 @@ impl<N: NodeRpcClient, R: FeltRng, S: Store, A: TransactionAuthenticator> 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)
}

// ACCOUNT DATA RETRIEVAL
// --------------------------------------------------------------------------------------------

/// Returns summary info about the accounts managed by this client.
#[maybe_async]
pub fn get_account_stubs(&self) -> Result<Vec<(AccountStub, Option<Word>)>, 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<Word>), 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<Word>), 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.
Expand All @@ -191,15 +215,16 @@ impl<N: NodeRpcClient, R: FeltRng, S: Store, A: TransactionAuthenticator> 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<AuthSecretKey, ClientError> {
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},
Expand Down
102 changes: 100 additions & 2 deletions src/client/mod.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -89,7 +105,7 @@ impl<N: NodeRpcClient, R: FeltRng, S: Store, A: TransactionAuthenticator> 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
}
Expand All @@ -101,8 +117,90 @@ impl<N: NodeRpcClient, R: FeltRng, S: Store, A: TransactionAuthenticator> 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<N, R, S, A>,
note_id_prefix: &str,
) -> Result<InputNoteRecord, IdPrefixFetchError> {
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::<Vec<_>>();

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::<Vec<_>>();
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<NoteTag, NoteError> {
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),
}
}
Loading

0 comments on commit d960b02

Please sign in to comment.