diff --git a/CHANGELOG.md b/CHANGELOG.md index 56d27763c..f1ab0498e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,16 +5,23 @@ ### Features * Added support to import public accounts to `Client` (#733). +* [BREAKING] Merged `TonicRpcClient` with `WebTonicRpcClient` and added missing endpoints (#744). ### Changes +* Added wallet generation from seed & import from seed on web SDK (#710) * Add check for empty pay to id notes (#714). * [BREAKING] Refactored authentication out of the `Client` and added new separate authenticators (#718). +* Added import/export for web client db (#740). * Re-exported RemoteTransactionProver in `rust-client` (#752). * Moved error handling to the `TransactionRequestBuilder::build()` (#750). * [BREAKING] Added starting block number parameter to `CheckNullifiersByPrefix` and removed nullifiers from `SyncState` (#758). * Added `ClientBuilder` for client initialization (#741). +### Fixes + +* [BREAKING] Changed Snake Case Variables to Camel Case in JS/TS Files (#767). + ## 0.7.0 (2025-01-28) ### Features diff --git a/Cargo.lock b/Cargo.lock index d83d9377e..d483ef176 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -380,16 +380,16 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "chrono" -version = "0.4.39" +version = "0.4.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e36cc9d416881d2e24f9a963be5fb1cd90966419ac844274161d10488b3e825" +checksum = "1a7964611d71df112cb1730f2ee67324fcf4d0fc6606acbbe9bfe06df124637c" dependencies = [ "android-tzdata", "iana-time-zone", "js-sys", "num-traits", "wasm-bindgen", - "windows-targets 0.52.6", + "windows-link", ] [[package]] @@ -1422,7 +1422,6 @@ dependencies = [ "miden-objects", "miden-tx", "rand", - "serde", "serde-wasm-bindgen", "wasm-bindgen", "wasm-bindgen-futures", @@ -1481,7 +1480,7 @@ dependencies = [ [[package]] name = "miden-lib" version = "0.8.0" -source = "git+https://github.com/0xPolygonMiden/miden-base?branch=next#f2d50bfa4a83841875570d1301adccbe164ea111" +source = "git+https://github.com/0xPolygonMiden/miden-base?branch=next#6d82ff2a4a1f4dfcdb1a20e7baad92d0e2718397" dependencies = [ "miden-assembly", "miden-objects", @@ -1536,7 +1535,7 @@ dependencies = [ [[package]] name = "miden-objects" version = "0.8.0" -source = "git+https://github.com/0xPolygonMiden/miden-base?branch=next#f2d50bfa4a83841875570d1301adccbe164ea111" +source = "git+https://github.com/0xPolygonMiden/miden-base?branch=next#6d82ff2a4a1f4dfcdb1a20e7baad92d0e2718397" dependencies = [ "getrandom 0.2.15", "miden-assembly", @@ -1582,7 +1581,7 @@ dependencies = [ [[package]] name = "miden-proving-service-client" version = "0.8.0" -source = "git+https://github.com/0xPolygonMiden/miden-base?branch=next#f2d50bfa4a83841875570d1301adccbe164ea111" +source = "git+https://github.com/0xPolygonMiden/miden-base?branch=next#6d82ff2a4a1f4dfcdb1a20e7baad92d0e2718397" dependencies = [ "async-trait", "miden-objects", @@ -1601,7 +1600,7 @@ dependencies = [ [[package]] name = "miden-rpc-proto" version = "0.8.0" -source = "git+https://github.com/0xPolygonMiden/miden-node?branch=next#bea3138187a6fe7d0810c2ecd7cf5e06c29f7ab1" +source = "git+https://github.com/0xPolygonMiden/miden-node?branch=next#540e097521a5f31623d818474f00bc360fde81a9" [[package]] name = "miden-stdlib" @@ -1615,7 +1614,7 @@ dependencies = [ [[package]] name = "miden-tx" version = "0.8.0" -source = "git+https://github.com/0xPolygonMiden/miden-base?branch=next#f2d50bfa4a83841875570d1301adccbe164ea111" +source = "git+https://github.com/0xPolygonMiden/miden-base?branch=next#6d82ff2a4a1f4dfcdb1a20e7baad92d0e2718397" dependencies = [ "async-trait", "miden-lib", @@ -3170,9 +3169,9 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "uuid" -version = "1.14.0" +version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93d59ca99a559661b96bf898d8fce28ed87935fd2bea9f05983c1464dd6c71b1" +checksum = "bd8dcafa1ca14750d8d7a05aa05988c17aab20886e1f3ae33a40223c58d92ef7" dependencies = [ "getrandom 0.3.1", "serde", @@ -3429,6 +3428,12 @@ dependencies = [ "syn", ] +[[package]] +name = "windows-link" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dccfd733ce2b1753b03b6d3c65edf020262ea35e20ccdf3e288043e6dd620e3" + [[package]] name = "windows-result" version = "0.2.0" diff --git a/Makefile b/Makefile index 42103df3d..573431fa2 100644 --- a/Makefile +++ b/Makefile @@ -46,11 +46,11 @@ fix-wasm: ## Run Fix for the miden-client-web package .PHONY: format format: ## Run format using nightly toolchain - cargo +nightly fmt --all && yarn prettier . --write + cargo +nightly fmt --all && yarn prettier . --write && yarn eslint . --fix .PHONY: format-check format-check: ## Run format using nightly toolchain but only in check mode - cargo +nightly fmt --all --check && yarn prettier . --check + cargo +nightly fmt --all --check && yarn prettier . --check && yarn eslint . .PHONY: lint lint: format fix clippy fix-wasm clippy-wasm ## Run all linting tasks at once (clippy, fixing, formatting) diff --git a/bin/miden-cli/src/tests.rs b/bin/miden-cli/src/tests.rs index edd22915d..12ab0dfb4 100644 --- a/bin/miden-cli/src/tests.rs +++ b/bin/miden-cli/src/tests.rs @@ -10,24 +10,21 @@ use assert_cmd::Command; use config::RpcConfig; use miden_client::{ self, - account::{ - component::{BasicWallet, RpoFalcon512}, - AccountBuilder, AccountId, AccountStorageMode, AccountType, - }, + account::{AccountId, AccountStorageMode}, authenticator::{keystore::FilesystemKeyStore, ClientAuthenticator}, - crypto::{FeltRng, RpoRandomCoin, SecretKey}, + crypto::{FeltRng, RpoRandomCoin}, note::{ Note, NoteAssets, NoteExecutionHint, NoteExecutionMode, NoteFile, NoteInputs, NoteMetadata, NoteRecipient, NoteTag, NoteType, }, rpc::{Endpoint, TonicRpcClient}, - store::{sqlite_store::SqliteStore, NoteFilter}, + store::sqlite_store::SqliteStore, testing::account_id::ACCOUNT_ID_OFF_CHAIN_SENDER, transaction::{OutputNote, TransactionRequestBuilder}, utils::Serializable, Client, Felt, }; -use miden_client_tests::common::{execute_tx_and_sync, insert_new_wallet}; +use miden_client_tests::common::{execute_tx_and_sync, insert_new_wallet, ACCOUNT_ID_REGULAR}; use predicates::str::contains; use rand::Rng; use uuid::Uuid; @@ -55,13 +52,7 @@ mod config; #[test] fn test_init_without_params() { - let mut temp_dir = temp_dir(); - temp_dir.push(format!("{}", uuid::Uuid::new_v4())); - std::fs::create_dir(temp_dir.clone()).unwrap(); - - let mut init_cmd = Command::cargo_bin("miden").unwrap(); - init_cmd.args(["init", "--network", "localhost"]); - init_cmd.current_dir(&temp_dir).assert().success(); + let temp_dir = init_cli("localhost").1; sync_cli(&temp_dir); @@ -73,14 +64,7 @@ fn test_init_without_params() { #[test] fn test_init_with_params() { - let store_path = create_test_store_path(); - let mut temp_dir = temp_dir(); - temp_dir.push(format!("{}", uuid::Uuid::new_v4())); - std::fs::create_dir(temp_dir.clone()).unwrap(); - - let mut init_cmd = Command::cargo_bin("miden").unwrap(); - init_cmd.args(["init", "--network", "localhost", "--store-path", store_path.to_str().unwrap()]); - init_cmd.current_dir(&temp_dir).assert().success(); + let (store_path, temp_dir) = init_cli("localhost"); // Assert the config file contains the specified contents let mut config_path = temp_dir.clone(); @@ -106,69 +90,22 @@ fn test_init_with_params() { /// This test tries to run a mint TX using the CLI for an account that isn't tracked. #[tokio::test] async fn test_mint_with_untracked_account() { - let store_path = create_test_store_path(); - let mut temp_dir = temp_dir(); - temp_dir.push(format!("{}", uuid::Uuid::new_v4())); - std::fs::create_dir(temp_dir.clone()).unwrap(); - - let target_account_id = { - let other_store_path = create_test_store_path(); - let (mut client, _) = create_test_client_with_store_path(&other_store_path).await; - let key_pair = SecretKey::with_rng(client.rng()); - - let mut init_seed = [0u8; 32]; - client.rng().fill_bytes(&mut init_seed); - - let anchor_block = client.get_latest_epoch_block().await.unwrap(); - - let (new_account, seed) = AccountBuilder::new(init_seed) - .anchor((&anchor_block).try_into().unwrap()) - .account_type(AccountType::RegularAccountImmutableCode) - .storage_mode(AccountStorageMode::Private) - .with_component(RpoFalcon512::new(key_pair.public_key())) - .with_component(BasicWallet) - .build() - .unwrap(); - - client.add_account(&new_account, Some(seed), false).await.unwrap(); - - new_account.id().to_hex() - }; - - // On CLI create the faucet and mint - let mut init_cmd = Command::cargo_bin("miden").unwrap(); - init_cmd.args(["init", "--network", "localhost", "--store-path", store_path.to_str().unwrap()]); - init_cmd.current_dir(&temp_dir).assert().success(); + let temp_dir = init_cli("localhost").1; // Create faucet account - let mut create_faucet_cmd = Command::cargo_bin("miden").unwrap(); - create_faucet_cmd.args([ - "new-faucet", - "-s", - "private", - "-t", - "BTC", - "-d", - "8", - "-m", - "1000000000000", - ]); - create_faucet_cmd.current_dir(&temp_dir).assert().success(); - - let fungible_faucet_account_id = { - let client = create_test_client_with_store_path(&store_path).await.0; - let accounts = client.get_account_headers().await.unwrap(); - - accounts.first().unwrap().0.id().to_hex() - }; + let fungible_faucet_account_id = new_faucet_cli(&temp_dir, AccountStorageMode::Private); sync_cli(&temp_dir); // Let's try and mint - mint_cli(&temp_dir, &target_account_id, &fungible_faucet_account_id); + mint_cli( + &temp_dir, + &AccountId::try_from(ACCOUNT_ID_REGULAR).unwrap().to_hex(), + &fungible_faucet_account_id, + ); // Sleep for a while to ensure the note is committed on the node - sync_until_no_notes(&store_path, &temp_dir, NoteFilter::Expected).await; + sync_until_committed_note(&temp_dir); } // IMPORT TESTS @@ -186,10 +123,7 @@ const GENESIS_ACCOUNTS_FILENAMES: [&str; 1] = ["faucet.mac"]; #[tokio::test] #[ignore = "import genesis test gets ignored by default so integration tests can be ran with dockerized and remote nodes where we might not have the genesis data"] async fn test_import_genesis_accounts_can_be_used_for_transactions() { - let store_path = create_test_store_path(); - let mut temp_dir = temp_dir(); - temp_dir.push(format!("{}", uuid::Uuid::new_v4())); - std::fs::create_dir(temp_dir.clone()).unwrap(); + let (store_path, temp_dir) = init_cli("localhost"); for genesis_account_filename in GENESIS_ACCOUNTS_FILENAMES { let mut new_file_path = temp_dir.clone(); @@ -203,10 +137,6 @@ async fn test_import_genesis_accounts_can_be_used_for_transactions() { std::fs::copy(source_path, new_file_path).unwrap(); } - let mut init_cmd = Command::cargo_bin("miden").unwrap(); - init_cmd.args(["init", "--network", "localhost", "--store-path", store_path.to_str().unwrap()]); - init_cmd.current_dir(&temp_dir).assert().success(); - // Import genesis accounts let mut args = vec!["import"]; for filename in GENESIS_ACCOUNTS_FILENAMES { @@ -219,7 +149,7 @@ async fn test_import_genesis_accounts_can_be_used_for_transactions() { sync_cli(&temp_dir); let fungible_faucet_account_id = { - let (client, _) = create_test_client_with_store_path(&store_path).await; + let (client, _) = create_rust_client_with_store_path(&store_path).await; let accounts = client.get_account_headers().await.unwrap(); let account_ids = accounts.iter().map(|(acc, _seed)| acc.id()).collect::>(); @@ -236,11 +166,6 @@ async fn test_import_genesis_accounts_can_be_used_for_transactions() { show_cmd.args(&args); show_cmd.current_dir(&temp_dir).assert().success(); - // Create wallet account - let mut create_wallet_cmd = Command::cargo_bin("miden").unwrap(); - create_wallet_cmd.args(["new-wallet", "-s", "private"]); - create_wallet_cmd.current_dir(&temp_dir).assert().success(); - // Let's try and mint mint_cli( &temp_dir, @@ -249,7 +174,7 @@ async fn test_import_genesis_accounts_can_be_used_for_transactions() { ); // Wait until the note is committed on the node - sync_until_no_notes(&store_path, &temp_dir, NoteFilter::Expected).await; + sync_until_committed_note(&temp_dir); } // This tests that it's possible to export and import notes into other CLIs. To do so it: @@ -262,84 +187,20 @@ async fn test_import_genesis_accounts_can_be_used_for_transactions() { async fn test_cli_export_import_note() { const NOTE_FILENAME: &str = "test_note.mno"; - let store_path_1 = create_test_store_path(); - let mut temp_dir_1 = temp_dir(); - temp_dir_1.push(format!("{}", uuid::Uuid::new_v4())); - std::fs::create_dir(temp_dir_1.clone()).unwrap(); - - let store_path_2 = create_test_store_path(); - let mut temp_dir_2 = temp_dir(); - temp_dir_2.push(format!("{}", uuid::Uuid::new_v4())); - std::fs::create_dir(temp_dir_2.clone()).unwrap(); - - // Init and create basic wallet on second client - let mut init_cmd = Command::cargo_bin("miden").unwrap(); - init_cmd.args([ - "init", - "--network", - "localhost", - "--store-path", - store_path_2.to_str().unwrap(), - ]); - init_cmd.current_dir(&temp_dir_2).assert().success(); + let temp_dir_1 = init_cli("localhost").1; + let temp_dir_2 = init_cli("localhost").1; // Create wallet account - let mut create_wallet_cmd = Command::cargo_bin("miden").unwrap(); - create_wallet_cmd.args(["new-wallet", "-s", "private"]); - create_wallet_cmd.current_dir(&temp_dir_2).assert().success(); - - let first_basic_account_id = { - let client = create_test_client_with_store_path(&store_path_2).await.0; - let accounts = client.get_account_headers().await.unwrap(); - - accounts.first().unwrap().0.id().to_hex() - }; - - // On first client init, create a faucet and mint - let mut init_cmd = Command::cargo_bin("miden").unwrap(); - init_cmd.args([ - "init", - "--network", - "localhost", - "--store-path", - store_path_1.to_str().unwrap(), - ]); - init_cmd.current_dir(&temp_dir_1).assert().success(); + let first_basic_account_id = new_wallet_cli(&temp_dir_2, AccountStorageMode::Private); // Create faucet account - let mut create_faucet_cmd = Command::cargo_bin("miden").unwrap(); - create_faucet_cmd.args([ - "new-faucet", - "-s", - "private", - "-t", - "BTC", - "-d", - "8", - "-m", - "100000000000", - ]); - create_faucet_cmd.current_dir(&temp_dir_1).assert().success(); - - let fungible_faucet_account_id = { - let client = create_test_client_with_store_path(&store_path_1).await.0; - let accounts = client.get_account_headers().await.unwrap(); - - accounts.first().unwrap().0.id().to_hex() - }; + let fungible_faucet_account_id = new_faucet_cli(&temp_dir_1, AccountStorageMode::Private); sync_cli(&temp_dir_1); // Let's try and mint - mint_cli(&temp_dir_1, &first_basic_account_id, &fungible_faucet_account_id); - - // Create a Client to get notes - let note_to_export_id = { - let client = create_test_client_with_store_path(&store_path_1).await.0; - let output_notes = client.get_output_notes(NoteFilter::All).await.unwrap(); - - output_notes.first().unwrap().id().to_hex() - }; + let note_to_export_id = + mint_cli(&temp_dir_1, &first_basic_account_id, &fungible_faucet_account_id); // Export without type fails let mut export_cmd = Command::cargo_bin("miden").unwrap(); @@ -371,7 +232,7 @@ async fn test_cli_export_import_note() { import_cmd.current_dir(&temp_dir_2).assert().success(); // Wait until the note is committed on the node - sync_until_no_notes(&store_path_2, &temp_dir_2, NoteFilter::Expected).await; + sync_until_committed_note(&temp_dir_2); show_note_cli(&temp_dir_2, ¬e_to_export_id, false); // Consume the note @@ -392,74 +253,14 @@ async fn test_cli_export_import_account() { const FAUCET_FILENAME: &str = "test_faucet.mac"; const WALLET_FILENAME: &str = "test_wallet.wal"; - let store_path_1 = create_test_store_path(); - let mut temp_dir_1 = temp_dir(); - temp_dir_1.push(format!("{}", uuid::Uuid::new_v4())); - std::fs::create_dir(temp_dir_1.clone()).unwrap(); - - let store_path_2 = create_test_store_path(); - let mut temp_dir_2 = temp_dir(); - temp_dir_2.push(format!("{}", uuid::Uuid::new_v4())); - std::fs::create_dir(temp_dir_2.clone()).unwrap(); - - // Init the first client - let mut init_cmd = Command::cargo_bin("miden").unwrap(); - init_cmd.args([ - "init", - "--network", - "localhost", - "--store-path", - store_path_1.to_str().unwrap(), - ]); - init_cmd.current_dir(&temp_dir_1).assert().success(); - - // Init the second client - let mut init_cmd = Command::cargo_bin("miden").unwrap(); - init_cmd.args([ - "init", - "--network", - "localhost", - "--store-path", - store_path_2.to_str().unwrap(), - ]); - init_cmd.current_dir(&temp_dir_2).assert().success(); + let temp_dir_1 = init_cli("localhost").1; + let (store_path_2, temp_dir_2) = init_cli("localhost"); // Create faucet account - let mut create_wallet_cmd = Command::cargo_bin("miden").unwrap(); - create_wallet_cmd.args([ - "new-faucet", - "-s", - "private", - "-t", - "BTC", - "-d", - "8", - "-m", - "100000000000", - ]); - create_wallet_cmd.current_dir(&temp_dir_1).assert().success(); + let faucet_id = new_faucet_cli(&temp_dir_1, AccountStorageMode::Private); // Create wallet account - let mut create_wallet_cmd = Command::cargo_bin("miden").unwrap(); - create_wallet_cmd.args(["new-wallet", "-s", "private"]); - create_wallet_cmd.current_dir(&temp_dir_1).assert().success(); - - let (faucet_id, wallet_id) = { - let client = create_test_client_with_store_path(&store_path_1).await.0; - let accounts = client.get_account_headers().await.unwrap(); - - let faucet_id = - accounts.iter().find(|(acc, _)| acc.id().is_faucet()).unwrap().0.id().to_hex(); - let wallet_id = accounts - .iter() - .find(|(acc, _)| acc.id().is_regular_account()) - .unwrap() - .0 - .id() - .to_hex(); - - (faucet_id, wallet_id) - }; + let wallet_id = new_wallet_cli(&temp_dir_1, AccountStorageMode::Private); // Export the accounts let mut export_cmd = Command::cargo_bin("miden").unwrap(); @@ -487,22 +288,16 @@ async fn test_cli_export_import_account() { import_cmd.current_dir(&temp_dir_2).assert().success(); // Ensure the account was imported - let client_2 = create_test_client_with_store_path(&store_path_2).await.0; + let client_2 = create_rust_client_with_store_path(&store_path_2).await.0; assert!(client_2.get_account(AccountId::from_hex(&faucet_id).unwrap()).await.is_ok()); assert!(client_2.get_account(AccountId::from_hex(&wallet_id).unwrap()).await.is_ok()); sync_cli(&temp_dir_2); - mint_cli(&temp_dir_2, &wallet_id, &faucet_id); + let note_id = mint_cli(&temp_dir_2, &wallet_id, &faucet_id); // Wait until the note is committed on the node - sync_until_no_notes(&store_path_2, &temp_dir_2, NoteFilter::Expected).await; - - // Get consumable note - let client = create_test_client_with_store_path(&store_path_2).await.0; - let output_notes = client.get_output_notes(NoteFilter::All).await.unwrap(); - - let note_id = output_notes.first().unwrap().id().to_hex(); + sync_until_committed_note(&temp_dir_2); // Consume the note consume_note_cli(&temp_dir_2, &wallet_id, &[¬e_id]); @@ -510,87 +305,40 @@ async fn test_cli_export_import_account() { #[test] fn test_cli_empty_commands() { - let store_path = create_test_store_path(); - let mut temp_dir = temp_dir(); - temp_dir.push(format!("{}", uuid::Uuid::new_v4())); - std::fs::create_dir(temp_dir.clone()).unwrap(); - - let mut init_cmd = Command::cargo_bin("miden").unwrap(); - - init_cmd.args(["init", "--network", "localhost", "--store-path", store_path.to_str().unwrap()]); - init_cmd.current_dir(&temp_dir).assert().success(); + let temp_dir = init_cli("localhost").1; let mut create_faucet_cmd = Command::cargo_bin("miden").unwrap(); - assert_command_fails_but_does_not_panic(create_faucet_cmd.args(["new-faucet"])); + assert_command_fails_but_does_not_panic( + create_faucet_cmd.args(["new-faucet"]).current_dir(&temp_dir), + ); let mut import_cmd = Command::cargo_bin("miden").unwrap(); - assert_command_fails_but_does_not_panic(import_cmd.args(["export"])); + assert_command_fails_but_does_not_panic(import_cmd.args(["export"]).current_dir(&temp_dir)); let mut mint_cmd = Command::cargo_bin("miden").unwrap(); - assert_command_fails_but_does_not_panic(mint_cmd.args(["mint"])); + assert_command_fails_but_does_not_panic(mint_cmd.args(["mint"]).current_dir(&temp_dir)); let mut send_cmd = Command::cargo_bin("miden").unwrap(); - assert_command_fails_but_does_not_panic(send_cmd.args(["send"])); + assert_command_fails_but_does_not_panic(send_cmd.args(["send"]).current_dir(&temp_dir)); let mut swam_cmd = Command::cargo_bin("miden").unwrap(); - assert_command_fails_but_does_not_panic(swam_cmd.args(["swap"])); + assert_command_fails_but_does_not_panic(swam_cmd.args(["swap"]).current_dir(&temp_dir)); } #[tokio::test] async fn test_consume_unauthenticated_note() { - let store_path = create_test_store_path(); - let mut temp_dir = temp_dir(); - temp_dir.push(format!("{}", uuid::Uuid::new_v4())); - std::fs::create_dir(temp_dir.clone()).unwrap(); - - let mut init_cmd = Command::cargo_bin("miden").unwrap(); - init_cmd.args(["init", "--network", "localhost", "--store-path", store_path.to_str().unwrap()]); - init_cmd.current_dir(&temp_dir).assert().success(); + let temp_dir = init_cli("localhost").1; // Create wallet account - let mut create_wallet_cmd = Command::cargo_bin("miden").unwrap(); - create_wallet_cmd.args(["new-wallet", "-s", "public"]); - create_wallet_cmd.current_dir(&temp_dir).assert().success(); - - let wallet_account_id = { - let client = create_test_client_with_store_path(&store_path).await.0; - let accounts = client.get_account_headers().await.unwrap(); - - accounts[0].0.id().to_hex() - }; + let wallet_account_id = new_wallet_cli(&temp_dir, AccountStorageMode::Public); // Create faucet account - let mut create_faucet_cmd = Command::cargo_bin("miden").unwrap(); - create_faucet_cmd.args([ - "new-faucet", - "-s", - "public", - "-t", - "BTC", - "-d", - "8", - "-m", - "1000000000000", - ]); - create_faucet_cmd.current_dir(&temp_dir).assert().success(); - - let fungible_faucet_account_id = { - let client = create_test_client_with_store_path(&store_path).await.0; - let accounts = client.get_account_headers().await.unwrap(); - - accounts[1].0.id().to_hex() - }; + let fungible_faucet_account_id = new_faucet_cli(&temp_dir, AccountStorageMode::Public); sync_cli(&temp_dir); // Mint - mint_cli(&temp_dir, &wallet_account_id, &fungible_faucet_account_id); - - // Get consumable note to consume without authentication - let client = create_test_client_with_store_path(&store_path).await.0; - let output_notes = client.get_output_notes(NoteFilter::All).await.unwrap(); - - let note_id = output_notes.first().unwrap().id().to_hex(); + let note_id = mint_cli(&temp_dir, &wallet_account_id, &fungible_faucet_account_id); // Consume the note, internally this checks that the note was consumed correctly consume_note_cli(&temp_dir, &wallet_account_id, &[¬e_id]); @@ -601,14 +349,7 @@ async fn test_consume_unauthenticated_note() { #[tokio::test] async fn test_init_with_devnet() { - let store_path = create_test_store_path(); - let mut temp_dir = temp_dir(); - temp_dir.push(format!("{}", uuid::Uuid::new_v4())); - std::fs::create_dir(temp_dir.clone()).unwrap(); - - let mut init_cmd = Command::cargo_bin("miden").unwrap(); - init_cmd.args(["init", "--network", "devnet", "--store-path", store_path.to_str().unwrap()]); - init_cmd.current_dir(&temp_dir).assert().success(); + let temp_dir = init_cli("devnet").1; // Check in the config file that the network is devnet let mut config_path = temp_dir.clone(); @@ -622,14 +363,7 @@ async fn test_init_with_devnet() { #[tokio::test] async fn test_init_with_testnet() { - let store_path = create_test_store_path(); - let mut temp_dir = temp_dir(); - temp_dir.push(format!("{}", uuid::Uuid::new_v4())); - std::fs::create_dir(temp_dir.clone()).unwrap(); - - let mut init_cmd = Command::cargo_bin("miden").unwrap(); - init_cmd.args(["init", "--network", "testnet", "--store-path", store_path.to_str().unwrap()]); - init_cmd.current_dir(&temp_dir).assert().success(); + let temp_dir = init_cli("testnet").1; // Check in the config file that the network is testnet let mut config_path = temp_dir.clone(); @@ -641,18 +375,136 @@ async fn test_init_with_testnet() { assert!(config_file_str.contains(&Endpoint::testnet().to_string())); } +#[tokio::test] +async fn debug_mode_outputs_logs() { + // This test tries to execute a transaction with debug mode enabled and checks that the stack + // state is printed. We need to use the CLI for this because the debug logs are always printed + // to stdout and we can't capture them in a [`Client`] only test. + // We use the [`Client`] to create a custom note that will print the stack state and consume it + // using the CLI to check the stdout. + + const NOTE_FILENAME: &str = "test_note.mno"; + env::set_var("MIDEN_DEBUG", "true"); + + // Create a Client and a custom note + let store_path = create_test_store_path(); + let (mut client, authenticator) = create_rust_client_with_store_path(&store_path).await; + let (account, ..) = insert_new_wallet(&mut client, AccountStorageMode::Private, &authenticator) + .await + .unwrap(); + + // Create the custom note with a script that will print the stack state + let note_script = " + begin + debug.stack + assert_eq + end + "; + let note_script = client.compile_note_script(note_script).unwrap(); + let inputs = NoteInputs::new(vec![]).unwrap(); + let serial_num = client.rng().draw_word(); + let note_metadata = NoteMetadata::new( + account.id(), + NoteType::Private, + NoteTag::from_account_id(account.id(), NoteExecutionMode::Local).unwrap(), + NoteExecutionHint::None, + Felt::default(), + ) + .unwrap(); + let note_assets = NoteAssets::new(vec![]).unwrap(); + let note_recipient = NoteRecipient::new(serial_num, note_script, inputs); + let note = Note::new(note_assets, note_metadata, note_recipient); + + // Send transaction and wait for it to be committed + let transaction_request = TransactionRequestBuilder::new() + .with_own_output_notes(vec![OutputNote::Full(note.clone())]) + .build() + .unwrap(); + execute_tx_and_sync(&mut client, account.id(), transaction_request).await; + + // Export the note + let note_file: NoteFile = NoteFile::NoteDetails { + details: note.clone().into(), + after_block_num: 0.into(), + tag: Some(note.metadata().tag()), + }; + + // Import the note into the CLI + let temp_dir = init_cli_with_store_path("localhost", &store_path); + let note_path = temp_dir.join(NOTE_FILENAME); + let mut file = File::create(note_path.clone()).unwrap(); + file.write_all(¬e_file.to_bytes()).unwrap(); + + let mut import_cmd = Command::cargo_bin("miden").unwrap(); + import_cmd.args(["import", note_path.to_str().unwrap()]); + import_cmd.current_dir(&temp_dir).assert().success(); + + sync_cli(&temp_dir); + + // Create wallet account + let wallet_account_id = new_wallet_cli(&temp_dir, AccountStorageMode::Private); + + // Consume the note and check the output + let mut consume_note_cmd = Command::cargo_bin("miden").unwrap(); + let note_id = note.id().to_hex(); + let mut cli_args = vec!["consume-notes", "--account", &wallet_account_id[0..8], "--force"]; + cli_args.extend_from_slice(vec![note_id.as_str()].as_slice()); + consume_note_cmd.args(&cli_args); + consume_note_cmd + .current_dir(&temp_dir) + .assert() + .success() + .stdout(contains("Stack state")); +} + // HELPERS // ================================================================================================ +/// Initializes a CLI with the given network and returns the store path and the temp directory +/// where the CLI is running. +fn init_cli(network: &str) -> (PathBuf, PathBuf) { + let store_path = create_test_store_path(); + let temp_dir = init_cli_with_store_path(network, &store_path); + + (store_path, temp_dir) +} + +/// Initializes a CLI with the given network and store path and returns the temp directory where +/// the CLI is running. +fn init_cli_with_store_path(network: &str, store_path: &Path) -> PathBuf { + let mut temp_dir = temp_dir(); + temp_dir.push(format!("{}", uuid::Uuid::new_v4())); + std::fs::create_dir(temp_dir.clone()).unwrap(); + + // Init and create basic wallet on second client + let mut init_cmd = Command::cargo_bin("miden").unwrap(); + init_cmd.args(["init", "--network", network, "--store-path", store_path.to_str().unwrap()]); + init_cmd.current_dir(&temp_dir).assert().success(); + + temp_dir +} + // Syncs CLI on directory. It'll try syncing until the command executes successfully. If it never // executes successfully, eventually the test will time out (provided the nextest config has a -// timeout set). -fn sync_cli(cli_path: &Path) { +// timeout set). It returns the number of updated notes after the sync. +fn sync_cli(cli_path: &Path) -> u64 { loop { let mut sync_cmd = Command::cargo_bin("miden").unwrap(); sync_cmd.args(["sync"]); - if sync_cmd.current_dir(cli_path).assert().try_success().is_ok() { - break; + + let output = sync_cmd.current_dir(cli_path).output().unwrap(); + + if output.status.success() { + let updated_notes = String::from_utf8(output.stdout) + .unwrap() + .split_whitespace() + .skip_while(|&word| word != "notes:") + .find(|word| word.parse::().is_ok()) + .unwrap() + .parse() + .unwrap(); + + return updated_notes; } std::thread::sleep(std::time::Duration::from_secs(3)); } @@ -660,7 +512,7 @@ fn sync_cli(cli_path: &Path) { /// Mints 100 units of the corresponding faucet using the cli and checks that the command runs /// successfully given account using the CLI given by `cli_path`. -fn mint_cli(cli_path: &Path, target_account_id: &str, faucet_id: &str) { +fn mint_cli(cli_path: &Path, target_account_id: &str, faucet_id: &str) -> String { let mut mint_cmd = Command::cargo_bin("miden").unwrap(); mint_cmd.args([ "mint", @@ -672,7 +524,17 @@ fn mint_cli(cli_path: &Path, target_account_id: &str, faucet_id: &str) { "private", "--force", ]); - mint_cmd.current_dir(cli_path).assert().success(); + + let output = mint_cmd.current_dir(cli_path).output().unwrap(); + assert!(output.status.success()); + + String::from_utf8(output.stdout) + .unwrap() + .split_whitespace() + .skip_while(|&word| word != "Output") + .find(|word| word.starts_with("0x")) + .unwrap() + .to_string() } /// Shows note details using the cli and checks that the command runs @@ -707,12 +569,9 @@ fn send_cli(cli_path: &Path, from_account_id: &str, to_account_id: &str, faucet_ send_cmd.current_dir(cli_path).assert().success(); } -/// Syncs until there are no input notes satisfying the provided filter. -async fn sync_until_no_notes(store_path: &Path, cli_path: &Path, filter: NoteFilter) { - let client = create_test_client_with_store_path(store_path).await.0; - - while !client.get_input_notes(filter.clone()).await.unwrap().is_empty() { - sync_cli(cli_path); +/// Syncs until a tracked note gets committed. +fn sync_until_committed_note(cli_path: &Path) { + while sync_cli(cli_path) == 0 { std::thread::sleep(std::time::Duration::from_secs(1)); } } @@ -726,6 +585,52 @@ fn consume_note_cli(cli_path: &Path, account_id: &str, note_ids: &[&str]) { consume_note_cmd.current_dir(cli_path).assert().success(); } +/// Creates a new faucet account using the CLI given by `cli_path`. +fn new_faucet_cli(cli_path: &Path, storage_mode: AccountStorageMode) -> String { + let mut create_faucet_cmd = Command::cargo_bin("miden").unwrap(); + create_faucet_cmd.args([ + "new-faucet", + "-s", + storage_mode.to_string().as_str(), + "-t", + "BTC", + "-d", + "8", + "-m", + "1000000000000", + ]); + create_faucet_cmd.current_dir(cli_path).assert().success(); + + let output = create_faucet_cmd.current_dir(cli_path).output().unwrap(); + assert!(output.status.success()); + + String::from_utf8(output.stdout) + .unwrap() + .split_whitespace() + .find(|word| word.starts_with("0x")) + .unwrap() + .trim_end_matches(|c: char| !c.is_alphanumeric()) + .to_string() +} + +/// Creates a new wallet account using the CLI given by `cli_path`. +fn new_wallet_cli(cli_path: &Path, storage_mode: AccountStorageMode) -> String { + let mut create_wallet_cmd = Command::cargo_bin("miden").unwrap(); + create_wallet_cmd.args(["new-wallet", "-s", storage_mode.to_string().as_str()]); + + let output = create_wallet_cmd.current_dir(cli_path).output().unwrap(); + assert!(output.status.success()); + + String::from_utf8(output.stdout) + .unwrap() + .split_whitespace() + .find(|word| word.starts_with("0x")) + .unwrap() + .trim_end_matches(|c: char| !c.is_alphanumeric()) + .to_string() +} + +/// Creates a temporary sqlite store file. pub fn create_test_store_path() -> std::path::PathBuf { let mut temp_file = temp_dir(); temp_file.push(format!("{}.sqlite3", Uuid::new_v4())); @@ -734,12 +639,13 @@ pub fn create_test_store_path() -> std::path::PathBuf { pub type TestClient = Client; -async fn create_test_client_with_store_path(store_path: &Path) -> (TestClient, FilesystemKeyStore) { +/// Creates a new [`Client`] with a given store. Also returns the keystore associated with it. +async fn create_rust_client_with_store_path(store_path: &Path) -> (TestClient, FilesystemKeyStore) { let rpc_config = RpcConfig::default(); let store = { let sqlite_store = SqliteStore::new(PathBuf::from(store_path)).await.unwrap(); - Arc::new(sqlite_store) + std::sync::Arc::new(sqlite_store) }; let mut rng = rand::thread_rng(); @@ -755,110 +661,17 @@ async fn create_test_client_with_store_path(store_path: &Path) -> (TestClient, F Arc::new(TonicRpcClient::new(&rpc_config.endpoint.into(), rpc_config.timeout_ms)), rng, store, - Arc::new(authenticator), + std::sync::Arc::new(authenticator), true, ), keystore, ) } +/// Executes a command and asserts that it fails but does not panic. fn assert_command_fails_but_does_not_panic(command: &mut Command) { let output_error = command.ok().unwrap_err(); let exit_code = output_error.as_output().unwrap().status.code().unwrap(); assert_ne!(exit_code, 0); // Command failed assert_ne!(exit_code, 101); // Command didn't panic } - -#[tokio::test] -async fn debug_mode_outputs_logs() { - const NOTE_FILENAME: &str = "test_note.mno"; - env::set_var("MIDEN_DEBUG", "true"); - - // Create a Client and a custom note - let store_path = create_test_store_path(); - let (mut client, authenticator) = create_test_client_with_store_path(&store_path).await; - let (account, ..) = insert_new_wallet(&mut client, AccountStorageMode::Private, &authenticator) - .await - .unwrap(); - - // Create the custom note - let note_script = " - begin - debug.stack - assert_eq - end - "; - let note_script = client.compile_note_script(note_script).unwrap(); - let inputs = NoteInputs::new(vec![]).unwrap(); - let serial_num = client.rng().draw_word(); - let note_metadata = NoteMetadata::new( - account.id(), - NoteType::Private, - NoteTag::from_account_id(account.id(), NoteExecutionMode::Local).unwrap(), - NoteExecutionHint::None, - Felt::default(), - ) - .unwrap(); - let note_assets = NoteAssets::new(vec![]).unwrap(); - let note_recipient = NoteRecipient::new(serial_num, note_script, inputs); - let note = Note::new(note_assets, note_metadata, note_recipient); - - // Send transaction and wait for it to be committed - let transaction_request = TransactionRequestBuilder::new() - .with_own_output_notes(vec![OutputNote::Full(note.clone())]) - .build() - .unwrap(); - execute_tx_and_sync(&mut client, account.id(), transaction_request).await; - - // Export the note - let note_file: NoteFile = NoteFile::NoteDetails { - details: note.clone().into(), - after_block_num: 0.into(), - tag: Some(note.metadata().tag()), - }; - - // Serialize the note - let mut temp_dir = temp_dir(); - let note_path = temp_dir.join(NOTE_FILENAME); - let mut file = File::create(note_path.clone()).unwrap(); - file.write_all(¬e_file.to_bytes()).unwrap(); - - // Use the Cli to import the note - temp_dir.push(format!("{}", uuid::Uuid::new_v4())); - std::fs::create_dir(temp_dir.clone()).unwrap(); - - let mut init_cmd = Command::cargo_bin("miden").unwrap(); - init_cmd.args(["init", "--network", "localhost", "--store-path", store_path.to_str().unwrap()]); - init_cmd.current_dir(&temp_dir).assert().success(); - - // Import the note - let mut import_cmd = Command::cargo_bin("miden").unwrap(); - import_cmd.args(["import", note_path.to_str().unwrap()]); - import_cmd.current_dir(&temp_dir).assert().success(); - - sync_cli(&temp_dir); - - // Create wallet account - let mut create_wallet_cmd = Command::cargo_bin("miden").unwrap(); - create_wallet_cmd.args(["new-wallet", "-s", "public"]); - create_wallet_cmd.current_dir(&temp_dir).assert().success(); - - let wallet_account_id = { - let client = create_test_client_with_store_path(&store_path).await.0; - let accounts = client.get_account_headers().await.unwrap(); - - accounts[1].0.id().to_hex() - }; - - // Consume the note and check the output - let mut consume_note_cmd = Command::cargo_bin("miden").unwrap(); - let note_id = note.id().to_hex(); - let mut cli_args = vec!["consume-notes", "--account", &wallet_account_id[0..8], "--force"]; - cli_args.extend_from_slice(vec![note_id.as_str()].as_slice()); - consume_note_cmd.args(&cli_args); - consume_note_cmd - .current_dir(&temp_dir) - .assert() - .success() - .stdout(contains("Stack state")); -} diff --git a/crates/rust-client/README.md b/crates/rust-client/README.md index da1acb51e..13f001e5f 100644 --- a/crates/rust-client/README.md +++ b/crates/rust-client/README.md @@ -17,11 +17,11 @@ miden-client = { version = "0.8" } | `concurrent` | Used to enable concurrency during execution and proof generation. **Disabled by default.** | | `idxdb` | Includes `WebStore`, an IndexedDB implementation of the `Store` trait. **Disabled by default.** | | `sqlite` | Includes `SqliteStore`, a SQLite implementation of the `Store` trait. This relies on the standard library. **Disabled by default.** | -| `tonic` | Includes `TonicRpcClient`, a Tonic client to communicate with Miden node. This relies on the standard library. **Disabled by default.** | -| `web-tonic` | Includes `WebTonicRpcClient`, a Tonic client to communicate with the Miden node in the browser. **Disabled by default.** | +| `tonic` | Includes `TonicRpcClient`, a `std`-compatible Tonic client to communicate with Miden node. This relies on the `tonic` for the inner transport. **Disabled by default.** | +| `web-tonic` | Includes `TonicRpcClient`, a `wasm`-compatible Tonic client to communicate with the Miden node. This relies on `tonic-web-wasm-client` for the inner transport. **Disabled by default.** | | `testing` | Enables functions meant to be used in testing environments. **Disabled by default.** | -Features `sqlite` and `idxdb` are mutually exclusive. +Features `sqlite` and `idxdb` are mutually exclusive, the same goes for `tonic` and `web-tonic`. ### Store and RpcClient implementations diff --git a/crates/rust-client/src/account/mod.rs b/crates/rust-client/src/account/mod.rs index bfd66977f..5a269a4bb 100644 --- a/crates/rust-client/src/account/mod.rs +++ b/crates/rust-client/src/account/mod.rs @@ -41,7 +41,7 @@ use miden_lib::account::{auth::RpoFalcon512, wallets::BasicWallet}; use miden_objects::{ block::BlockHeader, crypto::{dsa::rpo_falcon512::PublicKey, rand::FeltRng}, - AccountError, Digest, Word, + AccountError, Word, }; use super::Client; @@ -152,7 +152,7 @@ impl Client { // If the tracked account is locked, check that the account hash matches the one // in the network let network_account_hash = - self.rpc_api.get_account_update(account.id()).await?.hash(); + self.rpc_api.get_account_details(account.id()).await?.hash(); if network_account_hash != account.hash() { return Err(ClientError::AccountHashMismatch(network_account_hash)); } @@ -172,7 +172,7 @@ impl Client { /// - If the account is private. /// - There was an error sending the request to the network. pub async fn import_account_by_id(&mut self, account_id: AccountId) -> Result<(), ClientError> { - let account_details = self.rpc_api.get_account_update(account_id).await?; + let account_details = self.rpc_api.get_account_details(account_id).await?; let account = match account_details { AccountDetails::Private(..) => { @@ -275,7 +275,7 @@ pub fn build_wallet_id( public_key: PublicKey, storage_mode: AccountStorageMode, is_mutable: bool, - anchor_block: BlockHeader, + anchor_block: &BlockHeader, ) -> Result { let account_type = if is_mutable { AccountType::RegularAccountUpdatableCode @@ -283,7 +283,7 @@ pub fn build_wallet_id( AccountType::RegularAccountImmutableCode }; - let accound_id_anchor = (&anchor_block).try_into().map_err(|_| { + let accound_id_anchor = anchor_block.try_into().map_err(|_| { ClientError::AccountError(AccountError::AssumptionViolated( "Provided block header is not an anchor block".to_string(), )) diff --git a/crates/rust-client/src/lib.rs b/crates/rust-client/src/lib.rs index 0446002f6..657fd4471 100644 --- a/crates/rust-client/src/lib.rs +++ b/crates/rust-client/src/lib.rs @@ -83,24 +83,11 @@ //! //! // Instantiate the client using a Tonic RPC client //! let endpoint = Endpoint::new("https".into(), "localhost".into(), Some(57291)); -//! let rpc_api = Arc::new(TonicRpcClient::new(&endpoint, 10_000)); -//! -//! let state_sync_component = StateSync::new( -//! rpc_api.clone(), -//! Box::new({ -//! let store_clone = store.clone(); -//! move |committed_note, public_note| { -//! Box::pin(on_note_received(store_clone.clone(), committed_note, public_note)) -//! } -//! }), -//! ); -//! //! let client: Client = Client::new( -//! rpc_api, +//! Arc::new(TonicRpcClient::new(&endpoint, 10_000)), //! rng, //! store, //! Arc::new(authenticator), -//! state_sync_component, //! false, // Set to true for debug mode, if needed. //! ); //! diff --git a/crates/rust-client/src/mock.rs b/crates/rust-client/src/mock.rs index a384815fd..7baee0d3b 100644 --- a/crates/rust-client/src/mock.rs +++ b/crates/rust-client/src/mock.rs @@ -8,14 +8,14 @@ use std::env::temp_dir; use async_trait::async_trait; use miden_lib::transaction::TransactionKernel; use miden_objects::{ - account::{AccountCode, AccountId}, + account::{AccountCode, AccountDelta, AccountId}, asset::{FungibleAsset, NonFungibleAsset}, block::{BlockHeader, BlockNumber, ProvenBlock}, crypto::{ - merkle::{Mmr, MmrProof}, + merkle::{Mmr, MmrProof, SmtProof}, rand::RpoRandomCoin, }, - note::{Note, NoteId, NoteLocation, NoteTag}, + note::{Note, NoteId, NoteLocation, NoteTag, Nullifier}, testing::{ account_id::{ACCOUNT_ID_NON_FUNGIBLE_FAUCET_OFF_CHAIN, ACCOUNT_ID_OFF_CHAIN_SENDER}, note::NoteBuilder, @@ -234,7 +234,7 @@ impl NodeRpcClient for MockRpcApi { include_mmr_proof: bool, ) -> Result<(BlockHeader, Option), RpcError> { if block_num == Some(0.into()) { - return Ok((self.blocks.first().unwrap().header(), None)); + return Ok((self.blocks.first().unwrap().header().clone(), None)); } let block = self .blocks @@ -248,7 +248,7 @@ impl NodeRpcClient for MockRpcApi { None }; - Ok((block.header(), mmr_proof)) + Ok((block.header().clone(), mmr_proof)) } async fn get_notes_by_id(&self, note_ids: &[NoteId]) -> Result, RpcError> { @@ -273,7 +273,10 @@ impl NodeRpcClient for MockRpcApi { Ok(()) } - async fn get_account_update(&self, _account_id: AccountId) -> Result { + async fn get_account_details( + &self, + _account_id: AccountId, + ) -> Result { panic!("shouldn't be used for now") } @@ -294,6 +297,30 @@ impl NodeRpcClient for MockRpcApi { // Always return an empty list for now since it's only used when importing Ok(vec![]) } + + async fn check_nullifiers(&self, _nullifiers: &[Nullifier]) -> Result, RpcError> { + unimplemented!("shouldn't be used for now") + } + + async fn get_account_state_delta( + &self, + _account_id: AccountId, + _from_block: BlockNumber, + _to_block: BlockNumber, + ) -> Result { + unimplemented!("shouldn't be used for now") + } + + async fn get_block_by_number(&self, block_num: BlockNumber) -> Result { + let block = self + .blocks + .iter() + .find(|b| b.header().block_num() == block_num) + .unwrap() + .clone(); + + Ok(block) + } } // HELPERS diff --git a/crates/rust-client/src/rpc/domain/account.rs b/crates/rust-client/src/rpc/domain/account.rs index 9f37d2906..e3af5637a 100644 --- a/crates/rust-client/src/rpc/domain/account.rs +++ b/crates/rust-client/src/rpc/domain/account.rs @@ -46,11 +46,20 @@ impl AccountDetails { } } + // Returns the account update summary hash pub fn hash(&self) -> Digest { match self { Self::Private(_, summary) | Self::Public(_, summary) => summary.hash, } } + + // Returns the associated account if the account is public, otherwise none + pub fn account(&self) -> Option<&Account> { + match self { + Self::Private(..) => None, + Self::Public(account, _) => Some(account), + } + } } // ACCOUNT UPDATE SUMMARY diff --git a/crates/rust-client/src/rpc/domain/block.rs b/crates/rust-client/src/rpc/domain/block.rs index 28b5c45c9..ed4605602 100644 --- a/crates/rust-client/src/rpc/domain/block.rs +++ b/crates/rust-client/src/rpc/domain/block.rs @@ -1,4 +1,4 @@ -use miden_objects::{block::BlockHeader, crypto::merkle::MerklePath}; +use miden_objects::block::BlockHeader; use super::MissingFieldHelper; use crate::rpc::{errors::RpcConversionError, generated::block}; @@ -81,41 +81,3 @@ impl TryFrom for BlockHeader { )) } } - -/// Data required to verify a block's inclusion proof. -#[derive(Clone, Debug)] -pub struct BlockInclusionProof { - pub block_header: BlockHeader, - pub mmr_path: MerklePath, - pub chain_length: u32, -} - -impl From for block::BlockInclusionProof { - fn from(value: BlockInclusionProof) -> Self { - Self { - block_header: Some(value.block_header.into()), - mmr_path: Some((&value.mmr_path).into()), - chain_length: value.chain_length, - } - } -} - -impl TryFrom for BlockInclusionProof { - type Error = RpcConversionError; - - fn try_from(value: block::BlockInclusionProof) -> Result { - let result = Self { - block_header: value - .block_header - .ok_or(block::BlockInclusionProof::missing_field("block_header"))? - .try_into()?, - mmr_path: (&value - .mmr_path - .ok_or(block::BlockInclusionProof::missing_field("mmr_path"))?) - .try_into()?, - chain_length: value.chain_length, - }; - - Ok(result) - } -} diff --git a/crates/rust-client/src/rpc/domain/mod.rs b/crates/rust-client/src/rpc/domain/mod.rs index 34a382340..28e1e0cab 100644 --- a/crates/rust-client/src/rpc/domain/mod.rs +++ b/crates/rust-client/src/rpc/domain/mod.rs @@ -8,6 +8,7 @@ pub mod digest; pub mod merkle; pub mod note; pub mod nullifier; +pub mod smt; pub mod sync; pub mod transaction; diff --git a/crates/rust-client/src/rpc/domain/smt.rs b/crates/rust-client/src/rpc/domain/smt.rs new file mode 100644 index 000000000..c9e601092 --- /dev/null +++ b/crates/rust-client/src/rpc/domain/smt.rs @@ -0,0 +1,126 @@ +use alloc::string::ToString; + +use miden_objects::{ + crypto::merkle::{LeafIndex, SmtLeaf, SmtProof, SMT_DEPTH}, + Digest, Word, +}; + +use super::MissingFieldHelper; +use crate::rpc::{errors::RpcConversionError, generated}; + +// SMT LEAF ENTRY +// ================================================================================================ + +impl From<&(Digest, Word)> for generated::smt::SmtLeafEntry { + fn from(value: &(Digest, Word)) -> Self { + generated::smt::SmtLeafEntry { + key: Some(value.0.into()), + value: Some(Digest::new(value.1).into()), + } + } +} + +impl TryFrom<&generated::smt::SmtLeafEntry> for (Digest, Word) { + type Error = RpcConversionError; + + fn try_from(value: &generated::smt::SmtLeafEntry) -> Result { + let key = match value.key { + Some(key) => key.try_into()?, + None => return Err(generated::smt::SmtLeafEntry::missing_field("key")), + }; + + let value: Digest = match value.value { + Some(value) => value.try_into()?, + None => return Err(generated::smt::SmtLeafEntry::missing_field("value")), + }; + + Ok((key, value.into())) + } +} + +// SMT LEAF +// ================================================================================================ + +impl From for generated::smt::SmtLeaf { + fn from(value: SmtLeaf) -> Self { + (&value).into() + } +} + +impl From<&SmtLeaf> for generated::smt::SmtLeaf { + fn from(value: &SmtLeaf) -> Self { + match value { + SmtLeaf::Empty(index) => generated::smt::SmtLeaf { + leaf: Some(generated::smt::smt_leaf::Leaf::Empty(index.value())), + }, + SmtLeaf::Single(entry) => generated::smt::SmtLeaf { + leaf: Some(generated::smt::smt_leaf::Leaf::Single(entry.into())), + }, + SmtLeaf::Multiple(entries) => generated::smt::SmtLeaf { + leaf: Some(generated::smt::smt_leaf::Leaf::Multiple( + generated::smt::SmtLeafEntries { + entries: entries.iter().map(Into::into).collect(), + }, + )), + }, + } + } +} + +impl TryFrom<&generated::smt::SmtLeaf> for SmtLeaf { + type Error = RpcConversionError; + + fn try_from(value: &generated::smt::SmtLeaf) -> Result { + match &value.leaf { + Some(generated::smt::smt_leaf::Leaf::Empty(index)) => Ok(SmtLeaf::Empty( + LeafIndex::::new(*index) + .map_err(|err| RpcConversionError::InvalidField(err.to_string()))?, + )), + Some(generated::smt::smt_leaf::Leaf::Single(entry)) => { + Ok(SmtLeaf::Single(entry.try_into()?)) + }, + Some(generated::smt::smt_leaf::Leaf::Multiple(entries)) => { + let entries = + entries.entries.iter().map(TryInto::try_into).collect::>()?; + Ok(SmtLeaf::Multiple(entries)) + }, + None => Err(generated::smt::SmtLeaf::missing_field("leaf")), + } + } +} + +// SMT PROOF +// ================================================================================================ + +impl From for generated::smt::SmtOpening { + fn from(value: SmtProof) -> Self { + (&value).into() + } +} + +impl From<&SmtProof> for generated::smt::SmtOpening { + fn from(value: &SmtProof) -> Self { + generated::smt::SmtOpening { + leaf: Some(value.leaf().into()), + path: Some(value.path().into()), + } + } +} + +impl TryFrom<&generated::smt::SmtOpening> for SmtProof { + type Error = RpcConversionError; + + fn try_from(value: &generated::smt::SmtOpening) -> Result { + let leaf = match &value.leaf { + Some(leaf) => leaf.try_into()?, + None => return Err(generated::smt::SmtOpening::missing_field("leaf")), + }; + + let path = match &value.path { + Some(path) => path.try_into()?, + None => return Err(generated::smt::SmtOpening::missing_field("path")), + }; + + SmtProof::new(path, leaf).map_err(|err| RpcConversionError::InvalidField(err.to_string())) + } +} diff --git a/crates/rust-client/src/rpc/errors.rs b/crates/rust-client/src/rpc/errors.rs index f296051a0..ff6e0f4b0 100644 --- a/crates/rust-client/src/rpc/errors.rs +++ b/crates/rust-client/src/rpc/errors.rs @@ -51,6 +51,8 @@ pub enum RpcConversionError { NotAValidFelt, #[error("invalid note type value")] NoteTypeError(#[from] NoteError), + #[error("failed to convert rpc data: {0}")] + InvalidField(String), #[error("field `{field_name}` expected to be present in protobuf representation of {entity}")] MissingFieldInProtobufRepresentation { entity: &'static str, diff --git a/crates/rust-client/src/rpc/generated/nostd/block.rs b/crates/rust-client/src/rpc/generated/nostd/block.rs index 915dce7ec..1dd74f21c 100644 --- a/crates/rust-client/src/rpc/generated/nostd/block.rs +++ b/crates/rust-client/src/rpc/generated/nostd/block.rs @@ -36,16 +36,3 @@ pub struct BlockHeader { #[prost(fixed32, tag = "11")] pub timestamp: u32, } -/// Represents a block inclusion proof. -#[derive(Clone, PartialEq, ::prost::Message)] -pub struct BlockInclusionProof { - /// Block header associated with the inclusion proof. - #[prost(message, optional, tag = "1")] - pub block_header: ::core::option::Option, - /// Merkle path associated with the inclusion proof. - #[prost(message, optional, tag = "2")] - pub mmr_path: ::core::option::Option, - /// The chain length associated with `mmr_path`. - #[prost(fixed32, tag = "3")] - pub chain_length: u32, -} diff --git a/crates/rust-client/src/rpc/generated/nostd/note.rs b/crates/rust-client/src/rpc/generated/nostd/note.rs index 77a9bae41..293eaddab 100644 --- a/crates/rust-client/src/rpc/generated/nostd/note.rs +++ b/crates/rust-client/src/rpc/generated/nostd/note.rs @@ -78,13 +78,3 @@ pub struct NoteSyncRecord { #[prost(message, optional, tag = "4")] pub merkle_path: ::core::option::Option, } -/// Represents proof of notes inclusion in the block(s) and block(s) inclusion in the chain. -#[derive(Clone, PartialEq, ::prost::Message)] -pub struct NoteAuthenticationInfo { - /// Proof of each note's inclusion in a block. - #[prost(message, repeated, tag = "1")] - pub note_proofs: ::prost::alloc::vec::Vec, - /// Proof of each block's inclusion in the chain. - #[prost(message, repeated, tag = "2")] - pub block_proofs: ::prost::alloc::vec::Vec, -} diff --git a/crates/rust-client/src/rpc/generated/nostd/requests.rs b/crates/rust-client/src/rpc/generated/nostd/requests.rs index 9194e084f..e4dc3e131 100644 --- a/crates/rust-client/src/rpc/generated/nostd/requests.rs +++ b/crates/rust-client/src/rpc/generated/nostd/requests.rs @@ -81,15 +81,26 @@ pub struct SyncNoteRequest { /// Returns data required to prove the next block. #[derive(Clone, PartialEq, ::prost::Message)] pub struct GetBlockInputsRequest { - /// ID of the account against which a transaction is executed. + /// IDs of all accounts updated in the proposed block for which to retrieve account witnesses. #[prost(message, repeated, tag = "1")] pub account_ids: ::prost::alloc::vec::Vec, - /// Set of nullifiers consumed by this transaction. + /// Nullifiers of all notes consumed by the block for which to retrieve witnesses. + /// + /// Due to note erasure it will generally not be possible to know the exact set of nullifiers + /// a block will create, unless we pre-execute note erasure. So in practice, this set of + /// nullifiers will be the set of nullifiers of all proven batches in the block, which is a + /// superset of the nullifiers the block may create. + /// + /// However, if it is known that a certain note will be erased, it would not be necessary to + /// provide a nullifier witness for it. #[prost(message, repeated, tag = "2")] pub nullifiers: ::prost::alloc::vec::Vec, - /// Array of note IDs to be checked for existence in the database. + /// Array of note IDs for which to retrieve note inclusion proofs, **if they exist in the store**. #[prost(message, repeated, tag = "3")] pub unauthenticated_notes: ::prost::alloc::vec::Vec, + /// Array of block numbers referenced by all batches in the block. + #[prost(fixed32, repeated, tag = "4")] + pub reference_blocks: ::prost::alloc::vec::Vec, } /// Returns the inputs for a transaction batch. #[derive(Clone, PartialEq, ::prost::Message)] diff --git a/crates/rust-client/src/rpc/generated/nostd/responses.rs b/crates/rust-client/src/rpc/generated/nostd/responses.rs index ac764735d..f0fd0d8ac 100644 --- a/crates/rust-client/src/rpc/generated/nostd/responses.rs +++ b/crates/rust-client/src/rpc/generated/nostd/responses.rs @@ -83,24 +83,25 @@ pub struct SyncNoteResponse { } /// An account returned as a response to the `GetBlockInputs`. #[derive(Clone, PartialEq, ::prost::Message)] -pub struct AccountBlockInputRecord { +pub struct AccountWitness { /// The account ID. #[prost(message, optional, tag = "1")] pub account_id: ::core::option::Option, - /// The latest account hash, zero hash if the account doesn't exist. + /// The latest account state commitment used as the initial state of the requested block. + /// This will be the zero digest if the account doesn't exist. #[prost(message, optional, tag = "2")] - pub account_hash: ::core::option::Option, - /// Merkle path to verify the account's inclusion in the MMR. + pub initial_state_commitment: ::core::option::Option, + /// Merkle path to verify the account's inclusion in the account tree. #[prost(message, optional, tag = "3")] pub proof: ::core::option::Option, } /// A nullifier returned as a response to the `GetBlockInputs`. #[derive(Clone, PartialEq, ::prost::Message)] -pub struct NullifierBlockInputRecord { - /// The nullifier ID. +pub struct NullifierWitness { + /// The nullifier. #[prost(message, optional, tag = "1")] pub nullifier: ::core::option::Option, - /// Merkle path to verify the nullifier's inclusion in the MMR. + /// The SMT proof to verify the nullifier's inclusion in the nullifier tree. #[prost(message, optional, tag = "2")] pub opening: ::core::option::Option, } @@ -109,21 +110,24 @@ pub struct NullifierBlockInputRecord { pub struct GetBlockInputsResponse { /// The latest block header. #[prost(message, optional, tag = "1")] - pub block_header: ::core::option::Option, - /// Peaks of the above block's mmr, The `forest` value is equal to the block number. + pub latest_block_header: ::core::option::Option, + /// Proof of each requested unauthenticated note's inclusion in a block, **if it existed in + /// the store**. #[prost(message, repeated, tag = "2")] - pub mmr_peaks: ::prost::alloc::vec::Vec, - /// The hashes of the requested accounts and their authentication paths. - #[prost(message, repeated, tag = "3")] - pub account_states: ::prost::alloc::vec::Vec, - /// The requested nullifiers and their authentication paths. - #[prost(message, repeated, tag = "4")] - pub nullifiers: ::prost::alloc::vec::Vec, - /// The list of requested notes which were found in the database. - #[prost(message, optional, tag = "5")] - pub found_unauthenticated_notes: ::core::option::Option< - super::note::NoteAuthenticationInfo, + pub unauthenticated_note_proofs: ::prost::alloc::vec::Vec< + super::note::NoteInclusionInBlockProof, >, + /// The serialized chain MMR which includes proofs for all blocks referenced by the + /// above note inclusion proofs as well as proofs for inclusion of the requested blocks + /// referenced by the batches in the block. + #[prost(bytes = "vec", tag = "3")] + pub chain_mmr: ::prost::alloc::vec::Vec, + /// The state commitments of the requested accounts and their authentication paths. + #[prost(message, repeated, tag = "4")] + pub account_witnesses: ::prost::alloc::vec::Vec, + /// The requested nullifiers and their authentication paths. + #[prost(message, repeated, tag = "5")] + pub nullifier_witnesses: ::prost::alloc::vec::Vec, } /// Represents the result of getting batch inputs. #[derive(Clone, PartialEq, ::prost::Message)] diff --git a/crates/rust-client/src/rpc/generated/std/block.rs b/crates/rust-client/src/rpc/generated/std/block.rs index 915dce7ec..1dd74f21c 100644 --- a/crates/rust-client/src/rpc/generated/std/block.rs +++ b/crates/rust-client/src/rpc/generated/std/block.rs @@ -36,16 +36,3 @@ pub struct BlockHeader { #[prost(fixed32, tag = "11")] pub timestamp: u32, } -/// Represents a block inclusion proof. -#[derive(Clone, PartialEq, ::prost::Message)] -pub struct BlockInclusionProof { - /// Block header associated with the inclusion proof. - #[prost(message, optional, tag = "1")] - pub block_header: ::core::option::Option, - /// Merkle path associated with the inclusion proof. - #[prost(message, optional, tag = "2")] - pub mmr_path: ::core::option::Option, - /// The chain length associated with `mmr_path`. - #[prost(fixed32, tag = "3")] - pub chain_length: u32, -} diff --git a/crates/rust-client/src/rpc/generated/std/note.rs b/crates/rust-client/src/rpc/generated/std/note.rs index 77a9bae41..293eaddab 100644 --- a/crates/rust-client/src/rpc/generated/std/note.rs +++ b/crates/rust-client/src/rpc/generated/std/note.rs @@ -78,13 +78,3 @@ pub struct NoteSyncRecord { #[prost(message, optional, tag = "4")] pub merkle_path: ::core::option::Option, } -/// Represents proof of notes inclusion in the block(s) and block(s) inclusion in the chain. -#[derive(Clone, PartialEq, ::prost::Message)] -pub struct NoteAuthenticationInfo { - /// Proof of each note's inclusion in a block. - #[prost(message, repeated, tag = "1")] - pub note_proofs: ::prost::alloc::vec::Vec, - /// Proof of each block's inclusion in the chain. - #[prost(message, repeated, tag = "2")] - pub block_proofs: ::prost::alloc::vec::Vec, -} diff --git a/crates/rust-client/src/rpc/generated/std/requests.rs b/crates/rust-client/src/rpc/generated/std/requests.rs index 9194e084f..e4dc3e131 100644 --- a/crates/rust-client/src/rpc/generated/std/requests.rs +++ b/crates/rust-client/src/rpc/generated/std/requests.rs @@ -81,15 +81,26 @@ pub struct SyncNoteRequest { /// Returns data required to prove the next block. #[derive(Clone, PartialEq, ::prost::Message)] pub struct GetBlockInputsRequest { - /// ID of the account against which a transaction is executed. + /// IDs of all accounts updated in the proposed block for which to retrieve account witnesses. #[prost(message, repeated, tag = "1")] pub account_ids: ::prost::alloc::vec::Vec, - /// Set of nullifiers consumed by this transaction. + /// Nullifiers of all notes consumed by the block for which to retrieve witnesses. + /// + /// Due to note erasure it will generally not be possible to know the exact set of nullifiers + /// a block will create, unless we pre-execute note erasure. So in practice, this set of + /// nullifiers will be the set of nullifiers of all proven batches in the block, which is a + /// superset of the nullifiers the block may create. + /// + /// However, if it is known that a certain note will be erased, it would not be necessary to + /// provide a nullifier witness for it. #[prost(message, repeated, tag = "2")] pub nullifiers: ::prost::alloc::vec::Vec, - /// Array of note IDs to be checked for existence in the database. + /// Array of note IDs for which to retrieve note inclusion proofs, **if they exist in the store**. #[prost(message, repeated, tag = "3")] pub unauthenticated_notes: ::prost::alloc::vec::Vec, + /// Array of block numbers referenced by all batches in the block. + #[prost(fixed32, repeated, tag = "4")] + pub reference_blocks: ::prost::alloc::vec::Vec, } /// Returns the inputs for a transaction batch. #[derive(Clone, PartialEq, ::prost::Message)] diff --git a/crates/rust-client/src/rpc/generated/std/responses.rs b/crates/rust-client/src/rpc/generated/std/responses.rs index ac764735d..f0fd0d8ac 100644 --- a/crates/rust-client/src/rpc/generated/std/responses.rs +++ b/crates/rust-client/src/rpc/generated/std/responses.rs @@ -83,24 +83,25 @@ pub struct SyncNoteResponse { } /// An account returned as a response to the `GetBlockInputs`. #[derive(Clone, PartialEq, ::prost::Message)] -pub struct AccountBlockInputRecord { +pub struct AccountWitness { /// The account ID. #[prost(message, optional, tag = "1")] pub account_id: ::core::option::Option, - /// The latest account hash, zero hash if the account doesn't exist. + /// The latest account state commitment used as the initial state of the requested block. + /// This will be the zero digest if the account doesn't exist. #[prost(message, optional, tag = "2")] - pub account_hash: ::core::option::Option, - /// Merkle path to verify the account's inclusion in the MMR. + pub initial_state_commitment: ::core::option::Option, + /// Merkle path to verify the account's inclusion in the account tree. #[prost(message, optional, tag = "3")] pub proof: ::core::option::Option, } /// A nullifier returned as a response to the `GetBlockInputs`. #[derive(Clone, PartialEq, ::prost::Message)] -pub struct NullifierBlockInputRecord { - /// The nullifier ID. +pub struct NullifierWitness { + /// The nullifier. #[prost(message, optional, tag = "1")] pub nullifier: ::core::option::Option, - /// Merkle path to verify the nullifier's inclusion in the MMR. + /// The SMT proof to verify the nullifier's inclusion in the nullifier tree. #[prost(message, optional, tag = "2")] pub opening: ::core::option::Option, } @@ -109,21 +110,24 @@ pub struct NullifierBlockInputRecord { pub struct GetBlockInputsResponse { /// The latest block header. #[prost(message, optional, tag = "1")] - pub block_header: ::core::option::Option, - /// Peaks of the above block's mmr, The `forest` value is equal to the block number. + pub latest_block_header: ::core::option::Option, + /// Proof of each requested unauthenticated note's inclusion in a block, **if it existed in + /// the store**. #[prost(message, repeated, tag = "2")] - pub mmr_peaks: ::prost::alloc::vec::Vec, - /// The hashes of the requested accounts and their authentication paths. - #[prost(message, repeated, tag = "3")] - pub account_states: ::prost::alloc::vec::Vec, - /// The requested nullifiers and their authentication paths. - #[prost(message, repeated, tag = "4")] - pub nullifiers: ::prost::alloc::vec::Vec, - /// The list of requested notes which were found in the database. - #[prost(message, optional, tag = "5")] - pub found_unauthenticated_notes: ::core::option::Option< - super::note::NoteAuthenticationInfo, + pub unauthenticated_note_proofs: ::prost::alloc::vec::Vec< + super::note::NoteInclusionInBlockProof, >, + /// The serialized chain MMR which includes proofs for all blocks referenced by the + /// above note inclusion proofs as well as proofs for inclusion of the requested blocks + /// referenced by the batches in the block. + #[prost(bytes = "vec", tag = "3")] + pub chain_mmr: ::prost::alloc::vec::Vec, + /// The state commitments of the requested accounts and their authentication paths. + #[prost(message, repeated, tag = "4")] + pub account_witnesses: ::prost::alloc::vec::Vec, + /// The requested nullifiers and their authentication paths. + #[prost(message, repeated, tag = "5")] + pub nullifier_witnesses: ::prost::alloc::vec::Vec, } /// Represents the result of getting batch inputs. #[derive(Clone, PartialEq, ::prost::Message)] diff --git a/crates/rust-client/src/rpc/mod.rs b/crates/rust-client/src/rpc/mod.rs index 2e454b3ba..1a187f44e 100644 --- a/crates/rust-client/src/rpc/mod.rs +++ b/crates/rust-client/src/rpc/mod.rs @@ -51,9 +51,9 @@ use domain::{ sync::StateSyncInfo, }; use miden_objects::{ - account::{Account, AccountCode, AccountHeader, AccountId}, - block::{BlockHeader, BlockNumber}, - crypto::merkle::MmrProof, + account::{Account, AccountCode, AccountDelta, AccountHeader, AccountId}, + block::{BlockHeader, BlockNumber, ProvenBlock}, + crypto::merkle::{MmrProof, SmtProof}, note::{NoteId, NoteTag, Nullifier}, transaction::ProvenTransaction, }; @@ -73,16 +73,14 @@ mod generated; #[cfg(test)] pub mod generated; -#[cfg(feature = "tonic")] +#[cfg(all(feature = "tonic", feature = "web-tonic"))] +compile_error!("features `tonic` and `web-tonic` are mutually exclusive"); + +#[cfg(any(feature = "tonic", feature = "web-tonic"))] mod tonic_client; -#[cfg(feature = "tonic")] +#[cfg(any(feature = "tonic", feature = "web-tonic"))] pub use tonic_client::TonicRpcClient; -#[cfg(feature = "web-tonic")] -mod web_tonic_client; -#[cfg(feature = "web-tonic")] -pub use web_tonic_client::WebTonicRpcClient; - use crate::{ store::{input_note_states::UnverifiedNoteState, InputNoteRecord}, transaction::ForeignAccount, @@ -117,6 +115,10 @@ pub trait NodeRpcClient { include_mmr_proof: bool, ) -> Result<(BlockHeader, Option), RpcError>; + /// Given a block number, fetches the block corresponding to that height from the node using + /// the `/GetBlockByNumber` RPC endpoint. + async fn get_block_by_number(&self, block_num: BlockNumber) -> Result; + /// Fetches note-related data for a list of [NoteId] using the `/GetNotesById` rpc endpoint. /// /// For any NoteType::Private note, the return data is only the @@ -147,8 +149,12 @@ pub trait NodeRpcClient { /// endpoint. /// /// - `account_id` is the ID of the wanted account. - async fn get_account_update(&self, account_id: AccountId) -> Result; + async fn get_account_details(&self, account_id: AccountId) -> Result; + /// Fetches the notes related to the specified tags using the `/SyncNotes` RPC endpoint. + /// + /// - `block_num` is the last block number known by the client. + /// - `note_tags` is a list of tags used to filter the notes the client is interested in. async fn sync_notes( &self, block_num: BlockNumber, @@ -167,6 +173,10 @@ pub trait NodeRpcClient { block_num: BlockNumber, ) -> Result, RpcError>; + /// Fetches the nullifier proofs corresponding to a list of nullifiers using the + /// `/CheckNullifiers` RPC endpoint. + async fn check_nullifiers(&self, nullifiers: &[Nullifier]) -> Result, RpcError>; + /// Fetches the account data needed to perform a Foreign Procedure Invocation (FPI) on the /// specified foreign accounts, using the `GetAccountProofs` endpoint. /// @@ -179,6 +189,15 @@ pub trait NodeRpcClient { known_account_codes: Vec, ) -> Result; + /// Fetches the account state delta for the specified account between the specified blocks + /// using the `/GetAccountStateDelta` RPC endpoint. + async fn get_account_state_delta( + &self, + account_id: AccountId, + from_block: BlockNumber, + to_block: BlockNumber, + ) -> Result; + /// Fetches the commit height where the nullifier was consumed. If the nullifier isn't found, /// then `None` is returned. /// The `block_num` parameter is the block number to start the search from. @@ -232,7 +251,7 @@ pub trait NodeRpcClient { /// The `local_accounts` parameter is a list of account headers that the client has /// stored locally and that it wants to check for updates. If an account is private or didn't /// change, it is ignored and will not be included in the returned list. - /// The default implementation of this method uses [NodeRpcClient::get_account_update]. + /// The default implementation of this method uses [NodeRpcClient::get_account_details]. async fn get_updated_public_accounts( &self, local_accounts: &[&AccountHeader], @@ -240,7 +259,7 @@ pub trait NodeRpcClient { let mut public_accounts = vec![]; for local_account in local_accounts { - let response = self.get_account_update(local_account.id()).await?; + let response = self.get_account_details(local_account.id()).await?; if let AccountDetails::Public(account, _) = response { // We should only return an account if it's newer, otherwise we ignore it @@ -283,9 +302,12 @@ pub trait NodeRpcClient { /// RPC methods for the Miden protocol. #[derive(Debug)] pub enum NodeRpcClientEndpoint { + CheckNullifiers, CheckNullifiersByPrefix, GetAccountDetails, + GetAccountStateDelta, GetAccountProofs, + GetBlockByNumber, GetBlockHeaderByNumber, SyncState, SubmitProvenTx, @@ -295,11 +317,14 @@ pub enum NodeRpcClientEndpoint { impl fmt::Display for NodeRpcClientEndpoint { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { + NodeRpcClientEndpoint::CheckNullifiers => write!(f, "check_nullifiers"), NodeRpcClientEndpoint::CheckNullifiersByPrefix => { write!(f, "check_nullifiers_by_prefix") }, NodeRpcClientEndpoint::GetAccountDetails => write!(f, "get_account_details"), + NodeRpcClientEndpoint::GetAccountStateDelta => write!(f, "get_account_state_delta"), NodeRpcClientEndpoint::GetAccountProofs => write!(f, "get_account_proofs"), + NodeRpcClientEndpoint::GetBlockByNumber => write!(f, "get_block_by_number"), NodeRpcClientEndpoint::GetBlockHeaderByNumber => { write!(f, "get_block_header_by_number") }, diff --git a/crates/rust-client/src/rpc/tonic_client/mod.rs b/crates/rust-client/src/rpc/tonic_client/mod.rs index d54bea266..b72659061 100644 --- a/crates/rust-client/src/rpc/tonic_client/mod.rs +++ b/crates/rust-client/src/rpc/tonic_client/mod.rs @@ -1,24 +1,26 @@ use alloc::{ boxed::Box, - collections::BTreeSet, + collections::{BTreeMap, BTreeSet}, string::{String, ToString}, vec::Vec, }; -use std::{collections::BTreeMap, time::Duration}; +#[allow(unused_imports)] +use core::time::Duration; use async_trait::async_trait; use miden_objects::{ - account::{Account, AccountCode, AccountId}, - block::{BlockHeader, BlockNumber}, - crypto::merkle::{MerklePath, MmrProof}, - note::{Note, NoteId, NoteInclusionProof, NoteTag}, + account::{Account, AccountCode, AccountDelta, AccountId}, + block::{BlockHeader, BlockNumber, ProvenBlock}, + crypto::merkle::{MerklePath, MmrProof, SmtProof}, + note::{Note, NoteId, NoteInclusionProof, NoteTag, Nullifier}, transaction::ProvenTransaction, utils::Deserializable, Digest, }; -use miden_tx::utils::Serializable; -use tokio::sync::{RwLock, RwLockWriteGuard}; -use tonic::transport::Channel; +use miden_tx::utils::{ + sync::{RwLock, RwLockWriteGuard}, + Serializable, +}; use tracing::info; use super::{ @@ -30,7 +32,8 @@ use super::{ generated::{ requests::{ get_account_proofs_request::{self}, - CheckNullifiersByPrefixRequest, GetAccountDetailsRequest, GetAccountProofsRequest, + CheckNullifiersByPrefixRequest, CheckNullifiersRequest, GetAccountDetailsRequest, + GetAccountProofsRequest, GetAccountStateDeltaRequest, GetBlockByNumberRequest, GetNotesByIdRequest, SubmitProvenTransactionRequest, SyncNoteRequest, SyncStateRequest, }, rpc::api_client::ApiClient, @@ -43,21 +46,38 @@ use crate::{rpc::generated::requests::GetBlockHeaderByNumberRequest, transaction // TONIC RPC CLIENT // ================================================================================================ +#[cfg(all(not(target_arch = "wasm32"), feature = "web-tonic"))] +compile_error!("The `web-tonic` feature is only supported when targeting wasm32."); + +#[cfg(feature = "web-tonic")] +type InnerClient = tonic_web_wasm_client::Client; +#[cfg(feature = "tonic")] +type InnerClient = tonic::transport::Channel; + /// Client for the Node RPC API using tonic. /// -/// Wraps the `ApiClient` which defers establishing a connection with a node until necessary. +/// If the `tonic` feature is enabled, this client will use a `tonic::transport::Channel` to +/// communicate with the node. In this case the connection will be established lazily when the +/// first request is made. +/// If the `web-tonic` feature is enabled, this client will use a `tonic_web_wasm_client::Client` +/// to communicate with the node. +/// +/// In both cases, the [`TonicRpcClient`] depends on the types inside the `generated` module, which +/// are generated by the build script and also depend on the target architecture. pub struct TonicRpcClient { - rpc_api: RwLock>>, + client: RwLock>>, endpoint: String, + #[allow(dead_code)] timeout_ms: u64, } +#[allow(clippy::await_holding_lock, clippy::unused_async)] impl TonicRpcClient { /// Returns a new instance of [`TonicRpcClient`] that'll do calls to the provided [`Endpoint`] /// with the given timeout in milliseconds. pub fn new(endpoint: &Endpoint, timeout_ms: u64) -> TonicRpcClient { TonicRpcClient { - rpc_api: RwLock::new(None), + client: RwLock::new(None), endpoint: endpoint.to_string(), timeout_ms, } @@ -67,23 +87,35 @@ impl TonicRpcClient { /// `rpc_api` field is initialized and returns a write guard to it. async fn ensure_connected( &self, - ) -> Result>>, RpcError> { - let mut rpc_api = self.rpc_api.write().await; - if rpc_api.is_none() { - let endpoint = tonic::transport::Endpoint::try_from(self.endpoint.clone()) - .map_err(|err| RpcError::ConnectionError(err.to_string()))? - .timeout(Duration::from_millis(self.timeout_ms)); - let connected_rpc_api = ApiClient::connect(endpoint) - .await - .map_err(|err| RpcError::ConnectionError(err.to_string()))?; - rpc_api.replace(connected_rpc_api); + ) -> Result>>, RpcError> { + let mut client = self.client.write(); + if client.is_none() { + #[cfg(feature = "web-tonic")] + let new_client = { + let wasm_client = tonic_web_wasm_client::Client::new(self.endpoint.clone()); + ApiClient::new(wasm_client) + }; + + #[cfg(feature = "tonic")] + let new_client = { + let endpoint = tonic::transport::Endpoint::try_from(self.endpoint.clone()) + .map_err(|err| RpcError::ConnectionError(err.to_string()))? + .timeout(Duration::from_millis(self.timeout_ms)); + + ApiClient::connect(endpoint) + .await + .map_err(|err| RpcError::ConnectionError(err.to_string()))? + }; + + client.replace(new_client); } - Ok(rpc_api) + Ok(client) } } #[async_trait(?Send)] +#[allow(clippy::await_holding_lock)] impl NodeRpcClient for TonicRpcClient { async fn submit_proven_transaction( &self, @@ -247,7 +279,7 @@ impl NodeRpcClient for TonicRpcClient { /// - The answer had a `None` for one of the expected fields (account, summary, account_hash, /// details). /// - There is an error during [Account] deserialization. - async fn get_account_update(&self, account_id: AccountId) -> Result { + async fn get_account_details(&self, account_id: AccountId) -> Result { let request = GetAccountDetailsRequest { account_id: Some(account_id.into()) }; let mut rpc_api = self.ensure_connected().await?; @@ -385,6 +417,8 @@ impl NodeRpcClient for TonicRpcClient { Ok((block_num, account_proofs)) } + /// Sends a `SyncNoteRequest` to the Miden node, and extracts a [NoteSyncInfo] from the + /// response. async fn sync_notes( &self, block_num: BlockNumber, @@ -434,4 +468,77 @@ impl NodeRpcClient for TonicRpcClient { Ok(nullifiers) } + + async fn check_nullifiers(&self, nullifiers: &[Nullifier]) -> Result, RpcError> { + let request = CheckNullifiersRequest { + nullifiers: nullifiers.iter().map(|nul| nul.inner().into()).collect(), + }; + + let mut rpc_api = self.ensure_connected().await?; + let rpc_api = rpc_api.as_mut().expect("rpc_api should be initialized"); + + let response = rpc_api.check_nullifiers(request).await.map_err(|err| { + RpcError::RequestError( + NodeRpcClientEndpoint::CheckNullifiers.to_string(), + err.to_string(), + ) + })?; + + let response = response.into_inner(); + let proofs = response.proofs.iter().map(TryInto::try_into).collect::>()?; + + Ok(proofs) + } + + async fn get_account_state_delta( + &self, + account_id: AccountId, + from_block: BlockNumber, + to_block: BlockNumber, + ) -> Result { + let request = GetAccountStateDeltaRequest { + account_id: Some(account_id.into()), + from_block_num: from_block.as_u32(), + to_block_num: to_block.as_u32(), + }; + + let mut rpc_api = self.ensure_connected().await?; + let rpc_api = rpc_api.as_mut().expect("rpc_api should be initialized"); + + let response = rpc_api.get_account_state_delta(request).await.map_err(|err| { + RpcError::RequestError( + NodeRpcClientEndpoint::GetAccountStateDelta.to_string(), + err.to_string(), + ) + })?; + + let response = response.into_inner(); + let delta = AccountDelta::read_from_bytes(&response.delta.ok_or( + RpcError::ExpectedDataMissing("GetAccountStateDeltaResponse.delta".to_string()), + )?)?; + + Ok(delta) + } + + async fn get_block_by_number(&self, block_num: BlockNumber) -> Result { + let request = GetBlockByNumberRequest { block_num: block_num.as_u32() }; + + let mut rpc_api = self.ensure_connected().await?; + let rpc_api = rpc_api.as_mut().expect("rpc_api should be initialized"); + + let response = rpc_api.get_block_by_number(request).await.map_err(|err| { + RpcError::RequestError( + NodeRpcClientEndpoint::GetBlockByNumber.to_string(), + err.to_string(), + ) + })?; + + let response = response.into_inner(); + let block = + ProvenBlock::read_from_bytes(&response.block.ok_or(RpcError::ExpectedDataMissing( + "GetBlockByNumberResponse.block".to_string(), + ))?)?; + + Ok(block) + } } diff --git a/crates/rust-client/src/rpc/web_tonic_client/mod.rs b/crates/rust-client/src/rpc/web_tonic_client/mod.rs deleted file mode 100644 index 4cf33a52e..000000000 --- a/crates/rust-client/src/rpc/web_tonic_client/mod.rs +++ /dev/null @@ -1,402 +0,0 @@ -use alloc::{ - boxed::Box, - collections::{BTreeMap, BTreeSet}, - string::{String, ToString}, - vec::Vec, -}; - -use async_trait::async_trait; -use miden_objects::{ - account::{Account, AccountCode, AccountId}, - block::{BlockHeader, BlockNumber}, - crypto::merkle::{MerklePath, MmrProof}, - note::{Note, NoteId, NoteInclusionProof, NoteTag}, - transaction::ProvenTransaction, - utils::Deserializable, - Digest, -}; -use miden_tx::utils::Serializable; -use tonic_web_wasm_client::Client; - -use super::{ - domain::{ - account::{AccountDetails, AccountProof, AccountProofs, AccountUpdateSummary}, - note::{NetworkNote, NoteSyncInfo}, - nullifier::NullifierUpdate, - sync::StateSyncInfo, - }, - generated::{ - requests::{ - get_account_proofs_request, CheckNullifiersByPrefixRequest, GetAccountDetailsRequest, - GetAccountProofsRequest, GetBlockHeaderByNumberRequest, GetNotesByIdRequest, - SubmitProvenTransactionRequest, SyncNoteRequest, SyncStateRequest, - }, - rpc::api_client::ApiClient, - }, - NodeRpcClient, NodeRpcClientEndpoint, RpcError, -}; -use crate::transaction::ForeignAccount; - -pub struct WebTonicRpcClient { - endpoint: String, -} - -impl WebTonicRpcClient { - pub fn new(endpoint: &str) -> Self { - Self { endpoint: endpoint.to_string() } - } - - pub fn build_api_client(&self) -> ApiClient { - let wasm_client = Client::new(self.endpoint.clone()); - ApiClient::new(wasm_client) - } -} - -#[async_trait(?Send)] -impl NodeRpcClient for WebTonicRpcClient { - async fn submit_proven_transaction( - &self, - proven_transaction: ProvenTransaction, - ) -> Result<(), RpcError> { - let mut query_client = self.build_api_client(); - - let request = SubmitProvenTransactionRequest { - transaction: proven_transaction.to_bytes(), - }; - - query_client.submit_proven_transaction(request).await.map_err(|err| { - RpcError::RequestError( - NodeRpcClientEndpoint::SubmitProvenTx.to_string(), - err.to_string(), - ) - })?; - - Ok(()) - } - - async fn get_block_header_by_number( - &self, - block_num: Option, - include_mmr_proof: bool, - ) -> Result<(BlockHeader, Option), RpcError> { - let mut query_client = self.build_api_client(); - - let request = GetBlockHeaderByNumberRequest { - block_num: block_num.as_ref().map(BlockNumber::as_u32), - include_mmr_proof: Some(include_mmr_proof), - }; - - // Attempt to send the request and process the response - let api_response = - query_client.get_block_header_by_number(request).await.map_err(|err| { - // log to console all the properties of block header - RpcError::RequestError( - NodeRpcClientEndpoint::GetBlockHeaderByNumber.to_string(), - err.to_string(), - ) - })?; - - let response = api_response.into_inner(); - - let block_header: BlockHeader = response - .block_header - .ok_or(RpcError::ExpectedDataMissing("BlockHeader".into()))? - .try_into()?; - - let mmr_proof = if include_mmr_proof { - let forest = response - .chain_length - .ok_or(RpcError::ExpectedDataMissing("ChainLength".into()))?; - let merkle_path: MerklePath = response - .mmr_path - .ok_or(RpcError::ExpectedDataMissing("MmrPath".into()))? - .try_into()?; - - Some(MmrProof { - forest: forest as usize, - position: block_header.block_num().as_usize(), - merkle_path, - }) - } else { - None - }; - - Ok((block_header, mmr_proof)) - } - - async fn get_notes_by_id(&self, note_ids: &[NoteId]) -> Result, RpcError> { - let mut query_client = self.build_api_client(); - - let request = GetNotesByIdRequest { - note_ids: note_ids.iter().map(|id| id.inner().into()).collect(), - }; - - let api_response = query_client.get_notes_by_id(request).await.map_err(|err| { - RpcError::RequestError( - NodeRpcClientEndpoint::GetBlockHeaderByNumber.to_string(), - err.to_string(), - ) - })?; - - let rpc_notes = api_response.into_inner().notes; - let mut response_notes = Vec::with_capacity(rpc_notes.len()); - for note in rpc_notes { - let inclusion_details = { - let merkle_path = note - .merkle_path - .ok_or(RpcError::ExpectedDataMissing("Notes.MerklePath".into()))? - .try_into()?; - - NoteInclusionProof::new( - note.block_num.into(), - u16::try_from(note.note_index).expect("note index out of range"), - merkle_path, - )? - }; - - let note = if let Some(details) = note.details { - let note = Note::read_from_bytes(&details)?; - - NetworkNote::Public(note, inclusion_details) - } else { - let note_metadata = note - .metadata - .ok_or(RpcError::ExpectedDataMissing("Metadata".into()))? - .try_into()?; - let note_id: miden_objects::Digest = note - .note_id - .ok_or(RpcError::ExpectedDataMissing("Notes.NoteId".into()))? - .try_into()?; - - NetworkNote::Private(NoteId::from(note_id), note_metadata, inclusion_details) - }; - response_notes.push(note); - } - Ok(response_notes) - } - - /// Sends a sync state request to the Miden node, validates and converts the response - /// into a [StateSyncInfo] struct. - async fn sync_state( - &self, - block_num: BlockNumber, - account_ids: &[AccountId], - note_tags: &[NoteTag], - ) -> Result { - let mut query_client = self.build_api_client(); - - let account_ids = account_ids.iter().map(|acc| (*acc).into()).collect(); - let note_tags = note_tags.iter().map(|¬e_tag| note_tag.into()).collect(); - - let request = SyncStateRequest { - block_num: block_num.as_u32(), - account_ids, - note_tags, - }; - - let response = query_client.sync_state(request).await.map_err(|err| { - RpcError::RequestError(NodeRpcClientEndpoint::SyncState.to_string(), err.to_string()) - })?; - response.into_inner().try_into() - } - - async fn sync_notes( - &self, - block_num: BlockNumber, - note_tags: &[NoteTag], - ) -> Result { - let mut query_client = self.build_api_client(); - - let note_tags = note_tags.iter().map(|¬e_tag| note_tag.into()).collect(); - - let request = SyncNoteRequest { block_num: block_num.as_u32(), note_tags }; - - let response = query_client.sync_notes(request).await.map_err(|err| { - RpcError::RequestError(NodeRpcClientEndpoint::SyncState.to_string(), err.to_string()) - })?; - response.into_inner().try_into() - } - - /// Sends a `GetAccountProofs` request to the Miden node, and extracts a list of [AccountProof] - /// from the response. - /// - /// # Errors - /// - /// This function will return an error if: - /// - /// - One of the requested Accounts isn't public, or isn't returned by the node. - /// - There was an error sending the request to the node. - /// - The answer had a `None` for one of the expected fields. - /// - There is an error during storage deserialization. - async fn get_account_proofs( - &self, - account_requests: &BTreeSet, - known_account_codes: Vec, - ) -> Result { - let mut query_client = self.build_api_client(); - let requested_accounts = account_requests.len(); - let mut rpc_account_requests: Vec = - Vec::with_capacity(account_requests.len()); - - for foreign_account in account_requests { - rpc_account_requests.push(get_account_proofs_request::AccountRequest { - account_id: Some(foreign_account.account_id().into()), - storage_requests: foreign_account.storage_slot_requirements().into(), - }); - } - - let known_account_codes: BTreeMap = - known_account_codes.into_iter().map(|c| (c.commitment(), c)).collect(); - - let request = GetAccountProofsRequest { - account_requests: rpc_account_requests, - include_headers: Some(true), - code_commitments: known_account_codes.keys().map(Into::into).collect(), - }; - - let response = query_client - .get_account_proofs(request) - .await - .map_err(|err| { - RpcError::RequestError( - NodeRpcClientEndpoint::GetAccountProofs.to_string(), - err.to_string(), - ) - })? - .into_inner(); - - let mut account_proofs = Vec::with_capacity(response.account_proofs.len()); - let block_num = response.block_num.into(); - - // sanity check response - if requested_accounts != response.account_proofs.len() { - return Err(RpcError::ExpectedDataMissing( - "AccountProof did not contain all account IDs".to_string(), - )); - } - - for account in response.account_proofs { - let merkle_proof = account - .account_proof - .ok_or(RpcError::ExpectedDataMissing("AccountProof".to_string()))? - .try_into()?; - let account_hash = account - .account_hash - .ok_or(RpcError::ExpectedDataMissing("AccountHash".to_string()))? - .try_into()?; - - let account_id: AccountId = account - .account_id - .ok_or(RpcError::ExpectedDataMissing("AccountId".to_string()))? - .try_into()?; - - // Because we set `include_headers` to true, for any public account we requeted we - // should have the corresponding `state_header` field - let headers = if account_id.is_public() { - Some( - account - .state_header - .ok_or(RpcError::ExpectedDataMissing("Account.StateHeader".to_string()))? - .into_domain(account_id, &known_account_codes)?, - ) - } else { - None - }; - - let proof = AccountProof::new(account_id, merkle_proof, account_hash, headers) - .map_err(|err| RpcError::InvalidResponse(err.to_string()))?; - account_proofs.push(proof); - } - - Ok((block_num, account_proofs)) - } - - /// Sends a [GetAccountDetailsRequest] to the Miden node, and extracts an [Account] from the - /// `GetAccountDetailsResponse` response. - /// - /// # Errors - /// - /// This function will return an error if: - /// - /// - The provided account isn't on-chain: this is due to the fact that for private accounts the - /// client is responsible. - /// - There was an error sending the request to the node. - /// - The answer had a `None` for its account, or the account had a `None` at the `details` - /// field. - /// - There is an error during [Account] deserialization. - async fn get_account_update(&self, account_id: AccountId) -> Result { - let mut query_client = self.build_api_client(); - - let request = GetAccountDetailsRequest { account_id: Some(account_id.into()) }; - - let response = query_client.get_account_details(request).await.map_err(|err| { - RpcError::RequestError( - NodeRpcClientEndpoint::GetAccountDetails.to_string(), - err.to_string(), - ) - })?; - - let response = response.into_inner(); - let account_info = response.details.ok_or(RpcError::ExpectedDataMissing( - "GetAccountDetails response should have an `account`".to_string(), - ))?; - - let account_summary = account_info.summary.ok_or(RpcError::ExpectedDataMissing( - "GetAccountDetails response's account should have a `summary`".to_string(), - ))?; - - let hash = account_summary.account_hash.ok_or(RpcError::ExpectedDataMissing( - "GetAccountDetails response's account should have an `account_hash`".to_string(), - ))?; - - let hash = hash.try_into()?; - - let update_summary = AccountUpdateSummary::new(hash, account_summary.block_num); - if account_id.is_public() { - let details_bytes = account_info.details.ok_or(RpcError::ExpectedDataMissing( - "GetAccountDetails response's account should have `details`".to_string(), - ))?; - - let account = Account::read_from_bytes(&details_bytes)?; - - Ok(AccountDetails::Public(account, update_summary)) - } else { - Ok(AccountDetails::Private(account_id, update_summary)) - } - } - - async fn check_nullifiers_by_prefix( - &self, - prefixes: &[u16], - block_num: BlockNumber, - ) -> Result, RpcError> { - let mut query_client = self.build_api_client(); - - let request = CheckNullifiersByPrefixRequest { - nullifiers: prefixes.iter().map(|&x| u32::from(x)).collect(), - prefix_len: 16, - block_num: block_num.as_u32(), - }; - - let response = query_client.check_nullifiers_by_prefix(request).await.map_err(|err| { - RpcError::RequestError( - NodeRpcClientEndpoint::CheckNullifiersByPrefix.to_string(), - err.to_string(), - ) - })?; - - let response = response.into_inner(); - let nullifiers = response - .nullifiers - .iter() - .map(|nul| { - let nullifier = nul.nullifier.ok_or(RpcError::ExpectedDataMissing( - "CheckNullifiersByPrefix response should have a `nullifier`".to_string(), - ))?; - let nullifier = nullifier.try_into()?; - Ok(NullifierUpdate { nullifier, block_num: nul.block_num }) - }) - .collect::, RpcError>>()?; - Ok(nullifiers) - } -} diff --git a/crates/rust-client/src/store/data_store.rs b/crates/rust-client/src/store/data_store.rs index c81cd912a..6fadc2a70 100644 --- a/crates/rust-client/src/store/data_store.rs +++ b/crates/rust-client/src/store/data_store.rs @@ -109,8 +109,8 @@ impl DataStore for ClientDataStore { .store .get_block_headers(&block_nums) .await? - .iter() - .map(|(header, _has_notes)| *header) + .into_iter() + .map(|(header, _has_notes)| header) .collect(); let partial_mmr = diff --git a/crates/rust-client/src/store/mod.rs b/crates/rust-client/src/store/mod.rs index 786b5abb4..14198e9ea 100644 --- a/crates/rust-client/src/store/mod.rs +++ b/crates/rust-client/src/store/mod.rs @@ -173,7 +173,7 @@ pub trait Store: Send + Sync { ) -> Result, StoreError> { self.get_block_headers(&[block_number]) .await - .map(|block_headers_list| block_headers_list.first().copied()) + .map(|mut block_headers_list| block_headers_list.pop()) } /// Retrieves a list of [`BlockHeader`] that include relevant notes to the client. @@ -210,7 +210,7 @@ pub trait Store: Send + Sync { /// column is updated to `true` to signify that the block now contains a relevant note. async fn insert_block_header( &self, - block_header: BlockHeader, + block_header: &BlockHeader, chain_mmr_peaks: MmrPeaks, has_client_notes: bool, ) -> Result<(), StoreError>; diff --git a/crates/rust-client/src/store/sqlite_store/chain_data.rs b/crates/rust-client/src/store/sqlite_store/chain_data.rs index 5c0e2e181..444b3e6da 100644 --- a/crates/rust-client/src/store/sqlite_store/chain_data.rs +++ b/crates/rust-client/src/store/sqlite_store/chain_data.rs @@ -322,6 +322,9 @@ mod test { .into_iter() .map(|(block_header, _has_notes)| block_header) .collect(); - assert_eq!(&[mock_block_headers[1], mock_block_headers[3]], &block_headers[..]); + assert_eq!( + &[mock_block_headers[1].clone(), mock_block_headers[3].clone()], + &block_headers[..] + ); } } diff --git a/crates/rust-client/src/store/sqlite_store/mod.rs b/crates/rust-client/src/store/sqlite_store/mod.rs index 3d9252e3b..1f94b3835 100644 --- a/crates/rust-client/src/store/sqlite_store/mod.rs +++ b/crates/rust-client/src/store/sqlite_store/mod.rs @@ -192,10 +192,11 @@ impl Store for SqliteStore { async fn insert_block_header( &self, - block_header: BlockHeader, + block_header: &BlockHeader, chain_mmr_peaks: MmrPeaks, has_client_notes: bool, ) -> Result<(), StoreError> { + let block_header = block_header.clone(); self.interact_with_connection(move |conn| { SqliteStore::insert_block_header( conn, diff --git a/crates/rust-client/src/store/web_store/account/models.rs b/crates/rust-client/src/store/web_store/account/models.rs index 450e388d3..b0fd19e25 100644 --- a/crates/rust-client/src/store/web_store/account/models.rs +++ b/crates/rust-client/src/store/web_store/account/models.rs @@ -4,6 +4,7 @@ use base64::{engine::general_purpose, Engine as _}; use serde::{de::Error, Deserialize, Deserializer, Serialize}; #[derive(Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] pub struct AccountCodeIdxdbObject { pub root: String, #[serde(deserialize_with = "base64_to_vec_u8_required", default)] @@ -11,6 +12,7 @@ pub struct AccountCodeIdxdbObject { } #[derive(Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] pub struct AccountStorageIdxdbObject { pub root: String, #[serde(deserialize_with = "base64_to_vec_u8_required", default)] @@ -18,6 +20,7 @@ pub struct AccountStorageIdxdbObject { } #[derive(Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] pub struct AccountVaultIdxdbObject { pub root: String, #[serde(deserialize_with = "base64_to_vec_u8_required", default)] @@ -25,6 +28,7 @@ pub struct AccountVaultIdxdbObject { } #[derive(Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] pub struct AccountRecordIdxdbObject { pub id: String, pub nonce: String, @@ -37,6 +41,7 @@ pub struct AccountRecordIdxdbObject { } #[derive(Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] pub struct ForeignAcountCodeIdxdbObject { pub account_id: String, #[serde(deserialize_with = "base64_to_vec_u8_required", default)] diff --git a/crates/rust-client/src/store/web_store/chain_data/models.rs b/crates/rust-client/src/store/web_store/chain_data/models.rs index c44400fc5..6f5fdcb03 100644 --- a/crates/rust-client/src/store/web_store/chain_data/models.rs +++ b/crates/rust-client/src/store/web_store/chain_data/models.rs @@ -4,6 +4,7 @@ use base64::{engine::general_purpose, Engine as _}; use serde::{de::Error, Deserialize, Deserializer, Serialize}; #[derive(Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] pub struct BlockHeaderIdxdbObject { pub block_num: String, #[serde(deserialize_with = "base64_to_vec_u8_required", default)] @@ -14,12 +15,14 @@ pub struct BlockHeaderIdxdbObject { } #[derive(Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] pub struct ChainMmrNodeIdxdbObject { pub id: String, pub node: String, } #[derive(Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] pub struct MmrPeaksIdxdbObject { #[serde(deserialize_with = "base64_to_vec_u8_optional", default)] pub peaks: Option>, diff --git a/crates/rust-client/src/store/web_store/export/js_bindings.rs b/crates/rust-client/src/store/web_store/export/js_bindings.rs new file mode 100644 index 000000000..cfbb2b966 --- /dev/null +++ b/crates/rust-client/src/store/web_store/export/js_bindings.rs @@ -0,0 +1,8 @@ +use wasm_bindgen::prelude::*; +use wasm_bindgen_futures::{js_sys, wasm_bindgen}; + +#[wasm_bindgen(module = "/src/store/web_store/js/export.js")] +extern "C" { + #[wasm_bindgen(js_name = exportStore)] + pub fn idxdb_export_store() -> js_sys::Promise; +} diff --git a/crates/rust-client/src/store/web_store/export/mod.rs b/crates/rust-client/src/store/web_store/export/mod.rs new file mode 100644 index 000000000..8fa688e84 --- /dev/null +++ b/crates/rust-client/src/store/web_store/export/mod.rs @@ -0,0 +1,17 @@ +use super::WebStore; +use crate::store::StoreError; + +mod js_bindings; +use js_bindings::idxdb_export_store; +use wasm_bindgen::JsValue; +use wasm_bindgen_futures::JsFuture; + +impl WebStore { + pub async fn export_store(&self) -> Result { + let promise = idxdb_export_store(); + let js_value = JsFuture::from(promise) + .await + .map_err(|err| StoreError::DatabaseError(format!("Failed to export store: {err:?}")))?; + Ok(js_value) + } +} diff --git a/crates/rust-client/src/store/web_store/import/js_bindings.rs b/crates/rust-client/src/store/web_store/import/js_bindings.rs new file mode 100644 index 000000000..285a1ca43 --- /dev/null +++ b/crates/rust-client/src/store/web_store/import/js_bindings.rs @@ -0,0 +1,9 @@ +use wasm_bindgen::prelude::*; +use wasm_bindgen_futures::{js_sys, wasm_bindgen}; + +#[wasm_bindgen(module = "/src/store/web_store/js/import.js")] +extern "C" { + #[wasm_bindgen(js_name = forceImportStore)] + pub fn idxdb_force_import_store(store_dump: JsValue) -> js_sys::Promise; + +} diff --git a/crates/rust-client/src/store/web_store/import/mod.rs b/crates/rust-client/src/store/web_store/import/mod.rs new file mode 100644 index 000000000..25b7a1c9e --- /dev/null +++ b/crates/rust-client/src/store/web_store/import/mod.rs @@ -0,0 +1,17 @@ +use super::WebStore; +use crate::store::StoreError; + +mod js_bindings; +use js_bindings::idxdb_force_import_store; +use wasm_bindgen::JsValue; +use wasm_bindgen_futures::JsFuture; + +impl WebStore { + pub async fn force_import_store(&self, store_dump: JsValue) -> Result<(), StoreError> { + let promise = idxdb_force_import_store(store_dump); + JsFuture::from(promise) + .await + .map_err(|err| StoreError::DatabaseError(format!("Failed to import store: {err:?}")))?; + Ok(()) + } +} diff --git a/crates/rust-client/src/store/web_store/js/accounts.js b/crates/rust-client/src/store/web_store/js/accounts.js index 0ea464e2b..0fd82ee02 100644 --- a/crates/rust-client/src/store/web_store/js/accounts.js +++ b/crates/rust-client/src/store/web_store/js/accounts.js @@ -54,10 +54,10 @@ export async function getAllAccountHeaders() { return { id: record.id, nonce: record.nonce, - vault_root: record.vaultRoot, - storage_root: record.storageRoot, - code_root: record.codeRoot, - account_seed: accountSeedBase64, // Now correctly formatted as Base64 + vaultRoot: record.vaultRoot, + storageRoot: record.storageRoot, + codeRoot: record.codeRoot, + accountSeed: accountSeedBase64, // Now correctly formatted as Base64 locked: record.locked, }; }) @@ -105,10 +105,10 @@ export async function getAccountHeader(accountId) { const AccountHeader = { id: mostRecentRecord.id, nonce: mostRecentRecord.nonce, - vault_root: mostRecentRecord.vaultRoot, - storage_root: mostRecentRecord.storageRoot, - code_root: mostRecentRecord.codeRoot, - account_seed: accountSeedBase64, + vaultRoot: mostRecentRecord.vaultRoot, + storageRoot: mostRecentRecord.storageRoot, + codeRoot: mostRecentRecord.codeRoot, + accountSeed: accountSeedBase64, locked: mostRecentRecord.locked, }; return AccountHeader; @@ -144,10 +144,10 @@ export async function getAccountHeaderByHash(accountHash) { const AccountHeader = { id: matchingRecord.id, nonce: matchingRecord.nonce, - vault_root: matchingRecord.vaultRoot, - storage_root: matchingRecord.storageRoot, - code_root: matchingRecord.codeRoot, - account_seed: accountSeedBase64, + vaultRoot: matchingRecord.vaultRoot, + storageRoot: matchingRecord.storageRoot, + codeRoot: matchingRecord.codeRoot, + accountSeed: accountSeedBase64, locked: matchingRecord.locked, }; return AccountHeader; @@ -304,26 +304,26 @@ export async function insertAccountAssetVault(vaultRoot, assets) { export async function insertAccountRecord( accountId, - code_root, - storage_root, - vault_root, + codeRoot, + storageRoot, + vaultRoot, nonce, committed, - account_seed, + accountSeed, hash ) { try { let accountSeedBlob = null; - if (account_seed) { - accountSeedBlob = new Blob([new Uint8Array(account_seed)]); + if (accountSeed) { + accountSeedBlob = new Blob([new Uint8Array(accountSeed)]); } // Prepare the data object to insert const data = { id: accountId, // Using accountId as the key - codeRoot: code_root, - storageRoot: storage_root, - vaultRoot: vault_root, + codeRoot: codeRoot, + storageRoot: storageRoot, + vaultRoot: vaultRoot, nonce: nonce, committed: committed, accountSeed: accountSeedBlob, diff --git a/crates/rust-client/src/store/web_store/js/chainData.js b/crates/rust-client/src/store/web_store/js/chainData.js index e9875941a..4eab0e34e 100644 --- a/crates/rust-client/src/store/web_store/js/chainData.js +++ b/crates/rust-client/src/store/web_store/js/chainData.js @@ -86,10 +86,10 @@ export async function getBlockHeaders(blockNumbers) { const chainMmrPeaksBase64 = uint8ArrayToBase64(chainMmrPeaksArray); return { - block_num: result.blockNum, + blockNum: result.blockNum, header: headerBase64, - chain_mmr: chainMmrPeaksBase64, - has_client_notes: result.hasClientNotes === "true", + chainMmr: chainMmrPeaksBase64, + hasClientNotes: result.hasClientNotes === "true", }; } }) @@ -123,10 +123,10 @@ export async function getTrackedBlockHeaders() { const chainMmrPeaksBase64 = uint8ArrayToBase64(chainMmrPeaksArray); return { - block_num: record.blockNum, + blockNum: record.blockNum, header: headerBase64, - chain_mmr: chainMmrPeaksBase64, - has_client_notes: record.hasClientNotes === "true", + chainMmr: chainMmrPeaksBase64, + hasClientNotes: record.hasClientNotes === "true", }; }) ); diff --git a/crates/rust-client/src/store/web_store/js/export.js b/crates/rust-client/src/store/web_store/js/export.js new file mode 100644 index 000000000..3a15937d6 --- /dev/null +++ b/crates/rust-client/src/store/web_store/js/export.js @@ -0,0 +1,45 @@ +import { db } from "./schema.js"; + +async function recursivelyTransformForExport(obj) { + if (obj instanceof Blob) { + const blobBuffer = await obj.arrayBuffer(); + return { + __type: "Blob", + data: uint8ArrayToBase64(new Uint8Array(blobBuffer)), + }; + } + + if (Array.isArray(obj)) { + return await Promise.all(obj.map(recursivelyTransformForExport)); + } + + if (obj && typeof obj === "object") { + const entries = await Promise.all( + Object.entries(obj).map(async ([key, value]) => [ + key, + await recursivelyTransformForExport(value), + ]) + ); + return Object.fromEntries(entries); + } + + return obj; +} + +export async function exportStore() { + const dbJson = {}; + for (const table of db.tables) { + const records = await table.toArray(); + + dbJson[table.name] = await Promise.all( + records.map(recursivelyTransformForExport) + ); + } + + const stringified = JSON.stringify(dbJson); + return stringified; +} + +function uint8ArrayToBase64(uint8Array) { + return btoa(String.fromCharCode(...uint8Array)); +} diff --git a/crates/rust-client/src/store/web_store/js/import.js b/crates/rust-client/src/store/web_store/js/import.js new file mode 100644 index 000000000..d06d16570 --- /dev/null +++ b/crates/rust-client/src/store/web_store/js/import.js @@ -0,0 +1,88 @@ +import { db, openDatabase } from "./schema.js"; + +async function recursivelyTransformForImport(obj) { + if (obj && typeof obj === "object") { + if (obj.__type === "Blob") { + return new Blob([base64ToUint8Array(obj.data)]); + } + + if (Array.isArray(obj)) { + return await Promise.all(obj.map(recursivelyTransformForImport)); + } + + const entries = await Promise.all( + Object.entries(obj).map(async ([key, value]) => [ + key, + await recursivelyTransformForImport(value), + ]) + ); + return Object.fromEntries(entries); + } + + return obj; // Return unchanged if it's neither Blob, Array, nor Object +} + +export async function forceImportStore(jsonStr) { + try { + if (!db.isOpen) { + await openDatabase(); + } + + let dbJson = JSON.parse(jsonStr); + if (typeof dbJson === "string") { + dbJson = JSON.parse(dbJson); + } + + const jsonTableNames = Object.keys(dbJson); + const dbTableNames = db.tables.map((t) => t.name); + + if (jsonTableNames.length === 0) { + throw new Error("No tables found in the provided JSON."); + } + + // Wrap everything in a transaction + await db.transaction( + "rw", + ...dbTableNames.map((name) => db.table(name)), + async () => { + // Clear all tables in the database + await Promise.all(db.tables.map((t) => t.clear())); + + // Import data from JSON into matching tables + for (const tableName of jsonTableNames) { + const table = db.table(tableName); + + if (!dbTableNames.includes(tableName)) { + console.warn( + `Table "${tableName}" does not exist in the database schema. Skipping.` + ); + continue; // Skip tables not in the Dexie schema + } + + const records = dbJson[tableName]; + + const transformedRecords = await Promise.all( + records.map(recursivelyTransformForImport) + ); + + await table.bulkPut(transformedRecords); + } + } + ); + + console.log("Store imported successfully."); + } catch (err) { + console.error("Failed to import store: ", err); + throw err; + } +} + +function base64ToUint8Array(base64) { + const binaryString = atob(base64); + const len = binaryString.length; + const bytes = new Uint8Array(len); + for (let i = 0; i < len; i++) { + bytes[i] = binaryString.charCodeAt(i); + } + return bytes; +} diff --git a/crates/rust-client/src/store/web_store/js/notes.js b/crates/rust-client/src/store/web_store/js/notes.js index 6e0f46684..8596dbdf3 100644 --- a/crates/rust-client/src/store/web_store/js/notes.js +++ b/crates/rust-client/src/store/web_store/js/notes.js @@ -257,10 +257,10 @@ async function processInputNotes(notes) { return { assets: note.assets, - serial_number: note.serialNumber, + serialNumber: note.serialNumber, inputs: note.inputs, - created_at: note.createdAt, - serialized_note_script: serializedNoteScriptBase64, + createdAt: note.createdAt, + serializedNoteScript: serializedNoteScriptBase64, state: note.state, }; }) @@ -292,9 +292,9 @@ async function processOutputNotes(notes) { return { assets: note.assets, - recipient_digest: note.recipientDigest, + recipientDigest: note.recipientDigest, metadata: note.metadata, - expected_height: note.expectedHeight, + expectedHeight: note.expectedHeight, state: note.state, }; }) diff --git a/crates/rust-client/src/store/web_store/js/sync.js b/crates/rust-client/src/store/web_store/js/sync.js index ed7cafd03..3a7cf614e 100644 --- a/crates/rust-client/src/store/web_store/js/sync.js +++ b/crates/rust-client/src/store/web_store/js/sync.js @@ -14,10 +14,10 @@ export async function getNoteTags() { let records = await tags.toArray(); let processedRecords = records.map((record) => { - record.source_note_id = - record.source_note_id == "" ? null : record.source_note_id; - record.source_account_id = - record.source_account_id == "" ? null : record.source_account_id; + record.sourceNoteId = + record.sourceNoteId == "" ? null : record.sourceNoteId; + record.sourceAccountId = + record.sourceAccountId == "" ? null : record.sourceAccountId; return record; }); @@ -33,7 +33,7 @@ export async function getSyncHeight() { const record = await stateSync.get(1); // Since id is the primary key and always 1 if (record) { let data = { - block_num: record.blockNum, + blockNum: record.blockNum, }; return data; } else { @@ -45,14 +45,14 @@ export async function getSyncHeight() { } } -export async function addNoteTag(tag, source_note_id, source_account_id) { +export async function addNoteTag(tag, sourceNoteId, sourceAccountId) { try { let tagArray = new Uint8Array(tag); let tagBase64 = uint8ArrayToBase64(tagArray); await tags.add({ tag: tagBase64, - source_note_id: source_note_id ? source_note_id : "", - source_account_id: source_account_id ? source_account_id : "", + sourceNoteId: sourceNoteId ? sourceNoteId : "", + sourceAccountId: sourceAccountId ? sourceAccountId : "", }); } catch (err) { console.error("Failed to add note tag: ", err); @@ -60,7 +60,7 @@ export async function addNoteTag(tag, source_note_id, source_account_id) { } } -export async function removeNoteTag(tag, source_note_id, source_account_id) { +export async function removeNoteTag(tag, sourceNoteId, sourceAccountId) { try { let tagArray = new Uint8Array(tag); let tagBase64 = uint8ArrayToBase64(tagArray); @@ -68,8 +68,8 @@ export async function removeNoteTag(tag, source_note_id, source_account_id) { return await tags .where({ tag: tagBase64, - source_note_id: source_note_id ? source_note_id : "", - source_account_id: source_account_id ? source_account_id : "", + sourceNoteId: sourceNoteId ? sourceNoteId : "", + sourceAccountId: sourceAccountId ? sourceAccountId : "", }) .delete(); } catch { diff --git a/crates/rust-client/src/store/web_store/js/transactions.js b/crates/rust-client/src/store/web_store/js/transactions.js index e13dcf7b2..3d42836ac 100644 --- a/crates/rust-client/src/store/web_store/js/transactions.js +++ b/crates/rust-client/src/store/web_store/js/transactions.js @@ -61,17 +61,17 @@ export async function getTransactions(filter) { let data = { id: transactionRecord.id, - account_id: transactionRecord.accountId, - init_account_state: transactionRecord.initAccountState, - final_account_state: transactionRecord.finalAccountState, - input_notes: transactionRecord.inputNotes, - output_notes: transactionRecord.outputNotes, - script_hash: transactionRecord.scriptHash + accountId: transactionRecord.accountId, + initAccountState: transactionRecord.initAccountState, + finalAccountState: transactionRecord.finalAccountState, + inputNotes: transactionRecord.inputNotes, + outputNotes: transactionRecord.outputNotes, + scriptHash: transactionRecord.scriptHash ? transactionRecord.scriptHash : null, - tx_script: txScriptBase64, - block_num: transactionRecord.blockNum, - commit_height: transactionRecord.commitHeight + txScript: txScriptBase64, + blockNum: transactionRecord.blockNum, + commitHeight: transactionRecord.commitHeight ? transactionRecord.commitHeight : null, }; diff --git a/crates/rust-client/src/store/web_store/mod.rs b/crates/rust-client/src/store/web_store/mod.rs index 7a433adb0..451c19cfb 100644 --- a/crates/rust-client/src/store/web_store/mod.rs +++ b/crates/rust-client/src/store/web_store/mod.rs @@ -36,6 +36,8 @@ compile_error!("The `idxdb` feature is only supported when targeting wasm32."); pub mod account; pub mod chain_data; +pub mod export; +pub mod import; pub mod note; pub mod sync; pub mod transaction; @@ -132,11 +134,11 @@ impl Store for WebStore { async fn insert_block_header( &self, - block_header: BlockHeader, + block_header: &BlockHeader, chain_mmr_peaks: MmrPeaks, has_client_notes: bool, ) -> Result<(), StoreError> { - self.insert_block_header(&block_header, chain_mmr_peaks, has_client_notes).await + self.insert_block_header(block_header, chain_mmr_peaks, has_client_notes).await } async fn get_block_headers( diff --git a/crates/rust-client/src/store/web_store/note/models.rs b/crates/rust-client/src/store/web_store/note/models.rs index 08856fa01..51a7bed75 100644 --- a/crates/rust-client/src/store/web_store/note/models.rs +++ b/crates/rust-client/src/store/web_store/note/models.rs @@ -4,6 +4,7 @@ use base64::{engine::general_purpose, Engine as _}; use serde::{de::Error, Deserialize, Deserializer, Serialize}; #[derive(Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] pub struct InputNoteIdxdbObject { #[serde(deserialize_with = "base64_to_vec_u8_required", default)] pub assets: Vec, @@ -19,6 +20,7 @@ pub struct InputNoteIdxdbObject { } #[derive(Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] pub struct OutputNoteIdxdbObject { #[serde(deserialize_with = "base64_to_vec_u8_required", default)] pub assets: Vec, diff --git a/crates/rust-client/src/store/web_store/sync/models.rs b/crates/rust-client/src/store/web_store/sync/models.rs index 7c4db9d61..7b75971ff 100644 --- a/crates/rust-client/src/store/web_store/sync/models.rs +++ b/crates/rust-client/src/store/web_store/sync/models.rs @@ -4,11 +4,13 @@ use base64::{engine::general_purpose, Engine as _}; use serde::{de::Error, Deserialize, Deserializer, Serialize}; #[derive(Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] pub struct SyncHeightIdxdbObject { pub block_num: String, } #[derive(Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] pub struct NoteTagIdxdbObject { #[serde(deserialize_with = "base64_to_vec_u8_required", default)] pub tag: Vec, diff --git a/crates/rust-client/src/store/web_store/transaction/models.rs b/crates/rust-client/src/store/web_store/transaction/models.rs index c0f1fceaa..0d4549db4 100644 --- a/crates/rust-client/src/store/web_store/transaction/models.rs +++ b/crates/rust-client/src/store/web_store/transaction/models.rs @@ -4,6 +4,7 @@ use base64::{engine::general_purpose, Engine as _}; use serde::{de::Error, Deserialize, Deserializer, Serialize}; #[derive(Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] pub struct TransactionIdxdbObject { pub id: String, pub account_id: String, // usually i64 diff --git a/crates/rust-client/src/sync/block_header.rs b/crates/rust-client/src/sync/block_header.rs index 614d01b87..83e70d546 100644 --- a/crates/rust-client/src/sync/block_header.rs +++ b/crates/rust-client/src/sync/block_header.rs @@ -43,7 +43,7 @@ impl Client { /// Attempts to retrieve the genesis block from the store. If not found, /// it requests it from the node and store it. - pub(crate) async fn ensure_genesis_in_place(&mut self) -> Result { + pub async fn ensure_genesis_in_place(&mut self) -> Result { let genesis = self.store.get_block_header_by_num(0.into()).await?; match genesis { @@ -64,7 +64,7 @@ impl Client { MmrPeaks::new(0, vec![]).expect("Blank MmrPeaks should not fail to instantiate"); // We specify that we want to store the MMR data from the genesis block as we might use it // as an anchor for created accounts. - self.store.insert_block_header(genesis_block, blank_mmr_peaks, true).await?; + self.store.insert_block_header(&genesis_block, blank_mmr_peaks, true).await?; Ok(genesis_block) } @@ -147,7 +147,7 @@ impl Client { // Insert header and MMR nodes self.store - .insert_block_header(block_header, current_partial_mmr.peaks(), true) + .insert_block_header(&block_header, current_partial_mmr.peaks(), true) .await?; self.store.insert_chain_mmr_nodes(&path_nodes).await?; diff --git a/crates/rust-client/src/sync/mod.rs b/crates/rust-client/src/sync/mod.rs index 411465076..96f7b4118 100644 --- a/crates/rust-client/src/sync/mod.rs +++ b/crates/rust-client/src/sync/mod.rs @@ -76,7 +76,7 @@ mod state_sync; pub use state_sync::{on_note_received, OnNoteReceived, StateSync}; mod state_sync_update; -pub use state_sync_update::StateSyncUpdate; +pub use state_sync_update::{AccountUpdates, BlockUpdates, StateSyncUpdate, TransactionUpdates}; // CONSTANTS // ================================================================================================ @@ -217,6 +217,7 @@ impl SyncSummary { && self.consumed_notes.is_empty() && self.updated_accounts.is_empty() && self.locked_accounts.is_empty() + && self.committed_transactions.is_empty() } pub fn combine_with(&mut self, mut other: Self) { @@ -225,5 +226,6 @@ impl SyncSummary { self.consumed_notes.append(&mut other.consumed_notes); self.updated_accounts.append(&mut other.updated_accounts); self.locked_accounts.append(&mut other.locked_accounts); + self.committed_transactions.append(&mut other.committed_transactions); } } diff --git a/crates/rust-client/src/sync/state_sync.rs b/crates/rust-client/src/sync/state_sync.rs index df3f180af..56e207877 100644 --- a/crates/rust-client/src/sync/state_sync.rs +++ b/crates/rust-client/src/sync/state_sync.rs @@ -10,16 +10,14 @@ use miden_objects::{ }; use tracing::info; -use super::{block_header::BlockUpdates, StateSyncUpdate}; +use super::{AccountUpdates, BlockUpdates, StateSyncUpdate, TransactionUpdates}; use crate::{ - account::AccountUpdates, note::{NoteScreener, NoteUpdates}, rpc::{ domain::{note::CommittedNote, nullifier::NullifierUpdate, transaction::TransactionUpdate}, NodeRpcClient, }, store::{InputNoteRecord, NoteFilter, OutputNoteRecord, Store, StoreError}, - transaction::TransactionUpdates, ClientError, }; @@ -183,7 +181,8 @@ impl StateSync { return Ok(false); } - state_sync_update.block_num = response.block_header.block_num(); + let new_block_num = response.block_header.block_num(); + state_sync_update.block_num = new_block_num; let account_updates = self.account_state_sync(accounts, &response.account_hash_updates).await?; @@ -214,7 +213,7 @@ impl StateSync { new_authentication_nodes, )); - if response.chain_tip == response.block_header.block_num() { + if response.chain_tip == new_block_num { Ok(false) } else { Ok(true) diff --git a/crates/rust-client/src/sync/state_sync_update.rs b/crates/rust-client/src/sync/state_sync_update.rs index e00cf5009..7ac455c95 100644 --- a/crates/rust-client/src/sync/state_sync_update.rs +++ b/crates/rust-client/src/sync/state_sync_update.rs @@ -1,3 +1,5 @@ +use alloc::vec::Vec; + use miden_objects::{ account::AccountId, block::{BlockHeader, BlockNumber}, diff --git a/crates/rust-client/src/transaction/mod.rs b/crates/rust-client/src/transaction/mod.rs index a50952ad3..efad02d46 100644 --- a/crates/rust-client/src/transaction/mod.rs +++ b/crates/rust-client/src/transaction/mod.rs @@ -95,7 +95,7 @@ use super::{Client, FeltRng}; use crate::{ account::procedure_roots::RPO_FALCON_512_AUTH, note::{NoteScreener, NoteUpdates}, - rpc::domain::{account::AccountProof, transaction::TransactionUpdate}, + rpc::domain::account::AccountProof, store::{ input_note_states::ExpectedNoteState, InputNoteRecord, InputNoteState, NoteFilter, OutputNoteRecord, StoreError, TransactionFilter, diff --git a/crates/web-client/Cargo.toml b/crates/web-client/Cargo.toml index 996e2f3db..38828542d 100644 --- a/crates/web-client/Cargo.toml +++ b/crates/web-client/Cargo.toml @@ -27,7 +27,6 @@ miden-lib = { workspace = true } miden-objects = { workspace = true } miden-tx = { workspace = true } rand = { workspace = true } -serde = { workspace = true } serde-wasm-bindgen = { version = "0.6" } wasm-bindgen = { version = "0.2", features = ["serde-serialize"] } wasm-bindgen-futures = { version = "0.4" } diff --git a/crates/web-client/js/index.js b/crates/web-client/js/index.js index f0716094b..7a6628a28 100644 --- a/crates/web-client/js/index.js +++ b/crates/web-client/js/index.js @@ -40,11 +40,7 @@ const { TransactionScriptInputPairArray, Word, WebClient, -} = await wasm({ - importHook: () => { - return new URL("assets/miden_client_web.wasm", import.meta.url); // the name before .wasm needs to match the package name in Cargo.toml - }, -}); +} = wasm; export { Account, diff --git a/crates/web-client/js/types/index.d.ts b/crates/web-client/js/types/index.d.ts index f8df2c87b..705755f53 100644 --- a/crates/web-client/js/types/index.d.ts +++ b/crates/web-client/js/types/index.d.ts @@ -11,7 +11,6 @@ export { FungibleAsset, InputNoteState, NewSwapTransactionResult, - NewTransactionResult, Note, NoteAssets, NoteConsumability, diff --git a/crates/web-client/js/wasm.js b/crates/web-client/js/wasm.js index aa6e8957d..08ea1dbe6 100644 --- a/crates/web-client/js/wasm.js +++ b/crates/web-client/js/wasm.js @@ -1 +1,2 @@ -export { default } from "../Cargo.toml"; +import * as wasmModule from "../Cargo.toml"; +export default wasmModule; diff --git a/crates/web-client/package.json b/crates/web-client/package.json index 15c5a0fb6..af8760bba 100644 --- a/crates/web-client/package.json +++ b/crates/web-client/package.json @@ -1,6 +1,6 @@ { "name": "@demox-labs/miden-sdk", - "version": "0.6.1-next.4", + "version": "0.7.1-next.0", "description": "Polygon Miden Wasm SDK", "collaborators": [ "Polygon Miden", @@ -31,7 +31,7 @@ "@types/chai-as-promised": "^8.0.0", "@types/mocha": "^10.0.7", "@types/node": "^22.4.1", - "@wasm-tool/rollup-plugin-rust": "wasm-tool/rollup-plugin-rust", + "@wasm-tool/rollup-plugin-rust": "^3.0.3", "chai": "^5.1.1", "cpr": "^3.0.1", "cross-env": "^7.0.3", diff --git a/crates/web-client/rollup.config.js b/crates/web-client/rollup.config.js index c02f61fa9..ddc965fb4 100644 --- a/crates/web-client/rollup.config.js +++ b/crates/web-client/rollup.config.js @@ -6,24 +6,31 @@ import commonjs from "@rollup/plugin-commonjs"; const testing = process.env.MIDEN_WEB_TESTING === "true"; /** - * Rollup configuration file for building a Cargo project and creating a WebAssembly (WASM) module. - * The configuration sets up two build processes: - * 1. Compiling Rust code into WASM using the @wasm-tool/rollup-plugin-rust plugin, with specific - * cargo arguments to enable WebAssembly features and set maximum memory limits. If the build is - * meant for testing, the WASM optimization level is set to 0 to improve build times, this is - * aimed at reducing the feedback loop during development. - * 2. Resolving and bundling the generated WASM module along with the main JavaScript file - * (`index.js`) into the `dist` directory. + * Rollup configuration file for building a Cargo project and creating a WebAssembly (WASM) module, + * as well as bundling a dedicated web worker file. * - * The first configuration targets `wasm.js` to generate the WASM module, while the second - * configuration targets `index.js` for the main entry point of the application. - * Both configurations output ES module format files with source maps for easier debugging. + * The configuration sets up three build processes: + * + * 1. **WASM Module Build:** + * Compiles Rust code into WASM using the @wasm-tool/rollup-plugin-rust plugin. This process + * applies specific cargo arguments to enable necessary WebAssembly features (such as atomics, + * bulk memory operations, and mutable globals) and to set maximum memory limits. For testing builds, + * the WASM optimization level is set to 0 to improve build times, reducing the feedback loop during development. + * + * 2. **Worker Build:** + * Bundles the dedicated web worker file (`web-client-methods-worker.js`) into the `dist/workers` directory. + * This configuration resolves WASM module imports and uses the copy plugin to ensure that the generated + * WASM assets are available to the worker. + * + * 3. **Main Entry Point Build:** + * Resolves and bundles the main JavaScript file (`index.js`) for the primary entry point of the application + * into the `dist` directory. + * + * Each build configuration outputs ES module format files with source maps to facilitate easier debugging. */ export default [ { - input: { - wasm: "./js/wasm.js", - }, + input: "./js/wasm.js", output: { dir: `dist`, format: "es", @@ -32,28 +39,29 @@ export default [ }, plugins: [ rust({ - cargoArgs: [ - "--features", - "testing", - "--config", - `build.rustflags=["-C", "target-feature=+atomics,+bulk-memory,+mutable-globals", "-C", "link-arg=--max-memory=4294967296"]`, - "--no-default-features", - ], - + extraArgs: { + cargo: [ + "--features", + "testing", + "--config", + `build.rustflags=["-C", "target-feature=+atomics,+bulk-memory,+mutable-globals", "-C", "link-arg=--max-memory=4294967296"]`, + "--no-default-features", + ], + wasmOpt: testing + ? ["-O0", "--enable-threads", "--enable-bulk-memory-opt"] + : ["--enable-threads", "--enable-bulk-memory-opt"], + }, experimental: { typescriptDeclarationDir: "dist/crates", }, - - wasmOptArgs: testing ? ["-O0"] : null, }), resolve(), commonjs(), ], }, + // Build the main entry point { - input: { - index: "./js/index.js", - }, + input: "./js/index.js", output: { dir: `dist`, format: "es", diff --git a/crates/web-client/src/account.rs b/crates/web-client/src/account.rs index db6cc31b1..8a9289062 100644 --- a/crates/web-client/src/account.rs +++ b/crates/web-client/src/account.rs @@ -9,6 +9,7 @@ use crate::{ #[wasm_bindgen] impl WebClient { + #[wasm_bindgen(js_name = "getAccounts")] pub async fn get_accounts(&mut self) -> Result, JsValue> { if let Some(client) = self.get_mut_inner() { let result = client @@ -22,6 +23,7 @@ impl WebClient { } } + #[wasm_bindgen(js_name = "getAccount")] pub async fn get_account( &mut self, account_id: &AccountId, diff --git a/crates/web-client/src/export.rs b/crates/web-client/src/export.rs index f6e1422ba..ea0938e30 100644 --- a/crates/web-client/src/export.rs +++ b/crates/web-client/src/export.rs @@ -13,6 +13,7 @@ pub enum ExportType { #[wasm_bindgen] impl WebClient { + #[wasm_bindgen(js_name = "exportNote")] pub async fn export_note( &mut self, note_id: String, @@ -69,4 +70,16 @@ impl WebClient { Err(JsValue::from_str("Client not initialized")) } } + + /// Retrieves the entire underlying web store and returns it as a JsValue + /// + /// Meant to be used in conjunction with the force_import_store method + #[wasm_bindgen(js_name = "exportStore")] + pub async fn export_store(&mut self) -> Result { + let store = self.store.as_ref().ok_or(JsValue::from_str("Store not initialized"))?; + let export = + store.export_store().await.map_err(|err| JsValue::from_str(&format!("{err}")))?; + + Ok(export) + } } diff --git a/crates/web-client/src/helpers.rs b/crates/web-client/src/helpers.rs new file mode 100644 index 000000000..a35dc405a --- /dev/null +++ b/crates/web-client/src/helpers.rs @@ -0,0 +1,75 @@ +use miden_client::{ + account::{Account, AccountBuilder, AccountType}, + crypto::{RpoRandomCoin, SecretKey}, + Client, +}; +use miden_lib::account::{auth::RpoFalcon512, wallets::BasicWallet}; +use miden_objects::Felt; +use rand::{rngs::StdRng, Rng, SeedableRng}; +use wasm_bindgen::JsValue; + +use crate::models::account_storage_mode::AccountStorageMode; + +// HELPERS +// ================================================================================================ +// These methods should not be exposed to the wasm interface + +/// Serves as a way to manage the logic of seed generation and retrieval of the anchor block +/// for creating a wallet account +/// +/// We currently use the genesis block as the anchor block to ensure deterministic outcomes +/// +/// # Errors: +/// - If rust client calls fail +/// - If the seed is passed in and is not exactly 32 bytes +pub(crate) async fn generate_wallet( + client: &mut Client, + storage_mode: &AccountStorageMode, + mutable: bool, + seed: Option>, +) -> Result<(Account, [Felt; 4], SecretKey), JsValue> { + let mut rng = match seed { + Some(seed_bytes) => { + // Attempt to convert the seed slice into a 32-byte array. + let seed_array: [u8; 32] = seed_bytes + .try_into() + .map_err(|_| JsValue::from_str("Seed must be exactly 32 bytes"))?; + let mut std_rng = StdRng::from_seed(seed_array); + let coin_seed: [u64; 4] = std_rng.gen(); + &mut RpoRandomCoin::new(coin_seed.map(Felt::new)) + }, + None => client.rng(), + }; + let key_pair = SecretKey::with_rng(&mut rng); + + let mut init_seed = [0u8; 32]; + rng.fill_bytes(&mut init_seed); + + let account_type = if mutable { + AccountType::RegularAccountUpdatableCode + } else { + AccountType::RegularAccountImmutableCode + }; + + let anchor_block = client + .ensure_genesis_in_place() + .await + .map_err(|err| JsValue::from_str(&format!("Failed to create new wallet: {err:?}")))?; + + let (new_account, account_seed) = match AccountBuilder::new(init_seed) + .anchor((&anchor_block).try_into().unwrap()) + .account_type(account_type) + .storage_mode(storage_mode.into()) + .with_component(RpoFalcon512::new(key_pair.public_key())) + .with_component(BasicWallet) + .build() + { + Ok(result) => result, + Err(err) => { + let error_message = format!("Failed to create new wallet: {err:?}"); + return Err(JsValue::from_str(&error_message)); + }, + }; + + Ok((new_account, account_seed, key_pair)) +} diff --git a/crates/web-client/src/import.rs b/crates/web-client/src/import.rs index 9c147df33..2c7a1dd5b 100644 --- a/crates/web-client/src/import.rs +++ b/crates/web-client/src/import.rs @@ -3,10 +3,14 @@ use miden_objects::{account::AccountFile, note::NoteFile, utils::Deserializable} use serde_wasm_bindgen::from_value; use wasm_bindgen::prelude::*; -use crate::WebClient; +use super::models::account::Account; +use crate::{ + helpers::generate_wallet, models::account_storage_mode::AccountStorageMode, WebClient, +}; #[wasm_bindgen] impl WebClient { + #[wasm_bindgen(js_name = "importAccount")] pub async fn import_account(&mut self, account_bytes: JsValue) -> Result { let keystore = self.keystore.clone(); if let Some(client) = self.get_mut_inner() { @@ -37,6 +41,28 @@ impl WebClient { } } + #[wasm_bindgen(js_name = "importPublicAccountFromSeed")] + pub async fn import_public_account_from_seed( + &mut self, + init_seed: Vec, + mutable: bool, + ) -> Result { + let client = self.get_mut_inner().ok_or(JsValue::from_str("Client not initialized"))?; + + let (generated_acct, ..) = + generate_wallet(client, &AccountStorageMode::public(), mutable, Some(init_seed)) + .await?; + + let account_id = generated_acct.id(); + client.import_account_by_id(account_id).await.map_err(|err| { + let error_message = format!("Failed to import account: {err:?}"); + JsValue::from_str(&error_message) + })?; + + Ok(Account::from(generated_acct)) + } + + #[wasm_bindgen(js_name = "importNote")] pub async fn import_note(&mut self, note_bytes: JsValue) -> Result { if let Some(client) = self.get_mut_inner() { let note_bytes_result: Vec = from_value(note_bytes).unwrap(); @@ -55,4 +81,18 @@ impl WebClient { Err(JsValue::from_str("Client not initialized")) } } + + // Destructive operation, will fully overwrite the current web store + // + // The input to this function should be the result of a call to `export_store` + #[wasm_bindgen(js_name = "forceImportStore")] + pub async fn force_import_store(&mut self, store_dump: JsValue) -> Result { + let store = self.store.as_ref().ok_or(JsValue::from_str("Store not initialized"))?; + store + .force_import_store(store_dump) + .await + .map_err(|err| JsValue::from_str(&format!("{err}")))?; + + Ok(JsValue::from_str("Store imported successfully")) + } } diff --git a/crates/web-client/src/lib.rs b/crates/web-client/src/lib.rs index 6dd96c9aa..e0eef5fbd 100644 --- a/crates/web-client/src/lib.rs +++ b/crates/web-client/src/lib.rs @@ -4,7 +4,7 @@ use alloc::sync::Arc; use console_error_panic_hook::set_once; use miden_client::{ authenticator::{keystore::WebKeyStore, ClientAuthenticator}, - rpc::WebTonicRpcClient, + rpc::{Endpoint, TonicRpcClient}, store::web_store::WebStore, Client, RemoteTransactionProver, }; @@ -14,6 +14,7 @@ use wasm_bindgen::prelude::*; pub mod account; pub mod export; +pub mod helpers; pub mod import; pub mod models; pub mod new_account; @@ -54,6 +55,7 @@ impl WebClient { self.inner.as_mut() } + #[wasm_bindgen(js_name = "createClient")] pub async fn create_client( &mut self, node_url: Option, @@ -83,9 +85,12 @@ impl WebClient { let keystore = WebKeyStore {}; let authenticator = Arc::new(ClientAuthenticator::new(rng, keystore.clone())); - let web_rpc_client = Arc::new(WebTonicRpcClient::new( - &node_url.unwrap_or_else(|| miden_client::rpc::Endpoint::testnet().to_string()), - )); + + let endpoint = node_url.map_or(Ok(Endpoint::testnet()), |url| { + Endpoint::try_from(url.as_str()).map_err(|_| JsValue::from_str("Invalid node URL")) + })?; + + let web_rpc_client = Arc::new(TonicRpcClient::new(&endpoint, 0)); self.remote_prover = prover_url.map(|prover_url| Arc::new(RemoteTransactionProver::new(prover_url))); diff --git a/crates/web-client/src/models/account.rs b/crates/web-client/src/models/account.rs index 2fd2e9d27..0d99baf4a 100644 --- a/crates/web-client/src/models/account.rs +++ b/crates/web-client/src/models/account.rs @@ -35,22 +35,27 @@ impl Account { self.0.code().into() } + #[wasm_bindgen(js_name = "isFaucet")] pub fn is_faucet(&self) -> bool { self.0.is_faucet() } + #[wasm_bindgen(js_name = "isRegularAccount")] pub fn is_regular_account(&self) -> bool { self.0.is_regular_account() } + #[wasm_bindgen(js_name = "isUpdatable")] pub fn is_updatable(&self) -> bool { matches!(self.0.account_type(), NativeAccountType::RegularAccountUpdatableCode) } + #[wasm_bindgen(js_name = "isPublic")] pub fn is_public(&self) -> bool { self.0.is_public() } + #[wasm_bindgen(js_name = "isNew")] pub fn is_new(&self) -> bool { self.0.is_new() } diff --git a/crates/web-client/src/models/account_delta.rs b/crates/web-client/src/models/account_delta.rs index e1e3edcc7..2c5015241 100644 --- a/crates/web-client/src/models/account_delta.rs +++ b/crates/web-client/src/models/account_delta.rs @@ -13,6 +13,7 @@ pub struct AccountDelta(NativeAccountDelta); #[wasm_bindgen] impl AccountDelta { + #[wasm_bindgen(js_name = "isEmpty")] pub fn is_empty(&self) -> bool { self.0.is_empty() } diff --git a/crates/web-client/src/models/account_header.rs b/crates/web-client/src/models/account_header.rs index 7d93007da..c539c6100 100644 --- a/crates/web-client/src/models/account_header.rs +++ b/crates/web-client/src/models/account_header.rs @@ -21,14 +21,17 @@ impl AccountHeader { self.0.nonce().into() } + #[wasm_bindgen(js_name = "vaultCommitment")] pub fn vault_commitment(&self) -> RpoDigest { self.0.vault_root().into() } + #[wasm_bindgen(js_name = "storageCommitment")] pub fn storage_commitment(&self) -> RpoDigest { self.0.storage_commitment().into() } + #[wasm_bindgen(js_name = "codeCommitment")] pub fn code_commitment(&self) -> RpoDigest { self.0.code_commitment().into() } diff --git a/crates/web-client/src/models/account_id.rs b/crates/web-client/src/models/account_id.rs index 296c2d4f7..2c8f00f90 100644 --- a/crates/web-client/src/models/account_id.rs +++ b/crates/web-client/src/models/account_id.rs @@ -9,19 +9,23 @@ pub struct AccountId(NativeAccountId); #[wasm_bindgen] impl AccountId { + #[wasm_bindgen(js_name = "fromHex")] pub fn from_hex(hex: &str) -> AccountId { let native_account_id = NativeAccountId::from_hex(hex).unwrap(); AccountId(native_account_id) } + #[wasm_bindgen(js_name = "isFaucet")] pub fn is_faucet(&self) -> bool { self.0.is_faucet() } + #[wasm_bindgen(js_name = "isRegularAccount")] pub fn is_regular_account(&self) -> bool { self.0.is_regular_account() } + #[wasm_bindgen(js_name = "toString")] #[allow(clippy::inherent_to_string)] pub fn to_string(&self) -> String { self.0.to_string() diff --git a/crates/web-client/src/models/account_storage_mode.rs b/crates/web-client/src/models/account_storage_mode.rs index ebd94e4cb..4493e5a74 100644 --- a/crates/web-client/src/models/account_storage_mode.rs +++ b/crates/web-client/src/models/account_storage_mode.rs @@ -30,3 +30,9 @@ impl From<&AccountStorageMode> for NativeAccountStorageMode { storage_mode.0 } } + +impl AccountStorageMode { + pub fn is_public(&self) -> bool { + self.0 == NativeAccountStorageMode::Public + } +} diff --git a/crates/web-client/src/models/accounts.rs b/crates/web-client/src/models/accounts.rs deleted file mode 100644 index 6738c56a7..000000000 --- a/crates/web-client/src/models/accounts.rs +++ /dev/null @@ -1,59 +0,0 @@ -use serde::{Deserialize, Serialize}; -use wasm_bindgen::prelude::*; - -// TODO - Revisit the Account struct and conform it to structure of -// the other structs in the models directory -#[wasm_bindgen] -#[derive(Serialize, Deserialize)] -#[allow(clippy::unsafe_derive_deserialize)] -pub struct SerializedAccountHeader { - id: String, - nonce: String, - vault_root: String, - storage_root: String, - code_root: String, -} - -#[wasm_bindgen] -impl SerializedAccountHeader { - pub fn new( - id: String, - nonce: String, - vault_root: String, - storage_root: String, - code_root: String, - ) -> SerializedAccountHeader { - SerializedAccountHeader { - id, - nonce, - vault_root, - storage_root, - code_root, - } - } - - #[wasm_bindgen(getter)] - pub fn id(&self) -> String { - self.id.clone() - } - - #[wasm_bindgen(getter)] - pub fn nonce(&self) -> String { - self.nonce.clone() - } - - #[wasm_bindgen(getter)] - pub fn vault_root(&self) -> String { - self.vault_root.clone() - } - - #[wasm_bindgen(getter)] - pub fn storage_root(&self) -> String { - self.storage_root.clone() - } - - #[wasm_bindgen(getter)] - pub fn code_root(&self) -> String { - self.code_root.clone() - } -} diff --git a/crates/web-client/src/models/advice_inputs.rs b/crates/web-client/src/models/advice_inputs.rs index 6e5b141ef..80c370ebb 100644 --- a/crates/web-client/src/models/advice_inputs.rs +++ b/crates/web-client/src/models/advice_inputs.rs @@ -19,6 +19,7 @@ impl AdviceInputs { self.0.stack().iter().map(Into::into).collect() } + #[wasm_bindgen(js_name = "mappedValues")] pub fn mapped_values(&self, key: &RpoDigest) -> Option> { self.0 .mapped_values(&key.into()) diff --git a/crates/web-client/src/models/asset_vault.rs b/crates/web-client/src/models/asset_vault.rs index 49d2dc602..c3f1c53d6 100644 --- a/crates/web-client/src/models/asset_vault.rs +++ b/crates/web-client/src/models/asset_vault.rs @@ -13,6 +13,7 @@ impl AssetVault { self.0.commitment().into() } + #[wasm_bindgen(js_name = "getBalance")] pub fn get_balance(&self, faucet_id: &AccountId) -> u64 { self.0.get_balance(faucet_id.into()).unwrap() } diff --git a/crates/web-client/src/models/auth_secret_key.rs b/crates/web-client/src/models/auth_secret_key.rs index ac2acce8c..b60176d50 100644 --- a/crates/web-client/src/models/auth_secret_key.rs +++ b/crates/web-client/src/models/auth_secret_key.rs @@ -12,6 +12,7 @@ pub struct AuthSecretKey(NativeAuthSecretKey); #[wasm_bindgen] impl AuthSecretKey { + #[wasm_bindgen(js_name = "getRpoFalcon512PublicKeyAsWord")] pub fn get_rpo_falcon_512_public_key_as_word(&self) -> Word { let public_key = match self.0 { NativeAuthSecretKey::RpoFalcon512(ref key) => key.public_key(), @@ -20,6 +21,7 @@ impl AuthSecretKey { public_key_as_native_word.into() } + #[wasm_bindgen(js_name = "getRpoFalcon512SecretKeyAsFelts")] pub fn get_rpo_falcon_512_secret_key_as_felts(&self) -> Vec { let secret_key_as_bytes = match self.0 { NativeAuthSecretKey::RpoFalcon512(ref key) => key.to_bytes(), diff --git a/crates/web-client/src/models/block_header.rs b/crates/web-client/src/models/block_header.rs index 6e2cc90e8..7d2a71955 100644 --- a/crates/web-client/src/models/block_header.rs +++ b/crates/web-client/src/models/block_header.rs @@ -17,42 +17,52 @@ impl BlockHeader { self.0.hash().into() } + #[wasm_bindgen(js_name = "subHash")] pub fn sub_hash(&self) -> RpoDigest { self.0.sub_hash().into() } + #[wasm_bindgen(js_name = "prevHash")] pub fn prev_hash(&self) -> RpoDigest { self.0.prev_hash().into() } + #[wasm_bindgen(js_name = "blockNum")] pub fn block_num(&self) -> u32 { self.0.block_num().as_u32() } + #[wasm_bindgen(js_name = "chainRoot")] pub fn chain_root(&self) -> RpoDigest { self.0.chain_root().into() } + #[wasm_bindgen(js_name = "accountRoot")] pub fn account_root(&self) -> RpoDigest { self.0.account_root().into() } + #[wasm_bindgen(js_name = "nullifierRoot")] pub fn nullifier_root(&self) -> RpoDigest { self.0.nullifier_root().into() } + #[wasm_bindgen(js_name = "noteRoot")] pub fn note_root(&self) -> RpoDigest { self.0.note_root().into() } + #[wasm_bindgen(js_name = "txHash")] pub fn tx_hash(&self) -> RpoDigest { self.0.tx_hash().into() } + #[wasm_bindgen(js_name = "kernelRoot")] pub fn kernel_root(&self) -> RpoDigest { self.0.kernel_root().into() } + #[wasm_bindgen(js_name = "proofHash")] pub fn proof_hash(&self) -> RpoDigest { self.0.proof_hash().into() } @@ -73,6 +83,6 @@ impl From for BlockHeader { impl From<&NativeBlockHeader> for BlockHeader { fn from(header: &NativeBlockHeader) -> Self { - BlockHeader(*header) + BlockHeader(header.clone()) } } diff --git a/crates/web-client/src/models/consumable_note_record.rs b/crates/web-client/src/models/consumable_note_record.rs index d8bbdfed3..10c1ab7cf 100644 --- a/crates/web-client/src/models/consumable_note_record.rs +++ b/crates/web-client/src/models/consumable_note_record.rs @@ -51,10 +51,12 @@ impl ConsumableNoteRecord { ConsumableNoteRecord { input_note_record, note_consumability } } + #[wasm_bindgen(js_name = "inputNoteRecord")] pub fn input_note_record(&self) -> InputNoteRecord { self.input_note_record.clone() } + #[wasm_bindgen(js_name = "noteConsumability")] pub fn note_consumability(&self) -> Vec { self.note_consumability.clone() } diff --git a/crates/web-client/src/models/executed_transaction.rs b/crates/web-client/src/models/executed_transaction.rs index 69c5efbad..e33697249 100644 --- a/crates/web-client/src/models/executed_transaction.rs +++ b/crates/web-client/src/models/executed_transaction.rs @@ -17,34 +17,42 @@ impl ExecutedTransaction { self.0.id().into() } + #[wasm_bindgen(js_name = "accountId")] pub fn account_id(&self) -> AccountId { self.0.account_id().into() } + #[wasm_bindgen(js_name = "initialAccount")] pub fn initial_account(&self) -> Account { self.0.initial_account().into() } + #[wasm_bindgen(js_name = "finalAccount")] pub fn final_account(&self) -> AccountHeader { self.0.final_account().into() } + #[wasm_bindgen(js_name = "inputNotes")] pub fn input_notes(&self) -> InputNotes { self.0.input_notes().into() } + #[wasm_bindgen(js_name = "outputNotes")] pub fn output_notes(&self) -> OutputNotes { self.0.output_notes().into() } + #[wasm_bindgen(js_name = "txArgs")] pub fn tx_args(&self) -> TransactionArgs { self.0.tx_args().into() } + #[wasm_bindgen(js_name = "blockHeader")] pub fn block_header(&self) -> BlockHeader { self.0.block_header().into() } + #[wasm_bindgen(js_name = "accountDelta")] pub fn account_delta(&self) -> AccountDelta { self.0.account_delta().into() } diff --git a/crates/web-client/src/models/felt.rs b/crates/web-client/src/models/felt.rs index 7c5d92632..bd88e8f74 100644 --- a/crates/web-client/src/models/felt.rs +++ b/crates/web-client/src/models/felt.rs @@ -12,10 +12,12 @@ impl Felt { Felt(NativeFelt::new(value)) } + #[wasm_bindgen(js_name = "asInt")] pub fn as_int(&self) -> u64 { self.0.as_int() } + #[wasm_bindgen(js_name = "toString")] #[allow(clippy::inherent_to_string)] pub fn to_string(&self) -> String { self.0.to_string() diff --git a/crates/web-client/src/models/fungible_asset.rs b/crates/web-client/src/models/fungible_asset.rs index 0a31ba404..c3f428689 100644 --- a/crates/web-client/src/models/fungible_asset.rs +++ b/crates/web-client/src/models/fungible_asset.rs @@ -21,6 +21,7 @@ impl FungibleAsset { FungibleAsset(native_asset) } + #[wasm_bindgen(js_name = "faucetId")] pub fn faucet_id(&self) -> AccountId { self.0.faucet_id().into() } @@ -29,6 +30,7 @@ impl FungibleAsset { self.0.amount() } + #[wasm_bindgen(js_name = "intoWord")] pub fn into_word(&self) -> Word { let native_word: NativeWord = self.0.into(); native_word.into() diff --git a/crates/web-client/src/models/input_note_record.rs b/crates/web-client/src/models/input_note_record.rs index 3d22ff70a..ea3626ea2 100644 --- a/crates/web-client/src/models/input_note_record.rs +++ b/crates/web-client/src/models/input_note_record.rs @@ -28,10 +28,12 @@ impl InputNoteRecord { self.0.metadata().map(Into::into) } + #[wasm_bindgen(js_name = "inclusionProof")] pub fn inclusion_proof(&self) -> Option { self.0.inclusion_proof().map(Into::into) } + #[wasm_bindgen(js_name = "consumerTransactionId")] pub fn consumer_transaction_id(&self) -> Option { self.0.consumer_transaction_id().map(ToString::to_string) } @@ -40,14 +42,17 @@ impl InputNoteRecord { self.0.nullifier().to_hex() } + #[wasm_bindgen(js_name = "isAuthenticated")] pub fn is_authenticated(&self) -> bool { self.0.is_authenticated() } + #[wasm_bindgen(js_name = "isConsumed")] pub fn is_consumed(&self) -> bool { self.0.is_consumed() } + #[wasm_bindgen(js_name = "isProcessing")] pub fn is_processing(&self) -> bool { self.0.is_processing() } diff --git a/crates/web-client/src/models/input_notes.rs b/crates/web-client/src/models/input_notes.rs index 4b768eeb2..9c697b9a2 100644 --- a/crates/web-client/src/models/input_notes.rs +++ b/crates/web-client/src/models/input_notes.rs @@ -13,14 +13,17 @@ impl InputNotes { self.0.commitment().into() } + #[wasm_bindgen(js_name = "numNotes")] pub fn num_notes(&self) -> u8 { u8::try_from(self.0.num_notes()).expect("only 256 input notes is allowed") } + #[wasm_bindgen(js_name = "accountId")] pub fn is_empty(&self) -> bool { self.0.is_empty() } + #[wasm_bindgen(js_name = "getNote")] pub fn get_note(&self, index: u8) -> InputNote { self.0.get_note(index as usize).into() } diff --git a/crates/web-client/src/models/merkle_path.rs b/crates/web-client/src/models/merkle_path.rs index c01413ccb..5e79d60c8 100644 --- a/crates/web-client/src/models/merkle_path.rs +++ b/crates/web-client/src/models/merkle_path.rs @@ -17,6 +17,7 @@ impl MerklePath { self.0.nodes().iter().map(Into::into).collect() } + #[wasm_bindgen(js_name = "computeRoot")] pub fn compute_root(&self, index: u64, node: &RpoDigest) -> RpoDigest { self.0.compute_root(index, node.clone().into()).unwrap().into() } diff --git a/crates/web-client/src/models/mod.rs b/crates/web-client/src/models/mod.rs index 74cae58e6..6953e2076 100644 --- a/crates/web-client/src/models/mod.rs +++ b/crates/web-client/src/models/mod.rs @@ -34,7 +34,6 @@ pub mod account_header; pub mod account_id; pub mod account_storage; pub mod account_storage_mode; -pub mod accounts; pub mod advice_inputs; pub mod advice_map; pub mod asset_vault; diff --git a/crates/web-client/src/models/note.rs b/crates/web-client/src/models/note.rs index 2102e00bb..7e77f24c8 100644 --- a/crates/web-client/src/models/note.rs +++ b/crates/web-client/src/models/note.rs @@ -39,6 +39,7 @@ impl Note { self.0.recipient().clone().into() } + #[wasm_bindgen(js_name = "createP2IDNote")] pub fn create_p2id_note( sender: &AccountId, target: &AccountId, @@ -66,6 +67,7 @@ impl Note { NativeNote::new(assets.into(), metadata, recipient).into() } + #[wasm_bindgen(js_name = "createP2IDRNote")] pub fn create_p2idr_note( sender: &AccountId, target: &AccountId, diff --git a/crates/web-client/src/models/note_execution_hint.rs b/crates/web-client/src/models/note_execution_hint.rs index de8e0ce8a..6c4b84bff 100644 --- a/crates/web-client/src/models/note_execution_hint.rs +++ b/crates/web-client/src/models/note_execution_hint.rs @@ -15,18 +15,22 @@ impl NoteExecutionHint { NoteExecutionHint(NativeNoteExecutionHint::Always) } + #[wasm_bindgen(js_name = "afterBlock")] pub fn after_block(block_num: u32) -> NoteExecutionHint { NoteExecutionHint(NativeNoteExecutionHint::after_block(block_num.into()).unwrap()) } + #[wasm_bindgen(js_name = "onBlockSlot")] pub fn on_block_slot(epoch_len: u8, slot_len: u8, slot_offset: u8) -> NoteExecutionHint { NoteExecutionHint(NativeNoteExecutionHint::on_block_slot(epoch_len, slot_len, slot_offset)) } + #[wasm_bindgen(js_name = "fromParts")] pub fn from_parts(tag: u8, payload: u32) -> NoteExecutionHint { NoteExecutionHint(NativeNoteExecutionHint::from_parts(tag, payload).unwrap()) } + #[wasm_bindgen(js_name = "canBeConsumed")] pub fn can_be_consumed(&self, block_num: u32) -> bool { self.0.can_be_consumed(block_num.into()).unwrap() } diff --git a/crates/web-client/src/models/note_execution_mode.rs b/crates/web-client/src/models/note_execution_mode.rs index a12df0669..d1c44c75e 100644 --- a/crates/web-client/src/models/note_execution_mode.rs +++ b/crates/web-client/src/models/note_execution_mode.rs @@ -7,14 +7,17 @@ pub struct NoteExecutionMode(NativeNoteExecutionMode); #[wasm_bindgen] impl NoteExecutionMode { + #[wasm_bindgen(js_name = "newLocal")] pub fn new_local() -> NoteExecutionMode { NoteExecutionMode(NativeNoteExecutionMode::Local) } + #[wasm_bindgen(js_name = "newNetwork")] pub fn new_network() -> NoteExecutionMode { NoteExecutionMode(NativeNoteExecutionMode::Network) } + #[wasm_bindgen(js_name = "toString")] #[allow(clippy::inherent_to_string)] pub fn to_string(&self) -> String { let note_execution_mode_as_str = match self.0 { diff --git a/crates/web-client/src/models/note_id.rs b/crates/web-client/src/models/note_id.rs index 8724b97f5..7c513b5ab 100644 --- a/crates/web-client/src/models/note_id.rs +++ b/crates/web-client/src/models/note_id.rs @@ -14,6 +14,7 @@ impl NoteId { NoteId(NativeNoteId::new(recipient_digest.into(), asset_commitment_digest.into())) } + #[wasm_bindgen(js_name = "toString")] #[allow(clippy::inherent_to_string)] pub fn to_string(&self) -> String { self.0.to_string() diff --git a/crates/web-client/src/models/note_inclusion_proof.rs b/crates/web-client/src/models/note_inclusion_proof.rs index 180c5abe4..4beb80e14 100644 --- a/crates/web-client/src/models/note_inclusion_proof.rs +++ b/crates/web-client/src/models/note_inclusion_proof.rs @@ -13,6 +13,7 @@ impl NoteInclusionProof { self.0.location().into() } + #[wasm_bindgen(js_name = "notePath")] pub fn note_path(&self) -> MerklePath { self.0.note_path().into() } diff --git a/crates/web-client/src/models/note_location.rs b/crates/web-client/src/models/note_location.rs index 4fa30f4b2..48420d819 100644 --- a/crates/web-client/src/models/note_location.rs +++ b/crates/web-client/src/models/note_location.rs @@ -7,10 +7,12 @@ pub struct NoteLocation(NativeNoteLocation); #[wasm_bindgen] impl NoteLocation { + #[wasm_bindgen(js_name = "blockNum")] pub fn block_num(&self) -> u32 { self.0.block_num().as_u32() } + #[wasm_bindgen(js_name = "nodeIndexInBlock")] pub fn node_index_in_block(&self) -> u16 { self.0.node_index_in_block() } diff --git a/crates/web-client/src/models/note_metadata.rs b/crates/web-client/src/models/note_metadata.rs index c16f5e41d..8d921a9ab 100644 --- a/crates/web-client/src/models/note_metadata.rs +++ b/crates/web-client/src/models/note_metadata.rs @@ -39,6 +39,7 @@ impl NoteMetadata { self.0.tag().into() } + #[wasm_bindgen(js_name = "noteType")] pub fn note_type(&self) -> NoteType { self.0.note_type().into() } diff --git a/crates/web-client/src/models/note_tag.rs b/crates/web-client/src/models/note_tag.rs index 54c3933f7..6f64f6913 100644 --- a/crates/web-client/src/models/note_tag.rs +++ b/crates/web-client/src/models/note_tag.rs @@ -12,6 +12,7 @@ pub struct NoteTag(NativeNoteTag); #[wasm_bindgen] impl NoteTag { + #[wasm_bindgen(js_name = "fromAccountId")] pub fn from_account_id(account_id: &AccountId, execution: &NoteExecutionMode) -> NoteTag { let native_account_id: NativeAccountId = account_id.into(); let native_execution: NativeNoteExecutionMode = execution.into(); @@ -20,6 +21,7 @@ impl NoteTag { NoteTag(native_note_tag) } + #[wasm_bindgen(js_name = "forPublicUseCase")] pub fn for_public_use_case( use_case_id: u16, payload: u16, @@ -31,15 +33,18 @@ impl NoteTag { NoteTag(native_note_tag) } + #[wasm_bindgen(js_name = "forLocalUseCase")] pub fn for_local_use_case(use_case_id: u16, payload: u16) -> NoteTag { let native_note_tag = NativeNoteTag::for_local_use_case(use_case_id, payload).unwrap(); NoteTag(native_note_tag) } + #[wasm_bindgen(js_name = "isSingleTarget")] pub fn is_single_target(&self) -> bool { self.0.is_single_target() } + #[wasm_bindgen(js_name = "executionMode")] pub fn execution_mode(&self) -> NoteExecutionMode { self.0.execution_mode().into() } diff --git a/crates/web-client/src/models/output_note.rs b/crates/web-client/src/models/output_note.rs index b5a87505d..e63c31b8c 100644 --- a/crates/web-client/src/models/output_note.rs +++ b/crates/web-client/src/models/output_note.rs @@ -38,6 +38,7 @@ impl OutputNote { self.0.id().into() } + #[wasm_bindgen(js_name = "recipientDigest")] pub fn recipient_digest(&self) -> Option { self.0.recipient_digest().map(Into::into) } diff --git a/crates/web-client/src/models/output_notes.rs b/crates/web-client/src/models/output_notes.rs index 8addd37f4..dc5a54f2b 100644 --- a/crates/web-client/src/models/output_notes.rs +++ b/crates/web-client/src/models/output_notes.rs @@ -13,15 +13,18 @@ impl OutputNotes { self.0.commitment().into() } + #[wasm_bindgen(js_name = "numNotes")] pub fn num_notes(&self) -> u32 { u32::try_from(self.0.num_notes()) .expect("only 1024 output notes is allowed per transaction") } + #[wasm_bindgen(js_name = "isEmpty")] pub fn is_empty(&self) -> bool { self.0.is_empty() } + #[wasm_bindgen(js_name = "getNote")] pub fn get_note(&self, index: u32) -> OutputNote { self.0.get_note(index as usize).into() } diff --git a/crates/web-client/src/models/partial_note.rs b/crates/web-client/src/models/partial_note.rs index 996c85747..524442996 100644 --- a/crates/web-client/src/models/partial_note.rs +++ b/crates/web-client/src/models/partial_note.rs @@ -21,6 +21,7 @@ impl PartialNote { self.0.metadata().into() } + #[wasm_bindgen(js_name = "recipientDigest")] pub fn recipient_digest(&self) -> RpoDigest { self.0.recipient_digest().into() } diff --git a/crates/web-client/src/models/provers.rs b/crates/web-client/src/models/provers.rs index 1efe072f1..354aed7d2 100644 --- a/crates/web-client/src/models/provers.rs +++ b/crates/web-client/src/models/provers.rs @@ -14,11 +14,13 @@ pub struct TransactionProver { #[wasm_bindgen] impl TransactionProver { + #[wasm_bindgen(js_name = "newLocalProver")] pub fn new_local_prover() -> TransactionProver { let local_prover = LocalTransactionProver::new(ProvingOptions::default()); TransactionProver { prover: Arc::new(local_prover) } } + #[wasm_bindgen(js_name = "newRemoteProver")] pub fn new_remote_prover(endpoint: &str) -> TransactionProver { let remote_prover = RemoteTransactionProver::new(endpoint); TransactionProver { prover: Arc::new(remote_prover) } diff --git a/crates/web-client/src/models/rpo256.rs b/crates/web-client/src/models/rpo256.rs index 0e072b2ce..b3b5f260e 100644 --- a/crates/web-client/src/models/rpo256.rs +++ b/crates/web-client/src/models/rpo256.rs @@ -11,6 +11,7 @@ pub struct Rpo256; #[wasm_bindgen] impl Rpo256 { + #[wasm_bindgen(js_name = "hashElements")] pub fn hash_elements(felt_array: &FeltArray) -> RpoDigest { let felts: Vec = felt_array.into(); let native_felts: Vec = felts.iter().map(Into::into).collect(); diff --git a/crates/web-client/src/models/rpo_digest.rs b/crates/web-client/src/models/rpo_digest.rs index bc554a950..3554b3f31 100644 --- a/crates/web-client/src/models/rpo_digest.rs +++ b/crates/web-client/src/models/rpo_digest.rs @@ -23,11 +23,13 @@ impl RpoDigest { RpoDigest(NativeRpoDigest::new(native_felts)) } + #[wasm_bindgen(js_name = "toWord")] pub fn to_word(&self) -> Word { let native_word: NativeWord = self.0.into(); native_word.into() } + #[wasm_bindgen(js_name = "toHex")] pub fn to_hex(&self) -> String { self.0.to_hex() } diff --git a/crates/web-client/src/models/sync_summary.rs b/crates/web-client/src/models/sync_summary.rs index 4a00acf81..f26697909 100644 --- a/crates/web-client/src/models/sync_summary.rs +++ b/crates/web-client/src/models/sync_summary.rs @@ -8,22 +8,27 @@ pub struct SyncSummary(NativeSyncSummary); #[wasm_bindgen] impl SyncSummary { + #[wasm_bindgen(js_name = "blockNum")] pub fn block_num(&self) -> u32 { self.0.block_num.as_u32() } + #[wasm_bindgen(js_name = "committedNotes")] pub fn committed_notes(&self) -> Vec { self.0.committed_notes.iter().map(Into::into).collect() } + #[wasm_bindgen(js_name = "consumedNotes")] pub fn consumed_notes(&self) -> Vec { self.0.consumed_notes.iter().map(Into::into).collect() } + #[wasm_bindgen(js_name = "updatedAccounts")] pub fn updated_accounts(&self) -> Vec { self.0.updated_accounts.iter().map(Into::into).collect() } + #[wasm_bindgen(js_name = "committedTransactions")] pub fn committed_transactions(&self) -> Vec { self.0.committed_transactions.iter().map(Into::into).collect() } diff --git a/crates/web-client/src/models/test_utils.rs b/crates/web-client/src/models/test_utils.rs index bd2203515..4bd229928 100644 --- a/crates/web-client/src/models/test_utils.rs +++ b/crates/web-client/src/models/test_utils.rs @@ -9,6 +9,7 @@ pub struct TestUtils; #[wasm_bindgen] impl TestUtils { + #[wasm_bindgen(js_name = "createMockAccountId")] pub fn create_mock_account_id() -> AccountId { let native_account_id: NativeAccountId = ACCOUNT_ID_REGULAR_ACCOUNT_UPDATABLE_CODE_OFF_CHAIN.try_into().unwrap(); diff --git a/crates/web-client/src/models/transaction_args.rs b/crates/web-client/src/models/transaction_args.rs index 19ab27780..ba29e9d90 100644 --- a/crates/web-client/src/models/transaction_args.rs +++ b/crates/web-client/src/models/transaction_args.rs @@ -11,14 +11,17 @@ pub struct TransactionArgs(NativeTransactionArgs); #[wasm_bindgen] impl TransactionArgs { + #[wasm_bindgen(js_name = "txScript")] pub fn tx_script(&self) -> Option { self.0.tx_script().map(Into::into) } + #[wasm_bindgen(js_name = "getNoteArgs")] pub fn get_note_args(&self, note_id: &NoteId) -> Option { self.0.get_note_args(note_id.into()).map(Into::into) } + #[wasm_bindgen(js_name = "adviceInputs")] pub fn advice_inputs(&self) -> AdviceInputs { self.0.advice_inputs().into() } diff --git a/crates/web-client/src/models/transaction_id.rs b/crates/web-client/src/models/transaction_id.rs index d53af88e8..887e0eebd 100644 --- a/crates/web-client/src/models/transaction_id.rs +++ b/crates/web-client/src/models/transaction_id.rs @@ -9,14 +9,17 @@ pub struct TransactionId(NativeTransactionId); #[wasm_bindgen] impl TransactionId { + #[wasm_bindgen(js_name = "asElements")] pub fn as_elements(&self) -> Vec { self.0.as_elements().iter().map(Into::into).collect() } + #[wasm_bindgen(js_name = "asBytes")] pub fn as_bytes(&self) -> Vec { self.0.as_bytes().to_vec() } + #[wasm_bindgen(js_name = "toHex")] pub fn to_hex(&self) -> String { self.0.to_hex() } diff --git a/crates/web-client/src/models/transaction_record.rs b/crates/web-client/src/models/transaction_record.rs index dbcecfc1c..6cca4930b 100644 --- a/crates/web-client/src/models/transaction_record.rs +++ b/crates/web-client/src/models/transaction_record.rs @@ -16,22 +16,27 @@ impl TransactionRecord { self.0.id.into() } + #[wasm_bindgen(js_name = "accountId")] pub fn account_id(&self) -> AccountId { self.0.account_id.into() } + #[wasm_bindgen(js_name = "initAccountState")] pub fn init_account_state(&self) -> RpoDigest { self.0.init_account_state.into() } + #[wasm_bindgen(js_name = "finalAccountState")] pub fn final_account_state(&self) -> RpoDigest { self.0.final_account_state.into() } + #[wasm_bindgen(js_name = "inputNoteNullifiers")] pub fn input_note_nullifiers(&self) -> Vec { self.0.input_note_nullifiers.iter().map(Into::into).collect() } + #[wasm_bindgen(js_name = "outputNotes")] pub fn output_notes(&self) -> OutputNotes { self.0.output_notes.clone().into() } @@ -40,10 +45,12 @@ impl TransactionRecord { // self.0.transaction_script.map(|script| script.into()) // } + #[wasm_bindgen(js_name = "blockNum")] pub fn block_num(&self) -> u32 { self.0.block_num.as_u32() } + #[wasm_bindgen(js_name = "transactionStatus")] pub fn transaction_status(&self) -> TransactionStatus { self.0.transaction_status.clone().into() } diff --git a/crates/web-client/src/models/transaction_request.rs b/crates/web-client/src/models/transaction_request.rs index 38acbb6e6..b9f85347e 100644 --- a/crates/web-client/src/models/transaction_request.rs +++ b/crates/web-client/src/models/transaction_request.rs @@ -233,12 +233,14 @@ impl TransactionRequestBuilder { TransactionRequestBuilder(native_transaction_request) } + #[wasm_bindgen(js_name = "withUnauthenticatedInputNotes")] pub fn with_unauthenticated_input_notes(mut self, notes: &NoteAndArgsArray) -> Self { let native_note_and_note_args: Vec<(NativeNote, Option)> = notes.into(); self.0 = self.0.clone().with_unauthenticated_input_notes(native_note_and_note_args); self } + #[wasm_bindgen(js_name = "withAuthenticatedInputNotes")] pub fn with_authenticated_input_notes(mut self, notes: &NoteIdAndArgsArray) -> Self { let native_note_id_and_note_args: Vec<(NativeNoteId, Option)> = notes.into(); @@ -246,24 +248,28 @@ impl TransactionRequestBuilder { self } + #[wasm_bindgen(js_name = "withOwnOutputNotes")] pub fn with_own_output_notes(mut self, notes: &OutputNotesArray) -> Self { let native_output_notes: Vec = notes.into(); self.0 = self.0.clone().with_own_output_notes(native_output_notes); self } + #[wasm_bindgen(js_name = "withCustomScript")] pub fn with_custom_script(mut self, script: &TransactionScript) -> Self { let native_script: NativeTransactionScript = script.into(); self.0 = self.0.clone().with_custom_script(native_script); self } + #[wasm_bindgen(js_name = "withExpectedOutputNotes")] pub fn with_expected_output_notes(mut self, notes: &NotesArray) -> Self { let native_notes: Vec = notes.into(); self.0 = self.0.clone().with_expected_output_notes(native_notes); self } + #[wasm_bindgen(js_name = "withExpectedFutureNotes")] pub fn with_expected_future_notes( mut self, note_details_and_tag: &NoteDetailsAndTagArray, @@ -274,6 +280,7 @@ impl TransactionRequestBuilder { self } + #[wasm_bindgen(js_name = "extendAdviceMap")] pub fn extend_advice_map(mut self, advice_map: &AdviceMap) -> Self { let native_advice_map: NativeAdviceMap = advice_map.into(); self.0 = self.0.clone().extend_advice_map(native_advice_map); diff --git a/crates/web-client/src/models/transaction_result.rs b/crates/web-client/src/models/transaction_result.rs index 841a234df..916a6046f 100644 --- a/crates/web-client/src/models/transaction_result.rs +++ b/crates/web-client/src/models/transaction_result.rs @@ -11,28 +11,34 @@ pub struct TransactionResult(NativeTransactionResult); #[wasm_bindgen] impl TransactionResult { + #[wasm_bindgen(js_name = "executedTransaction")] pub fn executed_transaction(&self) -> ExecutedTransaction { self.0.executed_transaction().into() } + #[wasm_bindgen(js_name = "createdNotes")] pub fn created_notes(&self) -> OutputNotes { self.0.created_notes().into() } // TODO: relevant_notes + #[wasm_bindgen(js_name = "blockNum")] pub fn block_num(&self) -> u32 { self.0.block_num().as_u32() } + #[wasm_bindgen(js_name = "transactionArguments")] pub fn transaction_arguments(&self) -> TransactionArgs { self.0.transaction_arguments().into() } + #[wasm_bindgen(js_name = "accountDelta")] pub fn account_delta(&self) -> AccountDelta { self.0.account_delta().into() } + #[wasm_bindgen(js_name = "consumedNotes")] pub fn consumed_notes(&self) -> InputNotes { self.0.consumed_notes().into() } diff --git a/crates/web-client/src/models/transaction_status.rs b/crates/web-client/src/models/transaction_status.rs index f11bd612c..b7e1cba1a 100644 --- a/crates/web-client/src/models/transaction_status.rs +++ b/crates/web-client/src/models/transaction_status.rs @@ -15,14 +15,17 @@ impl TransactionStatus { TransactionStatus(NativeTransactionStatus::Committed(block_num.into())) } + #[wasm_bindgen(js_name = "isPending")] pub fn is_pending(&self) -> bool { matches!(self.0, NativeTransactionStatus::Pending) } + #[wasm_bindgen(js_name = "isCommitted")] pub fn is_committed(&self) -> bool { matches!(self.0, NativeTransactionStatus::Committed(_)) } + #[wasm_bindgen(js_name = "getBlockNum")] pub fn get_block_num(&self) -> Option { match self.0 { NativeTransactionStatus::Committed(block_num) => Some(block_num.as_u32()), diff --git a/crates/web-client/src/models/transactions.rs b/crates/web-client/src/models/transactions.rs index cb388b1b1..0fa73619e 100644 --- a/crates/web-client/src/models/transactions.rs +++ b/crates/web-client/src/models/transactions.rs @@ -1,28 +1,5 @@ use wasm_bindgen::{prelude::*, JsValue}; -#[wasm_bindgen] -pub struct NewTransactionResult { - transaction_id: String, - created_note_ids: Vec, -} - -#[wasm_bindgen] -impl NewTransactionResult { - pub fn new(transaction_id: String, created_note_ids: Vec) -> NewTransactionResult { - NewTransactionResult { transaction_id, created_note_ids } - } - - #[wasm_bindgen(getter)] - pub fn transaction_id(&self) -> String { - self.transaction_id.clone() - } - - #[wasm_bindgen(getter)] - pub fn created_note_ids(&self) -> JsValue { - serde_wasm_bindgen::to_value(&self.created_note_ids).unwrap() - } -} - #[wasm_bindgen] pub struct NewSwapTransactionResult { transaction_id: String, @@ -47,26 +24,27 @@ impl NewSwapTransactionResult { } } + #[wasm_bindgen(js_name = "setNoteTag")] pub fn set_note_tag(&mut self, payback_note_tag: String) { self.payback_note_tag = payback_note_tag; } - #[wasm_bindgen(getter)] + #[wasm_bindgen(js_name = "transactionId")] pub fn transaction_id(&self) -> String { self.transaction_id.clone() } - #[wasm_bindgen(getter)] + #[wasm_bindgen(js_name = "expectedOutputNoteIds")] pub fn expected_output_note_ids(&self) -> JsValue { serde_wasm_bindgen::to_value(&self.expected_output_note_ids).unwrap() } - #[wasm_bindgen(getter)] + #[wasm_bindgen(js_name = "expectedPartialNoteIds")] pub fn expected_partial_note_ids(&self) -> JsValue { serde_wasm_bindgen::to_value(&self.expected_partial_note_ids).unwrap() } - #[wasm_bindgen(getter)] + #[wasm_bindgen(js_name = "paybackNoteTag")] pub fn payback_note_tag(&self) -> String { self.payback_note_tag.clone() } diff --git a/crates/web-client/src/new_account.rs b/crates/web-client/src/new_account.rs index 8f94011c0..87ff45967 100644 --- a/crates/web-client/src/new_account.rs +++ b/crates/web-client/src/new_account.rs @@ -1,62 +1,37 @@ use miden_client::{ - account::{ - component::{BasicFungibleFaucet, BasicWallet, RpoFalcon512}, - AccountBuilder, AccountType, - }, + account::{AccountBuilder, AccountType}, auth::AuthSecretKey, authenticator::keystore::KeyStore, crypto::SecretKey, Felt, }; +use miden_lib::account::{auth::RpoFalcon512, faucets::BasicFungibleFaucet}; use miden_objects::asset::TokenSymbol; use wasm_bindgen::prelude::*; use super::models::{account::Account, account_storage_mode::AccountStorageMode}; -use crate::WebClient; +use crate::{helpers::generate_wallet, WebClient}; #[wasm_bindgen] impl WebClient { + #[wasm_bindgen(js_name = "newWallet")] pub async fn new_wallet( &mut self, storage_mode: &AccountStorageMode, mutable: bool, + init_seed: Option>, ) -> Result { let keystore = self.keystore.clone(); if let Some(client) = self.get_mut_inner() { - let key_pair = SecretKey::with_rng(client.rng()); - let pub_key = key_pair.public_key(); - - let mut init_seed = [0u8; 32]; - client.rng().fill_bytes(&mut init_seed); - - let account_type = if mutable { - AccountType::RegularAccountUpdatableCode - } else { - AccountType::RegularAccountImmutableCode - }; - - let anchor_block = client.get_latest_epoch_block().await.unwrap(); - - let (new_account, seed) = match AccountBuilder::new(init_seed) - .anchor((&anchor_block).try_into().unwrap()) - .account_type(account_type) - .storage_mode(storage_mode.into()) - .with_component(RpoFalcon512::new(pub_key)) - .with_component(BasicWallet) - .build() - { - Ok(result) => result, - Err(err) => { - let error_message = format!("Failed to create new wallet: {err:?}"); - return Err(JsValue::from_str(&error_message)); - }, - }; + let (new_account, account_seed, key_pair) = + generate_wallet(client, storage_mode, mutable, init_seed).await?; keystore .expect("KeyStore should be initialized") .add_key(&AuthSecretKey::RpoFalcon512(key_pair)) .map_err(|err| err.to_string())?; - match client.add_account(&new_account, Some(seed), false).await { + + match client.add_account(&new_account, Some(account_seed), false).await { Ok(_) => Ok(new_account.into()), Err(err) => { let error_message = format!("Failed to insert new wallet: {err:?}"); @@ -68,6 +43,7 @@ impl WebClient { } } + #[wasm_bindgen(js_name = "newFaucet")] pub async fn new_faucet( &mut self, storage_mode: &AccountStorageMode, diff --git a/crates/web-client/src/new_transactions.rs b/crates/web-client/src/new_transactions.rs index 2da845f9d..900c7c33c 100644 --- a/crates/web-client/src/new_transactions.rs +++ b/crates/web-client/src/new_transactions.rs @@ -21,6 +21,7 @@ use crate::{ #[wasm_bindgen] impl WebClient { + #[wasm_bindgen(js_name = "newTransaction")] pub async fn new_transaction( &mut self, account_id: &AccountId, @@ -40,6 +41,7 @@ impl WebClient { } } + #[wasm_bindgen(js_name = "submitTransaction")] pub async fn submit_transaction( &mut self, transaction_result: &TransactionResult, @@ -72,6 +74,7 @@ impl WebClient { } } + #[wasm_bindgen(js_name = "submitTransactionWithProver")] pub async fn submit_transaction_with_prover( &mut self, transaction_result: &TransactionResult, @@ -92,6 +95,7 @@ impl WebClient { } } + #[wasm_bindgen(js_name = "newMintTransaction")] pub async fn new_mint_transaction( &mut self, target_account_id: &AccountId, @@ -137,6 +141,7 @@ impl WebClient { } } + #[wasm_bindgen(js_name = "newSendTransaction")] pub async fn new_send_transaction( &mut self, sender_account_id: &AccountId, @@ -205,6 +210,7 @@ impl WebClient { } } + #[wasm_bindgen(js_name = "newConsumeTransaction")] pub async fn new_consume_transaction( &mut self, account_id: &AccountId, @@ -246,6 +252,7 @@ impl WebClient { } } + #[wasm_bindgen(js_name = "newSwapTransaction")] pub async fn new_swap_transaction( &mut self, sender_account_id: String, diff --git a/crates/web-client/src/notes.rs b/crates/web-client/src/notes.rs index 6dbc0a869..2e64abbf1 100644 --- a/crates/web-client/src/notes.rs +++ b/crates/web-client/src/notes.rs @@ -16,6 +16,7 @@ use crate::{ #[wasm_bindgen] impl WebClient { + #[wasm_bindgen(js_name = "getInputNotes")] pub async fn get_input_notes( &mut self, filter: NoteFilter, @@ -32,6 +33,7 @@ impl WebClient { } } + #[wasm_bindgen(js_name = "getInputNote")] pub async fn get_input_note( &mut self, note_id: String, @@ -51,6 +53,7 @@ impl WebClient { } } + #[wasm_bindgen(js_name = "getOutputNotes")] pub async fn get_output_notes(&mut self, filter: NoteFilter) -> Result { if let Some(client) = self.get_mut_inner() { let notes: Vec = @@ -63,6 +66,7 @@ impl WebClient { } } + #[wasm_bindgen(js_name = "getOutputNote")] pub async fn get_output_note(&mut self, note_id: String) -> Result { if let Some(client) = self.get_mut_inner() { let note_id: NoteId = Digest::try_from(note_id) @@ -77,6 +81,7 @@ impl WebClient { } } + #[wasm_bindgen(js_name = "compileNoteScript")] pub fn compile_note_script(&mut self, script: &str) -> Result { if let Some(client) = self.get_mut_inner() { let native_note_script: NativeNoteScript = client.compile_note_script(script).unwrap(); @@ -87,6 +92,7 @@ impl WebClient { } } + #[wasm_bindgen(js_name = "getConsumableNotes")] pub async fn get_consumable_notes( &mut self, account_id: Option, diff --git a/crates/web-client/src/sync.rs b/crates/web-client/src/sync.rs index e78fabde6..c78151da8 100644 --- a/crates/web-client/src/sync.rs +++ b/crates/web-client/src/sync.rs @@ -4,6 +4,7 @@ use crate::{models::sync_summary::SyncSummary, WebClient}; #[wasm_bindgen] impl WebClient { + #[wasm_bindgen(js_name = "syncState")] pub async fn sync_state(&mut self) -> Result { if let Some(client) = self.get_mut_inner() { let sync_summary = client diff --git a/crates/web-client/src/tags.rs b/crates/web-client/src/tags.rs index 89124b677..7fb830da1 100644 --- a/crates/web-client/src/tags.rs +++ b/crates/web-client/src/tags.rs @@ -5,6 +5,7 @@ use crate::WebClient; #[wasm_bindgen] impl WebClient { + #[wasm_bindgen(js_name = "addTag")] pub async fn add_tag(&mut self, tag: String) -> Result { if let Some(client) = self.get_mut_inner() { let note_tag_as_u32 = tag.parse::().unwrap(); @@ -17,6 +18,7 @@ impl WebClient { } } + #[wasm_bindgen(js_name = "removeTag")] pub async fn remove_tag(&mut self, tag: String) -> Result { if let Some(client) = self.get_mut_inner() { let note_tag_as_u32 = tag.parse::().unwrap(); @@ -29,6 +31,7 @@ impl WebClient { } } + #[wasm_bindgen(js_name = "listTags")] pub async fn list_tags(&mut self) -> Result { if let Some(client) = self.get_mut_inner() { let tags: Vec = client diff --git a/crates/web-client/src/transactions.rs b/crates/web-client/src/transactions.rs index f2dd80af0..472705618 100644 --- a/crates/web-client/src/transactions.rs +++ b/crates/web-client/src/transactions.rs @@ -11,6 +11,7 @@ use crate::WebClient; #[wasm_bindgen] impl WebClient { + #[wasm_bindgen(js_name = "getTransactions")] pub async fn get_transactions( &mut self, transaction_filter: TransactionFilter, @@ -27,6 +28,7 @@ impl WebClient { } } + #[wasm_bindgen(js_name = "compileTxScript")] pub fn compile_tx_script(&mut self, script: &str) -> Result { if let Some(client) = self.get_mut_inner() { let native_tx_script: NativeTransactionScript = diff --git a/crates/web-client/test/account.test.ts b/crates/web-client/test/account.test.ts index 4ae88440f..da9ee44da 100644 --- a/crates/web-client/test/account.test.ts +++ b/crates/web-client/test/account.test.ts @@ -14,15 +14,15 @@ export const getAccountOneMatch = async (): Promise => { return await testingPage.evaluate(async () => { const client = window.client; - const newAccount = await client.new_wallet( + const newAccount = await client.newWallet( window.AccountStorageMode.private(), true ); - const result = await client.get_account(newAccount.id()); + const result = await client.getAccount(newAccount.id()); return { - hashOfCreatedAccount: newAccount.hash().to_hex(), - hashOfGetAccountResult: result.hash().to_hex(), + hashOfCreatedAccount: newAccount.hash().toHex(), + hashOfGetAccountResult: result.hash().toHex(), isAccountType: result instanceof window.Account, }; }); @@ -35,12 +35,12 @@ interface GetAccountFailureResult { export const getAccountNoMatch = async (): Promise => { return await testingPage.evaluate(async () => { const client = window.client; - const nonExistingAccountId = window.TestUtils.create_mock_account_id(); + const nonExistingAccountId = window.TestUtils.createMockAccountId(); - const result = await client.get_account(nonExistingAccountId); + const result = await client.getAccount(nonExistingAccountId); return { - hashOfGetAccountResult: result ? result.hash().to_hex() : undefined, + hashOfGetAccountResult: result ? result.hash().toHex() : undefined, }; }); }; @@ -73,26 +73,26 @@ export const getAccountsManyMatches = async (): Promise => { return await testingPage.evaluate(async () => { const client = window.client; - const newAccount1 = await client.new_wallet( + const newAccount1 = await client.newWallet( window.AccountStorageMode.private(), true ); - const newAccount2 = await client.new_wallet( + const newAccount2 = await client.newWallet( window.AccountStorageMode.private(), true ); const hashesOfCreatedAccounts = [ - newAccount1.hash().to_hex(), - newAccount2.hash().to_hex(), + newAccount1.hash().toHex(), + newAccount2.hash().toHex(), ]; - const result = await client.get_accounts(); + const result = await client.getAccounts(); const hashesOfGetAccountsResult = []; const resultTypes = []; for (let i = 0; i < result.length; i++) { - hashesOfGetAccountsResult.push(result[i].hash().to_hex()); + hashesOfGetAccountsResult.push(result[i].hash().toHex()); resultTypes.push(result[i] instanceof window.AccountHeader); } @@ -109,13 +109,13 @@ export const getAccountsNoMatches = return await testingPage.evaluate(async () => { const client = window.client; - const result = await client.get_accounts(); + const result = await client.getAccounts(); const hashesOfGetAccountsResult = []; const resultTypes = []; for (let i = 0; i < result.length; i++) { - hashesOfGetAccountsResult.push(result[i].hash().to_hex()); + hashesOfGetAccountsResult.push(result[i].hash().toHex()); resultTypes.push(result[i] instanceof window.AccountHeader); } @@ -127,7 +127,7 @@ export const getAccountsNoMatches = }); }; -describe("get_accounts tests", () => { +describe("getAccounts tests", () => { it("retrieves all existing accounts", async () => { const result = await getAccountsManyMatches(); diff --git a/crates/web-client/test/global.test.d.ts b/crates/web-client/test/global.test.d.ts index 882df1c88..3640a65e9 100644 --- a/crates/web-client/test/global.test.d.ts +++ b/crates/web-client/test/global.test.d.ts @@ -41,7 +41,7 @@ import { declare global { interface Window { client: WebClient; - remote_prover_url: string; + remoteProverUrl: string; Account: typeof Account; AccountHeader: typeof AccountHeader; AccountId: typeof AccountId; @@ -79,7 +79,7 @@ declare global { TransactionScriptInputPairArray: typeof TransactionScriptInputPairArray; WebClient: typeof WebClient; Word: typeof Word; - create_client: () => Promise; + createClient: () => Promise; // Add the helpers namespace helpers: { diff --git a/crates/web-client/test/import.test.ts b/crates/web-client/test/import.test.ts new file mode 100644 index 000000000..719408667 --- /dev/null +++ b/crates/web-client/test/import.test.ts @@ -0,0 +1,78 @@ +import { expect } from "chai"; +import { testingPage } from "./mocha.global.setup.mjs"; +import { + clearStore, + createNewFaucet, + createNewWallet, + fundAccountFromFaucet, + getAccount, + getAccountBalance, + StorageMode, +} from "./webClientTestUtils"; + +const importWalletFromSeed = async ( + walletSeed: Uint8Array, + mutable: boolean +) => { + const serializedWalletSeed = Array.from(walletSeed); + return await testingPage.evaluate( + async (_serializedWalletSeed, _mutable) => { + const client = window.client; + const _walletSeed = new Uint8Array(_serializedWalletSeed); + + const account = await client.importPublicAccountFromSeed( + _walletSeed, + _mutable + ); + return { + accountId: account.id().toString(), + accountHash: account.hash().toHex(), + }; + }, + serializedWalletSeed, + mutable + ); +}; + +describe("import from seed", () => { + it("should import same public account from seed", async () => { + const walletSeed = new Uint8Array(32); + crypto.getRandomValues(walletSeed); + + const mutable = false; + const storageMode = StorageMode.PUBLIC; + + const initialWallet = await createNewWallet({ + storageMode, + mutable, + walletSeed, + }); + + const faucet = await createNewFaucet(); + + const result = await fundAccountFromFaucet(initialWallet.id, faucet.id); + const initialBalance = result.targetAccountBalanace; + + const { hash: initialHash } = await getAccount(initialWallet.id); + + // Deleting the account + await clearStore(); + + const { accountId: restoredAccountId } = await importWalletFromSeed( + walletSeed, + mutable + ); + + expect(restoredAccountId).to.equal(initialWallet.id); + + const { hash: restoredAccountHash } = await getAccount(initialWallet.id); + + const restoredBalance = await getAccountBalance( + initialWallet.id, + faucet.id + ); + + expect(restoredBalance.toString()).to.equal(initialBalance); + expect(restoredAccountHash).to.equal(initialHash); + }); +}); diff --git a/crates/web-client/test/import_export.test.ts b/crates/web-client/test/import_export.test.ts new file mode 100644 index 000000000..7f04e8190 --- /dev/null +++ b/crates/web-client/test/import_export.test.ts @@ -0,0 +1,49 @@ +// TODO: Rename this / figure out rebasing with the other featuer which has import tests + +import { expect } from "chai"; +import { testingPage } from "./mocha.global.setup.mjs"; +import { clearStore, setupWalletAndFaucet } from "./webClientTestUtils"; + +const exportDb = async () => { + return await testingPage.evaluate(async () => { + const client = window.client; + const db = await client.exportStore(); + const serialized = JSON.stringify(db); + return serialized; + }); +}; + +const importDb = async (db: any) => { + return await testingPage.evaluate(async (_db) => { + const client = window.client; + await client.forceImportStore(_db); + }, db); +}; + +const getAccount = async (accountId: string) => { + return await testingPage.evaluate(async (_accountId) => { + const client = window.client; + const accountId = window.AccountId.fromHex(_accountId); + const account = await client.getAccount(accountId); + return { + accountId: account?.id().toString(), + accountHash: account?.hash().toHex(), + }; + }, accountId); +}; + +describe("export and import the db", () => { + it("export db with an account, find the account when re-importing", async () => { + const { accountHash: initialAccountHash, accountId } = + await setupWalletAndFaucet(); + const dbDump = await exportDb(); + + await clearStore(); + + await importDb(dbDump); + + const { accountHash } = await getAccount(accountId); + + expect(accountHash).to.equal(initialAccountHash); + }); +}); diff --git a/crates/web-client/test/mocha.global.setup.mjs b/crates/web-client/test/mocha.global.setup.mjs index 59fdd7c46..058dedc25 100644 --- a/crates/web-client/test/mocha.global.setup.mjs +++ b/crates/web-client/test/mocha.global.setup.mjs @@ -5,6 +5,7 @@ import { spawn } from "child_process"; import { register } from "ts-node"; import { env } from "process"; +import { clearStore } from "./webClientTestUtils.js"; chai.use(chaiAsPromised); @@ -49,7 +50,7 @@ before(async () => { // Creates the client in the test context and attach to window object await testingPage.evaluate( - async (rpc_port, remote_prover_port) => { + async (rpcPort, remoteProverPort) => { const { Account, AccountHeader, @@ -89,13 +90,13 @@ before(async () => { Word, WebClient, } = await import("./index.js"); - let rpc_url = `http://localhost:${rpc_port}`; - let prover_url = null; - if (remote_prover_port) { - prover_url = `http://localhost:${remote_prover_port}`; + let rpcUrl = `http://localhost:${rpcPort}`; + let proverUrl = null; + if (remoteProverPort) { + proverUrl = `http://localhost:${remoteProverPort}`; } const client = new WebClient(); - await client.create_client(rpc_url, prover_url); + await client.createClient(rpcUrl, proverUrl); window.client = client; window.Account = Account; @@ -140,7 +141,7 @@ before(async () => { window.helpers = window.helpers || {}; // Add the remote prover url to window - window.remote_prover_url = prover_url; + window.remoteProverUrl = proverUrl; window.helpers.waitForTransaction = async ( transactionId, @@ -153,12 +154,12 @@ before(async () => { if (timeWaited >= maxWaitTime) { throw new Error("Timeout waiting for transaction"); } - await client.sync_state(); - const uncomittedTransactions = await client.get_transactions( + await client.syncState(); + const uncomittedTransactions = await client.getTransactions( window.TransactionFilter.uncomitted() ); let uncomittedTransactionIds = uncomittedTransactions.map( - (transaction) => transaction.id().to_hex() + (transaction) => transaction.id().toHex() ); if (!uncomittedTransactionIds.includes(transactionId)) { break; @@ -170,7 +171,7 @@ before(async () => { window.helpers.refreshClient = async (initSeed) => { const client = new WebClient(); - await client.create_client(rpc_url, prover_url, initSeed); + await client.createClient(rpcUrl, proverUrl, initSeed); window.client = client; }; }, @@ -180,14 +181,7 @@ before(async () => { }); beforeEach(async () => { - await testingPage.evaluate(async () => { - // Open a connection to the list of databases - const databases = await indexedDB.databases(); - for (const db of databases) { - // Delete each database by name - indexedDB.deleteDatabase(db.name); - } - }); + await clearStore(); }); after(async () => { diff --git a/crates/web-client/test/new_account.test.ts b/crates/web-client/test/new_account.test.ts index 5352ce4de..dd6d5c635 100644 --- a/crates/web-client/test/new_account.test.ts +++ b/crates/web-client/test/new_account.test.ts @@ -1,76 +1,14 @@ import { expect } from "chai"; -import { testingPage } from "./mocha.global.setup.mjs"; -import { isValidAddress } from "./webClientTestUtils"; - -enum StorageMode { - PRIVATE = "private", - PUBLIC = "public", -} - -interface NewAccountTestResult { - id: string; - nonce: string; - vault_commitment: string; - storage_commitment: string; - code_commitment: string; - is_faucet: boolean; - is_regular_account: boolean; - is_updatable: boolean; - is_public: boolean; - is_new: boolean; -} +import { + createNewFaucet, + createNewWallet, + isValidAddress, + StorageMode, +} from "./webClientTestUtils"; // new_wallet tests // ======================================================================================================= -export const createNewWallet = async ( - storageMode: StorageMode, - mutable: boolean, - clientSeed?: Uint8Array, - isolatedClient?: boolean -): Promise => { - // Serialize initSeed for Puppeteer - const serializedClientSeed = clientSeed ? Array.from(clientSeed) : null; - - return await testingPage.evaluate( - async (_storageMode, _mutable, _serializedClientSeed, _isolatedClient) => { - if (_isolatedClient) { - // Reconstruct Uint8Array inside the browser context - const _clientSeed = _serializedClientSeed - ? new Uint8Array(_serializedClientSeed) - : undefined; - - await window.helpers.refreshClient(_clientSeed); - } - - let client = window.client; - const accountStorageMode = - _storageMode === "private" - ? window.AccountStorageMode.private() - : window.AccountStorageMode.public(); - - const newWallet = await client.new_wallet(accountStorageMode, _mutable); - - return { - id: newWallet.id().to_string(), - nonce: newWallet.nonce().to_string(), - vault_commitment: newWallet.vault().commitment().to_hex(), - storage_commitment: newWallet.storage().commitment().to_hex(), - code_commitment: newWallet.code().commitment().to_hex(), - is_faucet: newWallet.is_faucet(), - is_regular_account: newWallet.is_regular_account(), - is_updatable: newWallet.is_updatable(), - is_public: newWallet.is_public(), - is_new: newWallet.is_new(), - }; - }, - storageMode, - mutable, - serializedClientSeed, - isolatedClient - ); -}; - describe("new_wallet tests", () => { const testCases = [ { @@ -78,8 +16,8 @@ describe("new_wallet tests", () => { storageMode: StorageMode.PRIVATE, mutable: false, expected: { - is_public: false, - is_updatable: false, + isPublic: false, + isUpdatable: false, }, }, { @@ -87,8 +25,8 @@ describe("new_wallet tests", () => { storageMode: StorageMode.PUBLIC, mutable: false, expected: { - is_public: true, - is_updatable: false, + isPublic: true, + isUpdatable: false, }, }, { @@ -96,8 +34,8 @@ describe("new_wallet tests", () => { storageMode: StorageMode.PRIVATE, mutable: true, expected: { - is_public: false, - is_updatable: true, + isPublic: false, + isUpdatable: true, }, }, { @@ -105,26 +43,26 @@ describe("new_wallet tests", () => { storageMode: StorageMode.PUBLIC, mutable: true, expected: { - is_public: true, - is_updatable: true, + isPublic: true, + isUpdatable: true, }, }, ]; testCases.forEach(({ description, storageMode, mutable, expected }) => { it(description, async () => { - const result = await createNewWallet(storageMode, mutable); + const result = await createNewWallet({ storageMode, mutable }); isValidAddress(result.id); expect(result.nonce).to.equal("0"); - isValidAddress(result.vault_commitment); - isValidAddress(result.storage_commitment); - isValidAddress(result.code_commitment); - expect(result.is_faucet).to.equal(false); - expect(result.is_regular_account).to.equal(true); - expect(result.is_updatable).to.equal(expected.is_updatable); - expect(result.is_public).to.equal(expected.is_public); - expect(result.is_new).to.equal(true); + isValidAddress(result.vaultCommitment); + isValidAddress(result.storageCommitment); + isValidAddress(result.codeCommitment); + expect(result.isFaucet).to.equal(false); + expect(result.isRegularAccount).to.equal(true); + expect(result.isUpdatable).to.equal(expected.isUpdatable); + expect(result.isPublic).to.equal(expected.isPublic); + expect(result.isNew).to.equal(true); }); }); @@ -133,11 +71,21 @@ describe("new_wallet tests", () => { crypto.getRandomValues(clientSeed); // Isolate the client instance both times to ensure the outcome is deterministic - await createNewWallet(StorageMode.PUBLIC, false, clientSeed, true); + await createNewWallet({ + storageMode: StorageMode.PUBLIC, + mutable: false, + clientSeed, + isolatedClient: true, + }); // This should fail, as the wallet is already tracked within the same browser context await expect( - createNewWallet(StorageMode.PUBLIC, false, clientSeed, true) + createNewWallet({ + storageMode: StorageMode.PUBLIC, + mutable: false, + clientSeed, + isolatedClient: true, + }) ).to.be.rejectedWith(/Failed to insert new wallet: AccountAlreadyTracked/); }); }); @@ -145,76 +93,34 @@ describe("new_wallet tests", () => { // new_faucet tests // ======================================================================================================= -export const createNewFaucet = async ( - storageMode: StorageMode, - nonFungible: boolean, - tokenSymbol: string, - decimals: number, - maxSupply: bigint -): Promise => { - return await testingPage.evaluate( - async (_storageMode, _nonFungible, _tokenSymbol, _decimals, _maxSupply) => { - const client = window.client; - const accountStorageMode = - _storageMode === "private" - ? window.AccountStorageMode.private() - : window.AccountStorageMode.public(); - const newFaucet = await client.new_faucet( - accountStorageMode, - _nonFungible, - _tokenSymbol, - _decimals, - _maxSupply - ); - return { - id: newFaucet.id().to_string(), - nonce: newFaucet.nonce().to_string(), - vault_commitment: newFaucet.vault().commitment().to_hex(), - storage_commitment: newFaucet.storage().commitment().to_hex(), - code_commitment: newFaucet.code().commitment().to_hex(), - is_faucet: newFaucet.is_faucet(), - is_regular_account: newFaucet.is_regular_account(), - is_updatable: newFaucet.is_updatable(), - is_public: newFaucet.is_public(), - is_new: newFaucet.is_new(), - }; - }, - storageMode, - nonFungible, - tokenSymbol, - decimals, - maxSupply - ); -}; - describe("new_faucet tests", () => { const testCases = [ { description: "creates a new private, fungible faucet", storageMode: StorageMode.PRIVATE, - non_fungible: false, - token_symbol: "DAG", + nonFungible: false, + tokenSymbol: "DAG", decimals: 8, - max_supply: BigInt(10000000), + maxSupply: BigInt(10000000), expected: { - is_public: false, - is_updatable: false, - is_regular_account: false, - is_faucet: true, + isPublic: false, + isUpdatable: false, + isRegularAccount: false, + isFaucet: true, }, }, { description: "creates a new public, fungible faucet", storageMode: StorageMode.PUBLIC, - non_fungible: false, - token_symbol: "DAG", + nonFungible: false, + tokenSymbol: "DAG", decimals: 8, - max_supply: BigInt(10000000), + maxSupply: BigInt(10000000), expected: { - is_public: true, - is_updatable: false, - is_regular_account: false, - is_faucet: true, + isPublic: true, + isUpdatable: false, + isRegularAccount: false, + isFaucet: true, }, }, ]; @@ -223,31 +129,31 @@ describe("new_faucet tests", () => { ({ description, storageMode, - non_fungible, - token_symbol, + nonFungible, + tokenSymbol, decimals, - max_supply, + maxSupply, expected, }) => { it(description, async () => { const result = await createNewFaucet( storageMode, - non_fungible, - token_symbol, + nonFungible, + tokenSymbol, decimals, - max_supply + maxSupply ); isValidAddress(result.id); expect(result.nonce).to.equal("0"); - isValidAddress(result.vault_commitment); - isValidAddress(result.storage_commitment); - isValidAddress(result.code_commitment); - expect(result.is_faucet).to.equal(true); - expect(result.is_regular_account).to.equal(false); - expect(result.is_updatable).to.equal(false); - expect(result.is_public).to.equal(expected.is_public); - expect(result.is_new).to.equal(true); + isValidAddress(result.vaultCommitment); + isValidAddress(result.storageCommitment); + isValidAddress(result.codeCommitment); + expect(result.isFaucet).to.equal(true); + expect(result.isRegularAccount).to.equal(false); + expect(result.isUpdatable).to.equal(false); + expect(result.isPublic).to.equal(expected.isPublic); + expect(result.isNew).to.equal(true); }); } ); diff --git a/crates/web-client/test/new_transactions.test.ts b/crates/web-client/test/new_transactions.test.ts index 523658fa6..dc3ceae90 100644 --- a/crates/web-client/test/new_transactions.test.ts +++ b/crates/web-client/test/new_transactions.test.ts @@ -50,77 +50,77 @@ export const sendTransaction = async (): Promise => { return await testingPage.evaluate(async () => { const client = window.client; - const senderAccount = await client.new_wallet( + const senderAccount = await client.newWallet( window.AccountStorageMode.private(), true ); - const targetAccount = await client.new_wallet( + const targetAccount = await client.newWallet( window.AccountStorageMode.private(), true ); - const faucetAccount = await client.new_faucet( + const faucetAccount = await client.newFaucet( window.AccountStorageMode.private(), false, "DAG", 8, BigInt(10000000) ); - await client.sync_state(); + await client.syncState(); - let mint_transaction_result = await client.new_mint_transaction( + let mintTransactionResult = await client.newMintTransaction( senderAccount.id(), faucetAccount.id(), window.NoteType.private(), BigInt(1000) ); - let created_notes = mint_transaction_result.created_notes().notes(); - let created_note_ids = created_notes.map((note) => note.id().to_string()); + let createdNotes = mintTransactionResult.createdNotes().notes(); + let createdNoteIds = createdNotes.map((note) => note.id().toString()); await window.helpers.waitForTransaction( - mint_transaction_result.executed_transaction().id().to_hex() + mintTransactionResult.executedTransaction().id().toHex() ); - const senderConsumeTransactionResult = await client.new_consume_transaction( + const senderConsumeTransactionResult = await client.newConsumeTransaction( senderAccount.id(), - created_note_ids + createdNoteIds ); await window.helpers.waitForTransaction( - senderConsumeTransactionResult.executed_transaction().id().to_hex() + senderConsumeTransactionResult.executedTransaction().id().toHex() ); - let send_transaction_result = await client.new_send_transaction( + let sendTransactionResult = await client.newSendTransaction( senderAccount.id(), targetAccount.id(), faucetAccount.id(), window.NoteType.private(), BigInt(100) ); - let send_created_notes = send_transaction_result.created_notes().notes(); - let send_created_note_ids = send_created_notes.map((note) => - note.id().to_string() + let sendCreatedNotes = sendTransactionResult.createdNotes().notes(); + let sendCreatedNoteIds = sendCreatedNotes.map((note) => + note.id().toString() ); await window.helpers.waitForTransaction( - send_transaction_result.executed_transaction().id().to_hex() + sendTransactionResult.executedTransaction().id().toHex() ); - const targetConsumeTransactionResult = await client.new_consume_transaction( + const targetConsumeTransactionResult = await client.newConsumeTransaction( targetAccount.id(), - send_created_note_ids + sendCreatedNoteIds ); await window.helpers.waitForTransaction( - targetConsumeTransactionResult.executed_transaction().id().to_hex() + targetConsumeTransactionResult.executedTransaction().id().toHex() ); - const changedSenderAccount = await client.get_account(senderAccount.id()); - const changedTargetAccount = await client.get_account(targetAccount.id()); + const changedSenderAccount = await client.getAccount(senderAccount.id()); + const changedTargetAccount = await client.getAccount(targetAccount.id()); return { senderAccountBalance: changedSenderAccount .vault() - .get_balance(faucetAccount.id()) + .getBalance(faucetAccount.id()) .toString(), changedTargetBalance: changedTargetAccount .vault() - .get_balance(faucetAccount.id()) + .getBalance(faucetAccount.id()) .toString(), }; }); @@ -139,25 +139,25 @@ describe("new_send_transaction tests", () => { // ======================================================================================================= export const customTransaction = async ( - asserted_value: string, - with_custom_prover: boolean + assertedValue: string, + withCustomProver: boolean ): Promise => { return await testingPage.evaluate( - async (_asserted_value: string, _with_custom_prover: boolean) => { + async (_assertedValue: string, _withCustomProver: boolean) => { const client = window.client; - const walletAccount = await client.new_wallet( + const walletAccount = await client.newWallet( window.AccountStorageMode.private(), false ); - const faucetAccount = await client.new_faucet( + const faucetAccount = await client.newFaucet( window.AccountStorageMode.private(), false, "DAG", 8, BigInt(10000000) ); - await client.sync_state(); + await client.syncState(); // Creating Custom Note which needs the following: // - Note Assets @@ -185,20 +185,20 @@ export const customTransaction = async ( let noteMetadata = new window.NoteMetadata( faucetAccount.id(), window.NoteType.private(), - window.NoteTag.from_account_id( + window.NoteTag.fromAccountId( walletAccount.id(), - window.NoteExecutionMode.new_local() + window.NoteExecutionMode.newLocal() ), window.NoteExecutionHint.none(), undefined ); - let expectedNoteArgs = noteArgs.map((felt) => felt.as_int()); + let expectedNoteArgs = noteArgs.map((felt) => felt.asInt()); let memAddress = "1000"; let memAddress2 = "1004"; let expectedNoteArg1 = expectedNoteArgs.slice(0, 4).join("."); let expectedNoteArg2 = expectedNoteArgs.slice(4, 8).join("."); - let note_script = ` + let noteScript = ` # Custom P2ID note script # # This note script asserts that the note args are exactly the same as passed @@ -320,7 +320,7 @@ export const customTransaction = async ( end `; - let compiledNoteScript = await client.compile_note_script(note_script); + let compiledNoteScript = await client.compileNoteScript(noteScript); let noteInputs = new window.NoteInputs( new window.FeltArray([ walletAccount.id().prefix(), @@ -340,41 +340,41 @@ export const customTransaction = async ( let note = new window.Note(noteAssets, noteMetadata, noteRecipient); // Creating First Custom Transaction Request to Mint the Custom Note - let transaction_request = new window.TransactionRequestBuilder() - .with_own_output_notes( + let transactionRequest = new window.TransactionRequestBuilder() + .withOwnOutputNotes( new window.OutputNotesArray([window.OutputNote.full(note)]) ) .build(); // Execute and Submit Transaction - let transaction_result = await client.new_transaction( + let transactionResult = await client.newTransaction( faucetAccount.id(), - transaction_request + transactionRequest ); - if (_with_custom_prover) { - await client.submit_transaction_with_prover( - transaction_result, + if (_withCustomProver) { + await client.submitTransactionWithProver( + transactionResult, await selectProver() ); } else { - await client.submit_transaction(transaction_result); + await client.submitTransaction(transactionResult); } await window.helpers.waitForTransaction( - transaction_result.executed_transaction().id().to_hex() + transactionResult.executedTransaction().id().toHex() ); // Just like in the miden test, you can modify this script to get the execution to fail // by modifying the assert - let tx_script = ` + let txScript = ` use.miden::contracts::auth::basic->auth_tx use.miden::kernels::tx::prologue use.miden::kernels::tx::memory begin - push.0 push.${_asserted_value} - # => [0, ${_asserted_value}] + push.0 push.${_assertedValue} + # => [0, ${_assertedValue}] assert_eq call.auth_tx::auth_tx_rpo_falcon512 @@ -383,47 +383,45 @@ export const customTransaction = async ( // Creating Second Custom Transaction Request to Consume Custom Note // with Invalid/Valid Transaction Script - let transaction_script = await client.compile_tx_script(tx_script); - let note_id = note.id(); - let note_args_commitment = window.Rpo256.hash_elements(feltArray); // gets consumed by NoteIdAndArgs - let note_id_and_args = new window.NoteIdAndArgs( - note_id, - note_args_commitment.to_word() + let transactionScript = await client.compileTxScript(txScript); + let noteId = note.id(); + let noteArgsCommitment = window.Rpo256.hashElements(feltArray); // gets consumed by NoteIdAndArgs + let noteIdAndArgs = new window.NoteIdAndArgs( + noteId, + noteArgsCommitment.toWord() ); - let note_id_and_args_array = new window.NoteIdAndArgsArray([ - note_id_and_args, - ]); - let advice_map = new window.AdviceMap(); - let note_args_commitment_2 = window.Rpo256.hash_elements(feltArray); - advice_map.insert(note_args_commitment_2, feltArray); - - let transaction_request_2 = new window.TransactionRequestBuilder() - .with_authenticated_input_notes(note_id_and_args_array) - .with_custom_script(transaction_script) - .extend_advice_map(advice_map) + let noteIdAndArgsArray = new window.NoteIdAndArgsArray([noteIdAndArgs]); + let adviceMap = new window.AdviceMap(); + let noteArgsCommitment2 = window.Rpo256.hashElements(feltArray); + adviceMap.insert(noteArgsCommitment2, feltArray); + + let transactionRequest2 = new window.TransactionRequestBuilder() + .withAuthenticatedInputNotes(noteIdAndArgsArray) + .withCustomScript(transactionScript) + .extendAdviceMap(adviceMap) .build(); // Execute and Submit Transaction - let transaction_result_2 = await client.new_transaction( + let transactionResult2 = await client.newTransaction( walletAccount.id(), - transaction_request_2 + transactionRequest2 ); - if (_with_custom_prover) { - await client.submit_transaction_with_prover( - transaction_result_2, + if (_withCustomProver) { + await client.submitTransactionWithProver( + transactionResult2, await selectProver() ); } else { - await client.submit_transaction(transaction_result_2); + await client.submitTransaction(transactionResult2); } await window.helpers.waitForTransaction( - transaction_result_2.executed_transaction().id().to_hex() + transactionResult2.executedTransaction().id().toHex() ); }, - asserted_value, - with_custom_prover + assertedValue, + withCustomProver ); }; @@ -436,31 +434,31 @@ const customTxWithMultipleNotes = async ( async (_isSerialNumSame, _senderAccountId, _faucetAccountId) => { const client = window.client; const amount = BigInt(10); - const targetAccount = await client.new_wallet( + const targetAccount = await client.newWallet( window.AccountStorageMode.private(), true ); const targetAccountId = targetAccount.id(); - const senderAccountId = window.AccountId.from_hex(_senderAccountId); - const faucetAccountId = window.AccountId.from_hex(_faucetAccountId); + const senderAccountId = window.AccountId.fromHex(_senderAccountId); + const faucetAccountId = window.AccountId.fromHex(_faucetAccountId); // Create custom note with multiple assets to send to target account // Error should happen if serial numbers are the same in each set of // note assets. Otherwise, the transaction should go through. - let noteAssets_1 = new window.NoteAssets([ + let noteAssets1 = new window.NoteAssets([ new window.FungibleAsset(faucetAccountId, amount), ]); - let noteAssets_2 = new window.NoteAssets([ + let noteAssets2 = new window.NoteAssets([ new window.FungibleAsset(faucetAccountId, amount), ]); let noteMetadata = new window.NoteMetadata( senderAccountId, window.NoteType.public(), - window.NoteTag.from_account_id( + window.NoteTag.fromAccountId( targetAccountId, - window.NoteExecutionMode.new_local() + window.NoteExecutionMode.newLocal() ), window.NoteExecutionHint.none(), undefined @@ -493,11 +491,11 @@ const customTxWithMultipleNotes = async ( noteInputs ); - let note1 = new window.Note(noteAssets_1, noteMetadata, noteRecipient1); - let note2 = new window.Note(noteAssets_2, noteMetadata, noteRecipient2); + let note1 = new window.Note(noteAssets1, noteMetadata, noteRecipient1); + let note2 = new window.Note(noteAssets2, noteMetadata, noteRecipient2); - let transaction_request = new window.TransactionRequestBuilder() - .with_own_output_notes( + let transactionRequest = new window.TransactionRequestBuilder() + .withOwnOutputNotes( new window.OutputNotesArray([ window.OutputNote.full(note1), window.OutputNote.full(note2), @@ -505,15 +503,15 @@ const customTxWithMultipleNotes = async ( ) .build(); - let transactionResult = await client.new_transaction( + let transactionResult = await client.newTransaction( senderAccountId, - transaction_request + transactionRequest ); - await client.submit_transaction(transactionResult); + await client.submitTransaction(transactionResult); await window.helpers.waitForTransaction( - transactionResult.executed_transaction().id().to_hex() + transactionResult.executedTransaction().id().toHex() ); }, isSerialNumSame, @@ -562,10 +560,10 @@ describe("custom transaction with multiple output notes", () => { // ================================================================================================ export const selectProver = async (): Promise => { - if (window.remote_prover_url != null) { - return window.TransactionProver.new_remote_prover(window.remote_prover_url); + if (window.remoteProverUrl != null) { + return window.TransactionProver.newRemoteProver(window.remoteProverUrl); } else { - return window.TransactionProver.new_local_prover(); + return window.TransactionProver.newLocalProver(); } }; diff --git a/crates/web-client/test/notes.test.ts b/crates/web-client/test/notes.test.ts index 8b433b3e7..1f174c8c7 100644 --- a/crates/web-client/test/notes.test.ts +++ b/crates/web-client/test/notes.test.ts @@ -11,9 +11,9 @@ import { const getInputNote = async (noteId: string) => { return await testingPage.evaluate(async (_noteId) => { const client = window.client; - const note = await client.get_input_note(_noteId); + const note = await client.getInputNote(_noteId); return { - noteId: note ? note.id().to_string() : undefined, + noteId: note ? note.id().toString() : undefined, }; }, noteId); }; @@ -23,9 +23,9 @@ const getInputNotes = async () => { return await testingPage.evaluate(async () => { const client = window.client; const filter = new window.NoteFilter(window.NoteFilterTypes.All); - const notes = await client.get_input_notes(filter); + const notes = await client.getInputNotes(filter); return { - noteIds: notes.map((note) => note.id().to_string()), + noteIds: notes.map((note) => note.id().toString()), }; }); }; @@ -54,16 +54,16 @@ const getConsumableNotes = async (accountId?: string) => { let records; if (_accountId) { console.log({ _accountId }); - const accountId = window.AccountId.from_hex(_accountId); - records = await client.get_consumable_notes(accountId); + const accountId = window.AccountId.fromHex(_accountId); + records = await client.getConsumableNotes(accountId); } else { - records = await client.get_consumable_notes(); + records = await client.getConsumableNotes(); } return records.map((record) => ({ - noteId: record.input_note_record().id().to_string(), - consumability: record.note_consumability().map((c) => ({ - accountId: c.account_id().to_string(), + noteId: record.inputNoteRecord().id().toString(), + consumability: record.noteConsumability().map((c) => ({ + accountId: c.account_id().toString(), consumableAfterBlock: c.consumable_after_block(), })), })); diff --git a/crates/web-client/test/tags.test.ts b/crates/web-client/test/tags.test.ts index d63a49642..c96339eae 100644 --- a/crates/web-client/test/tags.test.ts +++ b/crates/web-client/test/tags.test.ts @@ -12,8 +12,8 @@ interface AddTagSuccessResult { export const addTag = async (tag: string): Promise => { return await testingPage.evaluate(async (tag) => { const client = window.client; - await client.add_tag(tag); - const tags = await client.list_tags(); + await client.addTag(tag); + const tags = await client.listTags(); return { tag: tag, @@ -44,10 +44,10 @@ export const removeTag = async ( ): Promise => { return await testingPage.evaluate(async (tag) => { const client = window.client; - await client.add_tag(tag); - await client.remove_tag(tag); + await client.addTag(tag); + await client.removeTag(tag); - const tags = await client.list_tags(); + const tags = await client.listTags(); return { tag: tag, diff --git a/crates/web-client/test/transactions.test.ts b/crates/web-client/test/transactions.test.ts index 518be517d..3dd9cfae4 100644 --- a/crates/web-client/test/transactions.test.ts +++ b/crates/web-client/test/transactions.test.ts @@ -18,17 +18,17 @@ const getAllTransactions = async (): Promise => { return await testingPage.evaluate(async () => { const client = window.client; - let transactions = await client.get_transactions( + let transactions = await client.getTransactions( window.TransactionFilter.all() ); - let uncomittedTransactions = await client.get_transactions( + let uncomittedTransactions = await client.getTransactions( window.TransactionFilter.uncomitted() ); let transactionIds = transactions.map((transaction) => - transaction.id().to_hex() + transaction.id().toHex() ); let uncomittedTransactionIds = uncomittedTransactions.map((transaction) => - transaction.id().to_hex() + transaction.id().toHex() ); return { @@ -94,15 +94,15 @@ export const compileTxScript = async ( return await testingPage.evaluate(async (_script) => { const client = window.client; - let walletAccount = await client.new_wallet( + let walletAccount = await client.newWallet( window.AccountStorageMode.private(), true ); - const compiledScript = await client.compile_tx_script(_script); + const compiledScript = await client.compileTxScript(_script); return { - scriptHash: compiledScript.hash().to_hex(), + scriptHash: compiledScript.hash().toHex(), }; }, script); }; diff --git a/crates/web-client/test/webClientTestUtils.ts b/crates/web-client/test/webClientTestUtils.ts index 33f657e53..5777e4489 100644 --- a/crates/web-client/test/webClientTestUtils.ts +++ b/crates/web-client/test/webClientTestUtils.ts @@ -8,6 +8,11 @@ interface MintTransactionResult { createdNoteId: string; } +export enum StorageMode { + PRIVATE = "private", + PUBLIC = "public", +} + // SDK functions export const mintTransaction = async ( @@ -19,10 +24,10 @@ export const mintTransaction = async ( async (_targetAccountId, _faucetAccountId, _sync) => { const client = window.client; - const targetAccountId = window.AccountId.from_hex(_targetAccountId); - const faucetAccountId = window.AccountId.from_hex(_faucetAccountId); + const targetAccountId = window.AccountId.fromHex(_targetAccountId); + const faucetAccountId = window.AccountId.fromHex(_faucetAccountId); - const new_mint_transaction_result = await client.new_mint_transaction( + const newMintTransactionResult = await client.newMintTransaction( targetAccountId, faucetAccountId, window.NoteType.private(), @@ -31,24 +36,24 @@ export const mintTransaction = async ( if (_sync) { await window.helpers.waitForTransaction( - new_mint_transaction_result.executed_transaction().id().to_hex() + newMintTransactionResult.executedTransaction().id().toHex() ); } return { - transactionId: new_mint_transaction_result - .executed_transaction() + transactionId: newMintTransactionResult + .executedTransaction() .id() - .to_hex(), - numOutputNotesCreated: new_mint_transaction_result - .created_notes() - .num_notes(), - nonce: new_mint_transaction_result.account_delta().nonce()?.to_string(), - createdNoteId: new_mint_transaction_result - .created_notes() + .toHex(), + numOutputNotesCreated: newMintTransactionResult + .createdNotes() + .numNotes(), + nonce: newMintTransactionResult.accountDelta().nonce()?.toString(), + createdNoteId: newMintTransactionResult + .createdNotes() .notes()[0] .id() - .to_string(), + .toString(), }; }, targetAccountId, @@ -74,31 +79,31 @@ export const sendTransaction = async ( ) => { const client = window.client; - const senderAccountId = window.AccountId.from_hex(_senderAccountId); - const targetAccountId = window.AccountId.from_hex(_targetAccountId); - const faucetAccountId = window.AccountId.from_hex(_faucetAccountId); + const senderAccountId = window.AccountId.fromHex(_senderAccountId); + const targetAccountId = window.AccountId.fromHex(_targetAccountId); + const faucetAccountId = window.AccountId.fromHex(_faucetAccountId); - let mint_transaction_result = await client.new_mint_transaction( + let mintTransactionResult = await client.newMintTransaction( senderAccountId, - window.AccountId.from_hex(_faucetAccountId), + window.AccountId.fromHex(_faucetAccountId), window.NoteType.private(), BigInt(_amount) ); - let created_notes = mint_transaction_result.created_notes().notes(); - let created_note_ids = created_notes.map((note) => note.id().to_string()); + let createdNotes = mintTransactionResult.createdNotes().notes(); + let createdNoteIds = createdNotes.map((note) => note.id().toString()); await window.helpers.waitForTransaction( - mint_transaction_result.executed_transaction().id().to_hex() + mintTransactionResult.executedTransaction().id().toHex() ); - const consume_transaction_result = await client.new_consume_transaction( + const consumeTransactionResult = await client.newConsumeTransaction( senderAccountId, - created_note_ids + createdNoteIds ); await window.helpers.waitForTransaction( - consume_transaction_result.executed_transaction().id().to_hex() + consumeTransactionResult.executedTransaction().id().toHex() ); - let send_transaction_result = await client.new_send_transaction( + let sendTransactionResult = await client.newSendTransaction( senderAccountId, targetAccountId, faucetAccountId, @@ -106,16 +111,16 @@ export const sendTransaction = async ( BigInt(_amount), _recallHeight ); - let send_created_notes = send_transaction_result.created_notes().notes(); - let send_created_note_ids = send_created_notes.map((note) => - note.id().to_string() + let sendCreatedNotes = sendTransactionResult.createdNotes().notes(); + let sendCreatedNoteIds = sendCreatedNotes.map((note) => + note.id().toString() ); await window.helpers.waitForTransaction( - send_transaction_result.executed_transaction().id().to_hex() + sendTransactionResult.executedTransaction().id().toHex() ); - return send_created_note_ids; + return sendCreatedNoteIds; }, senderAccountId, targetAccountId, @@ -125,6 +130,167 @@ export const sendTransaction = async ( ); }; +export interface NewAccountTestResult { + id: string; + nonce: string; + vaultCommitment: string; + storageCommitment: string; + codeCommitment: string; + isFaucet: boolean; + isRegularAccount: boolean; + isUpdatable: boolean; + isPublic: boolean; + isNew: boolean; +} +export const createNewWallet = async ({ + storageMode, + mutable, + clientSeed, + isolatedClient, + walletSeed, +}: { + storageMode: StorageMode; + mutable: boolean; + clientSeed?: Uint8Array; + isolatedClient?: boolean; + walletSeed?: Uint8Array; +}): Promise => { + // Serialize initSeed for Puppeteer + const serializedClientSeed = clientSeed ? Array.from(clientSeed) : null; + const serializedWalletSeed = walletSeed ? Array.from(walletSeed) : null; + + return await testingPage.evaluate( + async ( + _storageMode, + _mutable, + _serializedClientSeed, + _isolatedClient, + _serializedWalletSeed + ) => { + if (_isolatedClient) { + // Reconstruct Uint8Array inside the browser context + const _clientSeed = _serializedClientSeed + ? new Uint8Array(_serializedClientSeed) + : undefined; + + await window.helpers.refreshClient(_clientSeed); + } + + let _walletSeed; + if (_serializedWalletSeed) { + _walletSeed = new Uint8Array(_serializedWalletSeed); + } + + let client = window.client; + const accountStorageMode = + _storageMode === "private" + ? window.AccountStorageMode.private() + : window.AccountStorageMode.public(); + + const newWallet = await client.newWallet( + accountStorageMode, + _mutable, + _walletSeed + ); + + return { + id: newWallet.id().toString(), + nonce: newWallet.nonce().toString(), + vaultCommitment: newWallet.vault().commitment().toHex(), + storageCommitment: newWallet.storage().commitment().toHex(), + codeCommitment: newWallet.code().commitment().toHex(), + isFaucet: newWallet.isFaucet(), + isRegularAccount: newWallet.isRegularAccount(), + isUpdatable: newWallet.isUpdatable(), + isPublic: newWallet.isPublic(), + isNew: newWallet.isNew(), + }; + }, + storageMode, + mutable, + serializedClientSeed, + isolatedClient, + serializedWalletSeed + ); +}; + +export const createNewFaucet = async ( + storageMode: StorageMode = StorageMode.PUBLIC, + nonFungible: boolean = false, + tokenSymbol: string = "DAG", + decimals: number = 8, + maxSupply: bigint = BigInt(10000000) +): Promise => { + return await testingPage.evaluate( + async (_storageMode, _nonFungible, _tokenSymbol, _decimals, _maxSupply) => { + const client = window.client; + const accountStorageMode = + _storageMode === "private" + ? window.AccountStorageMode.private() + : window.AccountStorageMode.public(); + const newFaucet = await client.newFaucet( + accountStorageMode, + _nonFungible, + _tokenSymbol, + _decimals, + _maxSupply + ); + return { + id: newFaucet.id().toString(), + nonce: newFaucet.nonce().toString(), + vaultCommitment: newFaucet.vault().commitment().toHex(), + storageCommitment: newFaucet.storage().commitment().toHex(), + codeCommitment: newFaucet.code().commitment().toHex(), + isFaucet: newFaucet.isFaucet(), + isRegularAccount: newFaucet.isRegularAccount(), + isUpdatable: newFaucet.isUpdatable(), + isPublic: newFaucet.isPublic(), + isNew: newFaucet.isNew(), + }; + }, + storageMode, + nonFungible, + tokenSymbol, + decimals, + maxSupply + ); +}; + +export const fundAccountFromFaucet = async ( + accountId: string, + faucetId: string +) => { + const mintResult = await mintTransaction(accountId, faucetId); + return await consumeTransaction( + accountId, + faucetId, + mintResult.createdNoteId + ); +}; + +export const getAccountBalance = async ( + accountId: string, + faucetId: string +) => { + return await testingPage.evaluate( + async (_accountId, _faucetId) => { + const client = window.client; + const account = await client.getAccount( + window.AccountId.fromHex(_accountId) + ); + let balance = BigInt(0); + if (account) { + balance = account + .vault() + .getBalance(window.AccountId.fromHex(_faucetId)); + } + return balance; + }, + accountId, + faucetId + ); +}; + interface ConsumeTransactionResult { transactionId: string; nonce: string | undefined; @@ -141,29 +307,29 @@ export const consumeTransaction = async ( async (_targetAccountId, _faucetId, _noteId) => { const client = window.client; - const targetAccountId = window.AccountId.from_hex(_targetAccountId); - const faucetId = window.AccountId.from_hex(_faucetId); + const targetAccountId = window.AccountId.fromHex(_targetAccountId); + const faucetId = window.AccountId.fromHex(_faucetId); - const consumeTransactionResult = await client.new_consume_transaction( + const consumeTransactionResult = await client.newConsumeTransaction( targetAccountId, [_noteId] ); await window.helpers.waitForTransaction( - consumeTransactionResult.executed_transaction().id().to_hex() + consumeTransactionResult.executedTransaction().id().toHex() ); - const changedTargetAccount = await client.get_account(targetAccountId); + const changedTargetAccount = await client.getAccount(targetAccountId); return { transactionId: consumeTransactionResult - .executed_transaction() + .executedTransaction() .id() - .to_hex(), - nonce: consumeTransactionResult.account_delta().nonce()?.to_string(), - numConsumedNotes: consumeTransactionResult.consumed_notes().num_notes(), + .toHex(), + nonce: consumeTransactionResult.accountDelta().nonce()?.toString(), + numConsumedNotes: consumeTransactionResult.consumedNotes().numNotes(), targetAccountBalanace: changedTargetAccount .vault() - .get_balance(faucetId) + .getBalance(faucetId) .toString(), }; }, @@ -176,41 +342,71 @@ export const consumeTransaction = async ( interface SetupWalletFaucetResult { accountId: string; faucetId: string; + accountHash: string; } export const setupWalletAndFaucet = async (): Promise => { return await testingPage.evaluate(async () => { const client = window.client; - const account = await client.new_wallet( + const account = await client.newWallet( window.AccountStorageMode.private(), true ); - const faucetAccount = await client.new_faucet( + const faucetAccount = await client.newFaucet( window.AccountStorageMode.private(), false, "DAG", 8, BigInt(10000000) ); - await client.sync_state(); + await client.syncState(); return { - accountId: account.id().to_string(), - faucetId: faucetAccount.id().to_string(), + accountId: account.id().toString(), + accountHash: account.hash().toHex(), + faucetId: faucetAccount.id().toString(), }; }); }; +export const getAccount = async (accountId: string) => { + return await testingPage.evaluate(async (_accountId) => { + const client = window.client; + const accountId = window.AccountId.fromHex(_accountId); + const account = await client.getAccount(accountId); + return { + id: account?.id().toString(), + hash: account?.hash().toHex(), + nonce: account?.nonce().toString(), + vaultCommitment: account?.vault().commitment().toHex(), + storageCommitment: account?.storage().commitment().toHex(), + codeCommitment: account?.code().commitment().toHex(), + }; + }, accountId); +}; + export const syncState = async () => { return await testingPage.evaluate(async () => { const client = window.client; - const summary = await client.sync_state(); + const summary = await client.syncState(); return { - blockNum: summary.block_num(), + blockNum: summary.blockNum(), }; }); }; +export const clearStore = async () => { + await testingPage.evaluate(async () => { + // Open a connection to the list of databases + const databases = await indexedDB.databases(); + for (const db of databases) { + // Delete each database by name + if (db.name) { + indexedDB.deleteDatabase(db.name); + } + } + }); +}; // Misc test utils diff --git a/crates/web-client/yarn.lock b/crates/web-client/yarn.lock index 4d4615f93..02740ef04 100644 --- a/crates/web-client/yarn.lock +++ b/crates/web-client/yarn.lock @@ -49,6 +49,13 @@ wrap-ansi "^8.1.0" wrap-ansi-cjs "npm:wrap-ansi@^7.0.0" +"@isaacs/fs-minipass@^4.0.0": + version "4.0.1" + resolved "https://registry.yarnpkg.com/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz#2d59ae3ab4b38fb4270bfa23d30f8e2e86c7fe32" + integrity sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w== + dependencies: + minipass "^7.0.4" + "@jridgewell/resolve-uri@^3.0.3": version "3.1.2" resolved "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz" @@ -110,7 +117,7 @@ is-module "^1.0.0" resolve "^1.22.1" -"@rollup/pluginutils@^5.0.1", "@rollup/pluginutils@^5.0.2": +"@rollup/pluginutils@^5.0.1": version "5.1.0" resolved "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.1.0.tgz" integrity sha512-XTIWOPPcpvyKI6L1NHo0lFlCyznUEyPmPY1mc3KpPVDYulHSTvyeLNVW00QTLIAFNhR3kYnJTQHeGqU4M3n09g== @@ -119,6 +126,15 @@ estree-walker "^2.0.2" picomatch "^2.3.1" +"@rollup/pluginutils@^5.1.4": + version "5.1.4" + resolved "https://registry.yarnpkg.com/@rollup/pluginutils/-/pluginutils-5.1.4.tgz#bb94f1f9eaaac944da237767cdfee6c5b2262d4a" + integrity sha512-USm05zrsFxYLPdWWq+K3STlWiT/3ELn3RcV5hJMghpeAIhxfsUIg6mt12CBJBInWMV4VneoV7SfGv8xIwo2qNQ== + dependencies: + "@types/estree" "^1.0.0" + estree-walker "^2.0.2" + picomatch "^4.0.2" + "@tootallnate/quickjs-emscripten@^0.23.0": version "0.23.0" resolved "https://registry.npmjs.org/@tootallnate/quickjs-emscripten/-/quickjs-emscripten-0.23.0.tgz" @@ -185,18 +201,18 @@ dependencies: "@types/node" "*" -"@wasm-tool/rollup-plugin-rust@wasm-tool/rollup-plugin-rust": - version "2.4.5" - resolved "git+ssh://git@github.com/wasm-tool/rollup-plugin-rust.git#7a648c777cbfe48d2714b038f37ec0411c32cdcd" +"@wasm-tool/rollup-plugin-rust@^3.0.3": + version "3.0.4" + resolved "https://registry.yarnpkg.com/@wasm-tool/rollup-plugin-rust/-/rollup-plugin-rust-3.0.4.tgz#fc5e683642d82d247961100e573eb79ce7e93a4a" + integrity sha512-chkbVEcaPS0wcapi9uGdP8U4Txup8z0Ezys5LMKVZn4Sl43x+V2mAGZ2wbR5V8moQQj4er7UOzBQWHuFRRtBwg== dependencies: "@iarna/toml" "^2.2.5" - "@rollup/pluginutils" "^5.0.2" - binaryen "^111.0.0" - chalk "^4.0.0" - glob "^10.2.2" - node-fetch "^2.0.0" - rimraf "^5.0.0" - tar "^6.1.11" + "@rollup/pluginutils" "^5.1.4" + chalk "^5.4.1" + glob "^11.0.1" + node-fetch "^3.3.2" + rimraf "^6.0.1" + tar "^7.4.3" acorn-walk@^8.1.1: version "8.3.3" @@ -354,11 +370,6 @@ binary-extensions@^2.0.0: resolved "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz" integrity sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw== -binaryen@^111.0.0: - version "111.0.0" - resolved "https://registry.npmjs.org/binaryen/-/binaryen-111.0.0.tgz" - integrity sha512-PEXOSHFO85aj1aP4t+KGzvxQ00qXbjCysWlsDjlGkP1e9owNiYdpEkLej21Ax8LDD7xJ01rEmJDqZ/JPoW2GXw== - brace-expansion@^1.1.7: version "1.1.11" resolved "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz" @@ -432,7 +443,7 @@ chai-as-promised@^8.0.0: dependencies: check-error "^2.0.0" -chai@^5.1.1, "chai@>= 2.1.2 < 6": +chai@^5.1.1: version "5.1.1" resolved "https://registry.npmjs.org/chai/-/chai-5.1.1.tgz" integrity sha512-pT1ZgP8rPNqUgieVaEY+ryQr6Q4HXNg8Ei9UnLUrjN4IA7dvQC5JB+/kxVcPNDHyBcc/26CXPkbNzq3qwrOEKA== @@ -452,7 +463,7 @@ chalk@^2.4.2: escape-string-regexp "^1.0.5" supports-color "^5.3.0" -chalk@^4.0.0, chalk@^4.1.0, chalk@^4.1.2: +chalk@^4.1.0, chalk@^4.1.2: version "4.1.2" resolved "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz" integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== @@ -460,6 +471,11 @@ chalk@^4.0.0, chalk@^4.1.0, chalk@^4.1.2: ansi-styles "^4.1.0" supports-color "^7.1.0" +chalk@^5.4.1: + version "5.4.1" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-5.4.1.tgz#1b48bf0963ec158dce2aacf69c093ae2dd2092d8" + integrity sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w== + check-error@^2.0.0, check-error@^2.1.1: version "2.1.1" resolved "https://registry.npmjs.org/check-error/-/check-error-2.1.1.tgz" @@ -480,10 +496,10 @@ chokidar@^3.5.3: optionalDependencies: fsevents "~2.3.2" -chownr@^2.0.0: - version "2.0.0" - resolved "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz" - integrity sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ== +chownr@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/chownr/-/chownr-3.0.0.tgz#9855e64ecd240a9cc4267ce8a4aa5d24a1da15e4" + integrity sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g== chromium-bidi@0.6.5: version "0.6.5" @@ -526,16 +542,16 @@ color-convert@^2.0.1: dependencies: color-name "~1.1.4" -color-name@~1.1.4: - version "1.1.4" - resolved "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz" - integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== - color-name@1.1.3: version "1.1.3" resolved "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz" integrity sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw== +color-name@~1.1.4: + version "1.1.4" + resolved "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz" + integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== + commondir@^1.0.1: version "1.0.1" resolved "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz" @@ -592,11 +608,23 @@ cross-spawn@^7.0.0, cross-spawn@^7.0.1: shebang-command "^2.0.0" which "^2.0.1" +data-uri-to-buffer@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz#d8feb2b2881e6a4f58c2e08acfd0e2834e26222e" + integrity sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A== + data-uri-to-buffer@^6.0.2: version "6.0.2" resolved "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-6.0.2.tgz" integrity sha512-7hvf7/GW8e86rW0ptuwS3OcBGDjIi6SZva7hCyWC0yYry2cOPmLIjXAUHI6DK2HsnwJd9ifmt57i8eV2n4YNpw== +debug@4, debug@^4.1.1, debug@^4.3.4, debug@^4.3.5, debug@^4.3.6: + version "4.3.6" + resolved "https://registry.npmjs.org/debug/-/debug-4.3.6.tgz" + integrity sha512-O/09Bd4Z1fBrU4VzkhFqVgpPzaGbw6Sm9FEkBT1A/YBXQFGuuSxa1dN2nxgxS34JmKXqYx8CZAwEVoJFImUXIg== + dependencies: + ms "2.1.2" + debug@^3.2.7: version "3.2.7" resolved "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz" @@ -604,13 +632,6 @@ debug@^3.2.7: dependencies: ms "^2.1.1" -debug@^4.1.1, debug@^4.3.4, debug@^4.3.5, debug@^4.3.6, debug@4: - version "4.3.6" - resolved "https://registry.npmjs.org/debug/-/debug-4.3.6.tgz" - integrity sha512-O/09Bd4Z1fBrU4VzkhFqVgpPzaGbw6Sm9FEkBT1A/YBXQFGuuSxa1dN2nxgxS34JmKXqYx8CZAwEVoJFImUXIg== - dependencies: - ms "2.1.2" - decamelize@^4.0.0: version "4.0.0" resolved "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz" @@ -644,7 +665,7 @@ degenerator@^5.0.0: escodegen "^2.1.0" esprima "^4.0.1" -devtools-protocol@*, devtools-protocol@0.0.1330662: +devtools-protocol@0.0.1330662: version "0.0.1330662" resolved "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1330662.tgz" integrity sha512-pzh6YQ8zZfz3iKlCvgzVCu22NdpZ8hNmwU6WnQjNVquh0A9iVosPtNLWDwaWVGyrntQlltPFztTMK5Cg6lfCuw== @@ -789,6 +810,14 @@ fd-slicer@~1.1.0: dependencies: pend "~1.2.0" +fetch-blob@^3.1.2, fetch-blob@^3.1.4: + version "3.2.0" + resolved "https://registry.yarnpkg.com/fetch-blob/-/fetch-blob-3.2.0.tgz#f09b8d4bbd45adc6f0c20b7e787e793e309dcce9" + integrity sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ== + dependencies: + node-domexception "^1.0.0" + web-streams-polyfill "^3.0.3" + fill-range@^7.1.1: version "7.1.1" resolved "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz" @@ -822,6 +851,13 @@ foreground-child@^3.1.0: cross-spawn "^7.0.0" signal-exit "^4.0.1" +formdata-polyfill@^4.0.10: + version "4.0.10" + resolved "https://registry.yarnpkg.com/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz#24807c31c9d402e002ab3d8c720144ceb8848423" + integrity sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g== + dependencies: + fetch-blob "^3.1.2" + fs-extra@^11.2.0: version "11.2.0" resolved "https://registry.npmjs.org/fs-extra/-/fs-extra-11.2.0.tgz" @@ -831,13 +867,6 @@ fs-extra@^11.2.0: jsonfile "^6.0.1" universalify "^2.0.0" -fs-minipass@^2.0.0: - version "2.1.0" - resolved "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz" - integrity sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg== - dependencies: - minipass "^3.0.0" - fs.realpath@^1.0.0: version "1.0.0" resolved "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz" @@ -898,7 +927,7 @@ glob-parent@~5.1.2: dependencies: is-glob "^4.0.1" -glob@^10.2.2, glob@^10.3.7: +glob@^10.3.7: version "10.4.5" resolved "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz" integrity sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg== @@ -922,6 +951,18 @@ glob@^11.0.0: package-json-from-dist "^1.0.0" path-scurry "^2.0.0" +glob@^11.0.1: + version "11.0.1" + resolved "https://registry.yarnpkg.com/glob/-/glob-11.0.1.tgz#1c3aef9a59d680e611b53dcd24bb8639cef064d9" + integrity sha512-zrQDm8XPnYEKawJScsnM0QzobJxlT/kHOOlRTio8IH/GrmxRE5fjllkzdaHclIuNjUQTJYH2xHNIGfdpJkDJUw== + dependencies: + foreground-child "^3.1.0" + jackspeak "^4.0.1" + minimatch "^10.0.0" + minipass "^7.1.2" + package-json-from-dist "^1.0.0" + path-scurry "^2.0.0" + glob@^7.1.3: version "7.2.3" resolved "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz" @@ -934,18 +975,7 @@ glob@^7.1.3: once "^1.3.0" path-is-absolute "^1.0.0" -glob@^8.0.3: - version "8.1.0" - resolved "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz" - integrity sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ== - dependencies: - fs.realpath "^1.0.0" - inflight "^1.0.4" - inherits "2" - minimatch "^5.0.1" - once "^1.3.0" - -glob@^8.1.0: +glob@^8.0.3, glob@^8.1.0: version "8.1.0" resolved "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz" integrity sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ== @@ -1320,30 +1350,18 @@ minimist@^1.2.0, minimist@^1.2.6: resolved "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz" integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA== -minipass@^3.0.0: - version "3.3.6" - resolved "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz" - integrity sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw== - dependencies: - yallist "^4.0.0" - -"minipass@^5.0.0 || ^6.0.2 || ^7.0.0", minipass@^7.1.2: +"minipass@^5.0.0 || ^6.0.2 || ^7.0.0", minipass@^7.0.4, minipass@^7.1.2: version "7.1.2" resolved "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz" integrity sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw== -minipass@^5.0.0: - version "5.0.0" - resolved "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz" - integrity sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ== - -minizlib@^2.1.1: - version "2.1.2" - resolved "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz" - integrity sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg== +minizlib@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-3.0.1.tgz#46d5329d1eb3c83924eff1d3b858ca0a31581012" + integrity sha512-umcy022ILvb5/3Djuu8LWeqUa8D68JaBzlttKeMWen48SjabqS3iY5w/vzeMzMUNhLDifyhbOwKDSznB1vvrwg== dependencies: - minipass "^3.0.0" - yallist "^4.0.0" + minipass "^7.0.4" + rimraf "^5.0.5" mitt@3.0.1: version "3.0.1" @@ -1357,10 +1375,10 @@ mkdirp@^0.5.6, mkdirp@~0.5.1: dependencies: minimist "^1.2.6" -mkdirp@^1.0.3: - version "1.0.4" - resolved "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz" - integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw== +mkdirp@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-3.0.1.tgz#e44e4c5607fb279c168241713cc6e0fea9adcb50" + integrity sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg== mocha@^10.7.3: version "10.7.3" @@ -1388,27 +1406,34 @@ mocha@^10.7.3: yargs-parser "^20.2.9" yargs-unparser "^2.0.0" -ms@^2.1.1, ms@^2.1.3: - version "2.1.3" - resolved "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz" - integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== - ms@2.1.2: version "2.1.2" resolved "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz" integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== +ms@^2.1.1, ms@^2.1.3: + version "2.1.3" + resolved "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz" + integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== + netmask@^2.0.2: version "2.0.2" resolved "https://registry.npmjs.org/netmask/-/netmask-2.0.2.tgz" integrity sha512-dBpDMdxv9Irdq66304OLfEmQ9tbNRFnFTuZiLo+bD+r332bBmMJ8GBLXklIXXgxd3+v9+KUnZaUR5PJMa75Gsg== -node-fetch@^2.0.0: - version "2.7.0" - resolved "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz" - integrity sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A== +node-domexception@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/node-domexception/-/node-domexception-1.0.0.tgz#6888db46a1f71c0b76b3f7555016b63fe64766e5" + integrity sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ== + +node-fetch@^3.3.2: + version "3.3.2" + resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-3.3.2.tgz#d1e889bacdf733b4ff3b2b243eb7a12866a0b78b" + integrity sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA== dependencies: - whatwg-url "^5.0.0" + data-uri-to-buffer "^4.0.0" + fetch-blob "^3.1.4" + formdata-polyfill "^4.0.10" normalize-path@^3.0.0, normalize-path@~3.0.0: version "3.0.0" @@ -1546,6 +1571,11 @@ picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.3.1: resolved "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz" integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== +picomatch@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-4.0.2.tgz#77c742931e8f3b8820946c76cd0c1f13730d1dab" + integrity sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg== + portfinder@^1.0.28: version "1.0.32" resolved "https://registry.npmjs.org/portfinder/-/portfinder-1.0.32.tgz" @@ -1668,9 +1698,9 @@ rimraf@^2.5.4: dependencies: glob "^7.1.3" -rimraf@^5.0.0: +rimraf@^5.0.5: version "5.0.10" - resolved "https://registry.npmjs.org/rimraf/-/rimraf-5.0.10.tgz" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-5.0.10.tgz#23b9843d3dc92db71f96e1a2ce92e39fd2a8221c" integrity sha512-l0OE8wL34P4nJH/H2ffoaniAokM2qSmrtXHmlpvYr5AVVX8msAyW0l8NVJFDxlSK4u3Uh/f41cQheDVdnYijwQ== dependencies: glob "^10.3.7" @@ -1683,14 +1713,14 @@ rimraf@^6.0.1: glob "^11.0.0" package-json-from-dist "^1.0.0" -rollup@^1.20.0||^2.0.0||^3.0.0||^4.0.0, rollup@^2.68.0||^3.0.0||^4.0.0, rollup@^2.78.0||^3.0.0||^4.0.0, rollup@^3.27.2: +rollup@^3.27.2: version "3.29.4" resolved "https://registry.npmjs.org/rollup/-/rollup-3.29.4.tgz" integrity sha512-oWzmBZwvYrU0iJHtDmhsm662rC15FRXmcjCk1xD771dFDx5jJ02ufAQQTn0etB2emNk4J9EZg/yWKpsn9BWGRw== optionalDependencies: fsevents "~2.3.2" -safe-buffer@^5.1.0, safe-buffer@5.1.2: +safe-buffer@5.1.2, safe-buffer@^5.1.0: version "5.1.2" resolved "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz" integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== @@ -1898,17 +1928,17 @@ tar-stream@^3.1.5: fast-fifo "^1.2.0" streamx "^2.15.0" -tar@^6.1.11: - version "6.2.1" - resolved "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz" - integrity sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A== +tar@^7.4.3: + version "7.4.3" + resolved "https://registry.yarnpkg.com/tar/-/tar-7.4.3.tgz#88bbe9286a3fcd900e94592cda7a22b192e80571" + integrity sha512-5S7Va8hKfV7W5U6g3aYxXmlPoZVAwUMy9AOKyF2fVuZa2UD3qZjg578OrLRt8PcNN1PleVaL/5/yYATNL0ICUw== dependencies: - chownr "^2.0.0" - fs-minipass "^2.0.0" - minipass "^5.0.0" - minizlib "^2.1.1" - mkdirp "^1.0.3" - yallist "^4.0.0" + "@isaacs/fs-minipass" "^4.0.0" + chownr "^3.0.0" + minipass "^7.1.2" + minizlib "^3.0.1" + mkdirp "^3.0.1" + yallist "^5.0.0" text-decoder@^1.1.0: version "1.1.1" @@ -1929,11 +1959,6 @@ to-regex-range@^5.0.1: dependencies: is-number "^7.0.0" -tr46@~0.0.3: - version "0.0.3" - resolved "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz" - integrity sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw== - ts-node@^10.9.2: version "10.9.2" resolved "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz" @@ -1963,7 +1988,7 @@ typed-query-selector@^2.12.0: resolved "https://registry.npmjs.org/typed-query-selector/-/typed-query-selector-2.12.0.tgz" integrity sha512-SbklCd1F0EiZOyPiW192rrHZzZ5sBijB6xM+cpmrwDqObvdtunOHHIk9fCGsoK5JVIYXoyEp4iEdE3upFH3PAg== -typescript@^5.5.4, typescript@>=2.7, typescript@>=4.9.5: +typescript@^5.5.4: version "5.5.4" resolved "https://registry.npmjs.org/typescript/-/typescript-5.5.4.tgz" integrity sha512-Mtq29sKDAEYP7aljRgtPOpTvOfbwRWlS6dPRzwjdE+C0R4brX/GUyhHSecbHMFLNBLcJIPt9nl9yG5TZ1weH+Q== @@ -2008,10 +2033,10 @@ v8-compile-cache-lib@^3.0.1: resolved "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz" integrity sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg== -webidl-conversions@^3.0.0: - version "3.0.1" - resolved "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz" - integrity sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ== +web-streams-polyfill@^3.0.3: + version "3.3.3" + resolved "https://registry.yarnpkg.com/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz#2073b91a2fdb1fbfbd401e7de0ac9f8214cecb4b" + integrity sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw== whatwg-encoding@^2.0.0: version "2.0.0" @@ -2020,14 +2045,6 @@ whatwg-encoding@^2.0.0: dependencies: iconv-lite "0.6.3" -whatwg-url@^5.0.0: - version "5.0.0" - resolved "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz" - integrity sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw== - dependencies: - tr46 "~0.0.3" - webidl-conversions "^3.0.0" - which@^2.0.1: version "2.0.2" resolved "https://registry.npmjs.org/which/-/which-2.0.2.tgz" @@ -2082,10 +2099,10 @@ y18n@^5.0.5: resolved "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz" integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA== -yallist@^4.0.0: - version "4.0.0" - resolved "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz" - integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== +yallist@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/yallist/-/yallist-5.0.0.tgz#00e2de443639ed0d78fd87de0d27469fbcffb533" + integrity sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw== yargs-parser@^20.2.2, yargs-parser@^20.2.9: version "20.2.9" diff --git a/eslint.config.js b/eslint.config.js new file mode 100644 index 000000000..db8ac9b31 --- /dev/null +++ b/eslint.config.js @@ -0,0 +1,40 @@ +module.exports = [ + { + // Ignore patterns + ignores: [ + "crates/web-client/dist/**/*", + "target/**/*", + "**/target/**/*", + "miden-node/**/*", + "**/*.d.ts", + ], + }, + { + // Configuration for JavaScript files + files: ["**/*.js", "**/*.jsx"], + languageOptions: { + parserOptions: { + ecmaVersion: 2022, + sourceType: "module", + }, + }, + rules: { + camelcase: ["error", { properties: "always" }], + }, + }, + { + files: ["**/*.ts", "**/*.tsx"], + languageOptions: { + parser: require("@typescript-eslint/parser"), + parserOptions: { + ecmaVersion: 2022, + sourceType: "module", + project: "crates/web-client/tsconfig.json", // path to your tsconfig file + tsconfigRootDir: __dirname, + }, + }, + rules: { + camelcase: ["error", { properties: "always" }], + }, + }, +]; diff --git a/package.json b/package.json index 5933f5e16..efbb51506 100644 --- a/package.json +++ b/package.json @@ -2,5 +2,11 @@ "dependencies": { "prettier": "^3.4.2" }, - "packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e" + "packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e", + "devDependencies": { + "@typescript-eslint/eslint-plugin": "^8.25.0", + "@typescript-eslint/parser": "^8.25.0", + "eslint": "^9.21.0", + "typescript": "^5.5.4" + } } diff --git a/tests/integration/common.rs b/tests/integration/common.rs index cef8343e4..b321a5943 100644 --- a/tests/integration/common.rs +++ b/tests/integration/common.rs @@ -271,7 +271,7 @@ pub async fn wait_for_node(client: &mut TestClient) { std::thread::sleep(Duration::from_secs(NODE_TIME_BETWEEN_ATTEMPTS)); }, Err(other_error) => { - panic!("Unexpected error: {other_error:?}"); + panic!("Unexpected error: {other_error}"); }, _ => return, } diff --git a/tests/integration/main.rs b/tests/integration/main.rs index a191ccb3e..c2d3e8168 100644 --- a/tests/integration/main.rs +++ b/tests/integration/main.rs @@ -13,13 +13,13 @@ use miden_client::{ PaymentTransactionData, TransactionExecutorError, TransactionProver, TransactionProverError, TransactionRequestBuilder, TransactionStatus, }, - ClientBuilder, ClientError, + ClientBuilder, ClientError, ONE, }; use miden_objects::{ account::{AccountId, AccountStorageMode}, asset::{Asset, FungibleAsset}, note::{NoteFile, NoteType}, - transaction::{ProvenTransaction, TransactionWitness}, + transaction::{ProvenTransaction, ToInputNoteCommitments, TransactionWitness}, }; mod common; @@ -882,8 +882,8 @@ async fn test_get_account_update() { // TODO: should we expose the `get_account_update` endpoint from the Client? let (endpoint, timeout, ..) = get_client_config(); let rpc_api = TonicRpcClient::new(&endpoint, timeout); - let details1 = rpc_api.get_account_update(basic_wallet_1.id()).await.unwrap(); - let details2 = rpc_api.get_account_update(basic_wallet_2.id()).await.unwrap(); + let details1 = rpc_api.get_account_details(basic_wallet_1.id()).await.unwrap(); + let details2 = rpc_api.get_account_details(basic_wallet_2.id()).await.unwrap(); assert!(matches!(details1, AccountDetails::Private(_, _))); assert!(matches!(details2, AccountDetails::Public(_, _))); @@ -1558,3 +1558,66 @@ async fn test_expired_transaction_fails() { assert!(submited_tx_result.is_err()); } + +/// Tests that RPC methods that are not directly related to the client logic +/// (like GetBlockByNumber) work correctly +#[tokio::test] +async fn test_unused_rpc_api() { + let (mut client, keystore) = create_test_client().await; + + let (first_basic_account, _, faucet_account) = + setup(&mut client, AccountStorageMode::Public, &keystore).await; + + wait_for_node(&mut client).await; + client.sync_state().await.unwrap(); + + let first_block_num = client.get_sync_height().await.unwrap(); + + let (block_header, _) = client + .test_rpc_api() + .get_block_header_by_number(Some(first_block_num), false) + .await + .unwrap(); + let block = client.test_rpc_api().get_block_by_number(first_block_num).await.unwrap(); + + assert_eq!(&block_header, block.header()); + + let note = + mint_note(&mut client, first_basic_account.id(), faucet_account.id(), NoteType::Public) + .await; + + consume_notes(&mut client, first_basic_account.id(), &[note.clone()]).await; + + client.sync_state().await.unwrap(); + + let second_block_num = client.get_sync_height().await.unwrap(); + + let nullifier = note.nullifier(); + + let node_nullifier = client + .test_rpc_api() + .check_nullifiers_by_prefix(&[nullifier.prefix()], 0.into()) + .await + .unwrap() + .pop() + .unwrap(); + let node_nullifier_proof = client + .test_rpc_api() + .check_nullifiers(&[nullifier]) + .await + .unwrap() + .pop() + .unwrap(); + + assert_eq!(node_nullifier.nullifier, nullifier); + assert_eq!(node_nullifier_proof.leaf().entries().pop().unwrap().0, nullifier.inner()); + + let account_delta = client + .test_rpc_api() + .get_account_state_delta(first_basic_account.id(), first_block_num, second_block_num) + .await + .unwrap(); + + assert_eq!(account_delta.nonce(), Some(ONE)); + assert_eq!(*account_delta.vault().fungible().iter().next().unwrap().1, MINT_AMOUNT as i64); +}