From 7e2a8f4491e4b9de06235a7287a41b894f751bfc Mon Sep 17 00:00:00 2001 From: fryorcraken Date: Wed, 7 Jan 2026 16:10:38 +1100 Subject: [PATCH 01/18] add `wallet account list --long` option Display account details in the `account list` output when using the `-l|--long` option. --- wallet/src/cli/account.rs | 206 ++++++++++++++++++++++++++------------ 1 file changed, 141 insertions(+), 65 deletions(-) diff --git a/wallet/src/cli/account.rs b/wallet/src/cli/account.rs index d10c8c50..3a55c55f 100644 --- a/wallet/src/cli/account.rs +++ b/wallet/src/cli/account.rs @@ -31,7 +31,11 @@ pub enum AccountSubcommand { SyncPrivate {}, /// List all accounts owned by the wallet #[command(visible_alias = "ls")] - List {}, + List { + /// Show detailed account information (like `account get`) + #[arg(short, long)] + long: bool, + }, } /// Represents generic register CLI subcommand @@ -151,6 +155,50 @@ impl From for TokedHoldingAccountView { } } +/// Formats account details for display, returning (description, json_view) +fn format_account_details(account: &Account) -> (String, String) { + let auth_tr_prog_id = Program::authenticated_transfer_program().id(); + let token_prog_id = Program::token().id(); + + match &account.program_owner { + _ if account.program_owner == auth_tr_prog_id => { + let acc_view: AuthenticatedTransferAccountView = account.clone().into(); + ( + "Account owned by authenticated transfer program".to_string(), + serde_json::to_string(&acc_view).unwrap(), + ) + } + _ if account.program_owner == token_prog_id => { + if let Some(token_def) = TokenDefinition::parse(&account.data) { + let acc_view: TokedDefinitionAccountView = token_def.into(); + ( + "Definition account owned by token program".to_string(), + serde_json::to_string(&acc_view).unwrap(), + ) + } else if let Some(token_hold) = TokenHolding::parse(&account.data) { + let acc_view: TokedHoldingAccountView = token_hold.into(); + ( + "Holding account owned by token program".to_string(), + serde_json::to_string(&acc_view).unwrap(), + ) + } else { + let account_hr: HumanReadableAccount = account.clone().into(); + ( + "Unknown token program account".to_string(), + serde_json::to_string(&account_hr).unwrap(), + ) + } + } + _ => { + let account_hr: HumanReadableAccount = account.clone().into(); + ( + "Account".to_string(), + serde_json::to_string(&account_hr).unwrap(), + ) + } + } +} + impl WalletSubcommand for AccountSubcommand { async fn handle_subcommand( self, @@ -184,43 +232,9 @@ impl WalletSubcommand for AccountSubcommand { return Ok(SubcommandReturnValue::Empty); } - let auth_tr_prog_id = Program::authenticated_transfer_program().id(); - let token_prog_id = Program::token().id(); - - let acc_view = match &account.program_owner { - _ if account.program_owner == auth_tr_prog_id => { - let acc_view: AuthenticatedTransferAccountView = account.into(); - - println!("Account owned by authenticated transfer program"); - - serde_json::to_string(&acc_view)? - } - _ if account.program_owner == token_prog_id => { - if let Some(token_def) = TokenDefinition::parse(&account.data) { - let acc_view: TokedDefinitionAccountView = token_def.into(); - - println!("Definition account owned by token program"); - - serde_json::to_string(&acc_view)? - } else if let Some(token_hold) = TokenHolding::parse(&account.data) { - let acc_view: TokedHoldingAccountView = token_hold.into(); - - println!("Holding account owned by token program"); - - serde_json::to_string(&acc_view)? - } else { - anyhow::bail!( - "Invalid data for account {account_id:#?} with token program" - ); - } - } - _ => { - let account_hr: HumanReadableAccount = account.clone().into(); - serde_json::to_string(&account_hr).unwrap() - } - }; - - println!("{}", acc_view); + let (description, json_view) = format_account_details(&account); + println!("{description}"); + println!("{json_view}"); Ok(SubcommandReturnValue::Empty) } @@ -252,35 +266,97 @@ impl WalletSubcommand for AccountSubcommand { Ok(SubcommandReturnValue::SyncedToBlock(curr_last_block)) } - AccountSubcommand::List {} => { + AccountSubcommand::List { long } => { let user_data = &wallet_core.storage.user_data; - let accounts = user_data - .default_pub_account_signing_keys - .keys() - .map(|id| format!("Preconfigured Public/{id}")) - .chain( - user_data - .default_user_private_accounts - .keys() - .map(|id| format!("Preconfigured Private/{id}")), - ) - .chain( - user_data - .public_key_tree - .account_id_map - .iter() - .map(|(id, chain_index)| format!("{chain_index} Public/{id}")), - ) - .chain( - user_data - .private_key_tree - .account_id_map - .iter() - .map(|(id, chain_index)| format!("{chain_index} Private/{id}")), - ) - .format(",\n"); - println!("{accounts}"); + if !long { + let accounts = user_data + .default_pub_account_signing_keys + .keys() + .map(|id| format!("Preconfigured Public/{id}")) + .chain( + user_data + .default_user_private_accounts + .keys() + .map(|id| format!("Preconfigured Private/{id}")), + ) + .chain( + user_data + .public_key_tree + .account_id_map + .iter() + .map(|(id, chain_index)| format!("{chain_index} Public/{id}")), + ) + .chain( + user_data + .private_key_tree + .account_id_map + .iter() + .map(|(id, chain_index)| format!("{chain_index} Private/{id}")), + ) + .format(",\n"); + + println!("{accounts}"); + return Ok(SubcommandReturnValue::Empty); + } + + // Detailed listing with --long flag + // Preconfigured public accounts + for id in user_data.default_pub_account_signing_keys.keys() { + println!("Preconfigured Public/{id}"); + match wallet_core.get_account_public(*id).await { + Ok(account) if account != Account::default() => { + let (description, json_view) = format_account_details(&account); + println!(" {description}"); + println!(" {json_view}"); + } + Ok(_) => println!(" Uninitialized"), + Err(e) => println!(" Error fetching account: {e}"), + } + } + + // Preconfigured private accounts + for id in user_data.default_user_private_accounts.keys() { + println!("Preconfigured Private/{id}"); + match wallet_core.get_account_private(id) { + Some(account) if account != Account::default() => { + let (description, json_view) = format_account_details(&account); + println!(" {description}"); + println!(" {json_view}"); + } + Some(_) => println!(" Uninitialized"), + None => println!(" Not found in local storage"), + } + } + + // Public key tree accounts + for (id, chain_index) in user_data.public_key_tree.account_id_map.iter() { + println!("{chain_index} Public/{id}"); + match wallet_core.get_account_public(*id).await { + Ok(account) if account != Account::default() => { + let (description, json_view) = format_account_details(&account); + println!(" {description}"); + println!(" {json_view}"); + } + Ok(_) => println!(" Uninitialized"), + Err(e) => println!(" Error fetching account: {e}"), + } + } + + // Private key tree accounts + for (id, chain_index) in user_data.private_key_tree.account_id_map.iter() { + println!("{chain_index} Private/{id}"); + match wallet_core.get_account_private(id) { + Some(account) if account != Account::default() => { + let (description, json_view) = format_account_details(&account); + println!(" {description}"); + println!(" {json_view}"); + } + Some(_) => println!(" Uninitialized"), + None => println!(" Not found in local storage"), + } + } + Ok(SubcommandReturnValue::Empty) } } From deb6e9d8393e8d5c6ff86ed66127909d0706c079 Mon Sep 17 00:00:00 2001 From: Danish Arora <35004822+danisharora099@users.noreply.github.com> Date: Mon, 12 Jan 2026 02:43:34 -0500 Subject: [PATCH 02/18] fix: program deployment tutorial (#277) --- examples/program_deployment/README.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/examples/program_deployment/README.md b/examples/program_deployment/README.md index 14a2baba..b0d59151 100644 --- a/examples/program_deployment/README.md +++ b/examples/program_deployment/README.md @@ -41,13 +41,15 @@ In a second terminal, from the `lssa` root directory, compile the example Risc0 ```bash cargo risczero build --manifest-path examples/program_deployment/methods/guest/Cargo.toml ``` -The compiled `.bin` files will appear under: +Because this repository is organized as a Cargo workspace, build artifacts are written to the +shared `target/` directory at the workspace root by default. The compiled `.bin` files will +appear under: ``` -examples/program_deployment/methods/guest/target/riscv32im-risc0-zkvm-elf/docker/ +target/riscv32im-risc0-zkvm-elf/docker/ ``` For convenience, export this path: ```bash -export EXAMPLE_PROGRAMS_BUILD_DIR=$(pwd)/examples/program_deployment/methods/guest/target/riscv32im-risc0-zkvm-elf/docker +export EXAMPLE_PROGRAMS_BUILD_DIR=$(pwd)/target/riscv32im-risc0-zkvm-elf/docker ``` > [!IMPORTANT] From 529ca9a643a657fdd72d612e1ad6b14e64c1d91c Mon Sep 17 00:00:00 2001 From: Daniil Polyakov Date: Wed, 31 Dec 2025 04:02:25 +0300 Subject: [PATCH 03/18] feat: introduce parallel integration tests, wallet without global vars and etc --- .github/workflows/ci.yml | 28 +- Cargo.lock | 21 +- Cargo.toml | 1 - common/src/sequencer_client.rs | 8 +- .../src/bin/run_hello_world.rs | 9 +- .../src/bin/run_hello_world_private.rs | 9 +- .../bin/run_hello_world_through_tail_call.rs | 9 +- ...n_hello_world_through_tail_call_private.rs | 9 +- .../bin/run_hello_world_with_authorization.rs | 11 +- ...uthorization_through_tail_call_with_pda.rs | 9 +- .../bin/run_hello_world_with_move_function.rs | 9 +- integration_tests/Cargo.toml | 6 +- .../sequencer/sequencer_config.json | 4 +- .../{debug => }/wallet/wallet_config.json | 6 +- .../proc_macro_test_attribute/Cargo.toml | 9 - .../proc_macro_test_attribute/src/lib.rs | 49 - integration_tests/src/lib.rs | 296 +- integration_tests/src/main.rs | 15 - integration_tests/src/test_suite_map.rs | 3154 ----------------- integration_tests/tests/account.rs | 28 + integration_tests/tests/amm.rs | 405 +++ integration_tests/tests/auth_transfer/main.rs | 2 + .../tests/auth_transfer/private.rs | 417 +++ .../tests/auth_transfer/public.rs | 248 ++ integration_tests/tests/config.rs | 33 + integration_tests/tests/keys_restoration.rs | 217 ++ integration_tests/tests/pinata.rs | 155 + integration_tests/tests/program_deployment.rs | 64 + integration_tests/tests/token.rs | 968 +++++ .../{src/tps_test_utils.rs => tests/tps.rs} | 83 +- key_protocol/src/key_protocol_core/mod.rs | 8 + sequencer_core/Cargo.toml | 1 + sequencer_core/src/config.rs | 16 +- sequencer_rpc/src/net_utils.rs | 21 +- sequencer_runner/src/config.rs | 11 - sequencer_runner/src/lib.rs | 14 +- wallet/Cargo.toml | 3 +- wallet/src/cli/account.rs | 12 +- wallet/src/cli/config.rs | 4 +- wallet/src/cli/mod.rs | 87 +- .../src/cli/programs/native_token_transfer.rs | 32 +- wallet/src/cli/programs/pinata.rs | 4 +- wallet/src/cli/programs/token.rs | 64 +- wallet/src/config.rs | 125 +- wallet/src/helperfunctions.rs | 101 +- wallet/src/lib.rs | 140 +- wallet/src/main.rs | 77 +- .../native_token_transfer/private.rs | 3 +- 48 files changed, 3218 insertions(+), 3787 deletions(-) rename integration_tests/configs/{debug => }/sequencer/sequencer_config.json (98%) rename integration_tests/configs/{debug => }/wallet/wallet_config.json (99%) delete mode 100644 integration_tests/proc_macro_test_attribute/Cargo.toml delete mode 100644 integration_tests/proc_macro_test_attribute/src/lib.rs delete mode 100644 integration_tests/src/main.rs delete mode 100644 integration_tests/src/test_suite_map.rs create mode 100644 integration_tests/tests/account.rs create mode 100644 integration_tests/tests/amm.rs create mode 100644 integration_tests/tests/auth_transfer/main.rs create mode 100644 integration_tests/tests/auth_transfer/private.rs create mode 100644 integration_tests/tests/auth_transfer/public.rs create mode 100644 integration_tests/tests/config.rs create mode 100644 integration_tests/tests/keys_restoration.rs create mode 100644 integration_tests/tests/pinata.rs create mode 100644 integration_tests/tests/program_deployment.rs create mode 100644 integration_tests/tests/token.rs rename integration_tests/{src/tps_test_utils.rs => tests/tps.rs} (74%) delete mode 100644 sequencer_runner/src/config.rs diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 21c73d29..ace2d07b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -83,7 +83,7 @@ jobs: RISC0_SKIP_BUILD: "1" run: cargo clippy -p "*programs" -- -D warnings - unit-tests: + tests: runs-on: ubuntu-latest timeout-minutes: 60 steps: @@ -104,6 +104,7 @@ jobs: - name: Run unit tests env: RISC0_DEV_MODE: "1" + RUST_LOG: "info" run: cargo nextest run --no-fail-fast valid-proof-test: @@ -123,31 +124,8 @@ jobs: - name: Test valid proof env: - NSSA_WALLET_HOME_DIR: ./integration_tests/configs/debug/wallet RUST_LOG: "info" - run: cargo run --bin integration_tests -- ./integration_tests/configs/debug/ test_success_private_transfer_to_another_owned_account - - integration-tests: - runs-on: ubuntu-latest - timeout-minutes: 120 - steps: - - uses: actions/checkout@v5 - with: - ref: ${{ github.head_ref }} - - - uses: ./.github/actions/install-system-deps - - - uses: ./.github/actions/install-risc0 - - - name: Install active toolchain - run: rustup install - - - name: Run integration tests - env: - NSSA_WALLET_HOME_DIR: ./integration_tests/configs/debug/wallet - RUST_LOG: "info" - RISC0_DEV_MODE: "1" - run: cargo run --bin integration_tests -- ./integration_tests/configs/debug/ all + run: cargo test -p integration_tests -- --exact private::private_transfer_to_owned_account artifacts: runs-on: ubuntu-latest diff --git a/Cargo.lock b/Cargo.lock index 92f29eff..50edef2f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2849,20 +2849,18 @@ dependencies = [ name = "integration_tests" version = "0.1.0" dependencies = [ - "actix", "actix-web", "anyhow", "base64", "borsh", - "clap", "common", "env_logger", + "futures", "hex", "key_protocol", "log", "nssa", "nssa_core", - "proc_macro_test_attribute", "sequencer_core", "sequencer_runner", "tempfile", @@ -3804,6 +3802,17 @@ dependencies = [ "vcpkg", ] +[[package]] +name = "optfield" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "969ccca8ffc4fb105bd131a228107d5c9dd89d9d627edf3295cbe979156f9712" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.111", +] + [[package]] name = "option-ext" version = "0.2.0" @@ -4054,10 +4063,6 @@ dependencies = [ "version_check", ] -[[package]] -name = "proc_macro_test_attribute" -version = "0.1.0" - [[package]] name = "program_deployment" version = "0.1.0" @@ -4941,6 +4946,7 @@ dependencies = [ "nssa", "nssa_core", "serde", + "serde_json", "storage", "tempfile", "tokio", @@ -5897,6 +5903,7 @@ dependencies = [ "log", "nssa", "nssa_core", + "optfield", "rand 0.8.5", "risc0-zkvm", "serde", diff --git a/Cargo.toml b/Cargo.toml index aa9da563..30b4576a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,7 +16,6 @@ members = [ "program_methods/guest", "test_program_methods", "test_program_methods/guest", - "integration_tests/proc_macro_test_attribute", "examples/program_deployment", "examples/program_deployment/methods", "examples/program_deployment/methods/guest", diff --git a/common/src/sequencer_client.rs b/common/src/sequencer_client.rs index 622c0c11..0cb03f6f 100644 --- a/common/src/sequencer_client.rs +++ b/common/src/sequencer_client.rs @@ -44,8 +44,10 @@ impl SequencerClient { ) -> Result { Ok(Self { client: Client::builder() - //Add more fiedls if needed + // Add more fields if needed .timeout(std::time::Duration::from_secs(60)) + // Should be kept in sync with server keep-alive settings + .pool_idle_timeout(std::time::Duration::from_secs(5)) .build()?, sequencer_addr, basic_auth, @@ -60,6 +62,10 @@ impl SequencerClient { let request = rpc_primitives::message::Request::from_payload_version_2_0(method.to_string(), payload); + log::debug!( + "Calling method {method} with payload {request:?} to sequencer at {}", + self.sequencer_addr + ); let mut call_builder = self.client.post(&self.sequencer_addr); if let Some((username, password)) = &self.basic_auth { diff --git a/examples/program_deployment/src/bin/run_hello_world.rs b/examples/program_deployment/src/bin/run_hello_world.rs index a7dc0fca..3c0c9034 100644 --- a/examples/program_deployment/src/bin/run_hello_world.rs +++ b/examples/program_deployment/src/bin/run_hello_world.rs @@ -3,7 +3,7 @@ use nssa::{ program::Program, public_transaction::{Message, WitnessSet}, }; -use wallet::{WalletCore, helperfunctions::fetch_config}; +use wallet::WalletCore; // Before running this example, compile the `hello_world.rs` guest program with: // @@ -24,11 +24,8 @@ use wallet::{WalletCore, helperfunctions::fetch_config}; #[tokio::main] async fn main() { - // Load wallet config and storage - let wallet_config = fetch_config().await.unwrap(); - let wallet_core = WalletCore::start_from_config_update_chain(wallet_config) - .await - .unwrap(); + // Initialize wallet + let wallet_core = WalletCore::from_env().unwrap(); // Parse arguments // First argument is the path to the program binary diff --git a/examples/program_deployment/src/bin/run_hello_world_private.rs b/examples/program_deployment/src/bin/run_hello_world_private.rs index dcbe59a5..cb66d42f 100644 --- a/examples/program_deployment/src/bin/run_hello_world_private.rs +++ b/examples/program_deployment/src/bin/run_hello_world_private.rs @@ -1,5 +1,5 @@ use nssa::{AccountId, program::Program}; -use wallet::{PrivacyPreservingAccount, WalletCore, helperfunctions::fetch_config}; +use wallet::{PrivacyPreservingAccount, WalletCore}; // Before running this example, compile the `hello_world.rs` guest program with: // @@ -22,11 +22,8 @@ use wallet::{PrivacyPreservingAccount, WalletCore, helperfunctions::fetch_config #[tokio::main] async fn main() { - // Load wallet config and storage - let wallet_config = fetch_config().await.unwrap(); - let wallet_core = WalletCore::start_from_config_update_chain(wallet_config) - .await - .unwrap(); + // Initialize wallet + let wallet_core = WalletCore::from_env().unwrap(); // Parse arguments // First argument is the path to the program binary diff --git a/examples/program_deployment/src/bin/run_hello_world_through_tail_call.rs b/examples/program_deployment/src/bin/run_hello_world_through_tail_call.rs index d7c91f89..56d28084 100644 --- a/examples/program_deployment/src/bin/run_hello_world_through_tail_call.rs +++ b/examples/program_deployment/src/bin/run_hello_world_through_tail_call.rs @@ -3,7 +3,7 @@ use nssa::{ program::Program, public_transaction::{Message, WitnessSet}, }; -use wallet::{WalletCore, helperfunctions::fetch_config}; +use wallet::WalletCore; // Before running this example, compile the `simple_tail_call.rs` guest program with: // @@ -24,11 +24,8 @@ use wallet::{WalletCore, helperfunctions::fetch_config}; #[tokio::main] async fn main() { - // Load wallet config and storage - let wallet_config = fetch_config().await.unwrap(); - let wallet_core = WalletCore::start_from_config_update_chain(wallet_config) - .await - .unwrap(); + // Initialize wallet + let wallet_core = WalletCore::from_env().unwrap(); // Parse arguments // First argument is the path to the program binary diff --git a/examples/program_deployment/src/bin/run_hello_world_through_tail_call_private.rs b/examples/program_deployment/src/bin/run_hello_world_through_tail_call_private.rs index 5a014f24..bd67cfe6 100644 --- a/examples/program_deployment/src/bin/run_hello_world_through_tail_call_private.rs +++ b/examples/program_deployment/src/bin/run_hello_world_through_tail_call_private.rs @@ -4,7 +4,7 @@ use nssa::{ AccountId, ProgramId, privacy_preserving_transaction::circuit::ProgramWithDependencies, program::Program, }; -use wallet::{PrivacyPreservingAccount, WalletCore, helperfunctions::fetch_config}; +use wallet::{PrivacyPreservingAccount, WalletCore}; // Before running this example, compile the `simple_tail_call.rs` guest program with: // @@ -25,11 +25,8 @@ use wallet::{PrivacyPreservingAccount, WalletCore, helperfunctions::fetch_config #[tokio::main] async fn main() { - // Load wallet config and storage - let wallet_config = fetch_config().await.unwrap(); - let wallet_core = WalletCore::start_from_config_update_chain(wallet_config) - .await - .unwrap(); + // Initialize wallet + let wallet_core = WalletCore::from_env().unwrap(); // Parse arguments // First argument is the path to the simple_tail_call program binary diff --git a/examples/program_deployment/src/bin/run_hello_world_with_authorization.rs b/examples/program_deployment/src/bin/run_hello_world_with_authorization.rs index 21740ae9..09fd621e 100644 --- a/examples/program_deployment/src/bin/run_hello_world_with_authorization.rs +++ b/examples/program_deployment/src/bin/run_hello_world_with_authorization.rs @@ -3,7 +3,7 @@ use nssa::{ program::Program, public_transaction::{Message, WitnessSet}, }; -use wallet::{WalletCore, helperfunctions::fetch_config}; +use wallet::WalletCore; // Before running this example, compile the `hello_world_with_authorization.rs` guest program with: // @@ -26,11 +26,8 @@ use wallet::{WalletCore, helperfunctions::fetch_config}; #[tokio::main] async fn main() { - // Load wallet config and storage - let wallet_config = fetch_config().await.unwrap(); - let wallet_core = WalletCore::start_from_config_update_chain(wallet_config) - .await - .unwrap(); + // Initialize wallet + let wallet_core = WalletCore::from_env().unwrap(); // Parse arguments // First argument is the path to the program binary @@ -50,7 +47,7 @@ async fn main() { // Load signing keys to provide authorization let signing_key = wallet_core - .storage + .storage() .user_data .get_pub_account_signing_key(&account_id) .expect("Input account should be a self owned public account"); diff --git a/examples/program_deployment/src/bin/run_hello_world_with_authorization_through_tail_call_with_pda.rs b/examples/program_deployment/src/bin/run_hello_world_with_authorization_through_tail_call_with_pda.rs index 6c673d8b..43839ba9 100644 --- a/examples/program_deployment/src/bin/run_hello_world_with_authorization_through_tail_call_with_pda.rs +++ b/examples/program_deployment/src/bin/run_hello_world_with_authorization_through_tail_call_with_pda.rs @@ -4,7 +4,7 @@ use nssa::{ public_transaction::{Message, WitnessSet}, }; use nssa_core::program::PdaSeed; -use wallet::{WalletCore, helperfunctions::fetch_config}; +use wallet::WalletCore; // Before running this example, compile the `simple_tail_call.rs` guest program with: // @@ -27,11 +27,8 @@ const PDA_SEED: PdaSeed = PdaSeed::new([37; 32]); #[tokio::main] async fn main() { - // Load wallet config and storage - let wallet_config = fetch_config().await.unwrap(); - let wallet_core = WalletCore::start_from_config_update_chain(wallet_config) - .await - .unwrap(); + // Initialize wallet + let wallet_core = WalletCore::from_env().unwrap(); // Parse arguments // First argument is the path to the program binary diff --git a/examples/program_deployment/src/bin/run_hello_world_with_move_function.rs b/examples/program_deployment/src/bin/run_hello_world_with_move_function.rs index 4307315a..d879ab6f 100644 --- a/examples/program_deployment/src/bin/run_hello_world_with_move_function.rs +++ b/examples/program_deployment/src/bin/run_hello_world_with_move_function.rs @@ -1,6 +1,6 @@ use clap::{Parser, Subcommand}; use nssa::{PublicTransaction, program::Program, public_transaction}; -use wallet::{PrivacyPreservingAccount, WalletCore, helperfunctions::fetch_config}; +use wallet::{PrivacyPreservingAccount, WalletCore}; // Before running this example, compile the `hello_world_with_move_function.rs` guest program with: // @@ -62,11 +62,8 @@ async fn main() { let bytecode: Vec = std::fs::read(cli.program_path).unwrap(); let program = Program::new(bytecode).unwrap(); - // Load wallet config and storage - let wallet_config = fetch_config().await.unwrap(); - let wallet_core = WalletCore::start_from_config_update_chain(wallet_config) - .await - .unwrap(); + // Initialize wallet + let wallet_core = WalletCore::from_env().unwrap(); match cli.command { Command::WritePublic { diff --git a/integration_tests/Cargo.toml b/integration_tests/Cargo.toml index 52c6e385..b888c177 100644 --- a/integration_tests/Cargo.toml +++ b/integration_tests/Cargo.toml @@ -11,16 +11,14 @@ sequencer_runner.workspace = true wallet.workspace = true common.workspace = true key_protocol.workspace = true -proc_macro_test_attribute = { path = "./proc_macro_test_attribute" } -clap = { workspace = true, features = ["derive", "env"] } anyhow.workspace = true env_logger.workspace = true log.workspace = true -actix.workspace = true actix-web.workspace = true base64.workspace = true -tokio.workspace = true +tokio = { workspace = true, features = ["rt-multi-thread", "macros"] } hex.workspace = true tempfile.workspace = true borsh.workspace = true +futures.workspace = true diff --git a/integration_tests/configs/debug/sequencer/sequencer_config.json b/integration_tests/configs/sequencer/sequencer_config.json similarity index 98% rename from integration_tests/configs/debug/sequencer/sequencer_config.json rename to integration_tests/configs/sequencer/sequencer_config.json index db1c7f20..1548bb5b 100644 --- a/integration_tests/configs/debug/sequencer/sequencer_config.json +++ b/integration_tests/configs/sequencer/sequencer_config.json @@ -1,12 +1,12 @@ { - "home": "./sequencer", + "home": "", "override_rust_log": null, "genesis_id": 1, "is_genesis_random": true, "max_num_tx_in_block": 20, "mempool_max_size": 10000, "block_create_timeout_millis": 10000, - "port": 3040, + "port": 0, "initial_accounts": [ { "account_id": "BLgCRDXYdQPMMWVHYRFGQZbgeHx9frkipa8GtpG2Syqy", diff --git a/integration_tests/configs/debug/wallet/wallet_config.json b/integration_tests/configs/wallet/wallet_config.json similarity index 99% rename from integration_tests/configs/debug/wallet/wallet_config.json rename to integration_tests/configs/wallet/wallet_config.json index ad7b2792..2abab83e 100644 --- a/integration_tests/configs/debug/wallet/wallet_config.json +++ b/integration_tests/configs/wallet/wallet_config.json @@ -1,10 +1,11 @@ { "override_rust_log": null, - "sequencer_addr": "http://127.0.0.1:3040", + "sequencer_addr": "", "seq_poll_timeout_millis": 12000, "seq_tx_poll_max_blocks": 5, "seq_poll_max_retries": 5, "seq_block_poll_max_amount": 100, + "basic_auth": null, "initial_accounts": [ { "Public": { @@ -542,6 +543,5 @@ } } } - ], - "basic_auth": null + ] } \ No newline at end of file diff --git a/integration_tests/proc_macro_test_attribute/Cargo.toml b/integration_tests/proc_macro_test_attribute/Cargo.toml deleted file mode 100644 index a50c3f37..00000000 --- a/integration_tests/proc_macro_test_attribute/Cargo.toml +++ /dev/null @@ -1,9 +0,0 @@ -[package] -name = "proc_macro_test_attribute" -version = "0.1.0" -edition = "2024" - -[dependencies] - -[lib] -proc-macro = true diff --git a/integration_tests/proc_macro_test_attribute/src/lib.rs b/integration_tests/proc_macro_test_attribute/src/lib.rs deleted file mode 100644 index 29852a0b..00000000 --- a/integration_tests/proc_macro_test_attribute/src/lib.rs +++ /dev/null @@ -1,49 +0,0 @@ -extern crate proc_macro; - -use proc_macro::*; - -#[proc_macro_attribute] -pub fn nssa_integration_test(_attr: TokenStream, item: TokenStream) -> TokenStream { - let input = item.to_string(); - - let fn_keyword = "fn "; - let fn_keyword_alternative = "fn\n"; - - let mut start_opt = None; - let mut fn_name = String::new(); - - if let Some(start) = input.find(fn_keyword) { - start_opt = Some(start); - } else if let Some(start) = input.find(fn_keyword_alternative) { - start_opt = Some(start); - } - - if let Some(start) = start_opt { - let rest = &input[start + fn_keyword.len()..]; - if let Some(end) = rest.find(|c: char| c == '(' || c.is_whitespace()) { - let name = &rest[..end]; - fn_name = name.to_string(); - } - } else { - println!("ERROR: keyword fn not found"); - } - - let extension = format!( - r#" - {input} - - function_map.insert("{fn_name}".to_string(), |home_dir: PathBuf| Box::pin(async {{ - let res = pre_test(home_dir).await.unwrap(); - - info!("Waiting for first block creation"); - tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; - - {fn_name}().await; - - post_test(res).await; - }})); - "# - ); - - extension.parse().unwrap() -} diff --git a/integration_tests/src/lib.rs b/integration_tests/src/lib.rs index 401238fa..12d718ec 100644 --- a/integration_tests/src/lib.rs +++ b/integration_tests/src/lib.rs @@ -1,38 +1,25 @@ -use std::path::PathBuf; +//! This library contains common code for integration tests. + +use std::{net::SocketAddr, path::PathBuf, sync::LazyLock}; use actix_web::dev::ServerHandle; -use anyhow::Result; +use anyhow::{Context as _, Result}; use base64::{Engine, engine::general_purpose::STANDARD as BASE64}; -use clap::Parser; use common::{ sequencer_client::SequencerClient, transaction::{EncodedTransaction, NSSATransaction}, }; -use log::{info, warn}; +use futures::FutureExt as _; +use log::debug; use nssa::PrivacyPreservingTransaction; use nssa_core::Commitment; use sequencer_core::config::SequencerConfig; -use sequencer_runner::startup_sequencer; use tempfile::TempDir; use tokio::task::JoinHandle; +use wallet::{WalletCore, config::WalletConfigOverrides}; -use crate::test_suite_map::{prepare_function_map, tps_test}; - -#[macro_use] -extern crate proc_macro_test_attribute; - -pub mod test_suite_map; - -mod tps_test_utils; - -#[derive(Parser, Debug)] -#[clap(version)] -struct Args { - /// Path to configs - home_dir: PathBuf, - /// Test name - test_name: String, -} +// TODO: Remove this and control time from tests +pub const TIME_TO_WAIT_FOR_BLOCK_SECONDS: u64 = 12; pub const ACC_SENDER: &str = "BLgCRDXYdQPMMWVHYRFGQZbgeHx9frkipa8GtpG2Syqy"; pub const ACC_RECEIVER: &str = "Gj1mJy5W7J5pfmLRujmQaLfLMWidNxQ6uwnhb666ZwHw"; @@ -40,104 +27,181 @@ pub const ACC_RECEIVER: &str = "Gj1mJy5W7J5pfmLRujmQaLfLMWidNxQ6uwnhb666ZwHw"; pub const ACC_SENDER_PRIVATE: &str = "3oCG8gqdKLMegw4rRfyaMQvuPHpcASt7xwttsmnZLSkw"; pub const ACC_RECEIVER_PRIVATE: &str = "AKTcXgJ1xoynta1Ec7y6Jso1z1JQtHqd7aPQ1h9er6xX"; -pub const TIME_TO_WAIT_FOR_BLOCK_SECONDS: u64 = 12; - pub const NSSA_PROGRAM_FOR_TEST_DATA_CHANGER: &str = "data_changer.bin"; -fn make_public_account_input_from_str(account_id: &str) -> String { +static LOGGER: LazyLock<()> = LazyLock::new(env_logger::init); + +/// Test context which sets up a sequencer and a wallet for integration tests. +/// +/// It's memory and logically safe to create multiple instances of this struct in parallel tests, +/// as each instance uses its own temporary directories for sequencer and wallet data. +pub struct TestContext { + sequencer_server_handle: ServerHandle, + sequencer_loop_handle: JoinHandle>, + sequencer_client: SequencerClient, + wallet: WalletCore, + _temp_sequencer_dir: TempDir, + _temp_wallet_dir: TempDir, +} + +impl TestContext { + /// Create new test context. + pub async fn new() -> Result { + let manifest_dir = env!("CARGO_MANIFEST_DIR"); + + let sequencer_config_path = + PathBuf::from(manifest_dir).join("configs/sequencer/sequencer_config.json"); + + let sequencer_config = SequencerConfig::from_path(&sequencer_config_path) + .context("Failed to create sequencer config from file")?; + + Self::new_with_sequencer_config(sequencer_config).await + } + + /// Create new test context with custom sequencer config. + /// + /// `home` and `port` fields of the provided config will be overridden to meet tests parallelism + /// requirements. + pub async fn new_with_sequencer_config(sequencer_config: SequencerConfig) -> Result { + // Ensure logger is initialized only once + *LOGGER; + + debug!("Test context setup"); + + let (sequencer_server_handle, sequencer_addr, sequencer_loop_handle, temp_sequencer_dir) = + Self::setup_sequencer(sequencer_config) + .await + .context("Failed to setup sequencer")?; + + // Convert 0.0.0.0 to 127.0.0.1 for client connections + // When binding to port 0, the server binds to 0.0.0.0: + // but clients need to connect to 127.0.0.1: to work reliably + let sequencer_addr = if sequencer_addr.ip().is_unspecified() { + format!("http://127.0.0.1:{}", sequencer_addr.port()) + } else { + format!("http://{sequencer_addr}") + }; + + let (wallet, temp_wallet_dir) = Self::setup_wallet(sequencer_addr.clone()) + .await + .context("Failed to setup wallet")?; + + let sequencer_client = + SequencerClient::new(sequencer_addr).context("Failed to create sequencer client")?; + + Ok(Self { + sequencer_server_handle, + sequencer_loop_handle, + sequencer_client, + wallet, + _temp_sequencer_dir: temp_sequencer_dir, + _temp_wallet_dir: temp_wallet_dir, + }) + } + + async fn setup_sequencer( + mut config: SequencerConfig, + ) -> Result<(ServerHandle, SocketAddr, JoinHandle>, TempDir)> { + let temp_sequencer_dir = + tempfile::tempdir().context("Failed to create temp dir for sequencer home")?; + + debug!( + "Using temp sequencer home at {:?}", + temp_sequencer_dir.path() + ); + config.home = temp_sequencer_dir.path().to_owned(); + // Setting port to 0 lets the OS choose a free port for us + config.port = 0; + + let (sequencer_server_handle, sequencer_addr, sequencer_loop_handle) = + sequencer_runner::startup_sequencer(config).await?; + + Ok(( + sequencer_server_handle, + sequencer_addr, + sequencer_loop_handle, + temp_sequencer_dir, + )) + } + + async fn setup_wallet(sequencer_addr: String) -> Result<(WalletCore, TempDir)> { + let manifest_dir = env!("CARGO_MANIFEST_DIR"); + let wallet_config_source_path = + PathBuf::from(manifest_dir).join("configs/wallet/wallet_config.json"); + + let temp_wallet_dir = + tempfile::tempdir().context("Failed to create temp dir for wallet home")?; + + let config_path = temp_wallet_dir.path().join("wallet_config.json"); + std::fs::copy(&wallet_config_source_path, &config_path) + .context("Failed to copy wallet config to temp dir")?; + + let storage_path = temp_wallet_dir.path().join("storage.json"); + let config_overrides = WalletConfigOverrides { + sequencer_addr: Some(sequencer_addr), + ..Default::default() + }; + + let wallet = WalletCore::new_init_storage( + config_path, + storage_path, + Some(config_overrides), + "test_pass".to_owned(), + ) + .context("Failed to init wallet")?; + wallet + .store_persistent_data() + .await + .context("Failed to store wallet persistent data")?; + + Ok((wallet, temp_wallet_dir)) + } + + /// Get reference to the wallet. + pub fn wallet(&self) -> &WalletCore { + &self.wallet + } + + /// Get mutable reference to the wallet. + pub fn wallet_mut(&mut self) -> &mut WalletCore { + &mut self.wallet + } + + /// Get reference to the sequencer client. + pub fn sequencer_client(&self) -> &SequencerClient { + &self.sequencer_client + } +} + +impl Drop for TestContext { + fn drop(&mut self) { + debug!("Test context cleanup"); + + let Self { + sequencer_server_handle, + sequencer_loop_handle, + sequencer_client: _, + wallet: _, + _temp_sequencer_dir, + _temp_wallet_dir, + } = self; + + sequencer_loop_handle.abort(); + + // Can't wait here as Drop can't be async, but anyway stop signal should be sent + sequencer_server_handle.stop(true).now_or_never(); + } +} + +pub fn format_public_account_id(account_id: &str) -> String { format!("Public/{account_id}") } -fn make_private_account_input_from_str(account_id: &str) -> String { +pub fn format_private_account_id(account_id: &str) -> String { format!("Private/{account_id}") } -#[allow(clippy::type_complexity)] -pub async fn pre_test( - home_dir: PathBuf, -) -> Result<(ServerHandle, JoinHandle>, TempDir)> { - wallet::cli::execute_setup("test_pass".to_owned()).await?; - - let home_dir_sequencer = home_dir.join("sequencer"); - - let mut sequencer_config = - sequencer_runner::config::from_file(home_dir_sequencer.join("sequencer_config.json")) - .unwrap(); - - let temp_dir_sequencer = replace_home_dir_with_temp_dir_in_configs(&mut sequencer_config); - - let (seq_http_server_handle, sequencer_loop_handle) = - startup_sequencer(sequencer_config).await?; - - Ok(( - seq_http_server_handle, - sequencer_loop_handle, - temp_dir_sequencer, - )) -} - -pub fn replace_home_dir_with_temp_dir_in_configs( - sequencer_config: &mut SequencerConfig, -) -> TempDir { - let temp_dir_sequencer = tempfile::tempdir().unwrap(); - - sequencer_config.home = temp_dir_sequencer.path().to_path_buf(); - - temp_dir_sequencer -} - -#[allow(clippy::type_complexity)] -pub async fn post_test(residual: (ServerHandle, JoinHandle>, TempDir)) { - let (seq_http_server_handle, sequencer_loop_handle, _) = residual; - - info!("Cleanup"); - - sequencer_loop_handle.abort(); - seq_http_server_handle.stop(true).await; - - let wallet_home = wallet::helperfunctions::get_home().unwrap(); - let persistent_data_home = wallet_home.join("storage.json"); - - // Removing persistent accounts after run to not affect other executions - // Not necessary an error, if fails as there is tests for failure scenario - let _ = std::fs::remove_file(persistent_data_home) - .inspect_err(|err| warn!("Failed to remove persistent data with err {err:#?}")); - - // At this point all of the references to sequencer_core must be lost. - // So they are dropped and tempdirs will be dropped too, -} - -pub async fn main_tests_runner() -> Result<()> { - env_logger::init(); - - let args = Args::parse(); - let Args { - home_dir, - test_name, - } = args; - - let function_map = prepare_function_map(); - - match test_name.as_str() { - "all" => { - // Tests that use default config - for (_, fn_pointer) in function_map { - fn_pointer(home_dir.clone()).await; - } - // Run TPS test with its own specific config - tps_test().await; - } - _ => { - let fn_pointer = function_map.get(&test_name).expect("Unknown test name"); - - fn_pointer(home_dir.clone()).await; - } - } - - Ok(()) -} - -async fn fetch_privacy_preserving_tx( +pub async fn fetch_privacy_preserving_tx( seq_client: &SequencerClient, tx_hash: String, ) -> PrivacyPreservingTransaction { @@ -161,7 +225,7 @@ async fn fetch_privacy_preserving_tx( } } -async fn verify_commitment_is_in_state( +pub async fn verify_commitment_is_in_state( commitment: Commitment, seq_client: &SequencerClient, ) -> bool { @@ -173,15 +237,15 @@ async fn verify_commitment_is_in_state( #[cfg(test)] mod tests { - use crate::{make_private_account_input_from_str, make_public_account_input_from_str}; + use super::{format_private_account_id, format_public_account_id}; #[test] fn correct_account_id_from_prefix() { let account_id1 = "cafecafe"; let account_id2 = "deadbeaf"; - let account_id1_pub = make_public_account_input_from_str(account_id1); - let account_id2_priv = make_private_account_input_from_str(account_id2); + let account_id1_pub = format_public_account_id(account_id1); + let account_id2_priv = format_private_account_id(account_id2); assert_eq!(account_id1_pub, "Public/cafecafe".to_string()); assert_eq!(account_id2_priv, "Private/deadbeaf".to_string()); diff --git a/integration_tests/src/main.rs b/integration_tests/src/main.rs deleted file mode 100644 index 583df2aa..00000000 --- a/integration_tests/src/main.rs +++ /dev/null @@ -1,15 +0,0 @@ -use anyhow::Result; -use integration_tests::main_tests_runner; - -pub const NUM_THREADS: usize = 8; - -fn main() -> Result<()> { - actix::System::with_tokio_rt(|| { - tokio::runtime::Builder::new_multi_thread() - .worker_threads(NUM_THREADS) - .enable_all() - .build() - .unwrap() - }) - .block_on(main_tests_runner()) -} diff --git a/integration_tests/src/test_suite_map.rs b/integration_tests/src/test_suite_map.rs deleted file mode 100644 index e722cc5f..00000000 --- a/integration_tests/src/test_suite_map.rs +++ /dev/null @@ -1,3154 +0,0 @@ -use std::{ - collections::HashMap, - path::PathBuf, - pin::Pin, - str::FromStr, - time::{Duration, Instant}, -}; - -use actix_web::dev::ServerHandle; -use anyhow::Result; -use common::{PINATA_BASE58, sequencer_client::SequencerClient}; -use key_protocol::key_management::key_tree::chain_index::ChainIndex; -use log::info; -use nssa::{AccountId, program::Program}; -use nssa_core::{NullifierPublicKey, encryption::shared_key_derivation::Secp256k1Point}; -use sequencer_runner::startup_sequencer; -use tempfile::TempDir; -use tokio::task::JoinHandle; -use wallet::{ - WalletCore, - cli::{ - Command, SubcommandReturnValue, - account::{AccountSubcommand, NewSubcommand}, - config::ConfigSubcommand, - programs::{ - amm::AmmProgramAgnosticSubcommand, native_token_transfer::AuthTransferSubcommand, - pinata::PinataProgramAgnosticSubcommand, token::TokenProgramAgnosticSubcommand, - }, - }, - config::PersistentStorage, - helperfunctions::{fetch_config, fetch_persistent_storage}, -}; - -use crate::{ - ACC_RECEIVER, ACC_RECEIVER_PRIVATE, ACC_SENDER, ACC_SENDER_PRIVATE, - NSSA_PROGRAM_FOR_TEST_DATA_CHANGER, TIME_TO_WAIT_FOR_BLOCK_SECONDS, - fetch_privacy_preserving_tx, make_private_account_input_from_str, - make_public_account_input_from_str, post_test, pre_test, - replace_home_dir_with_temp_dir_in_configs, tps_test_utils::TpsTestManager, - verify_commitment_is_in_state, -}; - -type TestFunction = fn(PathBuf) -> Pin>>; - -pub fn prepare_function_map() -> HashMap { - let mut function_map: HashMap = HashMap::new(); - - #[nssa_integration_test] - pub async fn test_success() { - info!("########## test_success ##########"); - let command = Command::AuthTransfer(AuthTransferSubcommand::Send { - from: make_public_account_input_from_str(ACC_SENDER), - to: Some(make_public_account_input_from_str(ACC_RECEIVER)), - to_npk: None, - to_ipk: None, - amount: 100, - }); - - let wallet_config = fetch_config().await.unwrap(); - - let seq_client = SequencerClient::new(wallet_config.sequencer_addr.clone()).unwrap(); - - wallet::cli::execute_subcommand(command).await.unwrap(); - - info!("Waiting for next block creation"); - tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; - - info!("Checking correct balance move"); - let acc_1_balance = seq_client - .get_account_balance(ACC_SENDER.to_string()) - .await - .unwrap(); - let acc_2_balance = seq_client - .get_account_balance(ACC_RECEIVER.to_string()) - .await - .unwrap(); - - info!("Balance of sender : {acc_1_balance:#?}"); - info!("Balance of receiver : {acc_2_balance:#?}"); - - assert_eq!(acc_1_balance.balance, 9900); - assert_eq!(acc_2_balance.balance, 20100); - - info!("Success!"); - } - - #[nssa_integration_test] - pub async fn test_success_move_to_another_account() { - info!("########## test_success_move_to_another_account ##########"); - let command = Command::Account(AccountSubcommand::New(NewSubcommand::Public { cci: None })); - - let wallet_config = fetch_config().await.unwrap(); - - let seq_client = SequencerClient::new(wallet_config.sequencer_addr.clone()).unwrap(); - - wallet::cli::execute_subcommand(command).await.unwrap(); - - let PersistentStorage { - accounts: persistent_accounts, - last_synced_block: _, - } = fetch_persistent_storage().await.unwrap(); - - let mut new_persistent_account_id = String::new(); - - for per_acc in persistent_accounts { - if (per_acc.account_id().to_string() != ACC_RECEIVER) - && (per_acc.account_id().to_string() != ACC_SENDER) - { - new_persistent_account_id = per_acc.account_id().to_string(); - } - } - - if new_persistent_account_id == String::new() { - panic!("Failed to produce new account, not present in persistent accounts"); - } - - let command = Command::AuthTransfer(AuthTransferSubcommand::Send { - from: make_public_account_input_from_str(ACC_SENDER), - to: Some(make_public_account_input_from_str( - &new_persistent_account_id, - )), - to_npk: None, - to_ipk: None, - amount: 100, - }); - - wallet::cli::execute_subcommand(command).await.unwrap(); - - info!("Waiting for next block creation"); - tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; - - info!("Checking correct balance move"); - let acc_1_balance = seq_client - .get_account_balance(ACC_SENDER.to_string()) - .await - .unwrap(); - let acc_2_balance = seq_client - .get_account_balance(new_persistent_account_id) - .await - .unwrap(); - - info!("Balance of sender : {acc_1_balance:#?}"); - info!("Balance of receiver : {acc_2_balance:#?}"); - - assert_eq!(acc_1_balance.balance, 9900); - assert_eq!(acc_2_balance.balance, 100); - - info!("Success!"); - } - - #[nssa_integration_test] - pub async fn test_failure() { - info!("########## test_failure ##########"); - let command = Command::AuthTransfer(AuthTransferSubcommand::Send { - from: make_public_account_input_from_str(ACC_SENDER), - to: Some(make_public_account_input_from_str(ACC_RECEIVER)), - to_npk: None, - to_ipk: None, - amount: 1000000, - }); - - let wallet_config = fetch_config().await.unwrap(); - - let seq_client = SequencerClient::new(wallet_config.sequencer_addr.clone()).unwrap(); - - let failed_send = wallet::cli::execute_subcommand(command).await; - - assert!(failed_send.is_err()); - - info!("Waiting for next block creation"); - tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; - - info!("Checking correct balance move"); - let acc_1_balance = seq_client - .get_account_balance(ACC_SENDER.to_string()) - .await - .unwrap(); - let acc_2_balance = seq_client - .get_account_balance(ACC_RECEIVER.to_string()) - .await - .unwrap(); - - info!("Balance of sender : {acc_1_balance:#?}"); - info!("Balance of receiver : {acc_2_balance:#?}"); - - assert_eq!(acc_1_balance.balance, 10000); - assert_eq!(acc_2_balance.balance, 20000); - - info!("Success!"); - } - - #[nssa_integration_test] - pub async fn test_success_two_transactions() { - info!("########## test_success_two_transactions ##########"); - let command = Command::AuthTransfer(AuthTransferSubcommand::Send { - from: make_public_account_input_from_str(ACC_SENDER), - to: Some(make_public_account_input_from_str(ACC_RECEIVER)), - to_npk: None, - to_ipk: None, - amount: 100, - }); - - let wallet_config = fetch_config().await.unwrap(); - - let seq_client = SequencerClient::new(wallet_config.sequencer_addr.clone()).unwrap(); - - wallet::cli::execute_subcommand(command).await.unwrap(); - - info!("Waiting for next block creation"); - tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; - - info!("Checking correct balance move"); - let acc_1_balance = seq_client - .get_account_balance(ACC_SENDER.to_string()) - .await - .unwrap(); - let acc_2_balance = seq_client - .get_account_balance(ACC_RECEIVER.to_string()) - .await - .unwrap(); - - info!("Balance of sender : {acc_1_balance:#?}"); - info!("Balance of receiver : {acc_2_balance:#?}"); - - assert_eq!(acc_1_balance.balance, 9900); - assert_eq!(acc_2_balance.balance, 20100); - - info!("First TX Success!"); - - let command = Command::AuthTransfer(AuthTransferSubcommand::Send { - from: make_public_account_input_from_str(ACC_SENDER), - to: Some(make_public_account_input_from_str(ACC_RECEIVER)), - to_npk: None, - to_ipk: None, - amount: 100, - }); - - wallet::cli::execute_subcommand(command).await.unwrap(); - - info!("Waiting for next block creation"); - tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; - - info!("Checking correct balance move"); - let acc_1_balance = seq_client - .get_account_balance(ACC_SENDER.to_string()) - .await - .unwrap(); - let acc_2_balance = seq_client - .get_account_balance(ACC_RECEIVER.to_string()) - .await - .unwrap(); - - info!("Balance of sender : {acc_1_balance:#?}"); - info!("Balance of receiver : {acc_2_balance:#?}"); - - assert_eq!(acc_1_balance.balance, 9800); - assert_eq!(acc_2_balance.balance, 20200); - - info!("Second TX Success!"); - } - - #[nssa_integration_test] - pub async fn test_get_account() { - info!("########## test_get_account ##########"); - let wallet_config = fetch_config().await.unwrap(); - let seq_client = SequencerClient::new(wallet_config.sequencer_addr.clone()).unwrap(); - - let account = seq_client - .get_account(ACC_SENDER.to_string()) - .await - .unwrap() - .account; - - assert_eq!( - account.program_owner, - Program::authenticated_transfer_program().id() - ); - assert_eq!(account.balance, 10000); - assert!(account.data.is_empty()); - assert_eq!(account.nonce, 0); - } - - /// This test creates a new token using the token program. After creating the token, the test - /// executes a token transfer to a new account. - #[nssa_integration_test] - pub async fn test_success_token_program() { - info!("########## test_success_token_program ##########"); - let wallet_config = fetch_config().await.unwrap(); - - // Create new account for the token definition - let SubcommandReturnValue::RegisterAccount { - account_id: definition_account_id, - } = wallet::cli::execute_subcommand(Command::Account(AccountSubcommand::New( - NewSubcommand::Public { cci: None }, - ))) - .await - .unwrap() - else { - panic!("invalid subcommand return value"); - }; - // Create new account for the token supply holder - let SubcommandReturnValue::RegisterAccount { - account_id: supply_account_id, - } = wallet::cli::execute_subcommand(Command::Account(AccountSubcommand::New( - NewSubcommand::Public { cci: None }, - ))) - .await - .unwrap() - else { - panic!("invalid subcommand return value"); - }; - // Create new account for receiving a token transaction - let SubcommandReturnValue::RegisterAccount { - account_id: recipient_account_id, - } = wallet::cli::execute_subcommand(Command::Account(AccountSubcommand::New( - NewSubcommand::Public { cci: None }, - ))) - .await - .unwrap() - else { - panic!("invalid subcommand return value"); - }; - - // Create new token - let subcommand = TokenProgramAgnosticSubcommand::New { - definition_account_id: make_public_account_input_from_str( - &definition_account_id.to_string(), - ), - supply_account_id: make_public_account_input_from_str(&supply_account_id.to_string()), - name: "A NAME".to_string(), - total_supply: 37, - }; - wallet::cli::execute_subcommand(Command::Token(subcommand)) - .await - .unwrap(); - info!("Waiting for next block creation"); - tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; - - let seq_client = SequencerClient::new(wallet_config.sequencer_addr.clone()).unwrap(); - - // Check the status of the token definition account is the expected after the execution - let definition_acc = seq_client - .get_account(definition_account_id.to_string()) - .await - .unwrap() - .account; - - assert_eq!(definition_acc.program_owner, Program::token().id()); - // The data of a token definition account has the following layout: - // [ 0x00 || name (6 bytes) || total supply (little endian 16 bytes) || metadata id (32 - // bytes)] - assert_eq!( - definition_acc.data.as_ref(), - &[ - 0, 65, 32, 78, 65, 77, 69, 37, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0 - ] - ); - - // Check the status of the token holding account with the total supply is the expected after - // the execution - let supply_acc = seq_client - .get_account(supply_account_id.to_string()) - .await - .unwrap() - .account; - - // The account must be owned by the token program - assert_eq!(supply_acc.program_owner, Program::token().id()); - // The data of a token definition account has the following layout: - // [ 0x01 || corresponding_token_definition_id (32 bytes) || balance (little endian 16 - // bytes) ] First byte of the data equal to 1 means it's a token holding account - assert_eq!(supply_acc.data.as_ref()[0], 1); - // Bytes from 1 to 33 represent the id of the token this account is associated with. - // In this example, this is a token account of the newly created token, so it is expected - // to be equal to the account_id of the token definition account. - assert_eq!( - &supply_acc.data.as_ref()[1..33], - definition_account_id.to_bytes() - ); - assert_eq!( - u128::from_le_bytes(supply_acc.data[33..].try_into().unwrap()), - 37 - ); - - // Transfer 7 tokens from `supply_acc` to the account at account_id `recipient_account_id` - let subcommand = TokenProgramAgnosticSubcommand::Send { - from: make_public_account_input_from_str(&supply_account_id.to_string()), - to: Some(make_public_account_input_from_str( - &recipient_account_id.to_string(), - )), - to_npk: None, - to_ipk: None, - amount: 7, - }; - - wallet::cli::execute_subcommand(Command::Token(subcommand)) - .await - .unwrap(); - info!("Waiting for next block creation"); - tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; - - // Check the status of the account at `supply_account_id` is the expected after the - // execution - let supply_acc = seq_client - .get_account(supply_account_id.to_string()) - .await - .unwrap() - .account; - // The account must be owned by the token program - assert_eq!(supply_acc.program_owner, Program::token().id()); - // First byte equal to 1 means it's a token holding account - assert_eq!(supply_acc.data[0], 1); - // Bytes from 1 to 33 represent the id of the token this account is associated with. - assert_eq!(&supply_acc.data[1..33], definition_account_id.to_bytes()); - assert_eq!( - u128::from_le_bytes(supply_acc.data[33..].try_into().unwrap()), - 30 - ); - - // Check the status of the account at `recipient_account_id` is the expected after the - // execution - let recipient_acc = seq_client - .get_account(recipient_account_id.to_string()) - .await - .unwrap() - .account; - - // The account must be owned by the token program - assert_eq!(recipient_acc.program_owner, Program::token().id()); - // First byte equal to 1 means it's a token holding account - assert_eq!(recipient_acc.data[0], 1); - // Bytes from 1 to 33 represent the id of the token this account is associated with. - assert_eq!(&recipient_acc.data[1..33], definition_account_id.to_bytes()); - assert_eq!( - u128::from_le_bytes(recipient_acc.data[33..].try_into().unwrap()), - 7 - ); - - // Burn 3 tokens from `recipient_acc` - let subcommand = TokenProgramAgnosticSubcommand::Burn { - definition: make_public_account_input_from_str(&definition_account_id.to_string()), - holder: make_public_account_input_from_str(&recipient_account_id.to_string()), - amount: 3, - }; - - wallet::cli::execute_subcommand(Command::Token(subcommand)) - .await - .unwrap(); - info!("Waiting for next block creation"); - tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; - - // Check the status of the token definition account is the expected after the execution - let definition_acc = seq_client - .get_account(definition_account_id.to_string()) - .await - .unwrap() - .account; - - assert_eq!( - definition_acc.data.as_ref(), - &[ - 0, 65, 32, 78, 65, 77, 69, 34, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0 - ] - ); - - // Check the status of the account at `recipient_account_id` is the expected after the - // execution - let recipient_acc = seq_client - .get_account(recipient_account_id.to_string()) - .await - .unwrap() - .account; - - assert_eq!( - u128::from_le_bytes(recipient_acc.data[33..].try_into().unwrap()), - 4 - ); - - // Mint 10 tokens at `recipient_acc` - let subcommand = TokenProgramAgnosticSubcommand::Mint { - definition: make_public_account_input_from_str(&definition_account_id.to_string()), - holder: Some(make_public_account_input_from_str( - &recipient_account_id.to_string(), - )), - holder_npk: None, - holder_ipk: None, - amount: 10, - }; - - wallet::cli::execute_subcommand(Command::Token(subcommand)) - .await - .unwrap(); - info!("Waiting for next block creation"); - tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; - - // Check the status of the token definition account is the expected after the execution - let definition_acc = seq_client - .get_account(definition_account_id.to_string()) - .await - .unwrap() - .account; - - assert_eq!( - definition_acc.data.as_ref(), - &[ - 0, 65, 32, 78, 65, 77, 69, 44, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0 - ] - ); - - // Check the status of the account at `recipient_account_id` is the expected after the - // execution - let recipient_acc = seq_client - .get_account(recipient_account_id.to_string()) - .await - .unwrap() - .account; - - assert_eq!( - u128::from_le_bytes(recipient_acc.data[33..].try_into().unwrap()), - 14 - ); - } - - /// This test creates a new private token using the token program. After creating the token, the - /// test executes a private token transfer to a new account. All accounts are private owned - /// except definition which is public. - #[nssa_integration_test] - pub async fn test_success_token_program_private_owned_supply() { - info!("########## test_success_token_program_private_owned_supply ##########"); - let wallet_config = fetch_config().await.unwrap(); - - // Create new account for the token definition (public) - let SubcommandReturnValue::RegisterAccount { - account_id: definition_account_id, - } = wallet::cli::execute_subcommand(Command::Account(AccountSubcommand::New( - NewSubcommand::Public { cci: None }, - ))) - .await - .unwrap() - else { - panic!("invalid subcommand return value"); - }; - // Create new account for the token supply holder (private) - let SubcommandReturnValue::RegisterAccount { - account_id: supply_account_id, - } = wallet::cli::execute_subcommand(Command::Account(AccountSubcommand::New( - NewSubcommand::Private { cci: None }, - ))) - .await - .unwrap() - else { - panic!("invalid subcommand return value"); - }; - // Create new account for receiving a token transaction - let SubcommandReturnValue::RegisterAccount { - account_id: recipient_account_id, - } = wallet::cli::execute_subcommand(Command::Account(AccountSubcommand::New( - NewSubcommand::Private { cci: None }, - ))) - .await - .unwrap() - else { - panic!("invalid subcommand return value"); - }; - - // Create new token - let subcommand = TokenProgramAgnosticSubcommand::New { - definition_account_id: make_public_account_input_from_str( - &definition_account_id.to_string(), - ), - supply_account_id: make_private_account_input_from_str(&supply_account_id.to_string()), - name: "A NAME".to_string(), - total_supply: 37, - }; - - wallet::cli::execute_subcommand(Command::Token(subcommand)) - .await - .unwrap(); - - info!("Waiting for next block creation"); - tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; - - let seq_client = SequencerClient::new(wallet_config.sequencer_addr.clone()).unwrap(); - - // Check the status of the token definition account is the expected after the execution - let definition_acc = seq_client - .get_account(definition_account_id.to_string()) - .await - .unwrap() - .account; - - assert_eq!(definition_acc.program_owner, Program::token().id()); - // The data of a token definition account has the following layout: - // [ 0x00 || name (6 bytes) || total supply (little endian 16 bytes) || metadata id (32 - // bytes)] - assert_eq!( - definition_acc.data.as_ref(), - &[ - 0, 65, 32, 78, 65, 77, 69, 37, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0 - ] - ); - - let wallet_config = fetch_config().await.unwrap(); - let wallet_storage = WalletCore::start_from_config_update_chain(wallet_config) - .await - .unwrap(); - - let new_commitment1 = wallet_storage - .get_private_account_commitment(&supply_account_id) - .unwrap(); - assert!(verify_commitment_is_in_state(new_commitment1, &seq_client).await); - - // Transfer 7 tokens from `supply_acc` to the account at account_id `recipient_account_id` - let subcommand = TokenProgramAgnosticSubcommand::Send { - from: make_private_account_input_from_str(&supply_account_id.to_string()), - to: Some(make_private_account_input_from_str( - &recipient_account_id.to_string(), - )), - to_npk: None, - to_ipk: None, - amount: 7, - }; - - wallet::cli::execute_subcommand(Command::Token(subcommand)) - .await - .unwrap(); - - info!("Waiting for next block creation"); - tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; - - let wallet_config = fetch_config().await.unwrap(); - let wallet_storage = WalletCore::start_from_config_update_chain(wallet_config) - .await - .unwrap(); - - let new_commitment1 = wallet_storage - .get_private_account_commitment(&supply_account_id) - .unwrap(); - assert!(verify_commitment_is_in_state(new_commitment1, &seq_client).await); - - let new_commitment2 = wallet_storage - .get_private_account_commitment(&recipient_account_id) - .unwrap(); - assert!(verify_commitment_is_in_state(new_commitment2, &seq_client).await); - - // Transfer additional 7 tokens from `supply_acc` to the account at account_id - // `recipient_account_id` - let subcommand = TokenProgramAgnosticSubcommand::Send { - from: make_private_account_input_from_str(&supply_account_id.to_string()), - to: Some(make_private_account_input_from_str( - &recipient_account_id.to_string(), - )), - to_npk: None, - to_ipk: None, - amount: 7, - }; - - wallet::cli::execute_subcommand(Command::Token(subcommand)) - .await - .unwrap(); - - info!("Waiting for next block creation"); - tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; - - let wallet_config = fetch_config().await.unwrap(); - let wallet_storage = WalletCore::start_from_config_update_chain(wallet_config) - .await - .unwrap(); - - let new_commitment1 = wallet_storage - .get_private_account_commitment(&supply_account_id) - .unwrap(); - assert!(verify_commitment_is_in_state(new_commitment1, &seq_client).await); - - let new_commitment2 = wallet_storage - .get_private_account_commitment(&recipient_account_id) - .unwrap(); - assert!(verify_commitment_is_in_state(new_commitment2, &seq_client).await); - - // Burn 3 tokens from `recipient_acc` - let subcommand = TokenProgramAgnosticSubcommand::Burn { - definition: make_public_account_input_from_str(&definition_account_id.to_string()), - holder: make_private_account_input_from_str(&recipient_account_id.to_string()), - amount: 3, - }; - - wallet::cli::execute_subcommand(Command::Token(subcommand)) - .await - .unwrap(); - info!("Waiting for next block creation"); - tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; - - // Check the status of the token definition account is the expected after the execution - let definition_acc = seq_client - .get_account(definition_account_id.to_string()) - .await - .unwrap() - .account; - - assert_eq!( - definition_acc.data.as_ref(), - &[ - 0, 65, 32, 78, 65, 77, 69, 34, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0 - ] - ); - - let wallet_config = fetch_config().await.unwrap(); - let wallet_storage = WalletCore::start_from_config_update_chain(wallet_config) - .await - .unwrap(); - - let new_commitment2 = wallet_storage - .get_private_account_commitment(&recipient_account_id) - .unwrap(); - assert!(verify_commitment_is_in_state(new_commitment2, &seq_client).await); - - // Check the status of the account at `recipient_account_id` is the expected after the - // execution - let recipient_acc = wallet_storage - .get_account_private(&recipient_account_id) - .unwrap(); - - assert_eq!( - u128::from_le_bytes(recipient_acc.data[33..].try_into().unwrap()), - 11 - ); - - // Mint 10 tokens at `recipient_acc` - let subcommand = TokenProgramAgnosticSubcommand::Mint { - definition: make_public_account_input_from_str(&definition_account_id.to_string()), - holder: Some(make_private_account_input_from_str( - &recipient_account_id.to_string(), - )), - holder_npk: None, - holder_ipk: None, - amount: 10, - }; - - wallet::cli::execute_subcommand(Command::Token(subcommand)) - .await - .unwrap(); - info!("Waiting for next block creation"); - tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; - - // Check the status of the token definition account is the expected after the execution - let definition_acc = seq_client - .get_account(definition_account_id.to_string()) - .await - .unwrap() - .account; - - assert_eq!( - definition_acc.data.as_ref(), - &[ - 0, 65, 32, 78, 65, 77, 69, 44, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0 - ] - ); - - let wallet_config = fetch_config().await.unwrap(); - let wallet_storage = WalletCore::start_from_config_update_chain(wallet_config) - .await - .unwrap(); - - let new_commitment2 = wallet_storage - .get_private_account_commitment(&recipient_account_id) - .unwrap(); - assert!(verify_commitment_is_in_state(new_commitment2, &seq_client).await); - - // Check the status of the account at `recipient_account_id` is the expected after the - // execution - let recipient_acc = wallet_storage - .get_account_private(&recipient_account_id) - .unwrap(); - - assert_eq!( - u128::from_le_bytes(recipient_acc.data[33..].try_into().unwrap()), - 21 - ); - - // Now the same mint, but in foreign way - - // Create new account for receiving a mint transaction - let SubcommandReturnValue::RegisterAccount { - account_id: recipient_account_id2, - } = wallet::cli::execute_subcommand(Command::Account(AccountSubcommand::New( - NewSubcommand::Private { cci: None }, - ))) - .await - .unwrap() - else { - panic!("invalid subcommand return value"); - }; - - let wallet_config = fetch_config().await.unwrap(); - let wallet_storage = WalletCore::start_from_config_update_chain(wallet_config) - .await - .unwrap(); - - let (holder_keys, _) = wallet_storage - .storage - .user_data - .get_private_account(&recipient_account_id2) - .unwrap(); - - // Mint 9 tokens at `recipient_acc2` - let subcommand = TokenProgramAgnosticSubcommand::Mint { - definition: make_public_account_input_from_str(&definition_account_id.to_string()), - holder: None, - holder_npk: Some(hex::encode(holder_keys.nullifer_public_key.0)), - holder_ipk: Some(hex::encode( - holder_keys.incoming_viewing_public_key.0.clone(), - )), - amount: 9, - }; - - wallet::cli::execute_subcommand(Command::Token(subcommand)) - .await - .unwrap(); - info!("Waiting for next block creation"); - tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; - - // Sync to claim holder - let command = Command::Account(AccountSubcommand::SyncPrivate {}); - - wallet::cli::execute_subcommand(command).await.unwrap(); - - // Check the status of the token definition account is the expected after the execution - let definition_acc = seq_client - .get_account(definition_account_id.to_string()) - .await - .unwrap() - .account; - - assert_eq!( - definition_acc.data.as_ref(), - &[ - 0, 65, 32, 78, 65, 77, 69, 53, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0 - ] - ); - - let wallet_config = fetch_config().await.unwrap(); - let wallet_storage = WalletCore::start_from_config_update_chain(wallet_config) - .await - .unwrap(); - - let new_commitment2 = wallet_storage - .get_private_account_commitment(&recipient_account_id2) - .unwrap(); - assert!(verify_commitment_is_in_state(new_commitment2, &seq_client).await); - - // Check the status of the account at `recipient_account_id2` is the expected after the - // execution - let recipient_acc = wallet_storage - .get_account_private(&recipient_account_id2) - .unwrap(); - - assert_eq!( - u128::from_le_bytes(recipient_acc.data[33..].try_into().unwrap()), - 9 - ); - } - - /// This test creates a new private token using the token program. All accounts are private - /// owned except supply which is public. - #[nssa_integration_test] - pub async fn test_success_token_program_private_owned_definition() { - info!("########## test_success_token_program_private_owned_definition ##########"); - let wallet_config = fetch_config().await.unwrap(); - - // Create new account for the token definition (private) - let SubcommandReturnValue::RegisterAccount { - account_id: definition_account_id, - } = wallet::cli::execute_subcommand(Command::Account(AccountSubcommand::New( - NewSubcommand::Private { - cci: Some(ChainIndex::root()), - }, - ))) - .await - .unwrap() - else { - panic!("invalid subcommand return value"); - }; - // Create new account for the token supply holder (public) - let SubcommandReturnValue::RegisterAccount { - account_id: supply_account_id, - } = wallet::cli::execute_subcommand(Command::Account(AccountSubcommand::New( - NewSubcommand::Public { - cci: Some(ChainIndex::root()), - }, - ))) - .await - .unwrap() - else { - panic!("invalid subcommand return value"); - }; - - // Create new token - let subcommand = TokenProgramAgnosticSubcommand::New { - definition_account_id: make_private_account_input_from_str( - &definition_account_id.to_string(), - ), - supply_account_id: make_public_account_input_from_str(&supply_account_id.to_string()), - name: "A NAME".to_string(), - total_supply: 37, - }; - - wallet::cli::execute_subcommand(Command::Token(subcommand)) - .await - .unwrap(); - - info!("Waiting for next block creation"); - tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; - - let seq_client = SequencerClient::new(wallet_config.sequencer_addr.clone()).unwrap(); - - let wallet_config = fetch_config().await.unwrap(); - let wallet_storage = WalletCore::start_from_config_update_chain(wallet_config) - .await - .unwrap(); - - let new_commitment1 = wallet_storage - .get_private_account_commitment(&definition_account_id) - .unwrap(); - assert!(verify_commitment_is_in_state(new_commitment1, &seq_client).await); - - // Check the status of the token definition account is the expected after the execution - let supply_acc = seq_client - .get_account(supply_account_id.to_string()) - .await - .unwrap() - .account; - - assert_eq!(supply_acc.program_owner, Program::token().id()); - // The data of a token holding account has the following layout: - // [ 0x01 || definition id (32 bytes) || balance (little endian 16 bytes) ] - assert_eq!( - supply_acc.data.as_ref(), - &[ - 1, 128, 101, 5, 31, 43, 36, 97, 108, 164, 92, 25, 157, 173, 5, 14, 194, 121, 239, - 84, 19, 160, 243, 47, 193, 2, 250, 247, 232, 253, 191, 232, 173, 37, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 - ] - ); - - // Create new account for receiving a mint transaction - let SubcommandReturnValue::RegisterAccount { - account_id: recipient_account_id_pr, - } = wallet::cli::execute_subcommand(Command::Account(AccountSubcommand::New( - NewSubcommand::Private { cci: None }, - ))) - .await - .unwrap() - else { - panic!("invalid subcommand return value"); - }; - - // Create new account for receiving a mint transaction - let SubcommandReturnValue::RegisterAccount { - account_id: recipient_account_id_pub, - } = wallet::cli::execute_subcommand(Command::Account(AccountSubcommand::New( - NewSubcommand::Public { cci: None }, - ))) - .await - .unwrap() - else { - panic!("invalid subcommand return value"); - }; - - // Mint 10 tokens at `recipient_acc_pub` - let subcommand = TokenProgramAgnosticSubcommand::Mint { - definition: make_private_account_input_from_str(&definition_account_id.to_string()), - holder: Some(make_public_account_input_from_str( - &recipient_account_id_pub.to_string(), - )), - holder_npk: None, - holder_ipk: None, - amount: 10, - }; - - wallet::cli::execute_subcommand(Command::Token(subcommand)) - .await - .unwrap(); - info!("Waiting for next block creation"); - tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; - - let wallet_config = fetch_config().await.unwrap(); - let wallet_storage = WalletCore::start_from_config_update_chain(wallet_config) - .await - .unwrap(); - - // Check the status of the token definition account is the expected after the execution - let definition_acc = wallet_storage - .get_account_private(&definition_account_id) - .unwrap(); - - assert_eq!( - definition_acc.data.as_ref(), - &[ - 0, 65, 32, 78, 65, 77, 69, 47, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0 - ] - ); - - // Check the status of the account at `recipient_account_id_pub` is the expected after the - // execution - let recipient_acc_pub = seq_client - .get_account(recipient_account_id_pub.to_string()) - .await - .unwrap() - .account; - - assert_eq!( - u128::from_le_bytes(recipient_acc_pub.data[33..].try_into().unwrap()), - 10 - ); - - let (holder_keys, _) = wallet_storage - .storage - .user_data - .get_private_account(&recipient_account_id_pr) - .unwrap(); - - // Mint 5 tokens at `recipient_acc_pr` - let subcommand = TokenProgramAgnosticSubcommand::Mint { - definition: make_private_account_input_from_str(&definition_account_id.to_string()), - holder: None, - holder_npk: Some(hex::encode(holder_keys.nullifer_public_key.0)), - holder_ipk: Some(hex::encode( - holder_keys.incoming_viewing_public_key.0.clone(), - )), - amount: 5, - }; - - wallet::cli::execute_subcommand(Command::Token(subcommand)) - .await - .unwrap(); - info!("Waiting for next block creation"); - tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; - - // Sync to claim holder - let command = Command::Account(AccountSubcommand::SyncPrivate {}); - - wallet::cli::execute_subcommand(command).await.unwrap(); - - let wallet_config = fetch_config().await.unwrap(); - let wallet_storage = WalletCore::start_from_config_update_chain(wallet_config) - .await - .unwrap(); - - // Check the status of the token definition account is the expected after the execution - let definition_acc = wallet_storage - .get_account_private(&definition_account_id) - .unwrap(); - - assert_eq!( - definition_acc.data.as_ref(), - &[ - 0, 65, 32, 78, 65, 77, 69, 52, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0 - ] - ); - - let new_commitment2 = wallet_storage - .get_private_account_commitment(&recipient_account_id_pr) - .unwrap(); - assert!(verify_commitment_is_in_state(new_commitment2, &seq_client).await); - - // Check the status of the account at `recipient_account_id_pr` is the expected after the - // execution - let recipient_acc_pr = wallet_storage - .get_account_private(&recipient_account_id_pr) - .unwrap(); - - assert_eq!( - u128::from_le_bytes(recipient_acc_pr.data[33..].try_into().unwrap()), - 5 - ); - - // Mint 5 tokens at `recipient_acc_pr` - let subcommand = TokenProgramAgnosticSubcommand::Mint { - definition: make_private_account_input_from_str(&definition_account_id.to_string()), - holder: Some(make_private_account_input_from_str( - &recipient_account_id_pr.to_string(), - )), - holder_npk: None, - holder_ipk: None, - amount: 5, - }; - - wallet::cli::execute_subcommand(Command::Token(subcommand)) - .await - .unwrap(); - info!("Waiting for next block creation"); - tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; - - let wallet_config = fetch_config().await.unwrap(); - let wallet_storage = WalletCore::start_from_config_update_chain(wallet_config) - .await - .unwrap(); - - // Check the status of the token definition account is the expected after the execution - let definition_acc = wallet_storage - .get_account_private(&definition_account_id) - .unwrap(); - - assert_eq!( - definition_acc.data.as_ref(), - &[ - 0, 65, 32, 78, 65, 77, 69, 57, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0 - ] - ); - - let new_commitment2 = wallet_storage - .get_private_account_commitment(&recipient_account_id_pr) - .unwrap(); - assert!(verify_commitment_is_in_state(new_commitment2, &seq_client).await); - - // Check the status of the account at `recipient_account_id_pr` is the expected after the - // execution - let recipient_acc = wallet_storage - .get_account_private(&recipient_account_id_pr) - .unwrap(); - - assert_eq!( - u128::from_le_bytes(recipient_acc.data[33..].try_into().unwrap()), - 10 - ); - - // Burn 5 tokens at `recipient_acc_pub` - let subcommand = TokenProgramAgnosticSubcommand::Burn { - definition: make_private_account_input_from_str(&definition_account_id.to_string()), - holder: make_public_account_input_from_str(&recipient_account_id_pub.to_string()), - amount: 5, - }; - - wallet::cli::execute_subcommand(Command::Token(subcommand)) - .await - .unwrap(); - info!("Waiting for next block creation"); - tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; - - let wallet_config = fetch_config().await.unwrap(); - let wallet_storage = WalletCore::start_from_config_update_chain(wallet_config) - .await - .unwrap(); - - // Check the status of the token definition account is the expected after the execution - let definition_acc = wallet_storage - .get_account_private(&definition_account_id) - .unwrap(); - - assert_eq!( - definition_acc.data.as_ref(), - &[ - 0, 65, 32, 78, 65, 77, 69, 52, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0 - ] - ); - - // Check the status of the account at `recipient_account_id_pub` is the expected after the - // execution - let recipient_acc_pub = seq_client - .get_account(recipient_account_id_pub.to_string()) - .await - .unwrap() - .account; - - assert_eq!( - u128::from_le_bytes(recipient_acc_pub.data[33..].try_into().unwrap()), - 5 - ); - - // Burn 5 tokens at `recipient_acc_pr` - let subcommand = TokenProgramAgnosticSubcommand::Burn { - definition: make_private_account_input_from_str(&definition_account_id.to_string()), - holder: make_private_account_input_from_str(&recipient_account_id_pr.to_string()), - amount: 5, - }; - - wallet::cli::execute_subcommand(Command::Token(subcommand)) - .await - .unwrap(); - info!("Waiting for next block creation"); - tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; - - let wallet_config = fetch_config().await.unwrap(); - let wallet_storage = WalletCore::start_from_config_update_chain(wallet_config) - .await - .unwrap(); - - // Check the status of the token definition account is the expected after the execution - let definition_acc = wallet_storage - .get_account_private(&definition_account_id) - .unwrap(); - - assert_eq!( - definition_acc.data.as_ref(), - &[ - 0, 65, 32, 78, 65, 77, 69, 47, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0 - ] - ); - - let new_commitment2 = wallet_storage - .get_private_account_commitment(&recipient_account_id_pr) - .unwrap(); - assert!(verify_commitment_is_in_state(new_commitment2, &seq_client).await); - - // Check the status of the account at `recipient_account_id_pr` is the expected after the - // execution - let recipient_acc = wallet_storage - .get_account_private(&recipient_account_id_pr) - .unwrap(); - - assert_eq!( - u128::from_le_bytes(recipient_acc.data[33..].try_into().unwrap()), - 5 - ); - } - - /// This test creates a new private token using the token program. All accounts are private - /// owned. - #[nssa_integration_test] - pub async fn test_success_token_program_private_owned_definition_and_supply() { - info!( - "########## test_success_token_program_private_owned_definition_and_supply ##########" - ); - let wallet_config = fetch_config().await.unwrap(); - - // Create new account for the token definition (private) - let SubcommandReturnValue::RegisterAccount { - account_id: definition_account_id, - } = wallet::cli::execute_subcommand(Command::Account(AccountSubcommand::New( - NewSubcommand::Private { - cci: Some(ChainIndex::root()), - }, - ))) - .await - .unwrap() - else { - panic!("invalid subcommand return value"); - }; - // Create new account for the token supply holder (private) - let SubcommandReturnValue::RegisterAccount { - account_id: supply_account_id, - } = wallet::cli::execute_subcommand(Command::Account(AccountSubcommand::New( - NewSubcommand::Private { - cci: Some(ChainIndex::root()), - }, - ))) - .await - .unwrap() - else { - panic!("invalid subcommand return value"); - }; - - // Create new token - let subcommand = TokenProgramAgnosticSubcommand::New { - definition_account_id: make_private_account_input_from_str( - &definition_account_id.to_string(), - ), - supply_account_id: make_private_account_input_from_str(&supply_account_id.to_string()), - name: "A NAME".to_string(), - total_supply: 37, - }; - - wallet::cli::execute_subcommand(Command::Token(subcommand)) - .await - .unwrap(); - - info!("Waiting for next block creation"); - tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; - - let seq_client = SequencerClient::new(wallet_config.sequencer_addr.clone()).unwrap(); - - let wallet_config = fetch_config().await.unwrap(); - let wallet_storage = WalletCore::start_from_config_update_chain(wallet_config) - .await - .unwrap(); - - let new_commitment1 = wallet_storage - .get_private_account_commitment(&definition_account_id) - .unwrap(); - assert!(verify_commitment_is_in_state(new_commitment1, &seq_client).await); - - let new_commitment2 = wallet_storage - .get_private_account_commitment(&supply_account_id) - .unwrap(); - assert!(verify_commitment_is_in_state(new_commitment2, &seq_client).await); - - let definition_acc = wallet_storage - .get_account_private(&definition_account_id) - .unwrap(); - let supply_acc = wallet_storage - .get_account_private(&supply_account_id) - .unwrap(); - - assert_eq!(definition_acc.program_owner, Program::token().id()); - // The data of a token definition account has the following layout: - // [ 0x00 || name (6 bytes) || total supply (little endian 16 bytes) || metadata id (32 - // bytes)] - assert_eq!( - definition_acc.data.as_ref(), - &[ - 0, 65, 32, 78, 65, 77, 69, 37, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0 - ] - ); - - assert_eq!(supply_acc.program_owner, Program::token().id()); - // The data of a token holding account has the following layout: - // [ 0x01 || definition id (32 bytes) || balance (little endian 16 bytes) ] - assert_eq!( - supply_acc.data.as_ref(), - &[ - 1, 128, 101, 5, 31, 43, 36, 97, 108, 164, 92, 25, 157, 173, 5, 14, 194, 121, 239, - 84, 19, 160, 243, 47, 193, 2, 250, 247, 232, 253, 191, 232, 173, 37, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 - ] - ); - } - - /// This test creates a new private token using the token program. After creating the token, the - /// test executes a private token transfer to a new account. - #[nssa_integration_test] - pub async fn test_success_token_program_private_claiming_path() { - info!("########## test_success_token_program_private_claiming_path ##########"); - let wallet_config = fetch_config().await.unwrap(); - - // Create new account for the token definition (public) - let SubcommandReturnValue::RegisterAccount { - account_id: definition_account_id, - } = wallet::cli::execute_subcommand(Command::Account(AccountSubcommand::New( - NewSubcommand::Public { cci: None }, - ))) - .await - .unwrap() - else { - panic!("invalid subcommand return value"); - }; - // Create new account for the token supply holder (private) - let SubcommandReturnValue::RegisterAccount { - account_id: supply_account_id, - } = wallet::cli::execute_subcommand(Command::Account(AccountSubcommand::New( - NewSubcommand::Private { cci: None }, - ))) - .await - .unwrap() - else { - panic!("invalid subcommand return value"); - }; - // Create new account for receiving a token transaction - let SubcommandReturnValue::RegisterAccount { - account_id: recipient_account_id, - } = wallet::cli::execute_subcommand(Command::Account(AccountSubcommand::New( - NewSubcommand::Private { cci: None }, - ))) - .await - .unwrap() - else { - panic!("invalid subcommand return value"); - }; - - // Create new token - let subcommand = TokenProgramAgnosticSubcommand::New { - definition_account_id: make_public_account_input_from_str( - &definition_account_id.to_string(), - ), - supply_account_id: make_private_account_input_from_str(&supply_account_id.to_string()), - name: "A NAME".to_string(), - total_supply: 37, - }; - - wallet::cli::execute_subcommand(Command::Token(subcommand)) - .await - .unwrap(); - - info!("Waiting for next block creation"); - tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; - - let seq_client = SequencerClient::new(wallet_config.sequencer_addr.clone()).unwrap(); - - // Check the status of the token definition account is the expected after the execution - let definition_acc = seq_client - .get_account(definition_account_id.to_string()) - .await - .unwrap() - .account; - - assert_eq!(definition_acc.program_owner, Program::token().id()); - // The data of a token definition account has the following layout: - // [ 0x00 || name (6 bytes) || total supply (little endian 16 bytes) || metadata id (32 - // bytes)] - assert_eq!( - definition_acc.data.as_ref(), - &[ - 0, 65, 32, 78, 65, 77, 69, 37, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0 - ] - ); - - let wallet_config = fetch_config().await.unwrap(); - let wallet_storage = WalletCore::start_from_config_update_chain(wallet_config) - .await - .unwrap(); - - let new_commitment1 = wallet_storage - .get_private_account_commitment(&supply_account_id) - .unwrap(); - assert!(verify_commitment_is_in_state(new_commitment1, &seq_client).await); - - let (recipient_keys, _) = wallet_storage - .storage - .user_data - .get_private_account(&recipient_account_id) - .unwrap(); - - // Transfer 7 tokens from `supply_acc` to the account at account_id `recipient_account_id` - let subcommand = TokenProgramAgnosticSubcommand::Send { - from: make_private_account_input_from_str(&supply_account_id.to_string()), - to: None, - to_npk: Some(hex::encode(recipient_keys.nullifer_public_key.0)), - to_ipk: Some(hex::encode( - recipient_keys.incoming_viewing_public_key.0.clone(), - )), - amount: 7, - }; - - let SubcommandReturnValue::PrivacyPreservingTransfer { tx_hash: _ } = - wallet::cli::execute_subcommand(Command::Token(subcommand)) - .await - .unwrap() - else { - panic!("invalid subcommand return value"); - }; - - info!("Waiting for next block creation"); - tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; - - let command = Command::Account(AccountSubcommand::SyncPrivate {}); - - wallet::cli::execute_subcommand(command).await.unwrap(); - - let wallet_config = fetch_config().await.unwrap(); - let wallet_storage = WalletCore::start_from_config_update_chain(wallet_config) - .await - .unwrap(); - - let new_commitment1 = wallet_storage - .get_private_account_commitment(&supply_account_id) - .unwrap(); - assert!(verify_commitment_is_in_state(new_commitment1, &seq_client).await); - - let new_commitment2 = wallet_storage - .get_private_account_commitment(&recipient_account_id) - .unwrap(); - assert!(verify_commitment_is_in_state(new_commitment2, &seq_client).await); - } - - /// This test creates a new public token using the token program. After creating the token, the - /// test executes a shielded token transfer to a new account. All accounts are owned except - /// definition. - #[nssa_integration_test] - pub async fn test_success_token_program_shielded_owned() { - info!("########## test_success_token_program_shielded_owned ##########"); - let wallet_config = fetch_config().await.unwrap(); - - // Create new account for the token definition (public) - let SubcommandReturnValue::RegisterAccount { - account_id: definition_account_id, - } = wallet::cli::execute_subcommand(Command::Account(AccountSubcommand::New( - NewSubcommand::Public { cci: None }, - ))) - .await - .unwrap() - else { - panic!("invalid subcommand return value"); - }; - // Create new account for the token supply holder (public) - let SubcommandReturnValue::RegisterAccount { - account_id: supply_account_id, - } = wallet::cli::execute_subcommand(Command::Account(AccountSubcommand::New( - NewSubcommand::Public { cci: None }, - ))) - .await - .unwrap() - else { - panic!("invalid subcommand return value"); - }; - // Create new account for receiving a token transaction - let SubcommandReturnValue::RegisterAccount { - account_id: recipient_account_id, - } = wallet::cli::execute_subcommand(Command::Account(AccountSubcommand::New( - NewSubcommand::Private { cci: None }, - ))) - .await - .unwrap() - else { - panic!("invalid subcommand return value"); - }; - - // Create new token - let subcommand = TokenProgramAgnosticSubcommand::New { - definition_account_id: make_public_account_input_from_str( - &definition_account_id.to_string(), - ), - supply_account_id: make_public_account_input_from_str(&supply_account_id.to_string()), - name: "A NAME".to_string(), - total_supply: 37, - }; - - wallet::cli::execute_subcommand(Command::Token(subcommand)) - .await - .unwrap(); - - info!("Waiting for next block creation"); - tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; - - let seq_client = SequencerClient::new(wallet_config.sequencer_addr.clone()).unwrap(); - - // Check the status of the token definition account is the expected after the execution - let definition_acc = seq_client - .get_account(definition_account_id.to_string()) - .await - .unwrap() - .account; - - assert_eq!(definition_acc.program_owner, Program::token().id()); - // The data of a token definition account has the following layout: - // [ 0x00 || name (6 bytes) || total supply (little endian 16 bytes) || metadata id (32 - // bytes)] - assert_eq!( - definition_acc.data.as_ref(), - &[ - 0, 65, 32, 78, 65, 77, 69, 37, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0 - ] - ); - - // Transfer 7 tokens from `supply_acc` to the account at account_id `recipient_account_id` - let subcommand = TokenProgramAgnosticSubcommand::Send { - from: make_public_account_input_from_str(&supply_account_id.to_string()), - to: Some(make_private_account_input_from_str( - &recipient_account_id.to_string(), - )), - to_npk: None, - to_ipk: None, - amount: 7, - }; - - wallet::cli::execute_subcommand(Command::Token(subcommand)) - .await - .unwrap(); - - info!("Waiting for next block creation"); - tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; - - let wallet_config = fetch_config().await.unwrap(); - let wallet_storage = WalletCore::start_from_config_update_chain(wallet_config) - .await - .unwrap(); - - let new_commitment2 = wallet_storage - .get_private_account_commitment(&recipient_account_id) - .unwrap(); - assert!(verify_commitment_is_in_state(new_commitment2, &seq_client).await); - - // Transfer additional 7 tokens from `supply_acc` to the account at account_id - // `recipient_account_id` - let subcommand = TokenProgramAgnosticSubcommand::Send { - from: make_public_account_input_from_str(&supply_account_id.to_string()), - to: Some(make_private_account_input_from_str( - &recipient_account_id.to_string(), - )), - to_npk: None, - to_ipk: None, - amount: 7, - }; - - wallet::cli::execute_subcommand(Command::Token(subcommand)) - .await - .unwrap(); - - info!("Waiting for next block creation"); - tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; - - let wallet_config = fetch_config().await.unwrap(); - let wallet_storage = WalletCore::start_from_config_update_chain(wallet_config) - .await - .unwrap(); - - let new_commitment2 = wallet_storage - .get_private_account_commitment(&recipient_account_id) - .unwrap(); - assert!(verify_commitment_is_in_state(new_commitment2, &seq_client).await); - } - - /// This test creates a new private token using the token program. After creating the token, the - /// test executes a deshielded token transfer to a new account. All accounts are owned - /// except definition. - #[nssa_integration_test] - pub async fn test_success_token_program_deshielded_owned() { - info!("########## test_success_token_program_deshielded_owned ##########"); - let wallet_config = fetch_config().await.unwrap(); - - // Create new account for the token definition (public) - let SubcommandReturnValue::RegisterAccount { - account_id: definition_account_id, - } = wallet::cli::execute_subcommand(Command::Account(AccountSubcommand::New( - NewSubcommand::Public { cci: None }, - ))) - .await - .unwrap() - else { - panic!("invalid subcommand return value"); - }; - // Create new account for the token supply holder (private) - let SubcommandReturnValue::RegisterAccount { - account_id: supply_account_id, - } = wallet::cli::execute_subcommand(Command::Account(AccountSubcommand::New( - NewSubcommand::Private { cci: None }, - ))) - .await - .unwrap() - else { - panic!("invalid subcommand return value"); - }; - // Create new account for receiving a token transaction - let SubcommandReturnValue::RegisterAccount { - account_id: recipient_account_id, - } = wallet::cli::execute_subcommand(Command::Account(AccountSubcommand::New( - NewSubcommand::Public { cci: None }, - ))) - .await - .unwrap() - else { - panic!("invalid subcommand return value"); - }; - - // Create new token - let subcommand = TokenProgramAgnosticSubcommand::New { - definition_account_id: make_public_account_input_from_str( - &definition_account_id.to_string(), - ), - supply_account_id: make_private_account_input_from_str(&supply_account_id.to_string()), - name: "A NAME".to_string(), - total_supply: 37, - }; - - wallet::cli::execute_subcommand(Command::Token(subcommand)) - .await - .unwrap(); - - info!("Waiting for next block creation"); - tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; - - let seq_client = SequencerClient::new(wallet_config.sequencer_addr.clone()).unwrap(); - - // Check the status of the token definition account is the expected after the execution - let definition_acc = seq_client - .get_account(definition_account_id.to_string()) - .await - .unwrap() - .account; - - assert_eq!(definition_acc.program_owner, Program::token().id()); - // The data of a token definition account has the following layout: - // [ 0x00 || name (6 bytes) || total supply (little endian 16 bytes) || metadata id (32 - // bytes)] - assert_eq!( - definition_acc.data.as_ref(), - &[ - 0, 65, 32, 78, 65, 77, 69, 37, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0 - ] - ); - - let wallet_config = fetch_config().await.unwrap(); - let wallet_storage = WalletCore::start_from_config_update_chain(wallet_config) - .await - .unwrap(); - - let new_commitment1 = wallet_storage - .get_private_account_commitment(&supply_account_id) - .unwrap(); - assert!(verify_commitment_is_in_state(new_commitment1, &seq_client).await); - - // Transfer 7 tokens from `supply_acc` to the account at account_id `recipient_account_id` - let subcommand = TokenProgramAgnosticSubcommand::Send { - from: make_private_account_input_from_str(&supply_account_id.to_string()), - to: Some(make_public_account_input_from_str( - &recipient_account_id.to_string(), - )), - to_npk: None, - to_ipk: None, - amount: 7, - }; - - wallet::cli::execute_subcommand(Command::Token(subcommand)) - .await - .unwrap(); - - info!("Waiting for next block creation"); - tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; - - let wallet_config = fetch_config().await.unwrap(); - let wallet_storage = WalletCore::start_from_config_update_chain(wallet_config) - .await - .unwrap(); - - let new_commitment1 = wallet_storage - .get_private_account_commitment(&supply_account_id) - .unwrap(); - assert!(verify_commitment_is_in_state(new_commitment1, &seq_client).await); - - // Transfer additional 7 tokens from `supply_acc` to the account at account_id - // `recipient_account_id` - let subcommand = TokenProgramAgnosticSubcommand::Send { - from: make_private_account_input_from_str(&supply_account_id.to_string()), - to: Some(make_public_account_input_from_str( - &recipient_account_id.to_string(), - )), - to_npk: None, - to_ipk: None, - amount: 7, - }; - - wallet::cli::execute_subcommand(Command::Token(subcommand)) - .await - .unwrap(); - - info!("Waiting for next block creation"); - tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; - - let wallet_config = fetch_config().await.unwrap(); - let wallet_storage = WalletCore::start_from_config_update_chain(wallet_config) - .await - .unwrap(); - - let new_commitment1 = wallet_storage - .get_private_account_commitment(&supply_account_id) - .unwrap(); - assert!(verify_commitment_is_in_state(new_commitment1, &seq_client).await); - } - - #[nssa_integration_test] - pub async fn test_success_private_transfer_to_another_owned_account() { - info!("########## test_success_private_transfer_to_another_owned_account ##########"); - let from: AccountId = ACC_SENDER_PRIVATE.parse().unwrap(); - let to: AccountId = ACC_RECEIVER_PRIVATE.parse().unwrap(); - - let command = Command::AuthTransfer(AuthTransferSubcommand::Send { - from: make_private_account_input_from_str(&from.to_string()), - to: Some(make_private_account_input_from_str(&to.to_string())), - to_npk: None, - to_ipk: None, - amount: 100, - }); - - wallet::cli::execute_subcommand(command).await.unwrap(); - - info!("Waiting for next block creation"); - tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; - - let wallet_config = fetch_config().await.unwrap(); - let seq_client = SequencerClient::new(wallet_config.sequencer_addr.clone()).unwrap(); - let wallet_storage = WalletCore::start_from_config_update_chain(wallet_config) - .await - .unwrap(); - - let new_commitment1 = wallet_storage - .get_private_account_commitment(&from) - .unwrap(); - assert!(verify_commitment_is_in_state(new_commitment1, &seq_client).await); - - let new_commitment2 = wallet_storage.get_private_account_commitment(&to).unwrap(); - assert!(verify_commitment_is_in_state(new_commitment2, &seq_client).await); - - info!("Success!"); - } - - #[nssa_integration_test] - pub async fn test_success_private_transfer_to_another_foreign_account() { - info!("########## test_success_private_transfer_to_another_foreign_account ##########"); - let from: AccountId = ACC_SENDER_PRIVATE.parse().unwrap(); - let to_npk = NullifierPublicKey([42; 32]); - let to_npk_string = hex::encode(to_npk.0); - let to_ipk = Secp256k1Point::from_scalar(to_npk.0); - - let command = Command::AuthTransfer(AuthTransferSubcommand::Send { - from: make_private_account_input_from_str(&from.to_string()), - to: None, - to_npk: Some(to_npk_string), - to_ipk: Some(hex::encode(to_ipk.0)), - amount: 100, - }); - - let SubcommandReturnValue::PrivacyPreservingTransfer { tx_hash } = - wallet::cli::execute_subcommand(command).await.unwrap() - else { - panic!("invalid subcommand return value"); - }; - - info!("Waiting for next block creation"); - tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; - - let wallet_config = fetch_config().await.unwrap(); - let seq_client = SequencerClient::new(wallet_config.sequencer_addr.clone()).unwrap(); - let wallet_storage = WalletCore::start_from_config_update_chain(wallet_config) - .await - .unwrap(); - - let new_commitment1 = wallet_storage - .get_private_account_commitment(&from) - .unwrap(); - - let tx = fetch_privacy_preserving_tx(&seq_client, tx_hash.clone()).await; - assert_eq!(tx.message.new_commitments[0], new_commitment1); - - assert_eq!(tx.message.new_commitments.len(), 2); - for commitment in tx.message.new_commitments.into_iter() { - assert!(verify_commitment_is_in_state(commitment, &seq_client).await); - } - - info!("Success!"); - } - - #[nssa_integration_test] - pub async fn test_success_private_transfer_to_another_owned_account_claiming_path() { - info!( - "########## test_success_private_transfer_to_another_owned_account_claiming_path ##########" - ); - let from: AccountId = ACC_SENDER_PRIVATE.parse().unwrap(); - - let command = - Command::Account(AccountSubcommand::New(NewSubcommand::Private { cci: None })); - - let sub_ret = wallet::cli::execute_subcommand(command).await.unwrap(); - let SubcommandReturnValue::RegisterAccount { - account_id: to_account_id, - } = sub_ret - else { - panic!("FAILED TO REGISTER ACCOUNT"); - }; - - let wallet_config = fetch_config().await.unwrap(); - let seq_client = SequencerClient::new(wallet_config.sequencer_addr.clone()).unwrap(); - let wallet_storage = WalletCore::start_from_config_update_chain(wallet_config.clone()) - .await - .unwrap(); - - let (to_keys, _) = wallet_storage - .storage - .user_data - .get_private_account(&to_account_id) - .cloned() - .unwrap(); - - let command = Command::AuthTransfer(AuthTransferSubcommand::Send { - from: make_private_account_input_from_str(&from.to_string()), - to: None, - to_npk: Some(hex::encode(to_keys.nullifer_public_key.0)), - to_ipk: Some(hex::encode(to_keys.incoming_viewing_public_key.0)), - amount: 100, - }); - - let sub_ret = wallet::cli::execute_subcommand(command).await.unwrap(); - let SubcommandReturnValue::PrivacyPreservingTransfer { tx_hash } = sub_ret else { - panic!("FAILED TO SEND TX"); - }; - - let tx = fetch_privacy_preserving_tx(&seq_client, tx_hash.clone()).await; - - let command = Command::Account(AccountSubcommand::SyncPrivate {}); - wallet::cli::execute_subcommand(command).await.unwrap(); - let wallet_storage = WalletCore::start_from_config_update_chain(wallet_config) - .await - .unwrap(); - - let new_commitment1 = wallet_storage - .get_private_account_commitment(&from) - .unwrap(); - assert_eq!(tx.message.new_commitments[0], new_commitment1); - - assert_eq!(tx.message.new_commitments.len(), 2); - for commitment in tx.message.new_commitments.into_iter() { - assert!(verify_commitment_is_in_state(commitment, &seq_client).await); - } - - let to_res_acc = wallet_storage.get_account_private(&to_account_id).unwrap(); - - assert_eq!(to_res_acc.balance, 100); - - info!("Success!"); - } - - // #[nssa_integration_test] - // pub async fn test_success_private_transfer_to_another_owned_account_cont_run_path() { - // info!( - // "########## test_success_private_transfer_to_another_owned_account_cont_run_path - // ##########" ); - // let continious_run_handle = tokio::spawn(wallet::cli::execute_continious_run()); - - // let from: AccountId = ACC_SENDER_PRIVATE.parse().unwrap(); - - // let command = Command::Account(AccountSubcommand::New(NewSubcommand::Private {})); - - // let sub_ret = wallet::cli::execute_subcommand(command).await.unwrap(); - // let SubcommandReturnValue::RegisterAccount { - // account_id: to_account_id, - // } = sub_ret - // else { - // panic!("FAILED TO REGISTER ACCOUNT"); - // }; - - // let wallet_config = fetch_config().await.unwrap(); - // let seq_client = SequencerClient::new(wallet_config.sequencer_addr.clone()).unwrap(); - // let wallet_storage = WalletCore::start_from_config_update_chain(wallet_config.clone()) - // .await - // .unwrap(); - - // let (to_keys, _) = wallet_storage - // .storage - // .user_data - // .user_private_accounts - // .get(&to_account_id) - // .cloned() - // .unwrap(); - - // let command = Command::AuthTransfer(AuthTransferSubcommand::Send { - // from: make_private_account_input_from_str(&from.to_string()), - // to: None, - // to_npk: Some(hex::encode(to_keys.nullifer_public_key.0)), - // to_ipk: Some(hex::encode(to_keys.incoming_viewing_public_key.0)), - // amount: 100, - // }); - - // let sub_ret = wallet::cli::execute_subcommand(command).await.unwrap(); - // let SubcommandReturnValue::PrivacyPreservingTransfer { tx_hash } = sub_ret else { - // panic!("FAILED TO SEND TX"); - // }; - - // let tx = fetch_privacy_preserving_tx(&seq_client, tx_hash.clone()).await; - // println!("Waiting for next blocks to check if continoius run fetch account"); - // tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; - // tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; - - // let wallet_storage = WalletCore::start_from_config_update_chain(wallet_config) - // .await - // .unwrap(); - - // assert_eq!(tx.message.new_commitments.len(), 2); - // for commitment in tx.message.new_commitments.into_iter() { - // assert!(verify_commitment_is_in_state(commitment, &seq_client).await); - // } - - // let to_res_acc = wallet_storage.get_account_private(&to_account_id).unwrap(); - - // assert_eq!(to_res_acc.balance, 100); - - // continious_run_handle.abort(); - - // info!("Success!"); - // } - - #[nssa_integration_test] - pub async fn test_success_deshielded_transfer_to_another_account() { - info!("########## test_success_deshielded_transfer_to_another_account ##########"); - let from: AccountId = ACC_SENDER_PRIVATE.parse().unwrap(); - let to: AccountId = ACC_RECEIVER.parse().unwrap(); - - let command = Command::AuthTransfer(AuthTransferSubcommand::Send { - from: make_private_account_input_from_str(&from.to_string()), - to: Some(make_public_account_input_from_str(&to.to_string())), - to_npk: None, - to_ipk: None, - amount: 100, - }); - - let wallet_config = fetch_config().await.unwrap(); - let seq_client = SequencerClient::new(wallet_config.sequencer_addr.clone()).unwrap(); - let wallet_storage = WalletCore::start_from_config_update_chain(wallet_config.clone()) - .await - .unwrap(); - - let from_acc = wallet_storage.get_account_private(&from).unwrap(); - assert_eq!(from_acc.balance, 10000); - - wallet::cli::execute_subcommand(command).await.unwrap(); - - info!("Waiting for next block creation"); - tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; - - let wallet_storage = WalletCore::start_from_config_update_chain(wallet_config) - .await - .unwrap(); - - let from_acc = wallet_storage.get_account_private(&from).unwrap(); - let new_commitment = wallet_storage - .get_private_account_commitment(&from) - .unwrap(); - assert!(verify_commitment_is_in_state(new_commitment, &seq_client).await); - - let acc_2_balance = seq_client - .get_account_balance(to.to_string()) - .await - .unwrap(); - - assert_eq!(from_acc.balance, 10000 - 100); - assert_eq!(acc_2_balance.balance, 20100); - - info!("Success!"); - } - - #[nssa_integration_test] - pub async fn test_success_shielded_transfer_to_another_owned_account() { - info!("########## test_success_shielded_transfer_to_another_owned_account ##########"); - let from: AccountId = ACC_SENDER.parse().unwrap(); - let to: AccountId = ACC_RECEIVER_PRIVATE.parse().unwrap(); - - let command = Command::AuthTransfer(AuthTransferSubcommand::Send { - from: make_public_account_input_from_str(&from.to_string()), - to: Some(make_private_account_input_from_str(&to.to_string())), - to_npk: None, - to_ipk: None, - amount: 100, - }); - - let wallet_config = fetch_config().await.unwrap(); - let seq_client = SequencerClient::new(wallet_config.sequencer_addr.clone()).unwrap(); - - wallet::cli::execute_subcommand(command).await.unwrap(); - - info!("Waiting for next block creation"); - tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; - - let wallet_config = fetch_config().await.unwrap(); - let wallet_storage = WalletCore::start_from_config_update_chain(wallet_config) - .await - .unwrap(); - - let acc_to = wallet_storage.get_account_private(&to).unwrap(); - let new_commitment = wallet_storage.get_private_account_commitment(&to).unwrap(); - assert!(verify_commitment_is_in_state(new_commitment, &seq_client).await); - - let acc_from_balance = seq_client - .get_account_balance(from.to_string()) - .await - .unwrap(); - - assert_eq!(acc_from_balance.balance, 9900); - assert_eq!(acc_to.balance, 20000 + 100); - - info!("Success!"); - } - - #[nssa_integration_test] - pub async fn test_success_shielded_transfer_to_another_foreign_account() { - info!("########## test_success_shielded_transfer_to_another_foreign_account ##########"); - let to_npk = NullifierPublicKey([42; 32]); - let to_npk_string = hex::encode(to_npk.0); - let to_ipk = Secp256k1Point::from_scalar(to_npk.0); - let from: AccountId = ACC_SENDER.parse().unwrap(); - - let command = Command::AuthTransfer(AuthTransferSubcommand::Send { - from: make_public_account_input_from_str(&from.to_string()), - to: None, - to_npk: Some(to_npk_string), - to_ipk: Some(hex::encode(to_ipk.0)), - amount: 100, - }); - - let wallet_config = fetch_config().await.unwrap(); - - let seq_client = SequencerClient::new(wallet_config.sequencer_addr.clone()).unwrap(); - - let SubcommandReturnValue::PrivacyPreservingTransfer { tx_hash } = - wallet::cli::execute_subcommand(command).await.unwrap() - else { - panic!("invalid subcommand return value"); - }; - - info!("Waiting for next block creation"); - tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; - - let tx = fetch_privacy_preserving_tx(&seq_client, tx_hash).await; - - let acc_1_balance = seq_client - .get_account_balance(from.to_string()) - .await - .unwrap(); - - assert!( - verify_commitment_is_in_state(tx.message.new_commitments[0].clone(), &seq_client).await - ); - - assert_eq!(acc_1_balance.balance, 9900); - - info!("Success!"); - } - - #[nssa_integration_test] - pub async fn test_pinata() { - info!("########## test_pinata ##########"); - let pinata_account_id = PINATA_BASE58; - - let pinata_prize = 150; - let command = Command::Pinata(PinataProgramAgnosticSubcommand::Claim { - to: make_public_account_input_from_str(ACC_SENDER), - }); - - let wallet_config = fetch_config().await.unwrap(); - - let seq_client = SequencerClient::new(wallet_config.sequencer_addr.clone()).unwrap(); - - let pinata_balance_pre = seq_client - .get_account_balance(pinata_account_id.to_string()) - .await - .unwrap() - .balance; - - wallet::cli::execute_subcommand(command).await.unwrap(); - - info!("Waiting for next block creation"); - tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; - - info!("Checking correct balance move"); - let pinata_balance_post = seq_client - .get_account_balance(pinata_account_id.to_string()) - .await - .unwrap() - .balance; - - let winner_balance_post = seq_client - .get_account_balance(ACC_SENDER.to_string()) - .await - .unwrap() - .balance; - - assert_eq!(pinata_balance_post, pinata_balance_pre - pinata_prize); - assert_eq!(winner_balance_post, 10000 + pinata_prize); - - info!("Success!"); - } - - #[nssa_integration_test] - pub async fn test_program_deployment() { - info!("########## test program deployment ##########"); - - let manifest_dir = env!("CARGO_MANIFEST_DIR"); - let binary_filepath: PathBuf = PathBuf::from(manifest_dir) - .join("../artifacts/test_program_methods") - .join(NSSA_PROGRAM_FOR_TEST_DATA_CHANGER); - - let command = Command::DeployProgram { - binary_filepath: binary_filepath.clone(), - }; - - wallet::cli::execute_subcommand(command).await.unwrap(); - - let wallet_config = fetch_config().await.unwrap(); - let seq_client = SequencerClient::new(wallet_config.sequencer_addr.clone()).unwrap(); - - info!("Waiting for next block creation"); - tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; - - // The program is the data changer and takes one account as input. - // We pass an uninitialized account and we expect after execution to be owned by the data - // changer program (NSSA account claiming mechanism) with data equal to [0] (due to program - // logic) - // - let bytecode = std::fs::read(binary_filepath).unwrap(); - let data_changer = Program::new(bytecode).unwrap(); - let account_id: AccountId = "11".repeat(16).parse().unwrap(); - let message = nssa::public_transaction::Message::try_new( - data_changer.id(), - vec![account_id], - vec![], - vec![0], - ) - .unwrap(); - let witness_set = nssa::public_transaction::WitnessSet::for_message(&message, &[]); - let transaction = nssa::PublicTransaction::new(message, witness_set); - let _response = seq_client.send_tx_public(transaction).await.unwrap(); - - info!("Waiting for next block creation"); - tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; - - let post_state_account = seq_client - .get_account(account_id.to_string()) - .await - .unwrap() - .account; - assert_eq!(post_state_account.program_owner, data_changer.id()); - assert_eq!(post_state_account.balance, 0); - assert_eq!(post_state_account.data.as_ref(), &[0]); - assert_eq!(post_state_account.nonce, 0); - - info!("Success!"); - } - - #[nssa_integration_test] - pub async fn test_authenticated_transfer_initialize_function() { - info!("########## test initialize account for authenticated transfer ##########"); - let command = Command::Account(AccountSubcommand::New(NewSubcommand::Public { cci: None })); - let SubcommandReturnValue::RegisterAccount { account_id } = - wallet::cli::execute_subcommand(command).await.unwrap() - else { - panic!("Error creating account"); - }; - - let command = Command::AuthTransfer(AuthTransferSubcommand::Init { - account_id: make_public_account_input_from_str(&account_id.to_string()), - }); - wallet::cli::execute_subcommand(command).await.unwrap(); - - info!("Checking correct execution"); - let wallet_config = fetch_config().await.unwrap(); - let seq_client = SequencerClient::new(wallet_config.sequencer_addr.clone()).unwrap(); - let account = seq_client - .get_account(account_id.to_string()) - .await - .unwrap() - .account; - - let expected_program_owner = Program::authenticated_transfer_program().id(); - let expected_nonce = 1; - let expected_balance = 0; - - assert_eq!(account.program_owner, expected_program_owner); - assert_eq!(account.balance, expected_balance); - assert_eq!(account.nonce, expected_nonce); - assert!(account.data.is_empty()); - - info!("Success!"); - } - - #[nssa_integration_test] - pub async fn test_authenticated_transfer_initialize_function_private() { - info!("########## test initialize private account for authenticated transfer ##########"); - let command = - Command::Account(AccountSubcommand::New(NewSubcommand::Private { cci: None })); - let SubcommandReturnValue::RegisterAccount { account_id } = - wallet::cli::execute_subcommand(command).await.unwrap() - else { - panic!("Error creating account"); - }; - - let command = Command::AuthTransfer(AuthTransferSubcommand::Init { - account_id: make_private_account_input_from_str(&account_id.to_string()), - }); - wallet::cli::execute_subcommand(command).await.unwrap(); - - tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; - - info!("Checking correct execution"); - let command = Command::Account(AccountSubcommand::SyncPrivate {}); - wallet::cli::execute_subcommand(command).await.unwrap(); - - let wallet_config = fetch_config().await.unwrap(); - let seq_client = SequencerClient::new(wallet_config.sequencer_addr.clone()).unwrap(); - let wallet_storage = WalletCore::start_from_config_update_chain(wallet_config) - .await - .unwrap(); - - let new_commitment1 = wallet_storage - .get_private_account_commitment(&account_id) - .unwrap(); - assert!(verify_commitment_is_in_state(new_commitment1, &seq_client).await); - - let account = wallet_storage.get_account_private(&account_id).unwrap(); - - let expected_program_owner = Program::authenticated_transfer_program().id(); - let expected_balance = 0; - - assert_eq!(account.program_owner, expected_program_owner); - assert_eq!(account.balance, expected_balance); - assert!(account.data.is_empty()); - } - - #[nssa_integration_test] - pub async fn test_pinata_private_receiver() { - info!("########## test_pinata_private_receiver ##########"); - let pinata_account_id = PINATA_BASE58; - let pinata_prize = 150; - - let command = Command::Pinata(PinataProgramAgnosticSubcommand::Claim { - to: make_private_account_input_from_str(ACC_SENDER_PRIVATE), - }); - - let wallet_config = fetch_config().await.unwrap(); - - let seq_client = SequencerClient::new(wallet_config.sequencer_addr.clone()).unwrap(); - - let pinata_balance_pre = seq_client - .get_account_balance(pinata_account_id.to_string()) - .await - .unwrap() - .balance; - - let SubcommandReturnValue::PrivacyPreservingTransfer { tx_hash: _ } = - wallet::cli::execute_subcommand(command).await.unwrap() - else { - panic!("invalid subcommand return value"); - }; - - info!("Waiting for next block creation"); - tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; - - info!("Checking correct balance move"); - let pinata_balance_post = seq_client - .get_account_balance(pinata_account_id.to_string()) - .await - .unwrap() - .balance; - - let command = Command::Account(AccountSubcommand::SyncPrivate {}); - wallet::cli::execute_subcommand(command).await.unwrap(); - - let wallet_config = fetch_config().await.unwrap(); - let seq_client = SequencerClient::new(wallet_config.sequencer_addr.clone()).unwrap(); - let wallet_storage = WalletCore::start_from_config_update_chain(wallet_config) - .await - .unwrap(); - - let new_commitment1 = wallet_storage - .get_private_account_commitment(&ACC_SENDER_PRIVATE.parse().unwrap()) - .unwrap(); - assert!(verify_commitment_is_in_state(new_commitment1, &seq_client).await); - - assert_eq!(pinata_balance_post, pinata_balance_pre - pinata_prize); - - info!("Success!"); - } - - #[nssa_integration_test] - pub async fn test_pinata_private_receiver_new_account() { - info!("########## test_pinata_private_receiver_new_account ##########"); - let pinata_account_id = PINATA_BASE58; - let pinata_prize = 150; - - // Create new account for the token supply holder (private) - let SubcommandReturnValue::RegisterAccount { - account_id: winner_account_id, - } = wallet::cli::execute_subcommand(Command::Account(AccountSubcommand::New( - NewSubcommand::Private { cci: None }, - ))) - .await - .unwrap() - else { - panic!("invalid subcommand return value"); - }; - - let command = Command::Pinata(PinataProgramAgnosticSubcommand::Claim { - to: make_private_account_input_from_str(&winner_account_id.to_string()), - }); - - let wallet_config = fetch_config().await.unwrap(); - - let seq_client = SequencerClient::new(wallet_config.sequencer_addr.clone()).unwrap(); - - let pinata_balance_pre = seq_client - .get_account_balance(pinata_account_id.to_string()) - .await - .unwrap() - .balance; - - wallet::cli::execute_subcommand(command).await.unwrap(); - - info!("Waiting for next block creation"); - tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; - - info!("Checking correct balance move"); - let pinata_balance_post = seq_client - .get_account_balance(pinata_account_id.to_string()) - .await - .unwrap() - .balance; - - let wallet_config = fetch_config().await.unwrap(); - let seq_client = SequencerClient::new(wallet_config.sequencer_addr.clone()).unwrap(); - let wallet_storage = WalletCore::start_from_config_update_chain(wallet_config) - .await - .unwrap(); - - let new_commitment1 = wallet_storage - .get_private_account_commitment(&winner_account_id) - .unwrap(); - assert!(verify_commitment_is_in_state(new_commitment1, &seq_client).await); - - assert_eq!(pinata_balance_post, pinata_balance_pre - pinata_prize); - - info!("Success!"); - } - - #[nssa_integration_test] - pub async fn test_modify_config_fields() { - info!("########## test_modify_config_fields ##########"); - - let wallet_config = fetch_config().await.unwrap(); - let old_seq_poll_timeout_millis = wallet_config.seq_poll_timeout_millis; - - // Change config field - let command = Command::Config(ConfigSubcommand::Set { - key: "seq_poll_timeout_millis".to_string(), - value: "1000".to_string(), - }); - wallet::cli::execute_subcommand(command).await.unwrap(); - - let wallet_config = fetch_config().await.unwrap(); - - assert_eq!(wallet_config.seq_poll_timeout_millis, 1000); - - // Return how it was at the beginning - let command = Command::Config(ConfigSubcommand::Set { - key: "seq_poll_timeout_millis".to_string(), - value: old_seq_poll_timeout_millis.to_string(), - }); - wallet::cli::execute_subcommand(command).await.unwrap(); - - info!("Success!"); - } - - #[nssa_integration_test] - pub async fn test_keys_restoration() { - info!("########## test_keys_restoration ##########"); - let from: AccountId = ACC_SENDER_PRIVATE.parse().unwrap(); - - let command = Command::Account(AccountSubcommand::New(NewSubcommand::Private { - cci: Some(ChainIndex::root()), - })); - - let sub_ret = wallet::cli::execute_subcommand(command).await.unwrap(); - let SubcommandReturnValue::RegisterAccount { - account_id: to_account_id1, - } = sub_ret - else { - panic!("FAILED TO REGISTER ACCOUNT"); - }; - - let command = Command::Account(AccountSubcommand::New(NewSubcommand::Private { - cci: Some(ChainIndex::from_str("/0").unwrap()), - })); - - let sub_ret = wallet::cli::execute_subcommand(command).await.unwrap(); - let SubcommandReturnValue::RegisterAccount { - account_id: to_account_id2, - } = sub_ret - else { - panic!("FAILED TO REGISTER ACCOUNT"); - }; - - let command = Command::AuthTransfer(AuthTransferSubcommand::Send { - from: make_private_account_input_from_str(&from.to_string()), - to: Some(make_private_account_input_from_str( - &to_account_id1.to_string(), - )), - to_npk: None, - to_ipk: None, - amount: 100, - }); - - wallet::cli::execute_subcommand(command).await.unwrap(); - - let command = Command::AuthTransfer(AuthTransferSubcommand::Send { - from: make_private_account_input_from_str(&from.to_string()), - to: Some(make_private_account_input_from_str( - &to_account_id2.to_string(), - )), - to_npk: None, - to_ipk: None, - amount: 101, - }); - - wallet::cli::execute_subcommand(command).await.unwrap(); - - let from: AccountId = ACC_SENDER.parse().unwrap(); - - let command = Command::Account(AccountSubcommand::New(NewSubcommand::Public { - cci: Some(ChainIndex::root()), - })); - - let sub_ret = wallet::cli::execute_subcommand(command).await.unwrap(); - let SubcommandReturnValue::RegisterAccount { - account_id: to_account_id3, - } = sub_ret - else { - panic!("FAILED TO REGISTER ACCOUNT"); - }; - - let command = Command::Account(AccountSubcommand::New(NewSubcommand::Public { - cci: Some(ChainIndex::from_str("/0").unwrap()), - })); - - let sub_ret = wallet::cli::execute_subcommand(command).await.unwrap(); - let SubcommandReturnValue::RegisterAccount { - account_id: to_account_id4, - } = sub_ret - else { - panic!("FAILED TO REGISTER ACCOUNT"); - }; - - let command = Command::AuthTransfer(AuthTransferSubcommand::Send { - from: make_public_account_input_from_str(&from.to_string()), - to: Some(make_public_account_input_from_str( - &to_account_id3.to_string(), - )), - to_npk: None, - to_ipk: None, - amount: 102, - }); - - wallet::cli::execute_subcommand(command).await.unwrap(); - - let command = Command::AuthTransfer(AuthTransferSubcommand::Send { - from: make_public_account_input_from_str(&from.to_string()), - to: Some(make_public_account_input_from_str( - &to_account_id4.to_string(), - )), - to_npk: None, - to_ipk: None, - amount: 103, - }); - - wallet::cli::execute_subcommand(command).await.unwrap(); - - info!("########## PREPARATION END ##########"); - - wallet::cli::execute_keys_restoration("test_pass".to_string(), 10) - .await - .unwrap(); - - let wallet_config = fetch_config().await.unwrap(); - let wallet_storage = WalletCore::start_from_config_update_chain(wallet_config.clone()) - .await - .unwrap(); - - let acc1 = wallet_storage - .storage - .user_data - .private_key_tree - .get_node(to_account_id1) - .expect("Acc 1 should be restored"); - - let acc2 = wallet_storage - .storage - .user_data - .private_key_tree - .get_node(to_account_id2) - .expect("Acc 2 should be restored"); - - let _ = wallet_storage - .storage - .user_data - .public_key_tree - .get_node(to_account_id3) - .expect("Acc 3 should be restored"); - - let _ = wallet_storage - .storage - .user_data - .public_key_tree - .get_node(to_account_id4) - .expect("Acc 4 should be restored"); - - assert_eq!( - acc1.value.1.program_owner, - Program::authenticated_transfer_program().id() - ); - assert_eq!( - acc2.value.1.program_owner, - Program::authenticated_transfer_program().id() - ); - - assert_eq!(acc1.value.1.balance, 100); - assert_eq!(acc2.value.1.balance, 101); - - info!("########## TREE CHECKS END ##########"); - - let command = Command::AuthTransfer(AuthTransferSubcommand::Send { - from: make_private_account_input_from_str(&to_account_id1.to_string()), - to: Some(make_private_account_input_from_str( - &to_account_id2.to_string(), - )), - to_npk: None, - to_ipk: None, - amount: 10, - }); - - wallet::cli::execute_subcommand(command).await.unwrap(); - - let command = Command::AuthTransfer(AuthTransferSubcommand::Send { - from: make_public_account_input_from_str(&to_account_id3.to_string()), - to: Some(make_public_account_input_from_str( - &to_account_id4.to_string(), - )), - to_npk: None, - to_ipk: None, - amount: 11, - }); - - wallet::cli::execute_subcommand(command).await.unwrap(); - - let wallet_config = fetch_config().await.unwrap(); - let seq_client = SequencerClient::new(wallet_config.sequencer_addr.clone()).unwrap(); - let wallet_storage = WalletCore::start_from_config_update_chain(wallet_config.clone()) - .await - .unwrap(); - - let comm1 = wallet_storage - .get_private_account_commitment(&to_account_id1) - .expect("Acc 1 commitment should exist"); - let comm2 = wallet_storage - .get_private_account_commitment(&to_account_id2) - .expect("Acc 2 commitment should exist"); - - assert!(verify_commitment_is_in_state(comm1, &seq_client).await); - assert!(verify_commitment_is_in_state(comm2, &seq_client).await); - - let acc3 = seq_client - .get_account_balance(to_account_id3.to_string()) - .await - .expect("Acc 3 must be present in public state"); - let acc4 = seq_client - .get_account_balance(to_account_id4.to_string()) - .await - .expect("Acc 4 must be present in public state"); - - assert_eq!(acc3.balance, 91); - assert_eq!(acc4.balance, 114); - - info!("Success!"); - } - - #[nssa_integration_test] - pub async fn test_amm_public() { - info!("########## test_amm_public ##########"); - let wallet_config = fetch_config().await.unwrap(); - - // Create new account for the token definition - let SubcommandReturnValue::RegisterAccount { - account_id: definition_account_id_1, - } = wallet::cli::execute_subcommand(Command::Account(AccountSubcommand::New( - NewSubcommand::Public { cci: None }, - ))) - .await - .unwrap() - else { - panic!("invalid subcommand return value"); - }; - // Create new account for the token supply holder - let SubcommandReturnValue::RegisterAccount { - account_id: supply_account_id_1, - } = wallet::cli::execute_subcommand(Command::Account(AccountSubcommand::New( - NewSubcommand::Public { cci: None }, - ))) - .await - .unwrap() - else { - panic!("invalid subcommand return value"); - }; - // Create new account for receiving a token transaction - let SubcommandReturnValue::RegisterAccount { - account_id: recipient_account_id_1, - } = wallet::cli::execute_subcommand(Command::Account(AccountSubcommand::New( - NewSubcommand::Public { cci: None }, - ))) - .await - .unwrap() - else { - panic!("invalid subcommand return value"); - }; - // Create new account for the token definition - let SubcommandReturnValue::RegisterAccount { - account_id: definition_account_id_2, - } = wallet::cli::execute_subcommand(Command::Account(AccountSubcommand::New( - NewSubcommand::Public { cci: None }, - ))) - .await - .unwrap() - else { - panic!("invalid subcommand return value"); - }; - // Create new account for the token supply holder - let SubcommandReturnValue::RegisterAccount { - account_id: supply_account_id_2, - } = wallet::cli::execute_subcommand(Command::Account(AccountSubcommand::New( - NewSubcommand::Public { cci: None }, - ))) - .await - .unwrap() - else { - panic!("invalid subcommand return value"); - }; - // Create new account for receiving a token transaction - let SubcommandReturnValue::RegisterAccount { - account_id: recipient_account_id_2, - } = wallet::cli::execute_subcommand(Command::Account(AccountSubcommand::New( - NewSubcommand::Public { cci: None }, - ))) - .await - .unwrap() - else { - panic!("invalid subcommand return value"); - }; - - // Create new token - let subcommand = TokenProgramAgnosticSubcommand::New { - definition_account_id: make_public_account_input_from_str( - &definition_account_id_1.to_string(), - ), - supply_account_id: make_public_account_input_from_str(&supply_account_id_1.to_string()), - name: "A NAM1".to_string(), - total_supply: 37, - }; - wallet::cli::execute_subcommand(Command::Token(subcommand)) - .await - .unwrap(); - info!("Waiting for next block creation"); - tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; - - // Transfer 7 tokens from `supply_acc` to the account at account_id `recipient_account_id_1` - let subcommand = TokenProgramAgnosticSubcommand::Send { - from: make_public_account_input_from_str(&supply_account_id_1.to_string()), - to: Some(make_public_account_input_from_str( - &recipient_account_id_1.to_string(), - )), - to_npk: None, - to_ipk: None, - amount: 7, - }; - - wallet::cli::execute_subcommand(Command::Token(subcommand)) - .await - .unwrap(); - info!("Waiting for next block creation"); - tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; - - // Create new token - let subcommand = TokenProgramAgnosticSubcommand::New { - definition_account_id: make_public_account_input_from_str( - &definition_account_id_2.to_string(), - ), - supply_account_id: make_public_account_input_from_str(&supply_account_id_2.to_string()), - name: "A NAM2".to_string(), - total_supply: 37, - }; - wallet::cli::execute_subcommand(Command::Token(subcommand)) - .await - .unwrap(); - info!("Waiting for next block creation"); - tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; - - let seq_client = SequencerClient::new(wallet_config.sequencer_addr.clone()).unwrap(); - - // Transfer 7 tokens from `supply_acc` to the account at account_id `recipient_account_id_1` - let subcommand = TokenProgramAgnosticSubcommand::Send { - from: make_public_account_input_from_str(&supply_account_id_2.to_string()), - to: Some(make_public_account_input_from_str( - &recipient_account_id_2.to_string(), - )), - to_npk: None, - to_ipk: None, - amount: 7, - }; - - wallet::cli::execute_subcommand(Command::Token(subcommand)) - .await - .unwrap(); - info!("Waiting for next block creation"); - tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; - - info!("=================== SETUP FINISHED ==============="); - - // Create new AMM - - // Setup accounts - // Create new account for the user holding lp - let SubcommandReturnValue::RegisterAccount { - account_id: user_holding_lp, - } = wallet::cli::execute_subcommand(Command::Account(AccountSubcommand::New( - NewSubcommand::Public { cci: None }, - ))) - .await - .unwrap() - else { - panic!("invalid subcommand return value"); - }; - - // Send creation tx - let subcommand = AmmProgramAgnosticSubcommand::New { - user_holding_a: make_public_account_input_from_str(&recipient_account_id_1.to_string()), - user_holding_b: make_public_account_input_from_str(&recipient_account_id_2.to_string()), - user_holding_lp: make_public_account_input_from_str(&user_holding_lp.to_string()), - balance_a: 3, - balance_b: 3, - }; - - wallet::cli::execute_subcommand(Command::AMM(subcommand)) - .await - .unwrap(); - info!("Waiting for next block creation"); - tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; - - let user_holding_a_acc = seq_client - .get_account(recipient_account_id_1.to_string()) - .await - .unwrap() - .account; - - let user_holding_b_acc = seq_client - .get_account(recipient_account_id_2.to_string()) - .await - .unwrap() - .account; - - let user_holding_lp_acc = seq_client - .get_account(user_holding_lp.to_string()) - .await - .unwrap() - .account; - - assert_eq!( - u128::from_le_bytes(user_holding_a_acc.data[33..].try_into().unwrap()), - 4 - ); - - assert_eq!( - u128::from_le_bytes(user_holding_b_acc.data[33..].try_into().unwrap()), - 4 - ); - - assert_eq!( - u128::from_le_bytes(user_holding_lp_acc.data[33..].try_into().unwrap()), - 3 - ); - - info!("=================== AMM DEFINITION FINISHED ==============="); - - // Make swap - - let subcommand = AmmProgramAgnosticSubcommand::Swap { - user_holding_a: make_public_account_input_from_str(&recipient_account_id_1.to_string()), - user_holding_b: make_public_account_input_from_str(&recipient_account_id_2.to_string()), - amount_in: 2, - min_amount_out: 1, - token_definition: definition_account_id_1.to_string(), - }; - - wallet::cli::execute_subcommand(Command::AMM(subcommand)) - .await - .unwrap(); - info!("Waiting for next block creation"); - tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; - - let user_holding_a_acc = seq_client - .get_account(recipient_account_id_1.to_string()) - .await - .unwrap() - .account; - - let user_holding_b_acc = seq_client - .get_account(recipient_account_id_2.to_string()) - .await - .unwrap() - .account; - - let user_holding_lp_acc = seq_client - .get_account(user_holding_lp.to_string()) - .await - .unwrap() - .account; - - assert_eq!( - u128::from_le_bytes(user_holding_a_acc.data[33..].try_into().unwrap()), - 2 - ); - - assert_eq!( - u128::from_le_bytes(user_holding_b_acc.data[33..].try_into().unwrap()), - 5 - ); - - assert_eq!( - u128::from_le_bytes(user_holding_lp_acc.data[33..].try_into().unwrap()), - 3 - ); - - info!("=================== FIRST SWAP FINISHED ==============="); - - // Make swap - - let subcommand = AmmProgramAgnosticSubcommand::Swap { - user_holding_a: make_public_account_input_from_str(&recipient_account_id_1.to_string()), - user_holding_b: make_public_account_input_from_str(&recipient_account_id_2.to_string()), - amount_in: 2, - min_amount_out: 1, - token_definition: definition_account_id_2.to_string(), - }; - - wallet::cli::execute_subcommand(Command::AMM(subcommand)) - .await - .unwrap(); - info!("Waiting for next block creation"); - tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; - - let user_holding_a_acc = seq_client - .get_account(recipient_account_id_1.to_string()) - .await - .unwrap() - .account; - - let user_holding_b_acc = seq_client - .get_account(recipient_account_id_2.to_string()) - .await - .unwrap() - .account; - - let user_holding_lp_acc = seq_client - .get_account(user_holding_lp.to_string()) - .await - .unwrap() - .account; - - assert_eq!( - u128::from_le_bytes(user_holding_a_acc.data[33..].try_into().unwrap()), - 4 - ); - - assert_eq!( - u128::from_le_bytes(user_holding_b_acc.data[33..].try_into().unwrap()), - 3 - ); - - assert_eq!( - u128::from_le_bytes(user_holding_lp_acc.data[33..].try_into().unwrap()), - 3 - ); - - info!("=================== SECOND SWAP FINISHED ==============="); - - // Add liquidity - - let subcommand = AmmProgramAgnosticSubcommand::AddLiquidity { - user_holding_a: make_public_account_input_from_str(&recipient_account_id_1.to_string()), - user_holding_b: make_public_account_input_from_str(&recipient_account_id_2.to_string()), - user_holding_lp: make_public_account_input_from_str(&user_holding_lp.to_string()), - min_amount_lp: 1, - max_amount_a: 2, - max_amount_b: 2, - }; - - wallet::cli::execute_subcommand(Command::AMM(subcommand)) - .await - .unwrap(); - info!("Waiting for next block creation"); - tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; - - let user_holding_a_acc = seq_client - .get_account(recipient_account_id_1.to_string()) - .await - .unwrap() - .account; - - let user_holding_b_acc = seq_client - .get_account(recipient_account_id_2.to_string()) - .await - .unwrap() - .account; - - let user_holding_lp_acc = seq_client - .get_account(user_holding_lp.to_string()) - .await - .unwrap() - .account; - - assert_eq!( - u128::from_le_bytes(user_holding_a_acc.data[33..].try_into().unwrap()), - 3 - ); - - assert_eq!( - u128::from_le_bytes(user_holding_b_acc.data[33..].try_into().unwrap()), - 1 - ); - - assert_eq!( - u128::from_le_bytes(user_holding_lp_acc.data[33..].try_into().unwrap()), - 4 - ); - - info!("=================== ADD LIQ FINISHED ==============="); - - // Remove liquidity - - let subcommand = AmmProgramAgnosticSubcommand::RemoveLiquidity { - user_holding_a: make_public_account_input_from_str(&recipient_account_id_1.to_string()), - user_holding_b: make_public_account_input_from_str(&recipient_account_id_2.to_string()), - user_holding_lp: make_public_account_input_from_str(&user_holding_lp.to_string()), - balance_lp: 2, - min_amount_a: 1, - min_amount_b: 1, - }; - - wallet::cli::execute_subcommand(Command::AMM(subcommand)) - .await - .unwrap(); - info!("Waiting for next block creation"); - tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; - - let user_holding_a_acc = seq_client - .get_account(recipient_account_id_1.to_string()) - .await - .unwrap() - .account; - - let user_holding_b_acc = seq_client - .get_account(recipient_account_id_2.to_string()) - .await - .unwrap() - .account; - - let user_holding_lp_acc = seq_client - .get_account(user_holding_lp.to_string()) - .await - .unwrap() - .account; - - assert_eq!( - u128::from_le_bytes(user_holding_a_acc.data[33..].try_into().unwrap()), - 5 - ); - - assert_eq!( - u128::from_le_bytes(user_holding_b_acc.data[33..].try_into().unwrap()), - 4 - ); - - assert_eq!( - u128::from_le_bytes(user_holding_lp_acc.data[33..].try_into().unwrap()), - 2 - ); - - info!("Success!"); - } - - println!("{function_map:#?}"); - - function_map -} - -#[allow(clippy::type_complexity)] -async fn pre_tps_test( - test: &TpsTestManager, -) -> Result<(ServerHandle, JoinHandle>, TempDir)> { - info!("Generating tps test config"); - let mut sequencer_config = test.generate_tps_test_config(); - info!("Done"); - - let temp_dir_sequencer = replace_home_dir_with_temp_dir_in_configs(&mut sequencer_config); - - let (seq_http_server_handle, sequencer_loop_handle) = - startup_sequencer(sequencer_config).await?; - - Ok(( - seq_http_server_handle, - sequencer_loop_handle, - temp_dir_sequencer, - )) -} - -pub async fn tps_test() { - let num_transactions = 300 * 5; - let target_tps = 12; - let tps_test = TpsTestManager::new(target_tps, num_transactions); - - let target_time = tps_test.target_time(); - info!("Target time: {:?} seconds", target_time.as_secs()); - let res = pre_tps_test(&tps_test).await.unwrap(); - - let wallet_config = fetch_config().await.unwrap(); - let seq_client = SequencerClient::new(wallet_config.sequencer_addr.clone()).unwrap(); - - info!("TPS test begin"); - let txs = tps_test.build_public_txs(); - let now = Instant::now(); - - let mut tx_hashes = vec![]; - for (i, tx) in txs.into_iter().enumerate() { - let tx_hash = seq_client.send_tx_public(tx).await.unwrap().tx_hash; - info!("Sent tx {i}"); - tx_hashes.push(tx_hash); - } - - for (i, tx_hash) in tx_hashes.iter().enumerate() { - loop { - if now.elapsed().as_millis() > target_time.as_millis() { - panic!("TPS test failed by timeout"); - } - - let tx_obj = seq_client - .get_transaction_by_hash(tx_hash.clone()) - .await - .inspect_err(|err| { - log::warn!( - "Failed to get transaction by hash {tx_hash:#?} with error: {err:#?}" - ) - }); - - if let Ok(tx_obj) = tx_obj - && tx_obj.transaction.is_some() - { - info!("Found tx {i} with hash {tx_hash}"); - break; - } - } - } - let time_elapsed = now.elapsed().as_secs(); - - info!("TPS test finished successfully"); - info!("Target TPS: {}", target_tps); - info!( - "Processed {} transactions in {}s", - tx_hashes.len(), - time_elapsed - ); - info!("Target time: {:?}s", target_time.as_secs()); - - post_test(res).await; -} diff --git a/integration_tests/tests/account.rs b/integration_tests/tests/account.rs new file mode 100644 index 00000000..e5e700dd --- /dev/null +++ b/integration_tests/tests/account.rs @@ -0,0 +1,28 @@ +use anyhow::Result; +use integration_tests::{ACC_SENDER, TestContext}; +use log::info; +use nssa::program::Program; +use tokio::test; + +#[test] +async fn get_existing_account() -> Result<()> { + let ctx = TestContext::new().await?; + + let account = ctx + .sequencer_client() + .get_account(ACC_SENDER.to_string()) + .await? + .account; + + assert_eq!( + account.program_owner, + Program::authenticated_transfer_program().id() + ); + assert_eq!(account.balance, 10000); + assert!(account.data.is_empty()); + assert_eq!(account.nonce, 0); + + info!("Successfully retrieved account with correct details"); + + Ok(()) +} diff --git a/integration_tests/tests/amm.rs b/integration_tests/tests/amm.rs new file mode 100644 index 00000000..073cd03d --- /dev/null +++ b/integration_tests/tests/amm.rs @@ -0,0 +1,405 @@ +use std::time::Duration; + +use anyhow::Result; +use integration_tests::{TIME_TO_WAIT_FOR_BLOCK_SECONDS, TestContext, format_public_account_id}; +use log::info; +use tokio::test; +use wallet::cli::{ + Command, SubcommandReturnValue, + account::{AccountSubcommand, NewSubcommand}, + programs::{amm::AmmProgramAgnosticSubcommand, token::TokenProgramAgnosticSubcommand}, +}; + +#[test] +async fn amm_public() -> Result<()> { + let mut ctx = TestContext::new().await?; + + // Create new account for the token definition + let SubcommandReturnValue::RegisterAccount { + account_id: definition_account_id_1, + } = wallet::cli::execute_subcommand( + ctx.wallet_mut(), + Command::Account(AccountSubcommand::New(NewSubcommand::Public { cci: None })), + ) + .await? + else { + anyhow::bail!("Expected RegisterAccount return value"); + }; + + // Create new account for the token supply holder + let SubcommandReturnValue::RegisterAccount { + account_id: supply_account_id_1, + } = wallet::cli::execute_subcommand( + ctx.wallet_mut(), + Command::Account(AccountSubcommand::New(NewSubcommand::Public { cci: None })), + ) + .await? + else { + anyhow::bail!("Expected RegisterAccount return value"); + }; + + // Create new account for receiving a token transaction + let SubcommandReturnValue::RegisterAccount { + account_id: recipient_account_id_1, + } = wallet::cli::execute_subcommand( + ctx.wallet_mut(), + Command::Account(AccountSubcommand::New(NewSubcommand::Public { cci: None })), + ) + .await? + else { + anyhow::bail!("Expected RegisterAccount return value"); + }; + + // Create new account for the token definition + let SubcommandReturnValue::RegisterAccount { + account_id: definition_account_id_2, + } = wallet::cli::execute_subcommand( + ctx.wallet_mut(), + Command::Account(AccountSubcommand::New(NewSubcommand::Public { cci: None })), + ) + .await? + else { + anyhow::bail!("Expected RegisterAccount return value"); + }; + + // Create new account for the token supply holder + let SubcommandReturnValue::RegisterAccount { + account_id: supply_account_id_2, + } = wallet::cli::execute_subcommand( + ctx.wallet_mut(), + Command::Account(AccountSubcommand::New(NewSubcommand::Public { cci: None })), + ) + .await? + else { + anyhow::bail!("Expected RegisterAccount return value"); + }; + + // Create new account for receiving a token transaction + let SubcommandReturnValue::RegisterAccount { + account_id: recipient_account_id_2, + } = wallet::cli::execute_subcommand( + ctx.wallet_mut(), + Command::Account(AccountSubcommand::New(NewSubcommand::Public { cci: None })), + ) + .await? + else { + anyhow::bail!("Expected RegisterAccount return value"); + }; + + // Create new token + let subcommand = TokenProgramAgnosticSubcommand::New { + definition_account_id: format_public_account_id(&definition_account_id_1.to_string()), + supply_account_id: format_public_account_id(&supply_account_id_1.to_string()), + name: "A NAM1".to_string(), + total_supply: 37, + }; + wallet::cli::execute_subcommand(ctx.wallet_mut(), Command::Token(subcommand)).await?; + info!("Waiting for next block creation"); + tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; + + // Transfer 7 tokens from `supply_acc` to the account at account_id `recipient_account_id_1` + let subcommand = TokenProgramAgnosticSubcommand::Send { + from: format_public_account_id(&supply_account_id_1.to_string()), + to: Some(format_public_account_id( + &recipient_account_id_1.to_string(), + )), + to_npk: None, + to_ipk: None, + amount: 7, + }; + + wallet::cli::execute_subcommand(ctx.wallet_mut(), Command::Token(subcommand)).await?; + info!("Waiting for next block creation"); + tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; + + // Create new token + let subcommand = TokenProgramAgnosticSubcommand::New { + definition_account_id: format_public_account_id(&definition_account_id_2.to_string()), + supply_account_id: format_public_account_id(&supply_account_id_2.to_string()), + name: "A NAM2".to_string(), + total_supply: 37, + }; + wallet::cli::execute_subcommand(ctx.wallet_mut(), Command::Token(subcommand)).await?; + info!("Waiting for next block creation"); + tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; + + // Transfer 7 tokens from `supply_acc` to the account at account_id `recipient_account_id_2` + let subcommand = TokenProgramAgnosticSubcommand::Send { + from: format_public_account_id(&supply_account_id_2.to_string()), + to: Some(format_public_account_id( + &recipient_account_id_2.to_string(), + )), + to_npk: None, + to_ipk: None, + amount: 7, + }; + + wallet::cli::execute_subcommand(ctx.wallet_mut(), Command::Token(subcommand)).await?; + info!("Waiting for next block creation"); + tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; + + info!("=================== SETUP FINISHED ==============="); + + // Create new AMM + + // Setup accounts + // Create new account for the user holding lp + let SubcommandReturnValue::RegisterAccount { + account_id: user_holding_lp, + } = wallet::cli::execute_subcommand( + ctx.wallet_mut(), + Command::Account(AccountSubcommand::New(NewSubcommand::Public { cci: None })), + ) + .await? + else { + anyhow::bail!("Expected RegisterAccount return value"); + }; + + // Send creation tx + let subcommand = AmmProgramAgnosticSubcommand::New { + user_holding_a: format_public_account_id(&recipient_account_id_1.to_string()), + user_holding_b: format_public_account_id(&recipient_account_id_2.to_string()), + user_holding_lp: format_public_account_id(&user_holding_lp.to_string()), + balance_a: 3, + balance_b: 3, + }; + + wallet::cli::execute_subcommand(ctx.wallet_mut(), Command::AMM(subcommand)).await?; + info!("Waiting for next block creation"); + tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; + + let user_holding_a_acc = ctx + .sequencer_client() + .get_account(recipient_account_id_1.to_string()) + .await? + .account; + + let user_holding_b_acc = ctx + .sequencer_client() + .get_account(recipient_account_id_2.to_string()) + .await? + .account; + + let user_holding_lp_acc = ctx + .sequencer_client() + .get_account(user_holding_lp.to_string()) + .await? + .account; + + assert_eq!( + u128::from_le_bytes(user_holding_a_acc.data[33..].try_into().unwrap()), + 4 + ); + + assert_eq!( + u128::from_le_bytes(user_holding_b_acc.data[33..].try_into().unwrap()), + 4 + ); + + assert_eq!( + u128::from_le_bytes(user_holding_lp_acc.data[33..].try_into().unwrap()), + 3 + ); + + info!("=================== AMM DEFINITION FINISHED ==============="); + + // Make swap + + let subcommand = AmmProgramAgnosticSubcommand::Swap { + user_holding_a: format_public_account_id(&recipient_account_id_1.to_string()), + user_holding_b: format_public_account_id(&recipient_account_id_2.to_string()), + amount_in: 2, + min_amount_out: 1, + token_definition: definition_account_id_1.to_string(), + }; + + wallet::cli::execute_subcommand(ctx.wallet_mut(), Command::AMM(subcommand)).await?; + info!("Waiting for next block creation"); + tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; + + let user_holding_a_acc = ctx + .sequencer_client() + .get_account(recipient_account_id_1.to_string()) + .await? + .account; + + let user_holding_b_acc = ctx + .sequencer_client() + .get_account(recipient_account_id_2.to_string()) + .await? + .account; + + let user_holding_lp_acc = ctx + .sequencer_client() + .get_account(user_holding_lp.to_string()) + .await? + .account; + + assert_eq!( + u128::from_le_bytes(user_holding_a_acc.data[33..].try_into().unwrap()), + 2 + ); + + assert_eq!( + u128::from_le_bytes(user_holding_b_acc.data[33..].try_into().unwrap()), + 5 + ); + + assert_eq!( + u128::from_le_bytes(user_holding_lp_acc.data[33..].try_into().unwrap()), + 3 + ); + + info!("=================== FIRST SWAP FINISHED ==============="); + + // Make swap + + let subcommand = AmmProgramAgnosticSubcommand::Swap { + user_holding_a: format_public_account_id(&recipient_account_id_1.to_string()), + user_holding_b: format_public_account_id(&recipient_account_id_2.to_string()), + amount_in: 2, + min_amount_out: 1, + token_definition: definition_account_id_2.to_string(), + }; + + wallet::cli::execute_subcommand(ctx.wallet_mut(), Command::AMM(subcommand)).await?; + info!("Waiting for next block creation"); + tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; + + let user_holding_a_acc = ctx + .sequencer_client() + .get_account(recipient_account_id_1.to_string()) + .await? + .account; + + let user_holding_b_acc = ctx + .sequencer_client() + .get_account(recipient_account_id_2.to_string()) + .await? + .account; + + let user_holding_lp_acc = ctx + .sequencer_client() + .get_account(user_holding_lp.to_string()) + .await? + .account; + + assert_eq!( + u128::from_le_bytes(user_holding_a_acc.data[33..].try_into().unwrap()), + 4 + ); + + assert_eq!( + u128::from_le_bytes(user_holding_b_acc.data[33..].try_into().unwrap()), + 3 + ); + + assert_eq!( + u128::from_le_bytes(user_holding_lp_acc.data[33..].try_into().unwrap()), + 3 + ); + + info!("=================== SECOND SWAP FINISHED ==============="); + + // Add liquidity + + let subcommand = AmmProgramAgnosticSubcommand::AddLiquidity { + user_holding_a: format_public_account_id(&recipient_account_id_1.to_string()), + user_holding_b: format_public_account_id(&recipient_account_id_2.to_string()), + user_holding_lp: format_public_account_id(&user_holding_lp.to_string()), + min_amount_lp: 1, + max_amount_a: 2, + max_amount_b: 2, + }; + + wallet::cli::execute_subcommand(ctx.wallet_mut(), Command::AMM(subcommand)).await?; + info!("Waiting for next block creation"); + tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; + + let user_holding_a_acc = ctx + .sequencer_client() + .get_account(recipient_account_id_1.to_string()) + .await? + .account; + + let user_holding_b_acc = ctx + .sequencer_client() + .get_account(recipient_account_id_2.to_string()) + .await? + .account; + + let user_holding_lp_acc = ctx + .sequencer_client() + .get_account(user_holding_lp.to_string()) + .await? + .account; + + assert_eq!( + u128::from_le_bytes(user_holding_a_acc.data[33..].try_into().unwrap()), + 3 + ); + + assert_eq!( + u128::from_le_bytes(user_holding_b_acc.data[33..].try_into().unwrap()), + 1 + ); + + assert_eq!( + u128::from_le_bytes(user_holding_lp_acc.data[33..].try_into().unwrap()), + 4 + ); + + info!("=================== ADD LIQ FINISHED ==============="); + + // Remove liquidity + + let subcommand = AmmProgramAgnosticSubcommand::RemoveLiquidity { + user_holding_a: format_public_account_id(&recipient_account_id_1.to_string()), + user_holding_b: format_public_account_id(&recipient_account_id_2.to_string()), + user_holding_lp: format_public_account_id(&user_holding_lp.to_string()), + balance_lp: 2, + min_amount_a: 1, + min_amount_b: 1, + }; + + wallet::cli::execute_subcommand(ctx.wallet_mut(), Command::AMM(subcommand)).await?; + info!("Waiting for next block creation"); + tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; + + let user_holding_a_acc = ctx + .sequencer_client() + .get_account(recipient_account_id_1.to_string()) + .await? + .account; + + let user_holding_b_acc = ctx + .sequencer_client() + .get_account(recipient_account_id_2.to_string()) + .await? + .account; + + let user_holding_lp_acc = ctx + .sequencer_client() + .get_account(user_holding_lp.to_string()) + .await? + .account; + + assert_eq!( + u128::from_le_bytes(user_holding_a_acc.data[33..].try_into().unwrap()), + 5 + ); + + assert_eq!( + u128::from_le_bytes(user_holding_b_acc.data[33..].try_into().unwrap()), + 4 + ); + + assert_eq!( + u128::from_le_bytes(user_holding_lp_acc.data[33..].try_into().unwrap()), + 2 + ); + + info!("Success!"); + + Ok(()) +} diff --git a/integration_tests/tests/auth_transfer/main.rs b/integration_tests/tests/auth_transfer/main.rs new file mode 100644 index 00000000..c97008bd --- /dev/null +++ b/integration_tests/tests/auth_transfer/main.rs @@ -0,0 +1,2 @@ +mod private; +mod public; diff --git a/integration_tests/tests/auth_transfer/private.rs b/integration_tests/tests/auth_transfer/private.rs new file mode 100644 index 00000000..4674ed0b --- /dev/null +++ b/integration_tests/tests/auth_transfer/private.rs @@ -0,0 +1,417 @@ +use std::time::Duration; + +use anyhow::{Context as _, Result}; +use integration_tests::{ + ACC_RECEIVER, ACC_RECEIVER_PRIVATE, ACC_SENDER, ACC_SENDER_PRIVATE, + TIME_TO_WAIT_FOR_BLOCK_SECONDS, TestContext, fetch_privacy_preserving_tx, + format_private_account_id, format_public_account_id, verify_commitment_is_in_state, +}; +use log::info; +use nssa::{AccountId, program::Program}; +use nssa_core::{NullifierPublicKey, encryption::shared_key_derivation::Secp256k1Point}; +use tokio::test; +use wallet::cli::{ + Command, SubcommandReturnValue, + account::{AccountSubcommand, NewSubcommand}, + programs::native_token_transfer::AuthTransferSubcommand, +}; + +#[test] +async fn private_transfer_to_owned_account() -> Result<()> { + let mut ctx = TestContext::new().await?; + + let from: AccountId = ACC_SENDER_PRIVATE.parse()?; + let to: AccountId = ACC_RECEIVER_PRIVATE.parse()?; + + let command = Command::AuthTransfer(AuthTransferSubcommand::Send { + from: format_private_account_id(&from.to_string()), + to: Some(format_private_account_id(&to.to_string())), + to_npk: None, + to_ipk: None, + amount: 100, + }); + + wallet::cli::execute_subcommand(ctx.wallet_mut(), command).await?; + + info!("Waiting for next block creation"); + tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; + + let new_commitment1 = ctx + .wallet() + .get_private_account_commitment(&from) + .context("Failed to get private account commitment for sender")?; + assert!(verify_commitment_is_in_state(new_commitment1, ctx.sequencer_client()).await); + + let new_commitment2 = ctx + .wallet() + .get_private_account_commitment(&to) + .context("Failed to get private account commitment for receiver")?; + assert!(verify_commitment_is_in_state(new_commitment2, ctx.sequencer_client()).await); + + info!("Successfully transferred privately to owned account"); + + Ok(()) +} + +#[test] +async fn private_transfer_to_foreign_account() -> Result<()> { + let mut ctx = TestContext::new().await?; + + let from: AccountId = ACC_SENDER_PRIVATE.parse()?; + let to_npk = NullifierPublicKey([42; 32]); + let to_npk_string = hex::encode(to_npk.0); + let to_ipk = Secp256k1Point::from_scalar(to_npk.0); + + let command = Command::AuthTransfer(AuthTransferSubcommand::Send { + from: format_private_account_id(&from.to_string()), + to: None, + to_npk: Some(to_npk_string), + to_ipk: Some(hex::encode(to_ipk.0)), + amount: 100, + }); + + let result = wallet::cli::execute_subcommand(ctx.wallet_mut(), command).await?; + let SubcommandReturnValue::PrivacyPreservingTransfer { tx_hash } = result else { + anyhow::bail!("Expected PrivacyPreservingTransfer return value"); + }; + + info!("Waiting for next block creation"); + tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; + + let new_commitment1 = ctx + .wallet() + .get_private_account_commitment(&from) + .context("Failed to get private account commitment for sender")?; + + let tx = fetch_privacy_preserving_tx(ctx.sequencer_client(), tx_hash.clone()).await; + assert_eq!(tx.message.new_commitments[0], new_commitment1); + + assert_eq!(tx.message.new_commitments.len(), 2); + for commitment in tx.message.new_commitments.into_iter() { + assert!(verify_commitment_is_in_state(commitment, ctx.sequencer_client()).await); + } + + info!("Successfully transferred privately to foreign account"); + + Ok(()) +} + +#[test] +async fn deshielded_transfer_to_public_account() -> Result<()> { + let mut ctx = TestContext::new().await?; + + let from: AccountId = ACC_SENDER_PRIVATE.parse()?; + let to: AccountId = ACC_RECEIVER.parse()?; + + // Check initial balance of the private sender + let from_acc = ctx + .wallet() + .get_account_private(&from) + .context("Failed to get sender's private account")?; + assert_eq!(from_acc.balance, 10000); + + let command = Command::AuthTransfer(AuthTransferSubcommand::Send { + from: format_private_account_id(&from.to_string()), + to: Some(format_public_account_id(&to.to_string())), + to_npk: None, + to_ipk: None, + amount: 100, + }); + + wallet::cli::execute_subcommand(ctx.wallet_mut(), command).await?; + + info!("Waiting for next block creation"); + tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; + + let from_acc = ctx + .wallet() + .get_account_private(&from) + .context("Failed to get sender's private account")?; + let new_commitment = ctx + .wallet() + .get_private_account_commitment(&from) + .context("Failed to get private account commitment")?; + assert!(verify_commitment_is_in_state(new_commitment, ctx.sequencer_client()).await); + + let acc_2_balance = ctx + .sequencer_client() + .get_account_balance(to.to_string()) + .await?; + + assert_eq!(from_acc.balance, 9900); + assert_eq!(acc_2_balance.balance, 20100); + + info!("Successfully deshielded transfer to public account"); + + Ok(()) +} + +#[test] +async fn private_transfer_to_owned_account_using_claiming_path() -> Result<()> { + let mut ctx = TestContext::new().await?; + + let from: AccountId = ACC_SENDER_PRIVATE.parse()?; + + // Create a new private account + let command = Command::Account(AccountSubcommand::New(NewSubcommand::Private { cci: None })); + + let sub_ret = wallet::cli::execute_subcommand(ctx.wallet_mut(), command).await?; + let SubcommandReturnValue::RegisterAccount { + account_id: to_account_id, + } = sub_ret + else { + anyhow::bail!("Expected RegisterAccount return value"); + }; + + // Get the keys for the newly created account + let (to_keys, _) = ctx + .wallet() + .storage() + .user_data + .get_private_account(&to_account_id) + .cloned() + .context("Failed to get private account")?; + + // Send to this account using claiming path (using npk and ipk instead of account ID) + let command = Command::AuthTransfer(AuthTransferSubcommand::Send { + from: format_private_account_id(&from.to_string()), + to: None, + to_npk: Some(hex::encode(to_keys.nullifer_public_key.0)), + to_ipk: Some(hex::encode(to_keys.incoming_viewing_public_key.0)), + amount: 100, + }); + + let sub_ret = wallet::cli::execute_subcommand(ctx.wallet_mut(), command).await?; + let SubcommandReturnValue::PrivacyPreservingTransfer { tx_hash } = sub_ret else { + anyhow::bail!("Expected PrivacyPreservingTransfer return value"); + }; + + let tx = fetch_privacy_preserving_tx(ctx.sequencer_client(), tx_hash.clone()).await; + + // Sync the wallet to claim the new account + let command = Command::Account(AccountSubcommand::SyncPrivate {}); + wallet::cli::execute_subcommand(ctx.wallet_mut(), command).await?; + + let new_commitment1 = ctx + .wallet() + .get_private_account_commitment(&from) + .context("Failed to get private account commitment for sender")?; + assert_eq!(tx.message.new_commitments[0], new_commitment1); + + assert_eq!(tx.message.new_commitments.len(), 2); + for commitment in tx.message.new_commitments.into_iter() { + assert!(verify_commitment_is_in_state(commitment, ctx.sequencer_client()).await); + } + + let to_res_acc = ctx + .wallet() + .get_account_private(&to_account_id) + .context("Failed to get recipient's private account")?; + assert_eq!(to_res_acc.balance, 100); + + info!("Successfully transferred using claiming path"); + + Ok(()) +} + +#[test] +async fn shielded_transfer_to_owned_private_account() -> Result<()> { + let mut ctx = TestContext::new().await?; + + let from: AccountId = ACC_SENDER.parse()?; + let to: AccountId = ACC_RECEIVER_PRIVATE.parse()?; + + let command = Command::AuthTransfer(AuthTransferSubcommand::Send { + from: format_public_account_id(&from.to_string()), + to: Some(format_private_account_id(&to.to_string())), + to_npk: None, + to_ipk: None, + amount: 100, + }); + + wallet::cli::execute_subcommand(ctx.wallet_mut(), command).await?; + + info!("Waiting for next block creation"); + tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; + + let acc_to = ctx + .wallet() + .get_account_private(&to) + .context("Failed to get receiver's private account")?; + let new_commitment = ctx + .wallet() + .get_private_account_commitment(&to) + .context("Failed to get receiver's commitment")?; + assert!(verify_commitment_is_in_state(new_commitment, ctx.sequencer_client()).await); + + let acc_from_balance = ctx + .sequencer_client() + .get_account_balance(from.to_string()) + .await?; + + assert_eq!(acc_from_balance.balance, 9900); + assert_eq!(acc_to.balance, 20100); + + info!("Successfully shielded transfer to owned private account"); + + Ok(()) +} + +#[test] +async fn shielded_transfer_to_foreign_account() -> Result<()> { + let mut ctx = TestContext::new().await?; + + let to_npk = NullifierPublicKey([42; 32]); + let to_npk_string = hex::encode(to_npk.0); + let to_ipk = Secp256k1Point::from_scalar(to_npk.0); + let from: AccountId = ACC_SENDER.parse()?; + + let command = Command::AuthTransfer(AuthTransferSubcommand::Send { + from: format_public_account_id(&from.to_string()), + to: None, + to_npk: Some(to_npk_string), + to_ipk: Some(hex::encode(to_ipk.0)), + amount: 100, + }); + + let result = wallet::cli::execute_subcommand(ctx.wallet_mut(), command).await?; + let SubcommandReturnValue::PrivacyPreservingTransfer { tx_hash } = result else { + anyhow::bail!("Expected PrivacyPreservingTransfer return value"); + }; + + info!("Waiting for next block creation"); + tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; + + let tx = fetch_privacy_preserving_tx(ctx.sequencer_client(), tx_hash).await; + + let acc_1_balance = ctx + .sequencer_client() + .get_account_balance(from.to_string()) + .await?; + + assert!( + verify_commitment_is_in_state( + tx.message.new_commitments[0].clone(), + ctx.sequencer_client() + ) + .await + ); + + assert_eq!(acc_1_balance.balance, 9900); + + info!("Successfully shielded transfer to foreign account"); + + Ok(()) +} + +#[test] +#[ignore = "Flaky, TODO: #197"] +async fn private_transfer_to_owned_account_continuous_run_path() -> Result<()> { + let mut ctx = TestContext::new().await?; + + // NOTE: This test needs refactoring - continuous run mode doesn't work well with TestContext + // The original implementation spawned wallet::cli::execute_continuous_run() in background + // but this conflicts with TestContext's wallet management + + let from: AccountId = ACC_SENDER_PRIVATE.parse()?; + + // Create a new private account + let command = Command::Account(AccountSubcommand::New(NewSubcommand::Private { cci: None })); + let sub_ret = wallet::cli::execute_subcommand(ctx.wallet_mut(), command).await?; + + let SubcommandReturnValue::RegisterAccount { + account_id: to_account_id, + } = sub_ret + else { + anyhow::bail!("Failed to register account"); + }; + + // Get the newly created account's keys + let (to_keys, _) = ctx + .wallet() + .storage() + .user_data + .get_private_account(&to_account_id) + .cloned() + .context("Failed to get private account")?; + + // Send transfer using nullifier and incoming viewing public keys + let command = Command::AuthTransfer(AuthTransferSubcommand::Send { + from: format_private_account_id(&from.to_string()), + to: None, + to_npk: Some(hex::encode(to_keys.nullifer_public_key.0)), + to_ipk: Some(hex::encode(to_keys.incoming_viewing_public_key.0)), + amount: 100, + }); + + let sub_ret = wallet::cli::execute_subcommand(ctx.wallet_mut(), command).await?; + let SubcommandReturnValue::PrivacyPreservingTransfer { tx_hash } = sub_ret else { + anyhow::bail!("Failed to send transaction"); + }; + + let tx = fetch_privacy_preserving_tx(ctx.sequencer_client(), tx_hash.clone()).await; + + info!("Waiting for next blocks to check if continuous run fetches account"); + tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; + tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; + + // Verify commitments are in state + assert_eq!(tx.message.new_commitments.len(), 2); + for commitment in tx.message.new_commitments.into_iter() { + assert!(verify_commitment_is_in_state(commitment, ctx.sequencer_client()).await); + } + + // Verify receiver account balance + let to_res_acc = ctx + .wallet() + .get_account_private(&to_account_id) + .context("Failed to get receiver account")?; + + assert_eq!(to_res_acc.balance, 100); + + Ok(()) +} + +#[test] +async fn initialize_private_account() -> Result<()> { + let mut ctx = TestContext::new().await?; + + let command = Command::Account(AccountSubcommand::New(NewSubcommand::Private { cci: None })); + let result = wallet::cli::execute_subcommand(ctx.wallet_mut(), command).await?; + let SubcommandReturnValue::RegisterAccount { account_id } = result else { + anyhow::bail!("Expected RegisterAccount return value"); + }; + + let command = Command::AuthTransfer(AuthTransferSubcommand::Init { + account_id: format_private_account_id(&account_id.to_string()), + }); + wallet::cli::execute_subcommand(ctx.wallet_mut(), command).await?; + + tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; + + info!("Syncing private accounts"); + let command = Command::Account(AccountSubcommand::SyncPrivate {}); + wallet::cli::execute_subcommand(ctx.wallet_mut(), command).await?; + + let new_commitment = ctx + .wallet() + .get_private_account_commitment(&account_id) + .context("Failed to get private account commitment")?; + assert!(verify_commitment_is_in_state(new_commitment, ctx.sequencer_client()).await); + + let account = ctx + .wallet() + .get_account_private(&account_id) + .context("Failed to get private account")?; + + assert_eq!( + account.program_owner, + Program::authenticated_transfer_program().id() + ); + assert_eq!(account.balance, 0); + assert!(account.data.is_empty()); + + info!("Successfully initialized private account"); + + Ok(()) +} diff --git a/integration_tests/tests/auth_transfer/public.rs b/integration_tests/tests/auth_transfer/public.rs new file mode 100644 index 00000000..467e3ebc --- /dev/null +++ b/integration_tests/tests/auth_transfer/public.rs @@ -0,0 +1,248 @@ +use std::time::Duration; + +use anyhow::Result; +use integration_tests::{ + ACC_RECEIVER, ACC_SENDER, TIME_TO_WAIT_FOR_BLOCK_SECONDS, TestContext, format_public_account_id, +}; +use log::info; +use nssa::program::Program; +use tokio::test; +use wallet::cli::{ + Command, SubcommandReturnValue, + account::{AccountSubcommand, NewSubcommand}, + programs::native_token_transfer::AuthTransferSubcommand, +}; + +#[test] +async fn successful_transfer_to_existing_account() -> Result<()> { + let mut ctx = TestContext::new().await?; + + let command = Command::AuthTransfer(AuthTransferSubcommand::Send { + from: format_public_account_id(ACC_SENDER), + to: Some(format_public_account_id(ACC_RECEIVER)), + to_npk: None, + to_ipk: None, + amount: 100, + }); + + wallet::cli::execute_subcommand(ctx.wallet_mut(), command).await?; + + info!("Waiting for next block creation"); + tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; + + info!("Checking correct balance move"); + let acc_1_balance = ctx + .sequencer_client() + .get_account_balance(ACC_SENDER.to_string()) + .await?; + let acc_2_balance = ctx + .sequencer_client() + .get_account_balance(ACC_RECEIVER.to_string()) + .await?; + + info!("Balance of sender: {acc_1_balance:#?}"); + info!("Balance of receiver: {acc_2_balance:#?}"); + + assert_eq!(acc_1_balance.balance, 9900); + assert_eq!(acc_2_balance.balance, 20100); + + Ok(()) +} + +#[test] +pub async fn successful_transfer_to_new_account() -> Result<()> { + let mut ctx = TestContext::new().await?; + + let command = Command::Account(AccountSubcommand::New(NewSubcommand::Public { cci: None })); + + wallet::cli::execute_subcommand(ctx.wallet_mut(), command) + .await + .unwrap(); + + let new_persistent_account_id = ctx + .wallet() + .storage() + .user_data + .account_ids() + .map(ToString::to_string) + .find(|acc_id| acc_id != ACC_SENDER && acc_id != ACC_RECEIVER) + .expect("Failed to find newly created account in the wallet storage"); + + if new_persistent_account_id == String::new() { + panic!("Failed to produce new account, not present in persistent accounts"); + } + + let command = Command::AuthTransfer(AuthTransferSubcommand::Send { + from: format_public_account_id(ACC_SENDER), + to: Some(format_public_account_id(&new_persistent_account_id)), + to_npk: None, + to_ipk: None, + amount: 100, + }); + + wallet::cli::execute_subcommand(ctx.wallet_mut(), command).await?; + + info!("Waiting for next block creation"); + tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; + + info!("Checking correct balance move"); + let acc_1_balance = ctx + .sequencer_client() + .get_account_balance(ACC_SENDER.to_string()) + .await?; + let acc_2_balance = ctx + .sequencer_client() + .get_account_balance(new_persistent_account_id) + .await?; + + info!("Balance of sender: {acc_1_balance:#?}"); + info!("Balance of receiver: {acc_2_balance:#?}"); + + assert_eq!(acc_1_balance.balance, 9900); + assert_eq!(acc_2_balance.balance, 100); + + Ok(()) +} + +#[test] +async fn failed_transfer_with_insufficient_balance() -> Result<()> { + let mut ctx = TestContext::new().await?; + + let command = Command::AuthTransfer(AuthTransferSubcommand::Send { + from: format_public_account_id(ACC_SENDER), + to: Some(format_public_account_id(ACC_RECEIVER)), + to_npk: None, + to_ipk: None, + amount: 1000000, + }); + + let failed_send = wallet::cli::execute_subcommand(ctx.wallet_mut(), command).await; + assert!(failed_send.is_err()); + + info!("Waiting for next block creation"); + tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; + + info!("Checking balances unchanged"); + let acc_1_balance = ctx + .sequencer_client() + .get_account_balance(ACC_SENDER.to_string()) + .await?; + let acc_2_balance = ctx + .sequencer_client() + .get_account_balance(ACC_RECEIVER.to_string()) + .await?; + + info!("Balance of sender: {acc_1_balance:#?}"); + info!("Balance of receiver: {acc_2_balance:#?}"); + + assert_eq!(acc_1_balance.balance, 10000); + assert_eq!(acc_2_balance.balance, 20000); + + Ok(()) +} + +#[test] +async fn two_consecutive_successful_transfers() -> Result<()> { + let mut ctx = TestContext::new().await?; + + // First transfer + let command = Command::AuthTransfer(AuthTransferSubcommand::Send { + from: format_public_account_id(ACC_SENDER), + to: Some(format_public_account_id(ACC_RECEIVER)), + to_npk: None, + to_ipk: None, + amount: 100, + }); + + wallet::cli::execute_subcommand(ctx.wallet_mut(), command).await?; + + info!("Waiting for next block creation"); + tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; + + info!("Checking correct balance move after first transfer"); + let acc_1_balance = ctx + .sequencer_client() + .get_account_balance(ACC_SENDER.to_string()) + .await?; + let acc_2_balance = ctx + .sequencer_client() + .get_account_balance(ACC_RECEIVER.to_string()) + .await?; + + info!("Balance of sender: {acc_1_balance:#?}"); + info!("Balance of receiver: {acc_2_balance:#?}"); + + assert_eq!(acc_1_balance.balance, 9900); + assert_eq!(acc_2_balance.balance, 20100); + + info!("First TX Success!"); + + // Second transfer + let command = Command::AuthTransfer(AuthTransferSubcommand::Send { + from: format_public_account_id(ACC_SENDER), + to: Some(format_public_account_id(ACC_RECEIVER)), + to_npk: None, + to_ipk: None, + amount: 100, + }); + + wallet::cli::execute_subcommand(ctx.wallet_mut(), command).await?; + + info!("Waiting for next block creation"); + tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; + + info!("Checking correct balance move after second transfer"); + let acc_1_balance = ctx + .sequencer_client() + .get_account_balance(ACC_SENDER.to_string()) + .await?; + let acc_2_balance = ctx + .sequencer_client() + .get_account_balance(ACC_RECEIVER.to_string()) + .await?; + + info!("Balance of sender: {acc_1_balance:#?}"); + info!("Balance of receiver: {acc_2_balance:#?}"); + + assert_eq!(acc_1_balance.balance, 9800); + assert_eq!(acc_2_balance.balance, 20200); + + info!("Second TX Success!"); + + Ok(()) +} + +#[test] +async fn initialize_public_account() -> Result<()> { + let mut ctx = TestContext::new().await?; + + let command = Command::Account(AccountSubcommand::New(NewSubcommand::Public { cci: None })); + let result = wallet::cli::execute_subcommand(ctx.wallet_mut(), command).await?; + let SubcommandReturnValue::RegisterAccount { account_id } = result else { + anyhow::bail!("Expected RegisterAccount return value"); + }; + + let command = Command::AuthTransfer(AuthTransferSubcommand::Init { + account_id: format_public_account_id(&account_id.to_string()), + }); + wallet::cli::execute_subcommand(ctx.wallet_mut(), command).await?; + + info!("Checking correct execution"); + let account = ctx + .sequencer_client() + .get_account(account_id.to_string()) + .await? + .account; + + assert_eq!( + account.program_owner, + Program::authenticated_transfer_program().id() + ); + assert_eq!(account.balance, 0); + assert_eq!(account.nonce, 1); + assert!(account.data.is_empty()); + + info!("Successfully initialized public account"); + + Ok(()) +} diff --git a/integration_tests/tests/config.rs b/integration_tests/tests/config.rs new file mode 100644 index 00000000..ca800d0f --- /dev/null +++ b/integration_tests/tests/config.rs @@ -0,0 +1,33 @@ +use anyhow::Result; +use integration_tests::TestContext; +use log::info; +use tokio::test; +use wallet::cli::{Command, config::ConfigSubcommand}; + +#[test] +async fn modify_config_field() -> Result<()> { + let mut ctx = TestContext::new().await?; + + let old_seq_poll_timeout_millis = ctx.wallet().config().seq_poll_timeout_millis; + + // Change config field + let command = Command::Config(ConfigSubcommand::Set { + key: "seq_poll_timeout_millis".to_string(), + value: "1000".to_string(), + }); + wallet::cli::execute_subcommand(ctx.wallet_mut(), command).await?; + + let new_seq_poll_timeout_millis = ctx.wallet().config().seq_poll_timeout_millis; + assert_eq!(new_seq_poll_timeout_millis, 1000); + + // Return how it was at the beginning + let command = Command::Config(ConfigSubcommand::Set { + key: "seq_poll_timeout_millis".to_string(), + value: old_seq_poll_timeout_millis.to_string(), + }); + wallet::cli::execute_subcommand(ctx.wallet_mut(), command).await?; + + info!("Successfully modified and restored config field"); + + Ok(()) +} diff --git a/integration_tests/tests/keys_restoration.rs b/integration_tests/tests/keys_restoration.rs new file mode 100644 index 00000000..9076c87f --- /dev/null +++ b/integration_tests/tests/keys_restoration.rs @@ -0,0 +1,217 @@ +use std::{str::FromStr, time::Duration}; + +use anyhow::Result; +use integration_tests::{ + ACC_SENDER, ACC_SENDER_PRIVATE, TIME_TO_WAIT_FOR_BLOCK_SECONDS, TestContext, + format_private_account_id, format_public_account_id, verify_commitment_is_in_state, +}; +use key_protocol::key_management::key_tree::chain_index::ChainIndex; +use log::info; +use nssa::{AccountId, program::Program}; +use tokio::test; +use wallet::cli::{ + Command, SubcommandReturnValue, + account::{AccountSubcommand, NewSubcommand}, + programs::native_token_transfer::AuthTransferSubcommand, +}; + +#[test] +async fn restore_keys_from_seed() -> Result<()> { + let mut ctx = TestContext::new().await?; + + let from: AccountId = ACC_SENDER_PRIVATE.parse()?; + + // Create first private account at root + let command = Command::Account(AccountSubcommand::New(NewSubcommand::Private { + cci: Some(ChainIndex::root()), + })); + let result = wallet::cli::execute_subcommand(ctx.wallet_mut(), command).await?; + let SubcommandReturnValue::RegisterAccount { + account_id: to_account_id1, + } = result + else { + anyhow::bail!("Expected RegisterAccount return value"); + }; + + // Create second private account at /0 + let command = Command::Account(AccountSubcommand::New(NewSubcommand::Private { + cci: Some(ChainIndex::from_str("/0")?), + })); + let result = wallet::cli::execute_subcommand(ctx.wallet_mut(), command).await?; + let SubcommandReturnValue::RegisterAccount { + account_id: to_account_id2, + } = result + else { + anyhow::bail!("Expected RegisterAccount return value"); + }; + + // Send to first private account + let command = Command::AuthTransfer(AuthTransferSubcommand::Send { + from: format_private_account_id(&from.to_string()), + to: Some(format_private_account_id(&to_account_id1.to_string())), + to_npk: None, + to_ipk: None, + amount: 100, + }); + wallet::cli::execute_subcommand(ctx.wallet_mut(), command).await?; + + // Send to second private account + let command = Command::AuthTransfer(AuthTransferSubcommand::Send { + from: format_private_account_id(&from.to_string()), + to: Some(format_private_account_id(&to_account_id2.to_string())), + to_npk: None, + to_ipk: None, + amount: 101, + }); + wallet::cli::execute_subcommand(ctx.wallet_mut(), command).await?; + + let from: AccountId = ACC_SENDER.parse()?; + + // Create first public account at root + let command = Command::Account(AccountSubcommand::New(NewSubcommand::Public { + cci: Some(ChainIndex::root()), + })); + let result = wallet::cli::execute_subcommand(ctx.wallet_mut(), command).await?; + let SubcommandReturnValue::RegisterAccount { + account_id: to_account_id3, + } = result + else { + anyhow::bail!("Expected RegisterAccount return value"); + }; + + // Create second public account at /0 + let command = Command::Account(AccountSubcommand::New(NewSubcommand::Public { + cci: Some(ChainIndex::from_str("/0")?), + })); + let result = wallet::cli::execute_subcommand(ctx.wallet_mut(), command).await?; + let SubcommandReturnValue::RegisterAccount { + account_id: to_account_id4, + } = result + else { + anyhow::bail!("Expected RegisterAccount return value"); + }; + + // Send to first public account + let command = Command::AuthTransfer(AuthTransferSubcommand::Send { + from: format_public_account_id(&from.to_string()), + to: Some(format_public_account_id(&to_account_id3.to_string())), + to_npk: None, + to_ipk: None, + amount: 102, + }); + wallet::cli::execute_subcommand(ctx.wallet_mut(), command).await?; + + // Send to second public account + let command = Command::AuthTransfer(AuthTransferSubcommand::Send { + from: format_public_account_id(&from.to_string()), + to: Some(format_public_account_id(&to_account_id4.to_string())), + to_npk: None, + to_ipk: None, + amount: 103, + }); + wallet::cli::execute_subcommand(ctx.wallet_mut(), command).await?; + + info!("Preparation complete, performing keys restoration"); + + // Restore keys from seed + wallet::cli::execute_keys_restoration(ctx.wallet_mut(), 10).await?; + + // Verify restored private accounts + let acc1 = ctx + .wallet() + .storage() + .user_data + .private_key_tree + .get_node(to_account_id1) + .expect("Acc 1 should be restored"); + + let acc2 = ctx + .wallet() + .storage() + .user_data + .private_key_tree + .get_node(to_account_id2) + .expect("Acc 2 should be restored"); + + // Verify restored public accounts + let _acc3 = ctx + .wallet() + .storage() + .user_data + .public_key_tree + .get_node(to_account_id3) + .expect("Acc 3 should be restored"); + + let _acc4 = ctx + .wallet() + .storage() + .user_data + .public_key_tree + .get_node(to_account_id4) + .expect("Acc 4 should be restored"); + + assert_eq!( + acc1.value.1.program_owner, + Program::authenticated_transfer_program().id() + ); + assert_eq!( + acc2.value.1.program_owner, + Program::authenticated_transfer_program().id() + ); + + assert_eq!(acc1.value.1.balance, 100); + assert_eq!(acc2.value.1.balance, 101); + + info!("Tree checks passed, testing restored accounts can transact"); + + // Test that restored accounts can send transactions + let command = Command::AuthTransfer(AuthTransferSubcommand::Send { + from: format_private_account_id(&to_account_id1.to_string()), + to: Some(format_private_account_id(&to_account_id2.to_string())), + to_npk: None, + to_ipk: None, + amount: 10, + }); + wallet::cli::execute_subcommand(ctx.wallet_mut(), command).await?; + + let command = Command::AuthTransfer(AuthTransferSubcommand::Send { + from: format_public_account_id(&to_account_id3.to_string()), + to: Some(format_public_account_id(&to_account_id4.to_string())), + to_npk: None, + to_ipk: None, + amount: 11, + }); + wallet::cli::execute_subcommand(ctx.wallet_mut(), command).await?; + + tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; + + // Verify commitments exist for private accounts + let comm1 = ctx + .wallet() + .get_private_account_commitment(&to_account_id1) + .expect("Acc 1 commitment should exist"); + let comm2 = ctx + .wallet() + .get_private_account_commitment(&to_account_id2) + .expect("Acc 2 commitment should exist"); + + assert!(verify_commitment_is_in_state(comm1, ctx.sequencer_client()).await); + assert!(verify_commitment_is_in_state(comm2, ctx.sequencer_client()).await); + + // Verify public account balances + let acc3 = ctx + .sequencer_client() + .get_account_balance(to_account_id3.to_string()) + .await?; + let acc4 = ctx + .sequencer_client() + .get_account_balance(to_account_id4.to_string()) + .await?; + + assert_eq!(acc3.balance, 91); // 102 - 11 + assert_eq!(acc4.balance, 114); // 103 + 11 + + info!("Successfully restored keys and verified transactions"); + + Ok(()) +} diff --git a/integration_tests/tests/pinata.rs b/integration_tests/tests/pinata.rs new file mode 100644 index 00000000..c627cea2 --- /dev/null +++ b/integration_tests/tests/pinata.rs @@ -0,0 +1,155 @@ +use std::time::Duration; + +use anyhow::{Context as _, Result}; +use common::PINATA_BASE58; +use integration_tests::{ + ACC_SENDER, ACC_SENDER_PRIVATE, TIME_TO_WAIT_FOR_BLOCK_SECONDS, TestContext, + format_private_account_id, format_public_account_id, verify_commitment_is_in_state, +}; +use log::info; +use tokio::test; +use wallet::cli::{ + Command, SubcommandReturnValue, + account::{AccountSubcommand, NewSubcommand}, + programs::pinata::PinataProgramAgnosticSubcommand, +}; + +#[test] +async fn claim_pinata_to_public_account() -> Result<()> { + let mut ctx = TestContext::new().await?; + + let pinata_prize = 150; + let command = Command::Pinata(PinataProgramAgnosticSubcommand::Claim { + to: format_public_account_id(ACC_SENDER), + }); + + let pinata_balance_pre = ctx + .sequencer_client() + .get_account_balance(PINATA_BASE58.to_string()) + .await? + .balance; + + wallet::cli::execute_subcommand(ctx.wallet_mut(), command).await?; + + info!("Waiting for next block creation"); + tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; + + info!("Checking correct balance move"); + let pinata_balance_post = ctx + .sequencer_client() + .get_account_balance(PINATA_BASE58.to_string()) + .await? + .balance; + + let winner_balance_post = ctx + .sequencer_client() + .get_account_balance(ACC_SENDER.to_string()) + .await? + .balance; + + assert_eq!(pinata_balance_post, pinata_balance_pre - pinata_prize); + assert_eq!(winner_balance_post, 10000 + pinata_prize); + + info!("Successfully claimed pinata to public account"); + + Ok(()) +} + +#[test] +async fn claim_pinata_to_existing_private_account() -> Result<()> { + let mut ctx = TestContext::new().await?; + + let pinata_prize = 150; + let command = Command::Pinata(PinataProgramAgnosticSubcommand::Claim { + to: format_private_account_id(ACC_SENDER_PRIVATE), + }); + + let pinata_balance_pre = ctx + .sequencer_client() + .get_account_balance(PINATA_BASE58.to_string()) + .await? + .balance; + + let result = wallet::cli::execute_subcommand(ctx.wallet_mut(), command).await?; + let SubcommandReturnValue::PrivacyPreservingTransfer { tx_hash: _ } = result else { + anyhow::bail!("Expected PrivacyPreservingTransfer return value"); + }; + + info!("Waiting for next block creation"); + tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; + + info!("Syncing private accounts"); + let command = Command::Account(AccountSubcommand::SyncPrivate {}); + wallet::cli::execute_subcommand(ctx.wallet_mut(), command).await?; + + let new_commitment = ctx + .wallet() + .get_private_account_commitment(&ACC_SENDER_PRIVATE.parse()?) + .context("Failed to get private account commitment")?; + assert!(verify_commitment_is_in_state(new_commitment, ctx.sequencer_client()).await); + + let pinata_balance_post = ctx + .sequencer_client() + .get_account_balance(PINATA_BASE58.to_string()) + .await? + .balance; + + assert_eq!(pinata_balance_post, pinata_balance_pre - pinata_prize); + + info!("Successfully claimed pinata to existing private account"); + + Ok(()) +} + +#[test] +async fn claim_pinata_to_new_private_account() -> Result<()> { + let mut ctx = TestContext::new().await?; + + let pinata_prize = 150; + + // Create new private account + let result = wallet::cli::execute_subcommand( + ctx.wallet_mut(), + Command::Account(AccountSubcommand::New(NewSubcommand::Private { cci: None })), + ) + .await?; + let SubcommandReturnValue::RegisterAccount { + account_id: winner_account_id, + } = result + else { + anyhow::bail!("Expected RegisterAccount return value"); + }; + + let command = Command::Pinata(PinataProgramAgnosticSubcommand::Claim { + to: format_private_account_id(&winner_account_id.to_string()), + }); + + let pinata_balance_pre = ctx + .sequencer_client() + .get_account_balance(PINATA_BASE58.to_string()) + .await? + .balance; + + wallet::cli::execute_subcommand(ctx.wallet_mut(), command).await?; + + info!("Waiting for next block creation"); + tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; + + let new_commitment = ctx + .wallet() + .get_private_account_commitment(&winner_account_id) + .context("Failed to get private account commitment")?; + assert!(verify_commitment_is_in_state(new_commitment, ctx.sequencer_client()).await); + + let pinata_balance_post = ctx + .sequencer_client() + .get_account_balance(PINATA_BASE58.to_string()) + .await? + .balance; + + assert_eq!(pinata_balance_post, pinata_balance_pre - pinata_prize); + + info!("Successfully claimed pinata to new private account"); + + Ok(()) +} diff --git a/integration_tests/tests/program_deployment.rs b/integration_tests/tests/program_deployment.rs new file mode 100644 index 00000000..25771ec2 --- /dev/null +++ b/integration_tests/tests/program_deployment.rs @@ -0,0 +1,64 @@ +use std::{path::PathBuf, time::Duration}; + +use anyhow::Result; +use integration_tests::{ + NSSA_PROGRAM_FOR_TEST_DATA_CHANGER, TIME_TO_WAIT_FOR_BLOCK_SECONDS, TestContext, +}; +use log::info; +use nssa::{AccountId, program::Program}; +use tokio::test; +use wallet::cli::Command; + +#[test] +async fn deploy_and_execute_program() -> Result<()> { + let mut ctx = TestContext::new().await?; + + let manifest_dir = env!("CARGO_MANIFEST_DIR"); + let binary_filepath: PathBuf = PathBuf::from(manifest_dir) + .join("../artifacts/test_program_methods") + .join(NSSA_PROGRAM_FOR_TEST_DATA_CHANGER); + + let command = Command::DeployProgram { + binary_filepath: binary_filepath.clone(), + }; + + wallet::cli::execute_subcommand(ctx.wallet_mut(), command).await?; + + info!("Waiting for next block creation"); + tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; + + // The program is the data changer and takes one account as input. + // We pass an uninitialized account and we expect after execution to be owned by the data + // changer program (NSSA account claiming mechanism) with data equal to [0] (due to program + // logic) + let bytecode = std::fs::read(binary_filepath)?; + let data_changer = Program::new(bytecode)?; + let account_id: AccountId = "11".repeat(16).parse()?; + let message = nssa::public_transaction::Message::try_new( + data_changer.id(), + vec![account_id], + vec![], + vec![0], + )?; + let witness_set = nssa::public_transaction::WitnessSet::for_message(&message, &[]); + let transaction = nssa::PublicTransaction::new(message, witness_set); + let _response = ctx.sequencer_client().send_tx_public(transaction).await?; + + info!("Waiting for next block creation"); + tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; + + let post_state_account = ctx + .sequencer_client() + .get_account(account_id.to_string()) + .await? + .account; + + assert_eq!(post_state_account.program_owner, data_changer.id()); + assert_eq!(post_state_account.balance, 0); + assert_eq!(post_state_account.data.as_ref(), &[0]); + assert_eq!(post_state_account.nonce, 0); + + info!("Successfully deployed and executed program"); + + Ok(()) +} diff --git a/integration_tests/tests/token.rs b/integration_tests/tests/token.rs new file mode 100644 index 00000000..9a8b714a --- /dev/null +++ b/integration_tests/tests/token.rs @@ -0,0 +1,968 @@ +use std::time::Duration; + +use anyhow::{Context as _, Result}; +use integration_tests::{ + TIME_TO_WAIT_FOR_BLOCK_SECONDS, TestContext, format_private_account_id, + format_public_account_id, verify_commitment_is_in_state, +}; +use key_protocol::key_management::key_tree::chain_index::ChainIndex; +use log::info; +use nssa::program::Program; +use tokio::test; +use wallet::cli::{ + Command, SubcommandReturnValue, + account::{AccountSubcommand, NewSubcommand}, + programs::token::TokenProgramAgnosticSubcommand, +}; + +#[test] +async fn create_and_transfer_public_token() -> Result<()> { + let mut ctx = TestContext::new().await?; + + // Create new account for the token definition + let result = wallet::cli::execute_subcommand( + ctx.wallet_mut(), + Command::Account(AccountSubcommand::New(NewSubcommand::Public { cci: None })), + ) + .await?; + let SubcommandReturnValue::RegisterAccount { + account_id: definition_account_id, + } = result + else { + anyhow::bail!("Expected RegisterAccount return value"); + }; + + // Create new account for the token supply holder + let result = wallet::cli::execute_subcommand( + ctx.wallet_mut(), + Command::Account(AccountSubcommand::New(NewSubcommand::Public { cci: None })), + ) + .await?; + let SubcommandReturnValue::RegisterAccount { + account_id: supply_account_id, + } = result + else { + anyhow::bail!("Expected RegisterAccount return value"); + }; + + // Create new account for receiving a token transaction + let result = wallet::cli::execute_subcommand( + ctx.wallet_mut(), + Command::Account(AccountSubcommand::New(NewSubcommand::Public { cci: None })), + ) + .await?; + let SubcommandReturnValue::RegisterAccount { + account_id: recipient_account_id, + } = result + else { + anyhow::bail!("Expected RegisterAccount return value"); + }; + + // Create new token + let subcommand = TokenProgramAgnosticSubcommand::New { + definition_account_id: format_public_account_id(&definition_account_id.to_string()), + supply_account_id: format_public_account_id(&supply_account_id.to_string()), + name: "A NAME".to_string(), + total_supply: 37, + }; + wallet::cli::execute_subcommand(ctx.wallet_mut(), Command::Token(subcommand)).await?; + + info!("Waiting for next block creation"); + tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; + + // Check the status of the token definition account + let definition_acc = ctx + .sequencer_client() + .get_account(definition_account_id.to_string()) + .await? + .account; + + assert_eq!(definition_acc.program_owner, Program::token().id()); + // The data of a token definition account has the following layout: + // [ 0x00 || name (6 bytes) || total supply (little endian 16 bytes) || metadata id (32 bytes)] + assert_eq!( + definition_acc.data.as_ref(), + &[ + 0, 65, 32, 78, 65, 77, 69, 37, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + ] + ); + + // Check the status of the token holding account with the total supply + let supply_acc = ctx + .sequencer_client() + .get_account(supply_account_id.to_string()) + .await? + .account; + + // The account must be owned by the token program + assert_eq!(supply_acc.program_owner, Program::token().id()); + // The data of a token holding account has the following layout: + // [ 0x01 || corresponding_token_definition_id (32 bytes) || balance (little endian 16 bytes) ] + // First byte of the data equal to 1 means it's a token holding account + assert_eq!(supply_acc.data.as_ref()[0], 1); + // Bytes from 1 to 33 represent the id of the token this account is associated with + assert_eq!( + &supply_acc.data.as_ref()[1..33], + definition_account_id.to_bytes() + ); + assert_eq!(u128::from_le_bytes(supply_acc.data[33..].try_into()?), 37); + + // Transfer 7 tokens from supply_acc to recipient_account_id + let subcommand = TokenProgramAgnosticSubcommand::Send { + from: format_public_account_id(&supply_account_id.to_string()), + to: Some(format_public_account_id(&recipient_account_id.to_string())), + to_npk: None, + to_ipk: None, + amount: 7, + }; + + wallet::cli::execute_subcommand(ctx.wallet_mut(), Command::Token(subcommand)).await?; + + info!("Waiting for next block creation"); + tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; + + // Check the status of the supply account after transfer + let supply_acc = ctx + .sequencer_client() + .get_account(supply_account_id.to_string()) + .await? + .account; + assert_eq!(supply_acc.program_owner, Program::token().id()); + assert_eq!(supply_acc.data[0], 1); + assert_eq!(&supply_acc.data[1..33], definition_account_id.to_bytes()); + assert_eq!(u128::from_le_bytes(supply_acc.data[33..].try_into()?), 30); + + // Check the status of the recipient account after transfer + let recipient_acc = ctx + .sequencer_client() + .get_account(recipient_account_id.to_string()) + .await? + .account; + assert_eq!(recipient_acc.program_owner, Program::token().id()); + assert_eq!(recipient_acc.data[0], 1); + assert_eq!(&recipient_acc.data[1..33], definition_account_id.to_bytes()); + assert_eq!(u128::from_le_bytes(recipient_acc.data[33..].try_into()?), 7); + + // Burn 3 tokens from recipient_acc + let subcommand = TokenProgramAgnosticSubcommand::Burn { + definition: format_public_account_id(&definition_account_id.to_string()), + holder: format_public_account_id(&recipient_account_id.to_string()), + amount: 3, + }; + + wallet::cli::execute_subcommand(ctx.wallet_mut(), Command::Token(subcommand)).await?; + + info!("Waiting for next block creation"); + tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; + + // Check the status of the token definition account after burn + let definition_acc = ctx + .sequencer_client() + .get_account(definition_account_id.to_string()) + .await? + .account; + + assert_eq!( + definition_acc.data.as_ref(), + &[ + 0, 65, 32, 78, 65, 77, 69, 34, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + ] + ); + + // Check the status of the recipient account after burn + let recipient_acc = ctx + .sequencer_client() + .get_account(recipient_account_id.to_string()) + .await? + .account; + + assert_eq!(u128::from_le_bytes(recipient_acc.data[33..].try_into()?), 4); + + // Mint 10 tokens at recipient_acc + let subcommand = TokenProgramAgnosticSubcommand::Mint { + definition: format_public_account_id(&definition_account_id.to_string()), + holder: Some(format_public_account_id(&recipient_account_id.to_string())), + holder_npk: None, + holder_ipk: None, + amount: 10, + }; + + wallet::cli::execute_subcommand(ctx.wallet_mut(), Command::Token(subcommand)).await?; + + info!("Waiting for next block creation"); + tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; + + // Check the status of the token definition account after mint + let definition_acc = ctx + .sequencer_client() + .get_account(definition_account_id.to_string()) + .await? + .account; + + assert_eq!( + definition_acc.data.as_ref(), + &[ + 0, 65, 32, 78, 65, 77, 69, 44, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + ] + ); + + // Check the status of the recipient account after mint + let recipient_acc = ctx + .sequencer_client() + .get_account(recipient_account_id.to_string()) + .await? + .account; + + assert_eq!( + u128::from_le_bytes(recipient_acc.data[33..].try_into()?), + 14 + ); + + info!("Successfully created and transferred public token"); + + Ok(()) +} + +#[test] +async fn create_and_transfer_token_with_private_supply() -> Result<()> { + let mut ctx = TestContext::new().await?; + + // Create new account for the token definition (public) + let result = wallet::cli::execute_subcommand( + ctx.wallet_mut(), + Command::Account(AccountSubcommand::New(NewSubcommand::Public { cci: None })), + ) + .await?; + let SubcommandReturnValue::RegisterAccount { + account_id: definition_account_id, + } = result + else { + anyhow::bail!("Expected RegisterAccount return value"); + }; + + // Create new account for the token supply holder (private) + let result = wallet::cli::execute_subcommand( + ctx.wallet_mut(), + Command::Account(AccountSubcommand::New(NewSubcommand::Private { cci: None })), + ) + .await?; + let SubcommandReturnValue::RegisterAccount { + account_id: supply_account_id, + } = result + else { + anyhow::bail!("Expected RegisterAccount return value"); + }; + + // Create new account for receiving a token transaction (private) + let result = wallet::cli::execute_subcommand( + ctx.wallet_mut(), + Command::Account(AccountSubcommand::New(NewSubcommand::Private { cci: None })), + ) + .await?; + let SubcommandReturnValue::RegisterAccount { + account_id: recipient_account_id, + } = result + else { + anyhow::bail!("Expected RegisterAccount return value"); + }; + + // Create new token + let subcommand = TokenProgramAgnosticSubcommand::New { + definition_account_id: format_public_account_id(&definition_account_id.to_string()), + supply_account_id: format_private_account_id(&supply_account_id.to_string()), + name: "A NAME".to_string(), + total_supply: 37, + }; + + wallet::cli::execute_subcommand(ctx.wallet_mut(), Command::Token(subcommand)).await?; + + info!("Waiting for next block creation"); + tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; + + // Check the status of the token definition account + let definition_acc = ctx + .sequencer_client() + .get_account(definition_account_id.to_string()) + .await? + .account; + + assert_eq!(definition_acc.program_owner, Program::token().id()); + assert_eq!( + definition_acc.data.as_ref(), + &[ + 0, 65, 32, 78, 65, 77, 69, 37, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + ] + ); + + let new_commitment1 = ctx + .wallet() + .get_private_account_commitment(&supply_account_id) + .context("Failed to get supply account commitment")?; + assert!(verify_commitment_is_in_state(new_commitment1, ctx.sequencer_client()).await); + + // Transfer 7 tokens from supply_acc to recipient_account_id + let subcommand = TokenProgramAgnosticSubcommand::Send { + from: format_private_account_id(&supply_account_id.to_string()), + to: Some(format_private_account_id(&recipient_account_id.to_string())), + to_npk: None, + to_ipk: None, + amount: 7, + }; + + wallet::cli::execute_subcommand(ctx.wallet_mut(), Command::Token(subcommand)).await?; + + info!("Waiting for next block creation"); + tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; + + let new_commitment1 = ctx + .wallet() + .get_private_account_commitment(&supply_account_id) + .context("Failed to get supply account commitment")?; + assert!(verify_commitment_is_in_state(new_commitment1, ctx.sequencer_client()).await); + + let new_commitment2 = ctx + .wallet() + .get_private_account_commitment(&recipient_account_id) + .context("Failed to get recipient account commitment")?; + assert!(verify_commitment_is_in_state(new_commitment2, ctx.sequencer_client()).await); + + // Burn 3 tokens from recipient_acc + let subcommand = TokenProgramAgnosticSubcommand::Burn { + definition: format_public_account_id(&definition_account_id.to_string()), + holder: format_private_account_id(&recipient_account_id.to_string()), + amount: 3, + }; + + wallet::cli::execute_subcommand(ctx.wallet_mut(), Command::Token(subcommand)).await?; + + info!("Waiting for next block creation"); + tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; + + // Check the token definition account after burn + let definition_acc = ctx + .sequencer_client() + .get_account(definition_account_id.to_string()) + .await? + .account; + + assert_eq!( + definition_acc.data.as_ref(), + &[ + 0, 65, 32, 78, 65, 77, 69, 34, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + ] + ); + + let new_commitment2 = ctx + .wallet() + .get_private_account_commitment(&recipient_account_id) + .context("Failed to get recipient account commitment")?; + assert!(verify_commitment_is_in_state(new_commitment2, ctx.sequencer_client()).await); + + // Check the recipient account balance after burn + let recipient_acc = ctx + .wallet() + .get_account_private(&recipient_account_id) + .context("Failed to get recipient account")?; + + assert_eq!( + u128::from_le_bytes(recipient_acc.data[33..].try_into()?), + 4 // 7 - 3 + ); + + info!("Successfully created and transferred token with private supply"); + + Ok(()) +} + +#[test] +async fn create_token_with_private_definition() -> Result<()> { + let mut ctx = TestContext::new().await?; + + // Create token definition account (private) + let result = wallet::cli::execute_subcommand( + ctx.wallet_mut(), + Command::Account(AccountSubcommand::New(NewSubcommand::Private { + cci: Some(ChainIndex::root()), + })), + ) + .await?; + let SubcommandReturnValue::RegisterAccount { + account_id: definition_account_id, + } = result + else { + anyhow::bail!("Expected RegisterAccount return value"); + }; + + // Create supply account (public) + let result = wallet::cli::execute_subcommand( + ctx.wallet_mut(), + Command::Account(AccountSubcommand::New(NewSubcommand::Public { + cci: Some(ChainIndex::root()), + })), + ) + .await?; + let SubcommandReturnValue::RegisterAccount { + account_id: supply_account_id, + } = result + else { + anyhow::bail!("Expected RegisterAccount return value"); + }; + + // Create token with private definition + let subcommand = TokenProgramAgnosticSubcommand::New { + definition_account_id: format_private_account_id(&definition_account_id.to_string()), + supply_account_id: format_public_account_id(&supply_account_id.to_string()), + name: "A NAME".to_string(), + total_supply: 37, + }; + + wallet::cli::execute_subcommand(ctx.wallet_mut(), Command::Token(subcommand)).await?; + + info!("Waiting for next block creation"); + tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; + + // Verify private definition commitment + let new_commitment = ctx + .wallet() + .get_private_account_commitment(&definition_account_id) + .context("Failed to get definition commitment")?; + assert!(verify_commitment_is_in_state(new_commitment, ctx.sequencer_client()).await); + + // Verify supply account + let supply_acc = ctx + .sequencer_client() + .get_account(supply_account_id.to_string()) + .await? + .account; + + assert_eq!(supply_acc.program_owner, Program::token().id()); + assert_eq!(supply_acc.data.as_ref()[0], 1); + assert_eq!(u128::from_le_bytes(supply_acc.data[33..].try_into()?), 37); + + // Create private recipient account + let result = wallet::cli::execute_subcommand( + ctx.wallet_mut(), + Command::Account(AccountSubcommand::New(NewSubcommand::Private { cci: None })), + ) + .await?; + let SubcommandReturnValue::RegisterAccount { + account_id: recipient_account_id_private, + } = result + else { + anyhow::bail!("Expected RegisterAccount return value"); + }; + + // Create public recipient account + let result = wallet::cli::execute_subcommand( + ctx.wallet_mut(), + Command::Account(AccountSubcommand::New(NewSubcommand::Public { cci: None })), + ) + .await?; + let SubcommandReturnValue::RegisterAccount { + account_id: recipient_account_id_public, + } = result + else { + anyhow::bail!("Expected RegisterAccount return value"); + }; + + // Mint to public account + let subcommand = TokenProgramAgnosticSubcommand::Mint { + definition: format_private_account_id(&definition_account_id.to_string()), + holder: Some(format_public_account_id( + &recipient_account_id_public.to_string(), + )), + holder_npk: None, + holder_ipk: None, + amount: 10, + }; + + wallet::cli::execute_subcommand(ctx.wallet_mut(), Command::Token(subcommand)).await?; + + info!("Waiting for next block creation"); + tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; + + // Verify definition account has updated supply + let definition_acc = ctx + .wallet() + .get_account_private(&definition_account_id) + .context("Failed to get definition account")?; + + assert_eq!( + u128::from_le_bytes(definition_acc.data[7..23].try_into()?), + 47 // 37 + 10 + ); + + // Verify public recipient received tokens + let recipient_acc = ctx + .sequencer_client() + .get_account(recipient_account_id_public.to_string()) + .await? + .account; + + assert_eq!( + u128::from_le_bytes(recipient_acc.data[33..].try_into()?), + 10 + ); + + // Mint to private account + let subcommand = TokenProgramAgnosticSubcommand::Mint { + definition: format_private_account_id(&definition_account_id.to_string()), + holder: Some(format_private_account_id( + &recipient_account_id_private.to_string(), + )), + holder_npk: None, + holder_ipk: None, + amount: 5, + }; + + wallet::cli::execute_subcommand(ctx.wallet_mut(), Command::Token(subcommand)).await?; + + info!("Waiting for next block creation"); + tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; + + // Verify private recipient commitment + let new_commitment = ctx + .wallet() + .get_private_account_commitment(&recipient_account_id_private) + .context("Failed to get recipient commitment")?; + assert!(verify_commitment_is_in_state(new_commitment, ctx.sequencer_client()).await); + + // Verify private recipient balance + let recipient_acc_private = ctx + .wallet() + .get_account_private(&recipient_account_id_private) + .context("Failed to get private recipient account")?; + + assert_eq!( + u128::from_le_bytes(recipient_acc_private.data[33..].try_into()?), + 5 + ); + + info!("Successfully created token with private definition and minted to both account types"); + + Ok(()) +} + +#[test] +async fn create_token_with_private_definition_and_supply() -> Result<()> { + let mut ctx = TestContext::new().await?; + + // Create token definition account (private) + let result = wallet::cli::execute_subcommand( + ctx.wallet_mut(), + Command::Account(AccountSubcommand::New(NewSubcommand::Private { cci: None })), + ) + .await?; + let SubcommandReturnValue::RegisterAccount { + account_id: definition_account_id, + } = result + else { + anyhow::bail!("Expected RegisterAccount return value"); + }; + + // Create supply account (private) + let result = wallet::cli::execute_subcommand( + ctx.wallet_mut(), + Command::Account(AccountSubcommand::New(NewSubcommand::Private { cci: None })), + ) + .await?; + let SubcommandReturnValue::RegisterAccount { + account_id: supply_account_id, + } = result + else { + anyhow::bail!("Expected RegisterAccount return value"); + }; + + // Create token with both private definition and supply + let subcommand = TokenProgramAgnosticSubcommand::New { + definition_account_id: format_private_account_id(&definition_account_id.to_string()), + supply_account_id: format_private_account_id(&supply_account_id.to_string()), + name: "A NAME".to_string(), + total_supply: 37, + }; + + wallet::cli::execute_subcommand(ctx.wallet_mut(), Command::Token(subcommand)).await?; + + info!("Waiting for next block creation"); + tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; + + // Verify definition commitment + let definition_commitment = ctx + .wallet() + .get_private_account_commitment(&definition_account_id) + .context("Failed to get definition commitment")?; + assert!(verify_commitment_is_in_state(definition_commitment, ctx.sequencer_client()).await); + + // Verify supply commitment + let supply_commitment = ctx + .wallet() + .get_private_account_commitment(&supply_account_id) + .context("Failed to get supply commitment")?; + assert!(verify_commitment_is_in_state(supply_commitment, ctx.sequencer_client()).await); + + // Verify supply balance + let supply_acc = ctx + .wallet() + .get_account_private(&supply_account_id) + .context("Failed to get supply account")?; + + assert_eq!(u128::from_le_bytes(supply_acc.data[33..].try_into()?), 37); + + // Create recipient account + let result = wallet::cli::execute_subcommand( + ctx.wallet_mut(), + Command::Account(AccountSubcommand::New(NewSubcommand::Private { cci: None })), + ) + .await?; + let SubcommandReturnValue::RegisterAccount { + account_id: recipient_account_id, + } = result + else { + anyhow::bail!("Expected RegisterAccount return value"); + }; + + // Transfer tokens + let subcommand = TokenProgramAgnosticSubcommand::Send { + from: format_private_account_id(&supply_account_id.to_string()), + to: Some(format_private_account_id(&recipient_account_id.to_string())), + to_npk: None, + to_ipk: None, + amount: 7, + }; + + wallet::cli::execute_subcommand(ctx.wallet_mut(), Command::Token(subcommand)).await?; + + info!("Waiting for next block creation"); + tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; + + // Verify both commitments updated + let supply_commitment = ctx + .wallet() + .get_private_account_commitment(&supply_account_id) + .context("Failed to get supply commitment")?; + assert!(verify_commitment_is_in_state(supply_commitment, ctx.sequencer_client()).await); + + let recipient_commitment = ctx + .wallet() + .get_private_account_commitment(&recipient_account_id) + .context("Failed to get recipient commitment")?; + assert!(verify_commitment_is_in_state(recipient_commitment, ctx.sequencer_client()).await); + + // Verify balances + let supply_acc = ctx + .wallet() + .get_account_private(&supply_account_id) + .context("Failed to get supply account")?; + assert_eq!(u128::from_le_bytes(supply_acc.data[33..].try_into()?), 30); + + let recipient_acc = ctx + .wallet() + .get_account_private(&recipient_account_id) + .context("Failed to get recipient account")?; + assert_eq!(u128::from_le_bytes(recipient_acc.data[33..].try_into()?), 7); + + info!("Successfully created and transferred token with both private definition and supply"); + + Ok(()) +} + +#[test] +async fn shielded_token_transfer() -> Result<()> { + let mut ctx = TestContext::new().await?; + + // Create token definition account (public) + let result = wallet::cli::execute_subcommand( + ctx.wallet_mut(), + Command::Account(AccountSubcommand::New(NewSubcommand::Public { cci: None })), + ) + .await?; + let SubcommandReturnValue::RegisterAccount { + account_id: definition_account_id, + } = result + else { + anyhow::bail!("Expected RegisterAccount return value"); + }; + + // Create supply account (public) + let result = wallet::cli::execute_subcommand( + ctx.wallet_mut(), + Command::Account(AccountSubcommand::New(NewSubcommand::Public { cci: None })), + ) + .await?; + let SubcommandReturnValue::RegisterAccount { + account_id: supply_account_id, + } = result + else { + anyhow::bail!("Expected RegisterAccount return value"); + }; + + // Create recipient account (private) for shielded transfer + let result = wallet::cli::execute_subcommand( + ctx.wallet_mut(), + Command::Account(AccountSubcommand::New(NewSubcommand::Private { cci: None })), + ) + .await?; + let SubcommandReturnValue::RegisterAccount { + account_id: recipient_account_id, + } = result + else { + anyhow::bail!("Expected RegisterAccount return value"); + }; + + // Create token + let subcommand = TokenProgramAgnosticSubcommand::New { + definition_account_id: format_public_account_id(&definition_account_id.to_string()), + supply_account_id: format_public_account_id(&supply_account_id.to_string()), + name: "A NAME".to_string(), + total_supply: 37, + }; + + wallet::cli::execute_subcommand(ctx.wallet_mut(), Command::Token(subcommand)).await?; + + info!("Waiting for next block creation"); + tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; + + // Perform shielded transfer: public supply -> private recipient + let subcommand = TokenProgramAgnosticSubcommand::Send { + from: format_public_account_id(&supply_account_id.to_string()), + to: Some(format_private_account_id(&recipient_account_id.to_string())), + to_npk: None, + to_ipk: None, + amount: 7, + }; + + wallet::cli::execute_subcommand(ctx.wallet_mut(), Command::Token(subcommand)).await?; + + info!("Waiting for next block creation"); + tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; + + // Verify supply account balance + let supply_acc = ctx + .sequencer_client() + .get_account(supply_account_id.to_string()) + .await? + .account; + assert_eq!(u128::from_le_bytes(supply_acc.data[33..].try_into()?), 30); + + // Verify recipient commitment exists + let new_commitment = ctx + .wallet() + .get_private_account_commitment(&recipient_account_id) + .context("Failed to get recipient commitment")?; + assert!(verify_commitment_is_in_state(new_commitment, ctx.sequencer_client()).await); + + // Verify recipient balance + let recipient_acc = ctx + .wallet() + .get_account_private(&recipient_account_id) + .context("Failed to get recipient account")?; + assert_eq!(u128::from_le_bytes(recipient_acc.data[33..].try_into()?), 7); + + info!("Successfully performed shielded token transfer"); + + Ok(()) +} + +#[test] +async fn deshielded_token_transfer() -> Result<()> { + let mut ctx = TestContext::new().await?; + + // Create token definition account (public) + let result = wallet::cli::execute_subcommand( + ctx.wallet_mut(), + Command::Account(AccountSubcommand::New(NewSubcommand::Public { cci: None })), + ) + .await?; + let SubcommandReturnValue::RegisterAccount { + account_id: definition_account_id, + } = result + else { + anyhow::bail!("Expected RegisterAccount return value"); + }; + + // Create supply account (private) + let result = wallet::cli::execute_subcommand( + ctx.wallet_mut(), + Command::Account(AccountSubcommand::New(NewSubcommand::Private { cci: None })), + ) + .await?; + let SubcommandReturnValue::RegisterAccount { + account_id: supply_account_id, + } = result + else { + anyhow::bail!("Expected RegisterAccount return value"); + }; + + // Create recipient account (public) for deshielded transfer + let result = wallet::cli::execute_subcommand( + ctx.wallet_mut(), + Command::Account(AccountSubcommand::New(NewSubcommand::Public { cci: None })), + ) + .await?; + let SubcommandReturnValue::RegisterAccount { + account_id: recipient_account_id, + } = result + else { + anyhow::bail!("Expected RegisterAccount return value"); + }; + + // Create token with private supply + let subcommand = TokenProgramAgnosticSubcommand::New { + definition_account_id: format_public_account_id(&definition_account_id.to_string()), + supply_account_id: format_private_account_id(&supply_account_id.to_string()), + name: "A NAME".to_string(), + total_supply: 37, + }; + + wallet::cli::execute_subcommand(ctx.wallet_mut(), Command::Token(subcommand)).await?; + + info!("Waiting for next block creation"); + tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; + + // Perform deshielded transfer: private supply -> public recipient + let subcommand = TokenProgramAgnosticSubcommand::Send { + from: format_private_account_id(&supply_account_id.to_string()), + to: Some(format_public_account_id(&recipient_account_id.to_string())), + to_npk: None, + to_ipk: None, + amount: 7, + }; + + wallet::cli::execute_subcommand(ctx.wallet_mut(), Command::Token(subcommand)).await?; + + info!("Waiting for next block creation"); + tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; + + // Verify supply account commitment exists + let new_commitment = ctx + .wallet() + .get_private_account_commitment(&supply_account_id) + .context("Failed to get supply commitment")?; + assert!(verify_commitment_is_in_state(new_commitment, ctx.sequencer_client()).await); + + // Verify supply balance + let supply_acc = ctx + .wallet() + .get_account_private(&supply_account_id) + .context("Failed to get supply account")?; + assert_eq!(u128::from_le_bytes(supply_acc.data[33..].try_into()?), 30); + + // Verify recipient balance + let recipient_acc = ctx + .sequencer_client() + .get_account(recipient_account_id.to_string()) + .await? + .account; + assert_eq!(u128::from_le_bytes(recipient_acc.data[33..].try_into()?), 7); + + info!("Successfully performed deshielded token transfer"); + + Ok(()) +} + +#[test] +async fn token_claiming_path_with_private_accounts() -> Result<()> { + let mut ctx = TestContext::new().await?; + + // Create token definition account (private) + let result = wallet::cli::execute_subcommand( + ctx.wallet_mut(), + Command::Account(AccountSubcommand::New(NewSubcommand::Private { cci: None })), + ) + .await?; + let SubcommandReturnValue::RegisterAccount { + account_id: definition_account_id, + } = result + else { + anyhow::bail!("Expected RegisterAccount return value"); + }; + + // Create supply account (private) + let result = wallet::cli::execute_subcommand( + ctx.wallet_mut(), + Command::Account(AccountSubcommand::New(NewSubcommand::Private { cci: None })), + ) + .await?; + let SubcommandReturnValue::RegisterAccount { + account_id: supply_account_id, + } = result + else { + anyhow::bail!("Expected RegisterAccount return value"); + }; + + // Create token + let subcommand = TokenProgramAgnosticSubcommand::New { + definition_account_id: format_private_account_id(&definition_account_id.to_string()), + supply_account_id: format_private_account_id(&supply_account_id.to_string()), + name: "A NAME".to_string(), + total_supply: 37, + }; + + wallet::cli::execute_subcommand(ctx.wallet_mut(), Command::Token(subcommand)).await?; + + info!("Waiting for next block creation"); + tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; + + // Create new private account for claiming path + let result = wallet::cli::execute_subcommand( + ctx.wallet_mut(), + Command::Account(AccountSubcommand::New(NewSubcommand::Private { cci: None })), + ) + .await?; + let SubcommandReturnValue::RegisterAccount { + account_id: recipient_account_id, + } = result + else { + anyhow::bail!("Expected RegisterAccount return value"); + }; + + // Get keys for foreign mint (claiming path) + let (holder_keys, _) = ctx + .wallet() + .storage() + .user_data + .get_private_account(&recipient_account_id) + .cloned() + .context("Failed to get private account keys")?; + + // Mint using claiming path (foreign account) + let subcommand = TokenProgramAgnosticSubcommand::Mint { + definition: format_private_account_id(&definition_account_id.to_string()), + holder: None, + holder_npk: Some(hex::encode(holder_keys.nullifer_public_key.0)), + holder_ipk: Some(hex::encode(holder_keys.incoming_viewing_public_key.0)), + amount: 9, + }; + + wallet::cli::execute_subcommand(ctx.wallet_mut(), Command::Token(subcommand)).await?; + + info!("Waiting for next block creation"); + tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; + + // Sync to claim the account + let command = Command::Account(AccountSubcommand::SyncPrivate {}); + wallet::cli::execute_subcommand(ctx.wallet_mut(), command).await?; + + // Verify commitment exists + let recipient_commitment = ctx + .wallet() + .get_private_account_commitment(&recipient_account_id) + .context("Failed to get recipient commitment")?; + assert!(verify_commitment_is_in_state(recipient_commitment, ctx.sequencer_client()).await); + + // Verify balance + let recipient_acc = ctx + .wallet() + .get_account_private(&recipient_account_id) + .context("Failed to get recipient account")?; + assert_eq!(u128::from_le_bytes(recipient_acc.data[33..].try_into()?), 9); + + info!("Successfully minted tokens using claiming path"); + + Ok(()) +} diff --git a/integration_tests/src/tps_test_utils.rs b/integration_tests/tests/tps.rs similarity index 74% rename from integration_tests/src/tps_test_utils.rs rename to integration_tests/tests/tps.rs index 154253c8..b62a3ce7 100644 --- a/integration_tests/src/tps_test_utils.rs +++ b/integration_tests/tests/tps.rs @@ -1,6 +1,9 @@ -use std::time::Duration; +use std::time::{Duration, Instant}; +use anyhow::Result; +use integration_tests::TestContext; use key_protocol::key_management::ephemeral_key_holder::EphemeralKeyHolder; +use log::info; use nssa::{ Account, AccountId, PrivacyPreservingTransaction, PrivateKey, PublicKey, PublicTransaction, privacy_preserving_transaction::{self as pptx, circuit}, @@ -13,6 +16,78 @@ use nssa_core::{ encryption::IncomingViewingPublicKey, }; use sequencer_core::config::{AccountInitialData, CommitmentsInitialData, SequencerConfig}; +use tokio::test; + +// TODO: Make a proper benchmark instead of an ad-hoc test +#[test] +pub async fn tps_test() -> Result<()> { + let num_transactions = 300 * 5; + let target_tps = 12; + + let tps_test = TpsTestManager::new(target_tps, num_transactions); + let ctx = TestContext::new_with_sequencer_config(tps_test.generate_sequencer_config()).await?; + + let target_time = tps_test.target_time(); + info!( + "TPS test begin. Target time is {target_time:?} for {num_transactions} transactions ({target_tps} TPS)" + ); + + let txs = tps_test.build_public_txs(); + let now = Instant::now(); + + let mut tx_hashes = vec![]; + for (i, tx) in txs.into_iter().enumerate() { + let tx_hash = ctx + .sequencer_client() + .send_tx_public(tx) + .await + .unwrap() + .tx_hash; + info!("Sent tx {i}"); + tx_hashes.push(tx_hash); + } + + for (i, tx_hash) in tx_hashes.iter().enumerate() { + loop { + if now.elapsed().as_millis() > target_time.as_millis() { + panic!("TPS test failed by timeout"); + } + + let tx_obj = ctx + .sequencer_client() + .get_transaction_by_hash(tx_hash.clone()) + .await + .inspect_err(|err| { + log::warn!( + "Failed to get transaction by hash {tx_hash:#?} with error: {err:#?}" + ) + }); + + if let Ok(tx_obj) = tx_obj + && tx_obj.transaction.is_some() + { + info!("Found tx {i} with hash {tx_hash}"); + break; + } + } + } + let time_elapsed = now.elapsed().as_secs(); + + let tx_processed = tx_hashes.len(); + let actual_tps = tx_processed as u64 / time_elapsed; + info!("Processed {tx_processed} transactions in {time_elapsed:?} ({actual_tps} TPS)",); + + assert_eq!(tx_processed, num_transactions); + + assert!( + time_elapsed <= target_time.as_secs(), + "Elapsed time {time_elapsed:?} exceeded target time {target_time:?}" + ); + + info!("TPS test finished successfully"); + + Ok(()) +} pub(crate) struct TpsTestManager { public_keypairs: Vec<(PrivateKey, AccountId)>, @@ -32,7 +107,7 @@ impl TpsTestManager { let account_id = AccountId::from(&public_key); (private_key, account_id) }) - .collect::>(); + .collect(); Self { public_keypairs, target_tps, @@ -72,7 +147,7 @@ impl TpsTestManager { /// Generates a sequencer configuration with initial balance in a number of public accounts. /// The transactions generated with the function `build_public_txs` will be valid in a node /// started with the config from this method. - pub(crate) fn generate_tps_test_config(&self) -> SequencerConfig { + pub(crate) fn generate_sequencer_config(&self) -> SequencerConfig { // Create public public keypairs let initial_public_accounts = self .public_keypairs @@ -118,7 +193,7 @@ impl TpsTestManager { /// it may take a while to run. In normal execution of the node this transaction will be accepted /// only once. Disabling the node's nullifier uniqueness check allows to submit this transaction /// multiple times with the purpose of testing the node's processing performance. -#[allow(unused)] +#[expect(dead_code, reason = "No idea if we need this, should we remove it?")] fn build_privacy_transaction() -> PrivacyPreservingTransaction { let program = Program::authenticated_transfer_program(); let sender_nsk = [1; 32]; diff --git a/key_protocol/src/key_protocol_core/mod.rs b/key_protocol/src/key_protocol_core/mod.rs index b46c46c9..4814552a 100644 --- a/key_protocol/src/key_protocol_core/mod.rs +++ b/key_protocol/src/key_protocol_core/mod.rs @@ -165,6 +165,14 @@ impl NSSAUserData { .map(Into::into) } } + + pub fn account_ids(&self) -> impl Iterator { + self.default_pub_account_signing_keys + .keys() + .chain(self.public_key_tree.account_id_map.keys()) + .chain(self.default_user_private_accounts.keys()) + .chain(self.private_key_tree.account_id_map.keys()) + } } impl Default for NSSAUserData { diff --git a/sequencer_core/Cargo.toml b/sequencer_core/Cargo.toml index 06164e9e..a844c524 100644 --- a/sequencer_core/Cargo.toml +++ b/sequencer_core/Cargo.toml @@ -13,6 +13,7 @@ mempool.workspace = true base58.workspace = true anyhow.workspace = true serde.workspace = true +serde_json.workspace = true tempfile.workspace = true chrono.workspace = true log.workspace = true diff --git a/sequencer_core/src/config.rs b/sequencer_core/src/config.rs index 2f4ee3be..4ef08803 100644 --- a/sequencer_core/src/config.rs +++ b/sequencer_core/src/config.rs @@ -1,5 +1,10 @@ -use std::path::PathBuf; +use std::{ + fs::File, + io::BufReader, + path::{Path, PathBuf}, +}; +use anyhow::Result; use serde::{Deserialize, Serialize}; #[derive(Debug, Serialize, Deserialize, Clone)] @@ -43,3 +48,12 @@ pub struct SequencerConfig { /// Sequencer own signing key pub signing_key: [u8; 32], } + +impl SequencerConfig { + pub fn from_path(config_home: &Path) -> Result { + let file = File::open(config_home)?; + let reader = BufReader::new(file); + + Ok(serde_json::from_reader(reader)?) + } +} diff --git a/sequencer_rpc/src/net_utils.rs b/sequencer_rpc/src/net_utils.rs index 33eacae7..8b9b7e64 100644 --- a/sequencer_rpc/src/net_utils.rs +++ b/sequencer_rpc/src/net_utils.rs @@ -1,4 +1,4 @@ -use std::{io, sync::Arc}; +use std::{io, net::SocketAddr, sync::Arc}; use actix_cors::Cors; use actix_web::{App, Error as HttpError, HttpResponse, HttpServer, http, middleware, web}; @@ -42,25 +42,24 @@ fn get_cors(cors_allowed_origins: &[String]) -> Cors { .max_age(3600) } -#[allow(clippy::too_many_arguments)] pub fn new_http_server( config: RpcConfig, seuquencer_core: Arc>, mempool_handle: MemPoolHandle, -) -> io::Result { +) -> io::Result<(actix_web::dev::Server, SocketAddr)> { let RpcConfig { addr, cors_allowed_origins, limits_config, } = config; - info!(target:NETWORK, "Starting http server at {addr}"); + info!(target:NETWORK, "Starting HTTP server at {addr}"); let handler = web::Data::new(JsonHandler { sequencer_state: seuquencer_core.clone(), mempool_handle, }); // HTTP server - Ok(HttpServer::new(move || { + let http_server = HttpServer::new(move || { App::new() .wrap(get_cors(&cors_allowed_origins)) .app_data(handler.clone()) @@ -70,6 +69,14 @@ pub fn new_http_server( }) .bind(addr)? .shutdown_timeout(SHUTDOWN_TIMEOUT_SECS) - .disable_signals() - .run()) + .disable_signals(); + + let [addr] = http_server + .addrs() + .try_into() + .expect("Exactly one address bound is expected for sequencer HTTP server"); + + info!(target:NETWORK, "HTTP server started at {addr}"); + + Ok((http_server.run(), addr)) } diff --git a/sequencer_runner/src/config.rs b/sequencer_runner/src/config.rs deleted file mode 100644 index 58f539bc..00000000 --- a/sequencer_runner/src/config.rs +++ /dev/null @@ -1,11 +0,0 @@ -use std::{fs::File, io::BufReader, path::PathBuf}; - -use anyhow::Result; -use sequencer_core::config::SequencerConfig; - -pub fn from_file(config_home: PathBuf) -> Result { - let file = File::open(config_home)?; - let reader = BufReader::new(file); - - Ok(serde_json::from_reader(reader)?) -} diff --git a/sequencer_runner/src/lib.rs b/sequencer_runner/src/lib.rs index e9f4a844..5c1ab920 100644 --- a/sequencer_runner/src/lib.rs +++ b/sequencer_runner/src/lib.rs @@ -1,4 +1,4 @@ -use std::{path::PathBuf, sync::Arc}; +use std::{net::SocketAddr, path::PathBuf, sync::Arc}; use actix_web::dev::ServerHandle; use anyhow::Result; @@ -9,8 +9,6 @@ use sequencer_core::{SequencerCore, config::SequencerConfig}; use sequencer_rpc::new_http_server; use tokio::{sync::Mutex, task::JoinHandle}; -pub mod config; - pub const RUST_LOG: &str = "RUST_LOG"; #[derive(Parser, Debug)] @@ -22,7 +20,7 @@ struct Args { pub async fn startup_sequencer( app_config: SequencerConfig, -) -> Result<(ServerHandle, JoinHandle>)> { +) -> Result<(ServerHandle, SocketAddr, JoinHandle>)> { let block_timeout = app_config.block_create_timeout_millis; let port = app_config.port; @@ -32,7 +30,7 @@ pub async fn startup_sequencer( let seq_core_wrapped = Arc::new(Mutex::new(sequencer_core)); - let http_server = new_http_server( + let (http_server, addr) = new_http_server( RpcConfig::with_port(port), Arc::clone(&seq_core_wrapped), mempool_handle, @@ -61,7 +59,7 @@ pub async fn startup_sequencer( } }); - Ok((http_server_handle, main_loop_handle)) + Ok((http_server_handle, addr, main_loop_handle)) } pub async fn main_runner() -> Result<()> { @@ -70,7 +68,7 @@ pub async fn main_runner() -> Result<()> { let args = Args::parse(); let Args { home_dir } = args; - let app_config = config::from_file(home_dir.join("sequencer_config.json"))?; + let app_config = SequencerConfig::from_path(&home_dir.join("sequencer_config.json"))?; if let Some(ref rust_log) = app_config.override_rust_log { info!("RUST_LOG env var set to {rust_log:?}"); @@ -81,7 +79,7 @@ pub async fn main_runner() -> Result<()> { } // ToDo: Add restart on failures - let (_, main_loop_handle) = startup_sequencer(app_config).await?; + let (_, _, main_loop_handle) = startup_sequencer(app_config).await?; main_loop_handle.await??; diff --git a/wallet/Cargo.toml b/wallet/Cargo.toml index 9e4b0785..0f88af26 100644 --- a/wallet/Cargo.toml +++ b/wallet/Cargo.toml @@ -25,6 +25,7 @@ rand.workspace = true itertools.workspace = true sha2.workspace = true futures.workspace = true +risc0-zkvm.workspace = true async-stream = "0.3.6" indicatif = { version = "0.18.3", features = ["improved_unicode"] } -risc0-zkvm.workspace = true +optfield = "0.4.0" diff --git a/wallet/src/cli/account.rs b/wallet/src/cli/account.rs index 3a55c55f..58e19847 100644 --- a/wallet/src/cli/account.rs +++ b/wallet/src/cli/account.rs @@ -68,9 +68,7 @@ impl WalletSubcommand for NewSubcommand { "Generated new account with account_id Public/{account_id} at path {chain_index}" ); - let path = wallet_core.store_persistent_data().await?; - - println!("Stored persistent accounts at {path:#?}"); + wallet_core.store_persistent_data().await?; Ok(SubcommandReturnValue::RegisterAccount { account_id }) } @@ -93,9 +91,7 @@ impl WalletSubcommand for NewSubcommand { hex::encode(key.incoming_viewing_public_key.to_bytes()) ); - let path = wallet_core.store_persistent_data().await?; - - println!("Stored persistent accounts at {path:#?}"); + wallet_core.store_persistent_data().await?; Ok(SubcommandReturnValue::RegisterAccount { account_id }) } @@ -257,9 +253,7 @@ impl WalletSubcommand for AccountSubcommand { { wallet_core.last_synced_block = curr_last_block; - let path = wallet_core.store_persistent_data().await?; - - println!("Stored persistent data at {path:#?}"); + wallet_core.store_persistent_data().await?; } else { wallet_core.sync_to_block(curr_last_block).await?; } diff --git a/wallet/src/cli/config.rs b/wallet/src/cli/config.rs index bfebf9bf..15467160 100644 --- a/wallet/src/cli/config.rs +++ b/wallet/src/cli/config.rs @@ -108,9 +108,7 @@ impl WalletSubcommand for ConfigSubcommand { } } - let path = wallet_core.store_config_changes().await?; - - println!("Stored changed config at {path:#?}"); + wallet_core.store_config_changes().await? } ConfigSubcommand::Description { key } => match key.as_str() { "override_rust_log" => { diff --git a/wallet/src/cli/mod.rs b/wallet/src/cli/mod.rs index cf3b2f1c..c742ecbd 100644 --- a/wallet/src/cli/mod.rs +++ b/wallet/src/cli/mod.rs @@ -15,7 +15,6 @@ use crate::{ pinata::PinataProgramAgnosticSubcommand, token::TokenProgramAgnosticSubcommand, }, }, - helperfunctions::{fetch_config, fetch_persistent_storage, merge_auth_config}, }; pub mod account; @@ -97,43 +96,22 @@ pub enum SubcommandReturnValue { SyncedToBlock(u64), } -pub async fn execute_subcommand(command: Command) -> Result { - execute_subcommand_with_auth(command, None).await -} - -pub async fn execute_subcommand_with_auth( +pub async fn execute_subcommand( + wallet_core: &mut WalletCore, command: Command, - auth: Option, ) -> Result { - if fetch_persistent_storage().await.is_err() { - println!("Persistent storage not found, need to execute setup"); - - let password = read_password_from_stdin()?; - execute_setup_with_auth(password, auth.clone()).await?; - } - - let wallet_config = fetch_config().await?; - let wallet_config = merge_auth_config(wallet_config, auth.clone())?; - let mut wallet_core = WalletCore::start_from_config_update_chain(wallet_config).await?; - let subcommand_ret = match command { Command::AuthTransfer(transfer_subcommand) => { - transfer_subcommand - .handle_subcommand(&mut wallet_core) - .await? + transfer_subcommand.handle_subcommand(wallet_core).await? } Command::ChainInfo(chain_subcommand) => { - chain_subcommand.handle_subcommand(&mut wallet_core).await? + chain_subcommand.handle_subcommand(wallet_core).await? } Command::Account(account_subcommand) => { - account_subcommand - .handle_subcommand(&mut wallet_core) - .await? + account_subcommand.handle_subcommand(wallet_core).await? } Command::Pinata(pinata_subcommand) => { - pinata_subcommand - .handle_subcommand(&mut wallet_core) - .await? + pinata_subcommand.handle_subcommand(wallet_core).await? } Command::CheckHealth {} => { let remote_program_ids = wallet_core @@ -165,18 +143,15 @@ pub async fn execute_subcommand_with_auth( SubcommandReturnValue::Empty } - Command::Token(token_subcommand) => { - token_subcommand.handle_subcommand(&mut wallet_core).await? - } - Command::AMM(amm_subcommand) => amm_subcommand.handle_subcommand(&mut wallet_core).await?, + Command::Token(token_subcommand) => token_subcommand.handle_subcommand(wallet_core).await?, + Command::AMM(amm_subcommand) => amm_subcommand.handle_subcommand(wallet_core).await?, Command::Config(config_subcommand) => { - config_subcommand - .handle_subcommand(&mut wallet_core) - .await? + config_subcommand.handle_subcommand(wallet_core).await? } Command::RestoreKeys { depth } => { let password = read_password_from_stdin()?; - execute_keys_restoration_with_auth(password, depth, auth).await?; + wallet_core.reset_storage(password)?; + execute_keys_restoration(wallet_core, depth).await?; SubcommandReturnValue::Empty } @@ -200,14 +175,7 @@ pub async fn execute_subcommand_with_auth( Ok(subcommand_ret) } -pub async fn execute_continuous_run() -> Result<()> { - execute_continuous_run_with_auth(None).await -} -pub async fn execute_continuous_run_with_auth(auth: Option) -> Result<()> { - let config = fetch_config().await?; - let config = merge_auth_config(config, auth)?; - let mut wallet_core = WalletCore::start_from_config_update_chain(config.clone()).await?; - +pub async fn execute_continuous_run(wallet_core: &mut WalletCore) -> Result<()> { loop { let latest_block_num = wallet_core .sequencer_client @@ -217,7 +185,7 @@ pub async fn execute_continuous_run_with_auth(auth: Option) -> Result<() wallet_core.sync_to_block(latest_block_num).await?; tokio::time::sleep(std::time::Duration::from_millis( - config.seq_poll_timeout_millis, + wallet_core.config().seq_poll_timeout_millis, )) .await; } @@ -233,34 +201,7 @@ pub fn read_password_from_stdin() -> Result { Ok(password.trim().to_string()) } -pub async fn execute_setup(password: String) -> Result<()> { - execute_setup_with_auth(password, None).await -} - -pub async fn execute_setup_with_auth(password: String, auth: Option) -> Result<()> { - let config = fetch_config().await?; - let config = merge_auth_config(config, auth)?; - let wallet_core = WalletCore::start_from_config_new_storage(config.clone(), password).await?; - - wallet_core.store_persistent_data().await?; - - Ok(()) -} - -pub async fn execute_keys_restoration(password: String, depth: u32) -> Result<()> { - execute_keys_restoration_with_auth(password, depth, None).await -} - -pub async fn execute_keys_restoration_with_auth( - password: String, - depth: u32, - auth: Option, -) -> Result<()> { - let config = fetch_config().await?; - let config = merge_auth_config(config, auth)?; - let mut wallet_core = - WalletCore::start_from_config_new_storage(config.clone(), password.clone()).await?; - +pub async fn execute_keys_restoration(wallet_core: &mut WalletCore, depth: u32) -> Result<()> { wallet_core .storage .user_data diff --git a/wallet/src/cli/programs/native_token_transfer.rs b/wallet/src/cli/programs/native_token_transfer.rs index 7868a7cd..0cfc0fb6 100644 --- a/wallet/src/cli/programs/native_token_transfer.rs +++ b/wallet/src/cli/programs/native_token_transfer.rs @@ -69,9 +69,7 @@ impl WalletSubcommand for AuthTransferSubcommand { println!("Transaction data is {transfer_tx:?}"); - let path = wallet_core.store_persistent_data().await?; - - println!("Stored persistent accounts at {path:#?}"); + wallet_core.store_persistent_data().await?; } AccountPrivacyKind::Private => { let account_id = account_id.parse()?; @@ -96,9 +94,7 @@ impl WalletSubcommand for AuthTransferSubcommand { )?; } - let path = wallet_core.store_persistent_data().await?; - - println!("Stored persistent accounts at {path:#?}"); + wallet_core.store_persistent_data().await?; } } @@ -337,9 +333,7 @@ impl WalletSubcommand for NativeTokenTransferProgramSubcommandPrivate { )?; } - let path = wallet_core.store_persistent_data().await?; - - println!("Stored persistent accounts at {path:#?}"); + wallet_core.store_persistent_data().await?; Ok(SubcommandReturnValue::PrivacyPreservingTransfer { tx_hash }) } @@ -381,9 +375,7 @@ impl WalletSubcommand for NativeTokenTransferProgramSubcommandPrivate { )?; } - let path = wallet_core.store_persistent_data().await?; - - println!("Stored persistent accounts at {path:#?}"); + wallet_core.store_persistent_data().await?; Ok(SubcommandReturnValue::PrivacyPreservingTransfer { tx_hash }) } @@ -421,9 +413,7 @@ impl WalletSubcommand for NativeTokenTransferProgramSubcommandShielded { )?; } - let path = wallet_core.store_persistent_data().await?; - - println!("Stored persistent accounts at {path:#?}"); + wallet_core.store_persistent_data().await?; Ok(SubcommandReturnValue::PrivacyPreservingTransfer { tx_hash }) } @@ -454,9 +444,7 @@ impl WalletSubcommand for NativeTokenTransferProgramSubcommandShielded { let tx_hash = res.tx_hash; - let path = wallet_core.store_persistent_data().await?; - - println!("Stored persistent accounts at {path:#?}"); + wallet_core.store_persistent_data().await?; Ok(SubcommandReturnValue::PrivacyPreservingTransfer { tx_hash }) } @@ -500,9 +488,7 @@ impl WalletSubcommand for NativeTokenTransferProgramSubcommand { )?; } - let path = wallet_core.store_persistent_data().await?; - - println!("Stored persistent accounts at {path:#?}"); + wallet_core.store_persistent_data().await?; Ok(SubcommandReturnValue::PrivacyPreservingTransfer { tx_hash }) } @@ -520,9 +506,7 @@ impl WalletSubcommand for NativeTokenTransferProgramSubcommand { println!("Transaction data is {transfer_tx:?}"); - let path = wallet_core.store_persistent_data().await?; - - println!("Stored persistent accounts at {path:#?}"); + wallet_core.store_persistent_data().await?; Ok(SubcommandReturnValue::Empty) } diff --git a/wallet/src/cli/programs/pinata.rs b/wallet/src/cli/programs/pinata.rs index e0b65709..192d3f47 100644 --- a/wallet/src/cli/programs/pinata.rs +++ b/wallet/src/cli/programs/pinata.rs @@ -168,9 +168,7 @@ impl WalletSubcommand for PinataProgramSubcommandPrivate { )?; } - let path = wallet_core.store_persistent_data().await?; - - println!("Stored persistent accounts at {path:#?}"); + wallet_core.store_persistent_data().await?; Ok(SubcommandReturnValue::PrivacyPreservingTransfer { tx_hash }) } diff --git a/wallet/src/cli/programs/token.rs b/wallet/src/cli/programs/token.rs index 25d61ff3..b5ea1b34 100644 --- a/wallet/src/cli/programs/token.rs +++ b/wallet/src/cli/programs/token.rs @@ -740,9 +740,7 @@ impl WalletSubcommand for TokenProgramSubcommandPrivate { )?; } - let path = wallet_core.store_persistent_data().await?; - - println!("Stored persistent accounts at {path:#?}"); + wallet_core.store_persistent_data().await?; Ok(SubcommandReturnValue::PrivacyPreservingTransfer { tx_hash }) } @@ -790,9 +788,7 @@ impl WalletSubcommand for TokenProgramSubcommandPrivate { )?; } - let path = wallet_core.store_persistent_data().await?; - - println!("Stored persistent accounts at {path:#?}"); + wallet_core.store_persistent_data().await?; Ok(SubcommandReturnValue::PrivacyPreservingTransfer { tx_hash }) } @@ -831,9 +827,7 @@ impl WalletSubcommand for TokenProgramSubcommandPrivate { )?; } - let path = wallet_core.store_persistent_data().await?; - - println!("Stored persistent accounts at {path:#?}"); + wallet_core.store_persistent_data().await?; Ok(SubcommandReturnValue::PrivacyPreservingTransfer { tx_hash }) } @@ -872,9 +866,7 @@ impl WalletSubcommand for TokenProgramSubcommandPrivate { )?; } - let path = wallet_core.store_persistent_data().await?; - - println!("Stored persistent accounts at {path:#?}"); + wallet_core.store_persistent_data().await?; Ok(SubcommandReturnValue::PrivacyPreservingTransfer { tx_hash }) } @@ -923,9 +915,7 @@ impl WalletSubcommand for TokenProgramSubcommandPrivate { )?; } - let path = wallet_core.store_persistent_data().await?; - - println!("Stored persistent accounts at {path:#?}"); + wallet_core.store_persistent_data().await?; Ok(SubcommandReturnValue::PrivacyPreservingTransfer { tx_hash }) } @@ -971,9 +961,7 @@ impl WalletSubcommand for TokenProgramSubcommandDeshielded { )?; } - let path = wallet_core.store_persistent_data().await?; - - println!("Stored persistent accounts at {path:#?}"); + wallet_core.store_persistent_data().await?; Ok(SubcommandReturnValue::PrivacyPreservingTransfer { tx_hash }) } @@ -1009,9 +997,7 @@ impl WalletSubcommand for TokenProgramSubcommandDeshielded { )?; } - let path = wallet_core.store_persistent_data().await?; - - println!("Stored persistent accounts at {path:#?}"); + wallet_core.store_persistent_data().await?; Ok(SubcommandReturnValue::PrivacyPreservingTransfer { tx_hash }) } @@ -1047,9 +1033,7 @@ impl WalletSubcommand for TokenProgramSubcommandDeshielded { )?; } - let path = wallet_core.store_persistent_data().await?; - - println!("Stored persistent accounts at {path:#?}"); + wallet_core.store_persistent_data().await?; Ok(SubcommandReturnValue::PrivacyPreservingTransfer { tx_hash }) } @@ -1102,9 +1086,7 @@ impl WalletSubcommand for TokenProgramSubcommandShielded { println!("Transaction data is {:?}", tx.message); } - let path = wallet_core.store_persistent_data().await?; - - println!("Stored persistent accounts at {path:#?}"); + wallet_core.store_persistent_data().await?; Ok(SubcommandReturnValue::PrivacyPreservingTransfer { tx_hash }) } @@ -1140,9 +1122,7 @@ impl WalletSubcommand for TokenProgramSubcommandShielded { )?; } - let path = wallet_core.store_persistent_data().await?; - - println!("Stored persistent accounts at {path:#?}"); + wallet_core.store_persistent_data().await?; Ok(SubcommandReturnValue::PrivacyPreservingTransfer { tx_hash }) } @@ -1178,9 +1158,7 @@ impl WalletSubcommand for TokenProgramSubcommandShielded { )?; } - let path = wallet_core.store_persistent_data().await?; - - println!("Stored persistent accounts at {path:#?}"); + wallet_core.store_persistent_data().await?; Ok(SubcommandReturnValue::PrivacyPreservingTransfer { tx_hash }) } @@ -1216,9 +1194,7 @@ impl WalletSubcommand for TokenProgramSubcommandShielded { )?; } - let path = wallet_core.store_persistent_data().await?; - - println!("Stored persistent accounts at {path:#?}"); + wallet_core.store_persistent_data().await?; Ok(SubcommandReturnValue::PrivacyPreservingTransfer { tx_hash }) } @@ -1262,9 +1238,7 @@ impl WalletSubcommand for TokenProgramSubcommandShielded { println!("Transaction data is {:?}", tx.message); } - let path = wallet_core.store_persistent_data().await?; - - println!("Stored persistent accounts at {path:#?}"); + wallet_core.store_persistent_data().await?; Ok(SubcommandReturnValue::PrivacyPreservingTransfer { tx_hash }) } @@ -1323,9 +1297,7 @@ impl WalletSubcommand for CreateNewTokenProgramSubcommand { )?; } - let path = wallet_core.store_persistent_data().await?; - - println!("Stored persistent accounts at {path:#?}"); + wallet_core.store_persistent_data().await?; Ok(SubcommandReturnValue::PrivacyPreservingTransfer { tx_hash }) } @@ -1371,9 +1343,7 @@ impl WalletSubcommand for CreateNewTokenProgramSubcommand { )?; } - let path = wallet_core.store_persistent_data().await?; - - println!("Stored persistent accounts at {path:#?}"); + wallet_core.store_persistent_data().await?; Ok(SubcommandReturnValue::PrivacyPreservingTransfer { tx_hash }) } @@ -1419,9 +1389,7 @@ impl WalletSubcommand for CreateNewTokenProgramSubcommand { )?; } - let path = wallet_core.store_persistent_data().await?; - - println!("Stored persistent accounts at {path:#?}"); + wallet_core.store_persistent_data().await?; Ok(SubcommandReturnValue::PrivacyPreservingTransfer { tx_hash }) } diff --git a/wallet/src/config.rs b/wallet/src/config.rs index c06ccc49..45407b6d 100644 --- a/wallet/src/config.rs +++ b/wallet/src/config.rs @@ -1,11 +1,17 @@ -use std::str::FromStr; +use std::{ + io::{BufReader, Write as _}, + path::Path, + str::FromStr, +}; +use anyhow::{Context as _, Result}; use key_protocol::key_management::{ KeyChain, key_tree::{ chain_index::ChainIndex, keys_private::ChildKeysPrivate, keys_public::ChildKeysPublic, }, }; +use log::warn; use serde::{Deserialize, Serialize}; #[derive(Debug, Clone, Serialize, Deserialize)] @@ -105,6 +111,25 @@ pub struct PersistentStorage { pub last_synced_block: u64, } +impl PersistentStorage { + pub fn from_path(path: &Path) -> Result { + match std::fs::File::open(path) { + Ok(file) => { + let storage_content = BufReader::new(file); + Ok(serde_json::from_reader(storage_content)?) + } + Err(err) => match err.kind() { + std::io::ErrorKind::NotFound => { + anyhow::bail!("Not found, please setup roots from config command beforehand"); + } + _ => { + anyhow::bail!("IO error {err:#?}"); + } + }, + } + } +} + impl InitialAccountData { pub fn account_id(&self) -> nssa::AccountId { match &self { @@ -172,9 +197,11 @@ pub struct GasConfig { pub gas_limit_runtime: u64, } +#[optfield::optfield(pub WalletConfigOverrides, rewrap, attrs = (derive(Debug, Default)))] #[derive(Debug, Clone, Serialize, Deserialize)] pub struct WalletConfig { /// Override rust log (env var logging level) + #[serde(skip_serializing_if = "Option::is_none")] pub override_rust_log: Option, /// Sequencer URL pub sequencer_addr: String, @@ -189,6 +216,7 @@ pub struct WalletConfig { /// Initial accounts for wallet pub initial_accounts: Vec, /// Basic authentication credentials + #[serde(skip_serializing_if = "Option::is_none")] pub basic_auth: Option, } @@ -748,3 +776,98 @@ impl Default for WalletConfig { } } } + +impl WalletConfig { + pub fn from_path_or_initialize_default(config_path: &Path) -> Result { + match std::fs::File::open(config_path) { + Ok(file) => { + let reader = std::io::BufReader::new(file); + Ok(serde_json::from_reader(reader)?) + } + Err(err) if err.kind() == std::io::ErrorKind::NotFound => { + println!("Config not found, setting up default config"); + + let config_home = config_path.parent().ok_or_else(|| { + anyhow::anyhow!( + "Could not get parent directory of config file at {config_path:#?}" + ) + })?; + std::fs::create_dir_all(config_home)?; + + println!("Created configs dir at path {config_home:#?}"); + + let mut file = std::fs::OpenOptions::new() + .write(true) + .create(true) + .truncate(true) + .open(config_path)?; + + let config = WalletConfig::default(); + let default_config_serialized = serde_json::to_vec_pretty(&config).unwrap(); + + file.write_all(&default_config_serialized)?; + + println!("Configs set up"); + Ok(config) + } + Err(err) => Err(err).context("IO error"), + } + } + + pub fn apply_overrides(&mut self, overrides: WalletConfigOverrides) { + let WalletConfig { + override_rust_log, + sequencer_addr, + seq_poll_timeout_millis, + seq_tx_poll_max_blocks, + seq_poll_max_retries, + seq_block_poll_max_amount, + initial_accounts, + basic_auth, + } = self; + + let WalletConfigOverrides { + override_rust_log: o_override_rust_log, + sequencer_addr: o_sequencer_addr, + seq_poll_timeout_millis: o_seq_poll_timeout_millis, + seq_tx_poll_max_blocks: o_seq_tx_poll_max_blocks, + seq_poll_max_retries: o_seq_poll_max_retries, + seq_block_poll_max_amount: o_seq_block_poll_max_amount, + initial_accounts: o_initial_accounts, + basic_auth: o_basic_auth, + } = overrides; + + if let Some(v) = o_override_rust_log { + warn!("Overriding wallet config 'override_rust_log' to {v:#?}"); + *override_rust_log = v; + } + if let Some(v) = o_sequencer_addr { + warn!("Overriding wallet config 'sequencer_addr' to {v}"); + *sequencer_addr = v; + } + if let Some(v) = o_seq_poll_timeout_millis { + warn!("Overriding wallet config 'seq_poll_timeout_millis' to {v}"); + *seq_poll_timeout_millis = v; + } + if let Some(v) = o_seq_tx_poll_max_blocks { + warn!("Overriding wallet config 'seq_tx_poll_max_blocks' to {v}"); + *seq_tx_poll_max_blocks = v; + } + if let Some(v) = o_seq_poll_max_retries { + warn!("Overriding wallet config 'seq_poll_max_retries' to {v}"); + *seq_poll_max_retries = v; + } + if let Some(v) = o_seq_block_poll_max_amount { + warn!("Overriding wallet config 'seq_block_poll_max_amount' to {v}"); + *seq_block_poll_max_amount = v; + } + if let Some(v) = o_initial_accounts { + warn!("Overriding wallet config 'initial_accounts' to {v:#?}"); + *initial_accounts = v; + } + if let Some(v) = o_basic_auth { + warn!("Overriding wallet config 'basic_auth' to {v:#?}"); + *basic_auth = v; + } + } +} diff --git a/wallet/src/helperfunctions.rs b/wallet/src/helperfunctions.rs index 5f1dcf77..23bf4bb8 100644 --- a/wallet/src/helperfunctions.rs +++ b/wallet/src/helperfunctions.rs @@ -7,23 +7,22 @@ use nssa::Account; use nssa_core::account::Nonce; use rand::{RngCore, rngs::OsRng}; use serde::Serialize; -use tokio::io::{AsyncReadExt, AsyncWriteExt}; use crate::{ HOME_DIR_ENV_VAR, config::{ - BasicAuth, InitialAccountData, InitialAccountDataPrivate, InitialAccountDataPublic, - PersistentAccountDataPrivate, PersistentAccountDataPublic, PersistentStorage, WalletConfig, + InitialAccountData, InitialAccountDataPrivate, InitialAccountDataPublic, + PersistentAccountDataPrivate, PersistentAccountDataPublic, PersistentStorage, }, }; /// Get home dir for wallet. Env var `NSSA_WALLET_HOME_DIR` must be set before execution to succeed. -pub fn get_home_nssa_var() -> Result { +fn get_home_nssa_var() -> Result { Ok(PathBuf::from_str(&std::env::var(HOME_DIR_ENV_VAR)?)?) } /// Get home dir for wallet. Env var `HOME` must be set before execution to succeed. -pub fn get_home_default_path() -> Result { +fn get_home_default_path() -> Result { std::env::home_dir() .map(|path| path.join(".nssa").join("wallet")) .ok_or(anyhow::anyhow!("Failed to get HOME")) @@ -38,96 +37,20 @@ pub fn get_home() -> Result { } } -/// Fetch config from default home -pub async fn fetch_config() -> Result { - let config_home = get_home()?; - let mut config_needs_setup = false; - - let config = match tokio::fs::OpenOptions::new() - .read(true) - .open(config_home.join("wallet_config.json")) - .await - { - Ok(mut file) => { - let mut config_contents = vec![]; - file.read_to_end(&mut config_contents).await?; - - serde_json::from_slice(&config_contents)? - } - Err(err) => match err.kind() { - std::io::ErrorKind::NotFound => { - config_needs_setup = true; - - println!("Config not found, setting up default config"); - - WalletConfig::default() - } - _ => anyhow::bail!("IO error {err:#?}"), - }, - }; - - if config_needs_setup { - tokio::fs::create_dir_all(&config_home).await?; - - println!("Created configs dir at path {config_home:#?}"); - - let mut file = tokio::fs::OpenOptions::new() - .write(true) - .create(true) - .truncate(true) - .open(config_home.join("wallet_config.json")) - .await?; - - let default_config_serialized = - serde_json::to_vec_pretty(&WalletConfig::default()).unwrap(); - - file.write_all(&default_config_serialized).await?; - - println!("Configs setted up"); - } - - Ok(config) +/// Fetch config path from default home +pub fn fetch_config_path() -> Result { + let home = get_home()?; + let config_path = home.join("wallet_config.json"); + Ok(config_path) } -/// Parse CLI auth string and merge with config auth, prioritizing CLI -pub fn merge_auth_config( - mut config: WalletConfig, - cli_auth: Option, -) -> Result { - if let Some(auth_str) = cli_auth { - let cli_auth_config: BasicAuth = auth_str.parse()?; - - if config.basic_auth.is_some() { - println!("Warning: CLI auth argument takes precedence over config basic-auth"); - } - - config.basic_auth = Some(cli_auth_config); - } - Ok(config) -} - -/// Fetch data stored at home +/// Fetch path to data storage from default home /// /// File must be created through setup beforehand. -pub async fn fetch_persistent_storage() -> Result { +pub fn fetch_persistent_storage_path() -> Result { let home = get_home()?; let accs_path = home.join("storage.json"); - let mut storage_content = vec![]; - - match tokio::fs::File::open(accs_path).await { - Ok(mut file) => { - file.read_to_end(&mut storage_content).await?; - Ok(serde_json::from_slice(&storage_content)?) - } - Err(err) => match err.kind() { - std::io::ErrorKind::NotFound => { - anyhow::bail!("Not found, please setup roots from config command beforehand"); - } - _ => { - anyhow::bail!("IO error {err:#?}"); - } - }, - } + Ok(accs_path) } /// Produces data for storage diff --git a/wallet/src/lib.rs b/wallet/src/lib.rs index bad6435a..7c3da737 100644 --- a/wallet/src/lib.rs +++ b/wallet/src/lib.rs @@ -1,6 +1,6 @@ use std::{path::PathBuf, sync::Arc}; -use anyhow::Result; +use anyhow::{Context, Result}; use base64::{Engine, engine::general_purpose::STANDARD as BASE64}; use chain_storage::WalletChainStore; use common::{ @@ -25,10 +25,8 @@ pub use privacy_preserving_tx::PrivacyPreservingAccount; use tokio::io::AsyncWriteExt; use crate::{ - config::PersistentStorage, - helperfunctions::{ - fetch_persistent_storage, get_home, produce_data_for_storage, produce_random_nonces, - }, + config::{PersistentStorage, WalletConfigOverrides}, + helperfunctions::{produce_data_for_storage, produce_random_nonces}, poller::TxPoller, }; @@ -124,91 +122,133 @@ impl TokenHolding { } pub struct WalletCore { - pub storage: WalletChainStore, - pub poller: TxPoller, + config_path: PathBuf, + storage: WalletChainStore, + storage_path: PathBuf, + poller: TxPoller, + // TODO: Make all fields private pub sequencer_client: Arc, pub last_synced_block: u64, } impl WalletCore { - pub async fn start_from_config_update_chain(config: WalletConfig) -> Result { - let basic_auth = config - .basic_auth - .as_ref() - .map(|auth| (auth.username.clone(), auth.password.clone())); - let client = Arc::new(SequencerClient::new_with_auth( - config.sequencer_addr.clone(), - basic_auth, - )?); - let tx_poller = TxPoller::new(config.clone(), client.clone()); + /// Construct wallet using [`HOME_DIR_ENV_VAR`] env var for paths or user home dir if not set. + pub fn from_env() -> Result { + let config_path = helperfunctions::fetch_config_path()?; + let storage_path = helperfunctions::fetch_persistent_storage_path()?; + Self::new_update_chain(config_path, storage_path, None) + } + + pub fn new_update_chain( + config_path: PathBuf, + storage_path: PathBuf, + config_overrides: Option, + ) -> Result { let PersistentStorage { accounts: persistent_accounts, last_synced_block, - } = fetch_persistent_storage().await?; + } = PersistentStorage::from_path(&storage_path) + .with_context(|| format!("Failed to read persistent storage at {storage_path:#?}"))?; - let storage = WalletChainStore::new(config, persistent_accounts)?; - - Ok(Self { - storage, - poller: tx_poller, - sequencer_client: client.clone(), + Self::new( + config_path, + storage_path, + config_overrides, + |config| WalletChainStore::new(config, persistent_accounts), last_synced_block, - }) + ) } - pub async fn start_from_config_new_storage( - config: WalletConfig, + pub fn new_init_storage( + config_path: PathBuf, + storage_path: PathBuf, + config_overrides: Option, password: String, ) -> Result { + Self::new( + config_path, + storage_path, + config_overrides, + |config| WalletChainStore::new_storage(config, password), + 0, + ) + } + + fn new( + config_path: PathBuf, + storage_path: PathBuf, + config_overrides: Option, + storage_ctor: impl FnOnce(WalletConfig) -> Result, + last_synced_block: u64, + ) -> Result { + let mut config = WalletConfig::from_path_or_initialize_default(&config_path) + .with_context(|| format!("Failed to deserialize wallet config at {config_path:#?}"))?; + if let Some(config_overrides) = config_overrides { + config.apply_overrides(config_overrides); + } + let basic_auth = config .basic_auth .as_ref() .map(|auth| (auth.username.clone(), auth.password.clone())); - let client = Arc::new(SequencerClient::new_with_auth( + let sequencer_client = Arc::new(SequencerClient::new_with_auth( config.sequencer_addr.clone(), basic_auth, )?); - let tx_poller = TxPoller::new(config.clone(), client.clone()); + let tx_poller = TxPoller::new(config.clone(), Arc::clone(&sequencer_client)); - let storage = WalletChainStore::new_storage(config, password)?; + let storage = storage_ctor(config)?; Ok(Self { + config_path, + storage_path, storage, poller: tx_poller, - sequencer_client: client.clone(), - last_synced_block: 0, + sequencer_client, + last_synced_block, }) } - /// Store persistent data at home - pub async fn store_persistent_data(&self) -> Result { - let home = get_home()?; - let storage_path = home.join("storage.json"); + /// Get configuration with applied overrides + pub fn config(&self) -> &WalletConfig { + &self.storage.wallet_config + } - let data = produce_data_for_storage(&self.storage.user_data, self.last_synced_block); - let storage = serde_json::to_vec_pretty(&data)?; + /// Get storage + pub fn storage(&self) -> &WalletChainStore { + &self.storage + } - let mut storage_file = tokio::fs::File::create(storage_path.as_path()).await?; - storage_file.write_all(&storage).await?; - - info!("Stored data at {storage_path:#?}"); - - Ok(storage_path) + /// Reset storage + pub fn reset_storage(&mut self, password: String) -> Result<()> { + self.storage = WalletChainStore::new_storage(self.storage.wallet_config.clone(), password)?; + Ok(()) } /// Store persistent data at home - pub async fn store_config_changes(&self) -> Result { - let home = get_home()?; - let config_path = home.join("wallet_config.json"); + pub async fn store_persistent_data(&self) -> Result<()> { + let data = produce_data_for_storage(&self.storage.user_data, self.last_synced_block); + let storage = serde_json::to_vec_pretty(&data)?; + + let mut storage_file = tokio::fs::File::create(&self.storage_path).await?; + storage_file.write_all(&storage).await?; + + println!("Stored persistent accounts at {:#?}", self.storage_path); + + Ok(()) + } + + /// Store persistent data at home + pub async fn store_config_changes(&self) -> Result<()> { let config = serde_json::to_vec_pretty(&self.storage.wallet_config)?; - let mut config_file = tokio::fs::File::create(config_path.as_path()).await?; + let mut config_file = tokio::fs::File::create(&self.config_path).await?; config_file.write_all(&config).await?; - info!("Stored data at {config_path:#?}"); + info!("Stored data at {:#?}", self.config_path); - Ok(config_path) + Ok(()) } pub fn create_new_account_public( diff --git a/wallet/src/main.rs b/wallet/src/main.rs index 708b1fcf..045b1b83 100644 --- a/wallet/src/main.rs +++ b/wallet/src/main.rs @@ -1,36 +1,65 @@ -use anyhow::Result; +use anyhow::{Context as _, Result}; use clap::{CommandFactory as _, Parser as _}; -use tokio::runtime::Builder; -use wallet::cli::{Args, execute_continuous_run_with_auth, execute_subcommand_with_auth}; - -pub const NUM_THREADS: usize = 2; +use wallet::{ + WalletCore, + cli::{Args, execute_continuous_run, execute_subcommand, read_password_from_stdin}, + config::WalletConfigOverrides, + helperfunctions::{fetch_config_path, fetch_persistent_storage_path}, +}; // TODO #169: We have sample configs for sequencer, but not for wallet // TODO #168: Why it requires config as a directory? Maybe better to deduce directory from config // file path? // TODO #172: Why it requires config as env var while sequencer_runner accepts as // argument? -fn main() -> Result<()> { - let runtime = Builder::new_multi_thread() - .worker_threads(NUM_THREADS) - .enable_all() - .build() - .unwrap(); - - let args = Args::parse(); +#[tokio::main] +async fn main() -> Result<()> { + let Args { + continuous_run, + auth, + command, + } = Args::parse(); env_logger::init(); - runtime.block_on(async move { - if let Some(command) = args.command { - let _output = execute_subcommand_with_auth(command, args.auth).await?; - Ok(()) - } else if args.continuous_run { - execute_continuous_run_with_auth(args.auth).await + let config_path = fetch_config_path().context("Could not fetch config path")?; + let storage_path = + fetch_persistent_storage_path().context("Could not fetch persistent storage path")?; + + // Override basic auth if provided via CLI + let config_overrides = WalletConfigOverrides { + basic_auth: auth.map(|auth| auth.parse()).transpose()?.map(Some), + ..Default::default() + }; + + if let Some(command) = command { + let mut wallet = if !storage_path.exists() { + // TODO: Maybe move to `WalletCore::from_env()` or similar? + + println!("Persistent storage not found, need to execute setup"); + + let password = read_password_from_stdin()?; + let wallet = WalletCore::new_init_storage( + config_path, + storage_path, + Some(config_overrides), + password, + )?; + + wallet.store_persistent_data().await?; + wallet } else { - let help = Args::command().render_long_help(); - println!("{help}"); - Ok(()) - } - }) + WalletCore::new_update_chain(config_path, storage_path, Some(config_overrides))? + }; + let _output = execute_subcommand(&mut wallet, command).await?; + Ok(()) + } else if continuous_run { + let mut wallet = + WalletCore::new_update_chain(config_path, storage_path, Some(config_overrides))?; + execute_continuous_run(&mut wallet).await + } else { + let help = Args::command().render_long_help(); + println!("{help}"); + Ok(()) + } } diff --git a/wallet/src/program_facades/native_token_transfer/private.rs b/wallet/src/program_facades/native_token_transfer/private.rs index 0baeeac8..da98aed1 100644 --- a/wallet/src/program_facades/native_token_transfer/private.rs +++ b/wallet/src/program_facades/native_token_transfer/private.rs @@ -15,11 +15,10 @@ impl NativeTokenTransfer<'_> { let instruction: u128 = 0; self.0 - .send_privacy_preserving_tx_with_pre_check( + .send_privacy_preserving_tx( vec![PrivacyPreservingAccount::PrivateOwned(from)], &Program::serialize_instruction(instruction).unwrap(), &Program::authenticated_transfer_program().into(), - |_| Ok(()), ) .await .map(|(resp, secrets)| { From f9032a676aa7004b9c9b2fc2df30a71fcb67fee3 Mon Sep 17 00:00:00 2001 From: Daniil Polyakov Date: Wed, 31 Dec 2025 04:33:18 +0300 Subject: [PATCH 04/18] chore: fix cargo machete --- Cargo.lock | 1 - sequencer_runner/Cargo.toml | 1 - 2 files changed, 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 50edef2f..9ccc65b4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4989,7 +4989,6 @@ dependencies = [ "log", "sequencer_core", "sequencer_rpc", - "serde_json", "tokio", ] diff --git a/sequencer_runner/Cargo.toml b/sequencer_runner/Cargo.toml index 72001451..55f56dec 100644 --- a/sequencer_runner/Cargo.toml +++ b/sequencer_runner/Cargo.toml @@ -10,7 +10,6 @@ sequencer_rpc.workspace = true clap = { workspace = true, features = ["derive", "env"] } anyhow.workspace = true -serde_json.workspace = true env_logger.workspace = true log.workspace = true actix.workspace = true From 4f5c3a831e021acafcafb88fa7ad7743ef28b01f Mon Sep 17 00:00:00 2001 From: Daniil Polyakov Date: Wed, 31 Dec 2025 13:50:11 +0300 Subject: [PATCH 05/18] fix: skip tps_test in ci --- .github/workflows/ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ace2d07b..38161a0f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -101,11 +101,11 @@ jobs: - name: Install nextest run: cargo install cargo-nextest - - name: Run unit tests + - name: Run tests env: RISC0_DEV_MODE: "1" RUST_LOG: "info" - run: cargo nextest run --no-fail-fast + run: cargo nextest run --no-fail-fast -- --skip tps_test valid-proof-test: runs-on: ubuntu-latest From 72a3e406b849de27c21770164e52eb1845358d0a Mon Sep 17 00:00:00 2001 From: Sergio Chouhy Date: Tue, 6 Jan 2026 16:57:03 -0300 Subject: [PATCH 06/18] add r0vm binary to runtime stage --- sequencer_runner/Dockerfile | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/sequencer_runner/Dockerfile b/sequencer_runner/Dockerfile index 3b2153c3..84df3f3c 100644 --- a/sequencer_runner/Dockerfile +++ b/sequencer_runner/Dockerfile @@ -7,6 +7,7 @@ RUN apt-get update && apt-get install -y \ libssl-dev \ libclang-dev \ clang \ + curl \ && rm -rf /var/lib/apt/lists/* WORKDIR /sequencer_runner @@ -31,6 +32,14 @@ RUN cargo build --release --bin sequencer_runner # Strip debug symbols to reduce binary size RUN strip /sequencer_runner/target/release/sequencer_runner +# Install r0vm +RUN curl -L https://risczero.com/install | bash +ENV PATH="/root/.cargo/bin:/root/.risc0/bin:${PATH}" +RUN rzup install +RUN cp "$(which r0vm)" /usr/local/bin/r0vm +RUN test -x /usr/local/bin/r0vm +RUN r0vm --version + # Runtime stage - minimal image FROM debian:trixie-slim @@ -47,6 +56,9 @@ RUN useradd -m -u 1000 -s /bin/bash sequencer_user && \ # Copy binary from builder COPY --from=builder --chown=sequencer_user:sequencer_user /sequencer_runner/target/release/sequencer_runner /usr/local/bin/sequencer_runner +# Copy r0vm binary from builder +COPY --from=builder --chown=sequencer_user:sequencer_user /usr/local/bin/r0vm /usr/local/bin/r0vm + # Copy entrypoint script COPY sequencer_runner/docker-entrypoint.sh /docker-entrypoint.sh RUN chmod +x /docker-entrypoint.sh @@ -71,6 +83,9 @@ HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \ # Run the application ENV RUST_LOG=info +# Set explicit location for r0vm binary +ENV RISC0_SERVER_PATH=/usr/local/bin/r0vm + USER root ENTRYPOINT ["/docker-entrypoint.sh"] From ccce2213da731e671af15e85859f994d0e855256 Mon Sep 17 00:00:00 2001 From: Sergio Chouhy Date: Tue, 13 Jan 2026 16:53:00 -0300 Subject: [PATCH 07/18] sequencer posts blocks to bedrock --- Cargo.lock | 332 ++++++++++++++++-- Cargo.toml | 5 +- bedrock_client/Cargo.toml | 1 + common/src/block.rs | 2 +- .../configs/sequencer/sequencer_config.json | 6 +- integration_tests/tests/tps.rs | 1 + sequencer_core/Cargo.toml | 7 + sequencer_core/src/block_settlement_client.rs | 118 +++++++ sequencer_core/src/config.rs | 10 + sequencer_core/src/lib.rs | 56 ++- sequencer_rpc/src/process.rs | 1 + sequencer_runner/Cargo.toml | 1 + .../configs/debug/sequencer_config.json | 8 +- sequencer_runner/src/lib.rs | 2 +- 14 files changed, 502 insertions(+), 48 deletions(-) create mode 100644 sequencer_core/src/block_settlement_client.rs diff --git a/Cargo.lock b/Cargo.lock index 9ccc65b4..deee8b1a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -367,6 +367,15 @@ version = "1.0.100" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" +[[package]] +name = "archery" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70e0a5f99dfebb87bb342d0f53bb92c81842e100bbb915223e38349580e5441d" +dependencies = [ + "triomphe", +] + [[package]] name = "ark-bls12-381" version = "0.4.0" @@ -948,6 +957,7 @@ version = "0.1.0" dependencies = [ "anyhow", "common-http-client", + "key-management-system-service", "reqwest", ] @@ -1098,7 +1108,7 @@ dependencies = [ [[package]] name = "broadcast-service" version = "0.1.0" -source = "git+https://github.com/logos-blockchain/logos-blockchain.git?branch=feat-skip-zk-build#fde542bb8398189fe5e9bad688a527618261f63d" +source = "git+https://github.com/logos-blockchain/logos-blockchain.git#b89238be3ad8111b9975e1023b87d8672d0edd74" dependencies = [ "async-trait", "derivative", @@ -1267,6 +1277,35 @@ dependencies = [ "cpufeatures", ] +[[package]] +name = "chain-service" +version = "0.1.0" +source = "git+https://github.com/logos-blockchain/logos-blockchain.git#b89238be3ad8111b9975e1023b87d8672d0edd74" +dependencies = [ + "async-trait", + "broadcast-service", + "bytes", + "cryptarchia-engine", + "cryptarchia-sync", + "futures", + "groth16", + "nomos-core", + "nomos-ledger", + "nomos-network", + "nomos-storage", + "nomos-utils", + "num-bigint", + "overwatch", + "serde", + "serde_with", + "services-utils", + "strum", + "thiserror 1.0.69", + "tokio", + "tracing", + "tracing-futures", +] + [[package]] name = "chrono" version = "0.4.42" @@ -1294,7 +1333,7 @@ dependencies = [ [[package]] name = "circuits-prover" version = "0.1.0" -source = "git+https://github.com/logos-blockchain/logos-blockchain.git?branch=feat-skip-zk-build#fde542bb8398189fe5e9bad688a527618261f63d" +source = "git+https://github.com/logos-blockchain/logos-blockchain.git#b89238be3ad8111b9975e1023b87d8672d0edd74" dependencies = [ "circuits-utils", "tempfile", @@ -1303,7 +1342,7 @@ dependencies = [ [[package]] name = "circuits-utils" version = "0.1.0" -source = "git+https://github.com/logos-blockchain/logos-blockchain.git?branch=feat-skip-zk-build#fde542bb8398189fe5e9bad688a527618261f63d" +source = "git+https://github.com/logos-blockchain/logos-blockchain.git#b89238be3ad8111b9975e1023b87d8672d0edd74" dependencies = [ "dirs", ] @@ -1395,9 +1434,10 @@ dependencies = [ [[package]] name = "common-http-client" version = "0.1.0" -source = "git+https://github.com/logos-blockchain/logos-blockchain.git?branch=feat-skip-zk-build#fde542bb8398189fe5e9bad688a527618261f63d" +source = "git+https://github.com/logos-blockchain/logos-blockchain.git#b89238be3ad8111b9975e1023b87d8672d0edd74" dependencies = [ "broadcast-service", + "chain-service", "futures", "nomos-core", "nomos-da-messages", @@ -1452,6 +1492,15 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" +[[package]] +name = "convert_case" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baaaa0ecca5b51987b9423ccdc971514dd8b0bb7b4060b983d3664dad3f1f89f" +dependencies = [ + "unicode-segmentation", +] + [[package]] name = "convert_case" version = "0.10.0" @@ -1530,7 +1579,7 @@ checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" [[package]] name = "cryptarchia-engine" version = "0.1.0" -source = "git+https://github.com/logos-blockchain/logos-blockchain.git?branch=feat-skip-zk-build#fde542bb8398189fe5e9bad688a527618261f63d" +source = "git+https://github.com/logos-blockchain/logos-blockchain.git#b89238be3ad8111b9975e1023b87d8672d0edd74" dependencies = [ "cfg_eval", "nomos-utils", @@ -1541,6 +1590,23 @@ dependencies = [ "tracing", ] +[[package]] +name = "cryptarchia-sync" +version = "0.1.0" +source = "git+https://github.com/logos-blockchain/logos-blockchain.git#b89238be3ad8111b9975e1023b87d8672d0edd74" +dependencies = [ + "bytes", + "cryptarchia-engine", + "futures", + "nomos-core", + "rand 0.8.5", + "serde", + "serde_with", + "thiserror 1.0.69", + "tokio", + "tracing", +] + [[package]] name = "crypto-bigint" version = "0.5.5" @@ -1707,7 +1773,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8d162beedaa69905488a8da94f5ac3edb4dd4788b732fadb7bd120b2625c1976" dependencies = [ "data-encoding", - "syn 1.0.109", + "syn 2.0.111", ] [[package]] @@ -2344,7 +2410,7 @@ dependencies = [ [[package]] name = "groth16" version = "0.1.0" -source = "git+https://github.com/logos-blockchain/logos-blockchain.git?branch=feat-skip-zk-build#fde542bb8398189fe5e9bad688a527618261f63d" +source = "git+https://github.com/logos-blockchain/logos-blockchain.git#b89238be3ad8111b9975e1023b87d8672d0edd74" dependencies = [ "ark-bn254 0.4.0", "ark-ec 0.4.2", @@ -2653,7 +2719,7 @@ dependencies = [ "libc", "percent-encoding", "pin-project-lite", - "socket2 0.6.1", + "socket2 0.5.10", "system-configuration", "tokio", "tower-service", @@ -3015,7 +3081,7 @@ dependencies = [ [[package]] name = "key-management-system-keys" version = "0.1.0" -source = "git+https://github.com/logos-blockchain/logos-blockchain.git?branch=feat-skip-zk-build#fde542bb8398189fe5e9bad688a527618261f63d" +source = "git+https://github.com/logos-blockchain/logos-blockchain.git#b89238be3ad8111b9975e1023b87d8672d0edd74" dependencies = [ "async-trait", "bytes", @@ -3040,13 +3106,28 @@ dependencies = [ [[package]] name = "key-management-system-macros" version = "0.1.0" -source = "git+https://github.com/logos-blockchain/logos-blockchain.git?branch=feat-skip-zk-build#fde542bb8398189fe5e9bad688a527618261f63d" +source = "git+https://github.com/logos-blockchain/logos-blockchain.git#b89238be3ad8111b9975e1023b87d8672d0edd74" dependencies = [ "proc-macro2", "quote", "syn 2.0.111", ] +[[package]] +name = "key-management-system-service" +version = "0.1.0" +source = "git+https://github.com/logos-blockchain/logos-blockchain.git#b89238be3ad8111b9975e1023b87d8672d0edd74" +dependencies = [ + "async-trait", + "key-management-system-keys", + "log", + "overwatch", + "serde", + "thiserror 2.0.17", + "tokio", + "tracing", +] + [[package]] name = "key_protocol" version = "0.1.0" @@ -3071,7 +3152,7 @@ dependencies = [ [[package]] name = "kzgrs" version = "0.1.0" -source = "git+https://github.com/logos-blockchain/logos-blockchain.git?branch=feat-skip-zk-build#fde542bb8398189fe5e9bad688a527618261f63d" +source = "git+https://github.com/logos-blockchain/logos-blockchain.git#b89238be3ad8111b9975e1023b87d8672d0edd74" dependencies = [ "ark-bls12-381", "ark-ec 0.4.2", @@ -3090,7 +3171,7 @@ dependencies = [ [[package]] name = "kzgrs-backend" version = "0.1.0" -source = "git+https://github.com/logos-blockchain/logos-blockchain.git?branch=feat-skip-zk-build#fde542bb8398189fe5e9bad688a527618261f63d" +source = "git+https://github.com/logos-blockchain/logos-blockchain.git#b89238be3ad8111b9975e1023b87d8672d0edd74" dependencies = [ "ark-ff 0.4.2", "ark-poly 0.4.2", @@ -3172,6 +3253,7 @@ dependencies = [ "bs58", "hkdf", "multihash", + "serde", "sha2", "thiserror 2.0.17", "tracing", @@ -3374,6 +3456,18 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "mmr" +version = "0.1.0" +source = "git+https://github.com/logos-blockchain/logos-blockchain.git#b89238be3ad8111b9975e1023b87d8672d0edd74" +dependencies = [ + "ark-ff 0.4.2", + "groth16", + "poseidon2", + "rpds", + "serde", +] + [[package]] name = "multiaddr" version = "0.18.2" @@ -3483,7 +3577,7 @@ dependencies = [ [[package]] name = "nomos-blend-crypto" version = "0.1.0" -source = "git+https://github.com/logos-blockchain/logos-blockchain.git?branch=feat-skip-zk-build#fde542bb8398189fe5e9bad688a527618261f63d" +source = "git+https://github.com/logos-blockchain/logos-blockchain.git#b89238be3ad8111b9975e1023b87d8672d0edd74" dependencies = [ "blake2", "groth16", @@ -3498,10 +3592,32 @@ dependencies = [ "zeroize", ] +[[package]] +name = "nomos-blend-message" +version = "0.1.0" +source = "git+https://github.com/logos-blockchain/logos-blockchain.git#b89238be3ad8111b9975e1023b87d8672d0edd74" +dependencies = [ + "blake2", + "derivative", + "groth16", + "itertools 0.14.0", + "key-management-system-keys", + "nomos-blend-crypto", + "nomos-blend-proofs", + "nomos-core", + "nomos-utils", + "serde", + "serde-big-array", + "serde_with", + "thiserror 1.0.69", + "tracing", + "zeroize", +] + [[package]] name = "nomos-blend-proofs" version = "0.1.0" -source = "git+https://github.com/logos-blockchain/logos-blockchain.git?branch=feat-skip-zk-build#fde542bb8398189fe5e9bad688a527618261f63d" +source = "git+https://github.com/logos-blockchain/logos-blockchain.git#b89238be3ad8111b9975e1023b87d8672d0edd74" dependencies = [ "ed25519-dalek", "generic-array 1.3.5", @@ -3516,7 +3632,7 @@ dependencies = [ [[package]] name = "nomos-core" version = "0.1.0" -source = "git+https://github.com/logos-blockchain/logos-blockchain.git?branch=feat-skip-zk-build#fde542bb8398189fe5e9bad688a527618261f63d" +source = "git+https://github.com/logos-blockchain/logos-blockchain.git#b89238be3ad8111b9975e1023b87d8672d0edd74" dependencies = [ "ark-ff 0.4.2", "async-trait", @@ -3546,7 +3662,7 @@ dependencies = [ [[package]] name = "nomos-da-messages" version = "0.1.0" -source = "git+https://github.com/logos-blockchain/logos-blockchain.git?branch=feat-skip-zk-build#fde542bb8398189fe5e9bad688a527618261f63d" +source = "git+https://github.com/logos-blockchain/logos-blockchain.git#b89238be3ad8111b9975e1023b87d8672d0edd74" dependencies = [ "blake2", "futures", @@ -3559,7 +3675,7 @@ dependencies = [ [[package]] name = "nomos-http-api-common" version = "0.1.0" -source = "git+https://github.com/logos-blockchain/logos-blockchain.git?branch=feat-skip-zk-build#fde542bb8398189fe5e9bad688a527618261f63d" +source = "git+https://github.com/logos-blockchain/logos-blockchain.git#b89238be3ad8111b9975e1023b87d8672d0edd74" dependencies = [ "axum", "governor", @@ -3571,10 +3687,68 @@ dependencies = [ "tower_governor", ] +[[package]] +name = "nomos-ledger" +version = "0.1.0" +source = "git+https://github.com/logos-blockchain/logos-blockchain.git#b89238be3ad8111b9975e1023b87d8672d0edd74" +dependencies = [ + "cryptarchia-engine", + "groth16", + "key-management-system-keys", + "mmr", + "nomos-blend-crypto", + "nomos-blend-message", + "nomos-blend-proofs", + "nomos-core", + "nomos-utils", + "num-bigint", + "rand 0.8.5", + "rpds", + "serde", + "thiserror 1.0.69", + "tracing", + "utxotree", +] + +[[package]] +name = "nomos-network" +version = "0.1.0" +source = "git+https://github.com/logos-blockchain/logos-blockchain.git#b89238be3ad8111b9975e1023b87d8672d0edd74" +dependencies = [ + "async-trait", + "cryptarchia-sync", + "futures", + "nomos-core", + "overwatch", + "serde", + "tokio", + "tokio-stream", + "tracing", +] + +[[package]] +name = "nomos-storage" +version = "0.1.0" +source = "git+https://github.com/logos-blockchain/logos-blockchain.git#b89238be3ad8111b9975e1023b87d8672d0edd74" +dependencies = [ + "async-trait", + "bytes", + "cryptarchia-engine", + "futures", + "libp2p-identity", + "multiaddr", + "nomos-core", + "overwatch", + "serde", + "thiserror 1.0.69", + "tokio", + "tracing", +] + [[package]] name = "nomos-utils" version = "0.1.0" -source = "git+https://github.com/logos-blockchain/logos-blockchain.git?branch=feat-skip-zk-build#fde542bb8398189fe5e9bad688a527618261f63d" +source = "git+https://github.com/logos-blockchain/logos-blockchain.git#b89238be3ad8111b9975e1023b87d8672d0edd74" dependencies = [ "async-trait", "blake2", @@ -3826,6 +4000,7 @@ source = "git+https://github.com/logos-co/Overwatch?rev=f5a9902#f5a99022f389d65a dependencies = [ "async-trait", "futures", + "overwatch-derive", "thiserror 2.0.17", "tokio", "tokio-stream", @@ -3833,6 +4008,18 @@ dependencies = [ "tracing", ] +[[package]] +name = "overwatch-derive" +version = "0.1.0" +source = "git+https://github.com/logos-co/Overwatch?rev=f5a9902#f5a99022f389d65adbd55e51f1e3f9eead62432a" +dependencies = [ + "convert_case 0.8.0", + "proc-macro-error2", + "proc-macro2", + "quote", + "syn 2.0.111", +] + [[package]] name = "parking_lot" version = "0.12.5" @@ -3939,7 +4126,7 @@ checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" [[package]] name = "pol" version = "0.1.0" -source = "git+https://github.com/logos-blockchain/logos-blockchain.git?branch=feat-skip-zk-build#fde542bb8398189fe5e9bad688a527618261f63d" +source = "git+https://github.com/logos-blockchain/logos-blockchain.git#b89238be3ad8111b9975e1023b87d8672d0edd74" dependencies = [ "circuits-prover", "circuits-utils", @@ -3967,7 +4154,7 @@ dependencies = [ [[package]] name = "poq" version = "0.1.0" -source = "git+https://github.com/logos-blockchain/logos-blockchain.git?branch=feat-skip-zk-build#fde542bb8398189fe5e9bad688a527618261f63d" +source = "git+https://github.com/logos-blockchain/logos-blockchain.git#b89238be3ad8111b9975e1023b87d8672d0edd74" dependencies = [ "circuits-prover", "circuits-utils", @@ -3989,7 +4176,7 @@ checksum = "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483" [[package]] name = "poseidon2" version = "0.1.0" -source = "git+https://github.com/logos-blockchain/logos-blockchain.git?branch=feat-skip-zk-build#fde542bb8398189fe5e9bad688a527618261f63d" +source = "git+https://github.com/logos-blockchain/logos-blockchain.git#b89238be3ad8111b9975e1023b87d8672d0edd74" dependencies = [ "ark-bn254 0.4.0", "ark-ff 0.4.2", @@ -4042,6 +4229,28 @@ dependencies = [ "toml_edit 0.23.10+spec-1.0.0", ] +[[package]] +name = "proc-macro-error-attr2" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96de42df36bb9bba5542fe9f1a054b8cc87e172759a1868aa05c1f3acc89dfc5" +dependencies = [ + "proc-macro2", + "quote", +] + +[[package]] +name = "proc-macro-error2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11ec05c52be0a07b08061f7dd003e7d7092e0472bc731b4af7bb1ef876109802" +dependencies = [ + "proc-macro-error-attr2", + "proc-macro2", + "quote", + "syn 2.0.111", +] + [[package]] name = "proc-macro2" version = "1.0.103" @@ -4155,7 +4364,7 @@ dependencies = [ "quinn-udp", "rustc-hash", "rustls", - "socket2 0.6.1", + "socket2 0.5.10", "thiserror 2.0.17", "tokio", "tracing", @@ -4192,7 +4401,7 @@ dependencies = [ "cfg_aliases", "libc", "once_cell", - "socket2 0.6.1", + "socket2 0.5.10", "tracing", "windows-sys 0.60.2", ] @@ -4660,6 +4869,16 @@ dependencies = [ "librocksdb-sys", ] +[[package]] +name = "rpds" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e75f485e819d4d3015e6c0d55d02a4fd3db47c1993d9e603e0361fba2bffb34" +dependencies = [ + "archery", + "serde", +] + [[package]] name = "rrs-lib" version = "0.1.0" @@ -4938,18 +5157,25 @@ version = "0.1.0" dependencies = [ "anyhow", "base58", + "bedrock_client", + "borsh", "chrono", "common", "futures", + "key-management-system-service", "log", "mempool", + "nomos-core", "nssa", "nssa_core", + "rand 0.8.5", + "reqwest", "serde", "serde_json", "storage", "tempfile", "tokio", + "zksign", ] [[package]] @@ -4990,6 +5216,7 @@ dependencies = [ "sequencer_core", "sequencer_rpc", "tokio", + "zksign", ] [[package]] @@ -5002,6 +5229,15 @@ dependencies = [ "serde_derive", ] +[[package]] +name = "serde-big-array" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11fc7cc2c76d73e0f27ee52abbd64eec84d46f370c88371120433196934e4b7f" +dependencies = [ + "serde", +] + [[package]] name = "serde_core" version = "1.0.228" @@ -5108,6 +5344,21 @@ dependencies = [ "serde", ] +[[package]] +name = "services-utils" +version = "0.1.0" +source = "git+https://github.com/logos-blockchain/logos-blockchain.git#b89238be3ad8111b9975e1023b87d8672d0edd74" +dependencies = [ + "async-trait", + "futures", + "log", + "overwatch", + "serde", + "serde_json", + "thiserror 1.0.69", + "tracing", +] + [[package]] name = "sha1" version = "0.10.6" @@ -5751,6 +6002,16 @@ dependencies = [ "valuable", ] +[[package]] +name = "tracing-futures" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97d095ae15e245a057c8e8451bab9b3ee1e1f68e9ba2b4fbc18d0ac5237835f2" +dependencies = [ + "pin-project", + "tracing", +] + [[package]] name = "tracing-subscriber" version = "0.2.25" @@ -5760,6 +6021,12 @@ dependencies = [ "tracing-core", ] +[[package]] +name = "triomphe" +version = "0.1.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd69c5aa8f924c7519d6372789a74eac5b94fb0f8fcf0d4a97eb0bfc3e785f39" + [[package]] name = "try-lock" version = "0.2.5" @@ -5863,6 +6130,21 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" +[[package]] +name = "utxotree" +version = "0.1.0" +source = "git+https://github.com/logos-blockchain/logos-blockchain.git#b89238be3ad8111b9975e1023b87d8672d0edd74" +dependencies = [ + "ark-ff 0.4.2", + "groth16", + "nomos-core", + "num-bigint", + "poseidon2", + "rpds", + "serde", + "thiserror 1.0.69", +] + [[package]] name = "valuable" version = "0.1.1" @@ -6310,7 +6592,7 @@ checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" [[package]] name = "witness-generator" version = "0.1.0" -source = "git+https://github.com/logos-blockchain/logos-blockchain.git?branch=feat-skip-zk-build#fde542bb8398189fe5e9bad688a527618261f63d" +source = "git+https://github.com/logos-blockchain/logos-blockchain.git#b89238be3ad8111b9975e1023b87d8672d0edd74" dependencies = [ "circuits-utils", "tempfile", @@ -6465,7 +6747,7 @@ dependencies = [ [[package]] name = "zksign" version = "0.1.0" -source = "git+https://github.com/logos-blockchain/logos-blockchain.git?branch=feat-skip-zk-build#fde542bb8398189fe5e9bad688a527618261f63d" +source = "git+https://github.com/logos-blockchain/logos-blockchain.git#b89238be3ad8111b9975e1023b87d8672d0edd74" dependencies = [ "circuits-prover", "circuits-utils", diff --git a/Cargo.toml b/Cargo.toml index 30b4576a..35b6597a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -34,6 +34,7 @@ sequencer_rpc = { path = "sequencer_rpc" } sequencer_runner = { path = "sequencer_runner" } wallet = { path = "wallet" } test_program_methods = { path = "test_program_methods" } +bedrock_client = { path = "bedrock_client" } tokio = { version = "1.28.2", features = [ "net", @@ -78,7 +79,9 @@ base58 = "0.2.0" itertools = "0.14.0" url = "2.5.4" -common-http-client = { git = "https://github.com/logos-blockchain/logos-blockchain.git", branch = "feat-skip-zk-build" } +common-http-client = { git = "https://github.com/logos-blockchain/logos-blockchain.git" } +key-management-system-service = { git = "https://github.com/logos-blockchain/logos-blockchain.git" } +nomos-core = { git = "https://github.com/logos-blockchain/logos-blockchain.git" } rocksdb = { version = "0.24.0", default-features = false, features = [ "snappy", diff --git a/bedrock_client/Cargo.toml b/bedrock_client/Cargo.toml index 034a093e..e771f0c8 100644 --- a/bedrock_client/Cargo.toml +++ b/bedrock_client/Cargo.toml @@ -7,3 +7,4 @@ edition = "2024" reqwest.workspace = true anyhow.workspace = true common-http-client.workspace = true +key-management-system-service.workspace = true diff --git a/common/src/block.rs b/common/src/block.rs index baba1e42..944b8989 100644 --- a/common/src/block.rs +++ b/common/src/block.rs @@ -43,7 +43,7 @@ pub struct Block { pub body: BlockBody, } -#[derive(Debug, PartialEq, Eq, BorshSerialize, BorshDeserialize)] +#[derive(Debug, Clone, PartialEq, Eq, BorshSerialize, BorshDeserialize)] pub struct HashableBlockData { pub block_id: BlockId, pub prev_block_hash: BlockHash, diff --git a/integration_tests/configs/sequencer/sequencer_config.json b/integration_tests/configs/sequencer/sequencer_config.json index 1548bb5b..51611141 100644 --- a/integration_tests/configs/sequencer/sequencer_config.json +++ b/integration_tests/configs/sequencer/sequencer_config.json @@ -154,5 +154,7 @@ 37, 37, 37 - ] -} \ No newline at end of file + ], + "bedrock_channel_id": [1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1], + "bedrock_node_url": "http://localhost:18080" +} diff --git a/integration_tests/tests/tps.rs b/integration_tests/tests/tps.rs index b62a3ce7..e5bd5d05 100644 --- a/integration_tests/tests/tps.rs +++ b/integration_tests/tests/tps.rs @@ -185,6 +185,7 @@ impl TpsTestManager { initial_accounts: initial_public_accounts, initial_commitments: vec![initial_commitment], signing_key: [37; 32], + bedrock_config: None } } } diff --git a/sequencer_core/Cargo.toml b/sequencer_core/Cargo.toml index a844c524..c0cd33ee 100644 --- a/sequencer_core/Cargo.toml +++ b/sequencer_core/Cargo.toml @@ -17,6 +17,13 @@ serde_json.workspace = true tempfile.workspace = true chrono.workspace = true log.workspace = true +bedrock_client.workspace = true +key-management-system-service = { git = "https://github.com/logos-blockchain/logos-blockchain.git" } +nomos-core = { git = "https://github.com/logos-blockchain/logos-blockchain.git" } +zksign = { git = "https://github.com/logos-blockchain/logos-blockchain.git", default-features = false } +rand.workspace = true +reqwest.workspace = true +borsh.workspace = true [features] default = [] diff --git a/sequencer_core/src/block_settlement_client.rs b/sequencer_core/src/block_settlement_client.rs new file mode 100644 index 00000000..8950ad22 --- /dev/null +++ b/sequencer_core/src/block_settlement_client.rs @@ -0,0 +1,118 @@ +use std::{fs, path::Path}; + +use anyhow::Result; +use bedrock_client::BedrockClient; +use common::block::HashableBlockData; +use key_management_system_service::keys::{ED25519_SECRET_KEY_SIZE, Ed25519Key, Ed25519PublicKey}; +use nomos_core::mantle::{ + MantleTx, Op, OpProof, SignedMantleTx, Transaction, TxHash, ledger, + ops::channel::{ChannelId, MsgId, inscribe::InscriptionOp}, +}; +use reqwest::Url; + +use crate::config::BedrockConfig; + +pub struct BlockSettlementClient { + bedrock_node_url: Url, + bedrock_client: BedrockClient, + bedrock_signing_key: Ed25519Key, + bedrock_channel_id: ChannelId, +} + +impl BlockSettlementClient { + pub fn new(home: &Path, config: &BedrockConfig) -> Self { + let bedrock_signing_key = + load_or_create_signing_key(&home.join("bedrock_signing_key")).unwrap(); + let bedrock_node_url = Url::parse(&config.node_url).unwrap(); + let bedrock_channel_id = ChannelId::from(config.channel_id); + let bedrock_client = + BedrockClient::new(None).expect("Bedrock client should be able to initialize"); + Self { + bedrock_node_url, + bedrock_client, + bedrock_signing_key, + bedrock_channel_id, + } + } + + /// Create and sign a transaction for inscribing data + pub fn create_inscribe_tx(&self, data: Vec, parent: MsgId) -> SignedMantleTx { + let verifying_key_bytes = self.bedrock_signing_key.public_key().to_bytes(); + let verifying_key = + Ed25519PublicKey::from_bytes(&verifying_key_bytes).expect("valid ed25519 public key"); + + let inscribe_op = InscriptionOp { + channel_id: self.bedrock_channel_id, + inscription: data, + parent, + signer: verifying_key, + }; + + let ledger_tx = ledger::Tx::new(vec![], vec![]); + + let inscribe_tx = MantleTx { + ops: vec![Op::ChannelInscribe(inscribe_op)], + ledger_tx, + storage_gas_price: 0, + execution_gas_price: 0, + }; + + let tx_hash = inscribe_tx.hash(); + let signature_bytes = self + .bedrock_signing_key + .sign_payload(tx_hash.as_signing_bytes().as_ref()) + .to_bytes(); + let signature = + key_management_system_service::keys::Ed25519Signature::from_bytes(&signature_bytes); + + SignedMantleTx { + ops_proofs: vec![OpProof::Ed25519Sig(signature)], + ledger_tx_proof: empty_ledger_signature(&tx_hash), + mantle_tx: inscribe_tx, + } + } + + /// Post a transaction to the node and wait for inclusion + pub async fn post_and_wait(&self, block_data: &HashableBlockData) -> Result { + let msg_id: MsgId = { + let mut this = [0; 32]; + this[0..8].copy_from_slice(&block_data.block_id.to_le_bytes()); + this.into() + }; + + let inscription_data = borsh::to_vec(&block_data)?; + let tx = self.create_inscribe_tx(inscription_data, msg_id); + + // Post the transaction + self.bedrock_client + .0 + .post_transaction(self.bedrock_node_url.clone(), tx.clone()) + .await?; + + Ok(block_data.block_id) + } +} + +/// Load signing key from file or generate a new one if it doesn't exist +fn load_or_create_signing_key(path: &Path) -> Result { + if path.exists() { + let key_bytes = fs::read(path).map_err(|_| ())?; + if key_bytes.len() != ED25519_SECRET_KEY_SIZE { + // TODO: proper error + return Err(()); + } + let key_array: [u8; ED25519_SECRET_KEY_SIZE] = + key_bytes.try_into().expect("length already checked"); + Ok(Ed25519Key::from_bytes(&key_array)) + } else { + let mut key_bytes = [0u8; ED25519_SECRET_KEY_SIZE]; + rand::RngCore::fill_bytes(&mut rand::thread_rng(), &mut key_bytes); + fs::write(path, key_bytes).map_err(|_| ())?; + Ok(Ed25519Key::from_bytes(&key_bytes)) + } +} + +fn empty_ledger_signature(tx_hash: &TxHash) -> key_management_system_service::keys::ZkSignature { + key_management_system_service::keys::ZkKey::multi_sign(&[], tx_hash.as_ref()) + .expect("multi-sign with empty key set works") +} diff --git a/sequencer_core/src/config.rs b/sequencer_core/src/config.rs index 4ef08803..ed42fa3a 100644 --- a/sequencer_core/src/config.rs +++ b/sequencer_core/src/config.rs @@ -47,6 +47,16 @@ pub struct SequencerConfig { pub initial_commitments: Vec, /// Sequencer own signing key pub signing_key: [u8; 32], + /// Bedrock configuration options + pub bedrock_config: Option, +} + +#[derive(Clone, Serialize, Deserialize)] +pub struct BedrockConfig { + /// Bedrock channel ID + pub channel_id: [u8; 32], + /// Bedrock Url + pub node_url: String, } impl SequencerConfig { diff --git a/sequencer_core/src/lib.rs b/sequencer_core/src/lib.rs index 8e193ff6..52b389f0 100644 --- a/sequencer_core/src/lib.rs +++ b/sequencer_core/src/lib.rs @@ -13,8 +13,9 @@ use log::warn; use mempool::{MemPool, MemPoolHandle}; use serde::{Deserialize, Serialize}; -use crate::block_store::SequencerBlockStore; +use crate::{block_settlement_client::BlockSettlementClient, block_store::SequencerBlockStore}; +mod block_settlement_client; pub mod block_store; pub mod config; @@ -24,6 +25,7 @@ pub struct SequencerCore { mempool: MemPool, sequencer_config: SequencerConfig, chain_height: u64, + block_settlement_client: Option, } #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] @@ -87,12 +89,18 @@ impl SequencerCore { state.add_pinata_program(PINATA_BASE58.parse().unwrap()); let (mempool, mempool_handle) = MemPool::new(config.mempool_max_size); + let block_settlement = config + .bedrock_config + .as_ref() + .map(|bedrock_config| BlockSettlementClient::new(&config.home, bedrock_config)); + let mut this = Self { state, block_store, mempool, chain_height: config.genesis_id, sequencer_config: config, + block_settlement_client: block_settlement, }; this.sync_state_with_stored_blocks(); @@ -137,9 +145,21 @@ impl SequencerCore { Ok(tx) } + pub async fn produce_new_block_and_post_to_settlement_layer(&mut self) -> Result { + let block_data = self.produce_new_block_with_mempool_transactions()?; + + if let Some(block_settlement) = &self.block_settlement_client { + block_settlement.post_and_wait(&block_data).await?; + log::info!("Posted block data to Bedrock"); + } + + Ok(self.chain_height) + } + /// Produces new block from transactions in mempool - pub fn produce_new_block_with_mempool_transactions(&mut self) -> Result { + pub fn produce_new_block_with_mempool_transactions(&mut self) -> Result { let now = Instant::now(); + let new_block_height = self.chain_height + 1; let mut valid_transactions = vec![]; @@ -167,8 +187,6 @@ impl SequencerCore { let curr_time = chrono::Utc::now().timestamp_millis() as u64; - let num_txs_in_block = valid_transactions.len(); - let hashable_data = HashableBlockData { block_id: new_block_height, transactions: valid_transactions, @@ -176,14 +194,16 @@ impl SequencerCore { timestamp: curr_time, }; - let block = hashable_data.into_block(self.block_store.signing_key()); + let block = hashable_data + .clone() + .into_block(self.block_store.signing_key()); self.block_store.put_block_at_id(block)?; self.chain_height = new_block_height; // TODO: Consider switching to `tracing` crate to have more structured and consistent logs - // e.g. + // // e.g. // // ``` // info!( @@ -194,11 +214,10 @@ impl SequencerCore { // ``` log::info!( "Created block with {} transactions in {} seconds", - num_txs_in_block, + hashable_data.transactions.len(), now.elapsed().as_secs() ); - - Ok(self.chain_height) + Ok(hashable_data) } pub fn state(&self) -> &nssa::V02State { @@ -277,6 +296,7 @@ mod tests { initial_accounts, initial_commitments: vec![], signing_key: *sequencer_sign_key_for_testing().value(), + bedrock_config: None, } } @@ -619,9 +639,9 @@ mod tests { let tx = common::test_utils::produce_dummy_empty_transaction(); mempool_handle.push(tx).await.unwrap(); - let block_id = sequencer.produce_new_block_with_mempool_transactions(); - assert!(block_id.is_ok()); - assert_eq!(block_id.unwrap(), genesis_height + 1); + let block = sequencer.produce_new_block_with_mempool_transactions(); + assert!(block.is_ok()); + assert_eq!(block.unwrap().block_id, genesis_height + 1); } #[tokio::test] @@ -658,7 +678,8 @@ mod tests { // Create block let current_height = sequencer .produce_new_block_with_mempool_transactions() - .unwrap(); + .unwrap() + .block_id; let block = sequencer .block_store .get_block_at_id(current_height) @@ -697,7 +718,8 @@ mod tests { mempool_handle.push(tx.clone()).await.unwrap(); let current_height = sequencer .produce_new_block_with_mempool_transactions() - .unwrap(); + .unwrap() + .block_id; let block = sequencer .block_store .get_block_at_id(current_height) @@ -708,7 +730,8 @@ mod tests { mempool_handle.push(tx.clone()).await.unwrap(); let current_height = sequencer .produce_new_block_with_mempool_transactions() - .unwrap(); + .unwrap() + .block_id; let block = sequencer .block_store .get_block_at_id(current_height) @@ -743,7 +766,8 @@ mod tests { mempool_handle.push(tx.clone()).await.unwrap(); let current_height = sequencer .produce_new_block_with_mempool_transactions() - .unwrap(); + .unwrap() + .block_id; let block = sequencer .block_store .get_block_at_id(current_height) diff --git a/sequencer_rpc/src/process.rs b/sequencer_rpc/src/process.rs index 387abf28..b89993f9 100644 --- a/sequencer_rpc/src/process.rs +++ b/sequencer_rpc/src/process.rs @@ -388,6 +388,7 @@ mod tests { initial_accounts, initial_commitments: vec![], signing_key: *sequencer_sign_key_for_testing().value(), + bedrock_config: None, } } diff --git a/sequencer_runner/Cargo.toml b/sequencer_runner/Cargo.toml index 55f56dec..8ee9f1db 100644 --- a/sequencer_runner/Cargo.toml +++ b/sequencer_runner/Cargo.toml @@ -9,6 +9,7 @@ sequencer_core = { workspace = true, features = ["testnet"] } sequencer_rpc.workspace = true clap = { workspace = true, features = ["derive", "env"] } +zksign = { git = "https://github.com/logos-blockchain/logos-blockchain.git", default-features = false } anyhow.workspace = true env_logger.workspace = true log.workspace = true diff --git a/sequencer_runner/configs/debug/sequencer_config.json b/sequencer_runner/configs/debug/sequencer_config.json index 58348f68..8d602d37 100644 --- a/sequencer_runner/configs/debug/sequencer_config.json +++ b/sequencer_runner/configs/debug/sequencer_config.json @@ -154,5 +154,9 @@ 37, 37, 37 - ] -} \ No newline at end of file + ], + "bedrock_config": { + "channel_id": [1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1], + "node_url": "http://localhost:34485" + } +} diff --git a/sequencer_runner/src/lib.rs b/sequencer_runner/src/lib.rs index 5c1ab920..dd4f38a0 100644 --- a/sequencer_runner/src/lib.rs +++ b/sequencer_runner/src/lib.rs @@ -50,7 +50,7 @@ pub async fn startup_sequencer( let id = { let mut state = seq_core_wrapped.lock().await; - state.produce_new_block_with_mempool_transactions()? + state.produce_new_block_and_post_to_settlement_layer().await? }; info!("Block with id {id} created"); From 26806daedc12a2056f27e6f8002fd78d54a7fd7d Mon Sep 17 00:00:00 2001 From: Sergio Chouhy Date: Wed, 14 Jan 2026 23:32:45 -0300 Subject: [PATCH 08/18] fix config --- integration_tests/configs/sequencer/sequencer_config.json | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/integration_tests/configs/sequencer/sequencer_config.json b/integration_tests/configs/sequencer/sequencer_config.json index 51611141..575d3de3 100644 --- a/integration_tests/configs/sequencer/sequencer_config.json +++ b/integration_tests/configs/sequencer/sequencer_config.json @@ -154,7 +154,5 @@ 37, 37, 37 - ], - "bedrock_channel_id": [1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1], - "bedrock_node_url": "http://localhost:18080" + ] } From 3dbe31501ce11682cad7223bc6c7f47307d2f11c Mon Sep 17 00:00:00 2001 From: Sergio Chouhy Date: Wed, 14 Jan 2026 23:45:58 -0300 Subject: [PATCH 09/18] fix merge --- sequencer_core/src/config.rs | 9 --------- sequencer_runner/configs/debug/sequencer_config.json | 2 +- 2 files changed, 1 insertion(+), 10 deletions(-) diff --git a/sequencer_core/src/config.rs b/sequencer_core/src/config.rs index 8b4d991c..ed42fa3a 100644 --- a/sequencer_core/src/config.rs +++ b/sequencer_core/src/config.rs @@ -67,12 +67,3 @@ impl SequencerConfig { Ok(serde_json::from_reader(reader)?) } } - -impl SequencerConfig { - pub fn from_path(config_home: &Path) -> Result { - let file = File::open(config_home)?; - let reader = BufReader::new(file); - - Ok(serde_json::from_reader(reader)?) - } -} diff --git a/sequencer_runner/configs/debug/sequencer_config.json b/sequencer_runner/configs/debug/sequencer_config.json index 8d602d37..ab65ea70 100644 --- a/sequencer_runner/configs/debug/sequencer_config.json +++ b/sequencer_runner/configs/debug/sequencer_config.json @@ -157,6 +157,6 @@ ], "bedrock_config": { "channel_id": [1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1], - "node_url": "http://localhost:34485" + "node_url": "http://localhost:45403" } } From aeb3fd3932d3e71679fb30a620763f6907c7b3f4 Mon Sep 17 00:00:00 2001 From: Sergio Chouhy Date: Thu, 15 Jan 2026 00:28:40 -0300 Subject: [PATCH 10/18] replace unwrap with expect --- sequencer_core/src/block_settlement_client.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/sequencer_core/src/block_settlement_client.rs b/sequencer_core/src/block_settlement_client.rs index 8950ad22..6ade6860 100644 --- a/sequencer_core/src/block_settlement_client.rs +++ b/sequencer_core/src/block_settlement_client.rs @@ -12,6 +12,7 @@ use reqwest::Url; use crate::config::BedrockConfig; +/// A component that posts block data to logos blockchain pub struct BlockSettlementClient { bedrock_node_url: Url, bedrock_client: BedrockClient, @@ -21,9 +22,9 @@ pub struct BlockSettlementClient { impl BlockSettlementClient { pub fn new(home: &Path, config: &BedrockConfig) -> Self { - let bedrock_signing_key = - load_or_create_signing_key(&home.join("bedrock_signing_key")).unwrap(); - let bedrock_node_url = Url::parse(&config.node_url).unwrap(); + let bedrock_signing_key = load_or_create_signing_key(&home.join("bedrock_signing_key")) + .expect("Signing key should load or be created successfully"); + let bedrock_node_url = Url::parse(&config.node_url).expect("Bedrock URL should be a valid URL"); let bedrock_channel_id = ChannelId::from(config.channel_id); let bedrock_client = BedrockClient::new(None).expect("Bedrock client should be able to initialize"); From 8da515da6b772bf902f3e0d25776ff00f1cb17fd Mon Sep 17 00:00:00 2001 From: Sergio Chouhy Date: Thu, 15 Jan 2026 00:29:45 -0300 Subject: [PATCH 11/18] fmt --- integration_tests/tests/tps.rs | 2 +- sequencer_core/src/block_settlement_client.rs | 3 ++- sequencer_runner/src/lib.rs | 4 +++- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/integration_tests/tests/tps.rs b/integration_tests/tests/tps.rs index e5bd5d05..87f7e45a 100644 --- a/integration_tests/tests/tps.rs +++ b/integration_tests/tests/tps.rs @@ -185,7 +185,7 @@ impl TpsTestManager { initial_accounts: initial_public_accounts, initial_commitments: vec![initial_commitment], signing_key: [37; 32], - bedrock_config: None + bedrock_config: None, } } } diff --git a/sequencer_core/src/block_settlement_client.rs b/sequencer_core/src/block_settlement_client.rs index 6ade6860..1964f37f 100644 --- a/sequencer_core/src/block_settlement_client.rs +++ b/sequencer_core/src/block_settlement_client.rs @@ -24,7 +24,8 @@ impl BlockSettlementClient { pub fn new(home: &Path, config: &BedrockConfig) -> Self { let bedrock_signing_key = load_or_create_signing_key(&home.join("bedrock_signing_key")) .expect("Signing key should load or be created successfully"); - let bedrock_node_url = Url::parse(&config.node_url).expect("Bedrock URL should be a valid URL"); + let bedrock_node_url = + Url::parse(&config.node_url).expect("Bedrock URL should be a valid URL"); let bedrock_channel_id = ChannelId::from(config.channel_id); let bedrock_client = BedrockClient::new(None).expect("Bedrock client should be able to initialize"); diff --git a/sequencer_runner/src/lib.rs b/sequencer_runner/src/lib.rs index dd4f38a0..fd4a6c08 100644 --- a/sequencer_runner/src/lib.rs +++ b/sequencer_runner/src/lib.rs @@ -50,7 +50,9 @@ pub async fn startup_sequencer( let id = { let mut state = seq_core_wrapped.lock().await; - state.produce_new_block_and_post_to_settlement_layer().await? + state + .produce_new_block_and_post_to_settlement_layer() + .await? }; info!("Block with id {id} created"); From 4105eecacec617dbc04cc3af3efd16c0b0f0629d Mon Sep 17 00:00:00 2001 From: Sergio Chouhy Date: Fri, 16 Jan 2026 19:50:54 -0300 Subject: [PATCH 12/18] fix msg id in mantle tx --- sequencer_core/src/block_settlement_client.rs | 21 ++++++++++--------- sequencer_core/src/lib.rs | 2 +- .../configs/debug/sequencer_config.json | 2 +- 3 files changed, 13 insertions(+), 12 deletions(-) diff --git a/sequencer_core/src/block_settlement_client.rs b/sequencer_core/src/block_settlement_client.rs index 1964f37f..68a95dd3 100644 --- a/sequencer_core/src/block_settlement_client.rs +++ b/sequencer_core/src/block_settlement_client.rs @@ -18,6 +18,7 @@ pub struct BlockSettlementClient { bedrock_client: BedrockClient, bedrock_signing_key: Ed25519Key, bedrock_channel_id: ChannelId, + last_message_id: MsgId, } impl BlockSettlementClient { @@ -34,11 +35,12 @@ impl BlockSettlementClient { bedrock_client, bedrock_signing_key, bedrock_channel_id, + last_message_id: MsgId::from([0; 32]), } } /// Create and sign a transaction for inscribing data - pub fn create_inscribe_tx(&self, data: Vec, parent: MsgId) -> SignedMantleTx { + pub fn create_inscribe_tx(&self, data: Vec) -> SignedMantleTx { let verifying_key_bytes = self.bedrock_signing_key.public_key().to_bytes(); let verifying_key = Ed25519PublicKey::from_bytes(&verifying_key_bytes).expect("valid ed25519 public key"); @@ -46,7 +48,7 @@ impl BlockSettlementClient { let inscribe_op = InscriptionOp { channel_id: self.bedrock_channel_id, inscription: data, - parent, + parent: self.last_message_id, signer: verifying_key, }; @@ -75,15 +77,9 @@ impl BlockSettlementClient { } /// Post a transaction to the node and wait for inclusion - pub async fn post_and_wait(&self, block_data: &HashableBlockData) -> Result { - let msg_id: MsgId = { - let mut this = [0; 32]; - this[0..8].copy_from_slice(&block_data.block_id.to_le_bytes()); - this.into() - }; - + pub async fn post_and_wait(&mut self, block_data: &HashableBlockData) -> Result { let inscription_data = borsh::to_vec(&block_data)?; - let tx = self.create_inscribe_tx(inscription_data, msg_id); + let tx = self.create_inscribe_tx(inscription_data); // Post the transaction self.bedrock_client @@ -91,6 +87,11 @@ impl BlockSettlementClient { .post_transaction(self.bedrock_node_url.clone(), tx.clone()) .await?; + match tx.mantle_tx.ops.first() { + Some(Op::ChannelInscribe(inscribe)) => self.last_message_id = inscribe.id(), + _ => {} + } + Ok(block_data.block_id) } } diff --git a/sequencer_core/src/lib.rs b/sequencer_core/src/lib.rs index 52b389f0..561f5ef6 100644 --- a/sequencer_core/src/lib.rs +++ b/sequencer_core/src/lib.rs @@ -148,7 +148,7 @@ impl SequencerCore { pub async fn produce_new_block_and_post_to_settlement_layer(&mut self) -> Result { let block_data = self.produce_new_block_with_mempool_transactions()?; - if let Some(block_settlement) = &self.block_settlement_client { + if let Some(block_settlement) = self.block_settlement_client.as_mut() { block_settlement.post_and_wait(&block_data).await?; log::info!("Posted block data to Bedrock"); } diff --git a/sequencer_runner/configs/debug/sequencer_config.json b/sequencer_runner/configs/debug/sequencer_config.json index ab65ea70..ad43ba65 100644 --- a/sequencer_runner/configs/debug/sequencer_config.json +++ b/sequencer_runner/configs/debug/sequencer_config.json @@ -157,6 +157,6 @@ ], "bedrock_config": { "channel_id": [1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1], - "node_url": "http://localhost:45403" + "node_url": "http://localhost:8080" } } From 654e92f0d703d465f598bb7a8ec8992747d8125a Mon Sep 17 00:00:00 2001 From: Sergio Chouhy Date: Mon, 19 Jan 2026 14:26:48 -0300 Subject: [PATCH 13/18] clippy --- sequencer_core/src/block_settlement_client.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/sequencer_core/src/block_settlement_client.rs b/sequencer_core/src/block_settlement_client.rs index 68a95dd3..c1af22f4 100644 --- a/sequencer_core/src/block_settlement_client.rs +++ b/sequencer_core/src/block_settlement_client.rs @@ -87,9 +87,8 @@ impl BlockSettlementClient { .post_transaction(self.bedrock_node_url.clone(), tx.clone()) .await?; - match tx.mantle_tx.ops.first() { - Some(Op::ChannelInscribe(inscribe)) => self.last_message_id = inscribe.id(), - _ => {} + if let Some(Op::ChannelInscribe(inscribe)) = tx.mantle_tx.ops.first() { + self.last_message_id = inscribe.id(); } Ok(block_data.block_id) From e397d6c5766ccb76cc3cb0b5f511612ccc0e89db Mon Sep 17 00:00:00 2001 From: Sergio Chouhy Date: Tue, 20 Jan 2026 11:50:20 -0300 Subject: [PATCH 14/18] add bedrock status flag to blocks and last_finalized_block metadata info to sequencer db --- common/src/block.rs | 17 +++++++++--- common/src/test_utils.rs | 2 +- sequencer_core/src/block_store.rs | 4 +-- sequencer_core/src/lib.rs | 4 +-- storage/src/lib.rs | 45 +++++++++++++++++++++++-------- 5 files changed, 52 insertions(+), 20 deletions(-) diff --git a/common/src/block.rs b/common/src/block.rs index 944b8989..84b7a419 100644 --- a/common/src/block.rs +++ b/common/src/block.rs @@ -23,7 +23,7 @@ pub type BlockHash = [u8; 32]; pub type BlockId = u64; pub type TimeStamp = u64; -#[derive(Debug, Clone)] +#[derive(Debug, Clone, BorshSerialize, BorshDeserialize)] pub struct BlockHeader { pub block_id: BlockId, pub prev_block_hash: BlockHash, @@ -32,15 +32,23 @@ pub struct BlockHeader { pub signature: nssa::Signature, } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, BorshSerialize, BorshDeserialize)] pub struct BlockBody { pub transactions: Vec, } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, BorshSerialize, BorshDeserialize)] +pub enum BedrockStatus { + Pending, + Safe, + Finalized, +} + +#[derive(Debug, BorshSerialize, BorshDeserialize)] pub struct Block { pub header: BlockHeader, pub body: BlockBody, + pub bedrock_status: BedrockStatus, } #[derive(Debug, Clone, PartialEq, Eq, BorshSerialize, BorshDeserialize)] @@ -52,7 +60,7 @@ pub struct HashableBlockData { } impl HashableBlockData { - pub fn into_block(self, signing_key: &nssa::PrivateKey) -> Block { + pub fn into_pending_block(self, signing_key: &nssa::PrivateKey) -> Block { let data_bytes = borsh::to_vec(&self).unwrap(); let signature = nssa::Signature::new(signing_key, &data_bytes); let hash = OwnHasher::hash(&data_bytes); @@ -67,6 +75,7 @@ impl HashableBlockData { body: BlockBody { transactions: self.transactions, }, + bedrock_status: BedrockStatus::Pending, } } } diff --git a/common/src/test_utils.rs b/common/src/test_utils.rs index 7e3c0ba8..1125b86e 100644 --- a/common/src/test_utils.rs +++ b/common/src/test_utils.rs @@ -30,7 +30,7 @@ pub fn produce_dummy_block( transactions, }; - block_data.into_block(&sequencer_sign_key_for_testing()) + block_data.into_pending_block(&sequencer_sign_key_for_testing()) } pub fn produce_dummy_empty_transaction() -> EncodedTransaction { diff --git a/sequencer_core/src/block_store.rs b/sequencer_core/src/block_store.rs index 67535022..cd9aa194 100644 --- a/sequencer_core/src/block_store.rs +++ b/sequencer_core/src/block_store.rs @@ -46,7 +46,7 @@ impl SequencerBlockStore { } pub fn get_block_at_id(&self, id: u64) -> Result { - Ok(self.dbio.get_block(id)?.into_block(&self.signing_key)) + Ok(self.dbio.get_block(id)?) } pub fn put_block_at_id(&mut self, block: Block) -> Result<()> { @@ -113,7 +113,7 @@ mod tests { transactions: vec![], }; - let genesis_block = genesis_block_hashable_data.into_block(&signing_key); + let genesis_block = genesis_block_hashable_data.into_pending_block(&signing_key); // Start an empty node store let mut node_store = SequencerBlockStore::open_db_with_genesis(path, Some(genesis_block), signing_key) diff --git a/sequencer_core/src/lib.rs b/sequencer_core/src/lib.rs index 561f5ef6..a7a16826 100644 --- a/sequencer_core/src/lib.rs +++ b/sequencer_core/src/lib.rs @@ -53,7 +53,7 @@ impl SequencerCore { }; let signing_key = nssa::PrivateKey::try_new(config.signing_key).unwrap(); - let genesis_block = hashable_data.into_block(&signing_key); + let genesis_block = hashable_data.into_pending_block(&signing_key); // Sequencer should panic if unable to open db, // as fixing this issue may require actions non-native to program scope @@ -196,7 +196,7 @@ impl SequencerCore { let block = hashable_data .clone() - .into_block(self.block_store.signing_key()); + .into_pending_block(self.block_store.signing_key()); self.block_store.put_block_at_id(block)?; diff --git a/storage/src/lib.rs b/storage/src/lib.rs index 87b78705..883684c2 100644 --- a/storage/src/lib.rs +++ b/storage/src/lib.rs @@ -1,6 +1,6 @@ use std::{path::Path, sync::Arc}; -use common::block::{Block, HashableBlockData}; +use common::block::Block; use error::DbError; use rocksdb::{ BoundColumnFamily, ColumnFamilyDescriptor, DBWithThreadMode, MultiThreaded, Options, @@ -26,6 +26,8 @@ pub const DB_META_FIRST_BLOCK_IN_DB_KEY: &str = "first_block_in_db"; pub const DB_META_LAST_BLOCK_IN_DB_KEY: &str = "last_block_in_db"; /// Key base for storing metainformation which describe if first block has been set pub const DB_META_FIRST_BLOCK_SET_KEY: &str = "first_block_set"; +/// Key base for storing metainformation about the last finalized block on Bedrock +pub const DB_META_LAST_FINALIZED_BLOCK_ID: &str = "last_finalized_block_id"; /// Key base for storing snapshot which describe block id pub const DB_SNAPSHOT_BLOCK_ID_KEY: &str = "block_id"; @@ -75,6 +77,7 @@ impl RocksDBIO { dbio.put_meta_first_block_in_db(block)?; dbio.put_meta_is_first_block_set()?; dbio.put_meta_last_block_in_db(block_id)?; + dbio.put_meta_last_finalized_block_id(None)?; Ok(dbio) } else { @@ -232,6 +235,28 @@ impl RocksDBIO { Ok(()) } + pub fn put_meta_last_finalized_block_id(&self, block_id: Option) -> DbResult<()> { + let cf_meta = self.meta_column(); + self.db + .put_cf( + &cf_meta, + borsh::to_vec(&DB_META_LAST_FINALIZED_BLOCK_ID).map_err(|err| { + DbError::borsh_cast_message( + err, + Some("Failed to serialize DB_META_LAST_FINALIZED_BLOCK_ID".to_string()), + ) + })?, + borsh::to_vec(&block_id).map_err(|err| { + DbError::borsh_cast_message( + err, + Some("Failed to serialize last block id".to_string()), + ) + })?, + ) + .map_err(|rerr| DbError::rocksdb_cast_message(rerr, None))?; + Ok(()) + } + pub fn put_meta_is_first_block_set(&self) -> DbResult<()> { let cf_meta = self.meta_column(); self.db @@ -269,7 +294,7 @@ impl RocksDBIO { Some("Failed to serialize block id".to_string()), ) })?, - borsh::to_vec(&HashableBlockData::from(block)).map_err(|err| { + borsh::to_vec(&block).map_err(|err| { DbError::borsh_cast_message( err, Some("Failed to serialize block data".to_string()), @@ -280,7 +305,7 @@ impl RocksDBIO { Ok(()) } - pub fn get_block(&self, block_id: u64) -> DbResult { + pub fn get_block(&self, block_id: u64) -> DbResult { let cf_block = self.block_column(); let res = self .db @@ -296,14 +321,12 @@ impl RocksDBIO { .map_err(|rerr| DbError::rocksdb_cast_message(rerr, None))?; if let Some(data) = res { - Ok( - borsh::from_slice::(&data).map_err(|serr| { - DbError::borsh_cast_message( - serr, - Some("Failed to deserialize block data".to_string()), - ) - })?, - ) + Ok(borsh::from_slice::(&data).map_err(|serr| { + DbError::borsh_cast_message( + serr, + Some("Failed to deserialize block data".to_string()), + ) + })?) } else { Err(DbError::db_interaction_error( "Block on this id not found".to_string(), From 56295d12d86a478bb8d6c4cbd4687cb3357fdf3c Mon Sep 17 00:00:00 2001 From: Sergio Chouhy Date: Wed, 21 Jan 2026 10:38:18 -0300 Subject: [PATCH 15/18] use anyhow for block settlement client error handling --- sequencer_core/src/block_settlement_client.rs | 20 ++++++++----------- sequencer_core/src/config.rs | 3 ++- 2 files changed, 10 insertions(+), 13 deletions(-) diff --git a/sequencer_core/src/block_settlement_client.rs b/sequencer_core/src/block_settlement_client.rs index c1af22f4..e14b1b47 100644 --- a/sequencer_core/src/block_settlement_client.rs +++ b/sequencer_core/src/block_settlement_client.rs @@ -1,6 +1,6 @@ use std::{fs, path::Path}; -use anyhow::Result; +use anyhow::{Result, anyhow}; use bedrock_client::BedrockClient; use common::block::HashableBlockData; use key_management_system_service::keys::{ED25519_SECRET_KEY_SIZE, Ed25519Key, Ed25519PublicKey}; @@ -25,8 +25,7 @@ impl BlockSettlementClient { pub fn new(home: &Path, config: &BedrockConfig) -> Self { let bedrock_signing_key = load_or_create_signing_key(&home.join("bedrock_signing_key")) .expect("Signing key should load or be created successfully"); - let bedrock_node_url = - Url::parse(&config.node_url).expect("Bedrock URL should be a valid URL"); + let bedrock_node_url = config.node_url.clone(); let bedrock_channel_id = ChannelId::from(config.channel_id); let bedrock_client = BedrockClient::new(None).expect("Bedrock client should be able to initialize"); @@ -96,20 +95,17 @@ impl BlockSettlementClient { } /// Load signing key from file or generate a new one if it doesn't exist -fn load_or_create_signing_key(path: &Path) -> Result { +fn load_or_create_signing_key(path: &Path) -> Result { if path.exists() { - let key_bytes = fs::read(path).map_err(|_| ())?; - if key_bytes.len() != ED25519_SECRET_KEY_SIZE { - // TODO: proper error - return Err(()); - } - let key_array: [u8; ED25519_SECRET_KEY_SIZE] = - key_bytes.try_into().expect("length already checked"); + let key_bytes = fs::read(path)?; + let key_array: [u8; ED25519_SECRET_KEY_SIZE] = key_bytes + .try_into() + .map_err(|_| anyhow!("Found key with incorrect length"))?; Ok(Ed25519Key::from_bytes(&key_array)) } else { let mut key_bytes = [0u8; ED25519_SECRET_KEY_SIZE]; rand::RngCore::fill_bytes(&mut rand::thread_rng(), &mut key_bytes); - fs::write(path, key_bytes).map_err(|_| ())?; + fs::write(path, key_bytes)?; Ok(Ed25519Key::from_bytes(&key_bytes)) } } diff --git a/sequencer_core/src/config.rs b/sequencer_core/src/config.rs index ed42fa3a..5911cc52 100644 --- a/sequencer_core/src/config.rs +++ b/sequencer_core/src/config.rs @@ -5,6 +5,7 @@ use std::{ }; use anyhow::Result; +use reqwest::Url; use serde::{Deserialize, Serialize}; #[derive(Debug, Serialize, Deserialize, Clone)] @@ -56,7 +57,7 @@ pub struct BedrockConfig { /// Bedrock channel ID pub channel_id: [u8; 32], /// Bedrock Url - pub node_url: String, + pub node_url: Url, } impl SequencerConfig { From f04b299b1ab2e49221e585ac585cd5192e521629 Mon Sep 17 00:00:00 2001 From: Sergio Chouhy Date: Wed, 21 Jan 2026 11:47:28 -0300 Subject: [PATCH 16/18] minor changes --- Cargo.lock | 1 + bedrock_client/Cargo.toml | 1 + bedrock_client/src/lib.rs | 24 +++++++++--- sequencer_core/src/block_settlement_client.rs | 39 ++++++++----------- sequencer_core/src/lib.rs | 12 +++--- 5 files changed, 42 insertions(+), 35 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4c543c49..2b5e93cd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -957,6 +957,7 @@ version = "0.1.0" dependencies = [ "anyhow", "common-http-client", + "nomos-core", "reqwest", ] diff --git a/bedrock_client/Cargo.toml b/bedrock_client/Cargo.toml index 034a093e..e6f6f02a 100644 --- a/bedrock_client/Cargo.toml +++ b/bedrock_client/Cargo.toml @@ -7,3 +7,4 @@ edition = "2024" reqwest.workspace = true anyhow.workspace = true common-http-client.workspace = true +nomos-core.workspace = true diff --git a/bedrock_client/src/lib.rs b/bedrock_client/src/lib.rs index 9315f083..a5e0a049 100644 --- a/bedrock_client/src/lib.rs +++ b/bedrock_client/src/lib.rs @@ -1,21 +1,33 @@ use anyhow::Result; use common_http_client::CommonHttpClient; pub use common_http_client::{BasicAuthCredentials, Error}; -use reqwest::Client; +use nomos_core::mantle::SignedMantleTx; +use reqwest::{Client, Url}; // Simple wrapper // maybe extend in the future for our purposes -pub struct BedrockClient(pub CommonHttpClient); +pub struct BedrockClient { + http_client: CommonHttpClient, + node_url: Url, +} impl BedrockClient { - pub fn new(auth: Option) -> Result { + pub fn new(auth: Option, node_url: Url) -> Result { let client = Client::builder() //Add more fiedls if needed .timeout(std::time::Duration::from_secs(60)) .build()?; - Ok(BedrockClient(CommonHttpClient::new_with_client( - client, auth, - ))) + let http_client = CommonHttpClient::new_with_client(client, auth); + Ok(Self { + http_client, + node_url, + }) + } + + pub async fn post_transaction(&self, tx: SignedMantleTx) -> Result<(), Error> { + self.http_client + .post_transaction(self.node_url.clone(), tx) + .await } } diff --git a/sequencer_core/src/block_settlement_client.rs b/sequencer_core/src/block_settlement_client.rs index e14b1b47..58f4d7f4 100644 --- a/sequencer_core/src/block_settlement_client.rs +++ b/sequencer_core/src/block_settlement_client.rs @@ -8,13 +8,11 @@ use nomos_core::mantle::{ MantleTx, Op, OpProof, SignedMantleTx, Transaction, TxHash, ledger, ops::channel::{ChannelId, MsgId, inscribe::InscriptionOp}, }; -use reqwest::Url; use crate::config::BedrockConfig; /// A component that posts block data to logos blockchain pub struct BlockSettlementClient { - bedrock_node_url: Url, bedrock_client: BedrockClient, bedrock_signing_key: Ed25519Key, bedrock_channel_id: ChannelId, @@ -22,24 +20,21 @@ pub struct BlockSettlementClient { } impl BlockSettlementClient { - pub fn new(home: &Path, config: &BedrockConfig) -> Self { - let bedrock_signing_key = load_or_create_signing_key(&home.join("bedrock_signing_key")) - .expect("Signing key should load or be created successfully"); - let bedrock_node_url = config.node_url.clone(); + pub fn try_new(home: &Path, config: &BedrockConfig) -> Result { + let bedrock_signing_key = load_or_create_signing_key(&home.join("bedrock_signing_key"))?; let bedrock_channel_id = ChannelId::from(config.channel_id); - let bedrock_client = - BedrockClient::new(None).expect("Bedrock client should be able to initialize"); - Self { - bedrock_node_url, + let bedrock_client = BedrockClient::new(None, config.node_url.clone())?; + let channel_genesis_msg = MsgId::from([0; 32]); + Ok(Self { bedrock_client, bedrock_signing_key, bedrock_channel_id, - last_message_id: MsgId::from([0; 32]), - } + last_message_id: channel_genesis_msg, + }) } /// Create and sign a transaction for inscribing data - pub fn create_inscribe_tx(&self, data: Vec) -> SignedMantleTx { + pub fn create_inscribe_tx(&self, data: Vec) -> (SignedMantleTx, MsgId) { let verifying_key_bytes = self.bedrock_signing_key.public_key().to_bytes(); let verifying_key = Ed25519PublicKey::from_bytes(&verifying_key_bytes).expect("valid ed25519 public key"); @@ -50,12 +45,14 @@ impl BlockSettlementClient { parent: self.last_message_id, signer: verifying_key, }; + let inscribe_op_id = inscribe_op.id(); let ledger_tx = ledger::Tx::new(vec![], vec![]); let inscribe_tx = MantleTx { ops: vec![Op::ChannelInscribe(inscribe_op)], ledger_tx, + // Altruistic test config storage_gas_price: 0, execution_gas_price: 0, }; @@ -68,27 +65,23 @@ impl BlockSettlementClient { let signature = key_management_system_service::keys::Ed25519Signature::from_bytes(&signature_bytes); - SignedMantleTx { + let signed_mantle_tx = SignedMantleTx { ops_proofs: vec![OpProof::Ed25519Sig(signature)], ledger_tx_proof: empty_ledger_signature(&tx_hash), mantle_tx: inscribe_tx, - } + }; + (signed_mantle_tx, inscribe_op_id) } /// Post a transaction to the node and wait for inclusion pub async fn post_and_wait(&mut self, block_data: &HashableBlockData) -> Result { let inscription_data = borsh::to_vec(&block_data)?; - let tx = self.create_inscribe_tx(inscription_data); + let (tx, new_msg_id) = self.create_inscribe_tx(inscription_data); // Post the transaction - self.bedrock_client - .0 - .post_transaction(self.bedrock_node_url.clone(), tx.clone()) - .await?; + self.bedrock_client.post_transaction(tx).await?; - if let Some(Op::ChannelInscribe(inscribe)) = tx.mantle_tx.ops.first() { - self.last_message_id = inscribe.id(); - } + self.last_message_id = new_msg_id; Ok(block_data.block_id) } diff --git a/sequencer_core/src/lib.rs b/sequencer_core/src/lib.rs index a7a16826..89cafc4c 100644 --- a/sequencer_core/src/lib.rs +++ b/sequencer_core/src/lib.rs @@ -89,10 +89,10 @@ impl SequencerCore { state.add_pinata_program(PINATA_BASE58.parse().unwrap()); let (mempool, mempool_handle) = MemPool::new(config.mempool_max_size); - let block_settlement = config - .bedrock_config - .as_ref() - .map(|bedrock_config| BlockSettlementClient::new(&config.home, bedrock_config)); + let block_settlement_client = config.bedrock_config.as_ref().map(|bedrock_config| { + BlockSettlementClient::try_new(&config.home, bedrock_config) + .expect("Block settlement client should be constructible") + }); let mut this = Self { state, @@ -100,7 +100,7 @@ impl SequencerCore { mempool, chain_height: config.genesis_id, sequencer_config: config, - block_settlement_client: block_settlement, + block_settlement_client, }; this.sync_state_with_stored_blocks(); @@ -203,7 +203,7 @@ impl SequencerCore { self.chain_height = new_block_height; // TODO: Consider switching to `tracing` crate to have more structured and consistent logs - // // e.g. + // e.g. // // ``` // info!( From 02702213dce649816c9ce6237984116763396564 Mon Sep 17 00:00:00 2001 From: Sergio Chouhy <41742639+schouhy@users.noreply.github.com> Date: Mon, 26 Jan 2026 13:34:38 -0300 Subject: [PATCH 17/18] Update sequencer_core/src/block_settlement_client.rs Co-authored-by: Daniil Polyakov --- sequencer_core/src/block_settlement_client.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sequencer_core/src/block_settlement_client.rs b/sequencer_core/src/block_settlement_client.rs index c41efa52..c0c727a8 100644 --- a/sequencer_core/src/block_settlement_client.rs +++ b/sequencer_core/src/block_settlement_client.rs @@ -23,9 +23,9 @@ pub struct BlockSettlementClient { impl BlockSettlementClient { pub fn try_new(home: &Path, config: &BedrockConfig) -> Result { - let bedrock_signing_key = load_or_create_signing_key(&home.join("bedrock_signing_key"))?; + let bedrock_signing_key = load_or_create_signing_key(&home.join("bedrock_signing_key")).context("Failed to load or create signing key")?; let bedrock_channel_id = ChannelId::from(config.channel_id); - let bedrock_client = BedrockClient::new(None, config.node_url.clone())?; + let bedrock_client = BedrockClient::new(None, config.node_url.clone()).context("Failed to initialize bedrock client")?; let channel_genesis_msg = MsgId::from([0; 32]); Ok(Self { bedrock_client, From e680544ca3dc1acb239900f0a3d00cd61396c070 Mon Sep 17 00:00:00 2001 From: Sergio Chouhy Date: Mon, 26 Jan 2026 13:46:47 -0300 Subject: [PATCH 18/18] use context --- sequencer_core/src/block_settlement_client.rs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/sequencer_core/src/block_settlement_client.rs b/sequencer_core/src/block_settlement_client.rs index c0c727a8..0aa22420 100644 --- a/sequencer_core/src/block_settlement_client.rs +++ b/sequencer_core/src/block_settlement_client.rs @@ -1,6 +1,6 @@ use std::{fs, path::Path}; -use anyhow::{Result, anyhow}; +use anyhow::{Context, Result, anyhow}; use bedrock_client::BedrockClient; use common::block::HashableBlockData; use logos_blockchain_core::mantle::{ @@ -23,9 +23,11 @@ pub struct BlockSettlementClient { impl BlockSettlementClient { pub fn try_new(home: &Path, config: &BedrockConfig) -> Result { - let bedrock_signing_key = load_or_create_signing_key(&home.join("bedrock_signing_key")).context("Failed to load or create signing key")?; + let bedrock_signing_key = load_or_create_signing_key(&home.join("bedrock_signing_key")) + .context("Failed to load or create signing key")?; let bedrock_channel_id = ChannelId::from(config.channel_id); - let bedrock_client = BedrockClient::new(None, config.node_url.clone()).context("Failed to initialize bedrock client")?; + let bedrock_client = BedrockClient::new(None, config.node_url.clone()) + .context("Failed to initialize bedrock client")?; let channel_genesis_msg = MsgId::from([0; 32]); Ok(Self { bedrock_client,