diff --git a/common/src/lib.rs b/common/src/lib.rs index b7f45ab..01ae261 100644 --- a/common/src/lib.rs +++ b/common/src/lib.rs @@ -68,6 +68,8 @@ pub enum ExecutionFailureKind { DecodeError(String), #[error("Inputs amounts does not match outputs")] AmountMismatchError, + #[error("Accounts key not found")] + KeyNotFoundError, #[error("Sequencer client error: {0:?}")] SequencerClientError(#[from] SequencerClientError), #[error("Insufficient gas for operation")] diff --git a/integration_tests/configs/debug/wallet/wallet_config.json b/integration_tests/configs/debug/wallet/wallet_config.json index 6085a36..d888119 100644 --- a/integration_tests/configs/debug/wallet/wallet_config.json +++ b/integration_tests/configs/debug/wallet/wallet_config.json @@ -1,45 +1,113 @@ { - "home": "./node", - "override_rust_log": null, - "sequencer_addr": "http://127.0.0.1:3040", - "seq_poll_timeout_secs": 10, - "initial_accounts": [ - { - "address": "1b84c5567b126440995d3ed5aaba0565d71e1834604819ff9c17f5e9d5dd078f", - "balance": 10000, - "key_holder": { - "address": "1b84c5567b126440995d3ed5aaba0565d71e1834604819ff9c17f5e9d5dd078f", - "nullifer_public_key": "03A340BECA9FAAB444CED0140681D72EA1318B5C611704FEE017DA9836B17DB718", - "pub_account_signing_key": [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], - "top_secret_key_holder": { - "secret_spending_key": "7BC46784DB1BC67825D8F029436846712BFDF9B5D79EA3AB11D39A52B9B229D4" + "home": "./node", + "override_rust_log": null, + "sequencer_addr": "http://127.0.0.1:3040", + "seq_poll_timeout_secs": 10, + "initial_accounts": [ + { + "address": "1b84c5567b126440995d3ed5aaba0565d71e1834604819ff9c17f5e9d5dd078f", + "pub_sign_key": [ + 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 + ], + "account": { + "program_owner": [ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0 + ], + "balance": 10000, + "nonce": 0, + "data": [] + } }, - "utxo_secret_key_holder": { - "nullifier_secret_key": "BB54A8D3C9C51B82C431082D1845A74677B0EF829A11B517E1D9885DE3139506", - "viewing_secret_key": "AD923E92F6A5683E30140CEAB2702AFB665330C1EE4EFA70FAF29767B6B52BAF" - }, - "viewing_public_key": "0361220C5D277E7A1709340FD31A52600C1432B9C45B9BCF88A43581D58824A8B6" - }, - "utxos": {} - }, - { - "address": "4d4b6cd1361032ca9bd2aeb9d900aa4d45d9ead80ac9423374c451a7254d0766", - "balance": 20000, - "key_holder": { - "address": "4d4b6cd1361032ca9bd2aeb9d900aa4d45d9ead80ac9423374c451a7254d0766", - "nullifer_public_key": "02172F50274DE67C4087C344F5D58E11DF761D90285B095060E0994FAA6BCDE271", - "pub_account_signing_key": [2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2], - "top_secret_key_holder": { - "secret_spending_key": "80A186737C8D38B4288A03F0F589957D9C040D79C19F3E0CC4BA80F8494E5179" - }, - "utxo_secret_key_holder": { - "nullifier_secret_key": "746928E63F0984F6F4818933493CE9C067562D9CB932FDC06D82C86CDF6D7122", - "viewing_secret_key": "89176CF4BC9E673807643FD52110EF99D4894335AFB10D881AC0B5041FE1FCB7" - }, - "viewing_public_key": "026072A8F83FEC3472E30CDD4767683F30B91661D25B1040AD9A5FC2E01D659F99" - }, - "utxos": {} - } - ] + { + "address": "4d4b6cd1361032ca9bd2aeb9d900aa4d45d9ead80ac9423374c451a7254d0766", + "pub_sign_key": [ + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2 + ], + "account": { + "program_owner": [ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0 + ], + "balance": 20000, + "nonce": 0, + "data": [] + } + } + ] } diff --git a/integration_tests/src/lib.rs b/integration_tests/src/lib.rs index b5bb265..812e44f 100644 --- a/integration_tests/src/lib.rs +++ b/integration_tests/src/lib.rs @@ -156,7 +156,9 @@ pub async fn test_failure() { let seq_client = SequencerClient::new(wallet_config.sequencer_addr.clone()).unwrap(); - wallet::execute_subcommand(command).await.unwrap(); + let failed_send = wallet::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; diff --git a/key_protocol/src/key_protocol_core/mod.rs b/key_protocol/src/key_protocol_core/mod.rs index cd9ab39..3b8092e 100644 --- a/key_protocol/src/key_protocol_core/mod.rs +++ b/key_protocol/src/key_protocol_core/mod.rs @@ -69,14 +69,26 @@ impl NSSAUserData { pub fn generate_new_account(&mut self) -> nssa::Address { let address = self.key_holder.generate_new_private_key(); - self.accounts.insert(address, nssa_core::account::Account::default()); + self.accounts + .insert(address, nssa_core::account::Account::default()); address } pub fn get_account_balance(&self, address: &nssa::Address) -> u128 { - self.accounts.get(address).map(|acc| acc.balance).unwrap_or(0) - } + self.accounts + .get(address) + .map(|acc| acc.balance) + .unwrap_or(0) + } + + pub fn get_account(&self, address: &nssa::Address) -> Option<&nssa_core::account::Account> { + self.accounts.get(address) + } + + pub fn get_account_signing_key(&self, address: &nssa::Address) -> Option<&nssa::PrivateKey> { + self.key_holder.get_pub_account_signing_key(address) + } pub fn encrypt_data( ephemeral_key_holder: &EphemeralKeyHolder, @@ -100,7 +112,7 @@ impl NSSAUserData { self.accounts .entry(address) .and_modify(|acc| acc.balance = new_balance) - .or_insert(nssa_core::account::Account::default()); + .or_default(); } //ToDo: Part of a private keys update diff --git a/wallet/Cargo.toml b/wallet/Cargo.toml index 13bea50..4a67791 100644 --- a/wallet/Cargo.toml +++ b/wallet/Cargo.toml @@ -22,6 +22,7 @@ risc0-zkvm = "2.3.1" hex.workspace = true actix-rt.workspace = true clap.workspace = true +nssa-core = { path = "../nssa/core" } [dependencies.key_protocol] path = "../key_protocol" diff --git a/wallet/src/chain_storage/accounts_store.rs b/wallet/src/chain_storage/accounts_store.rs deleted file mode 100644 index 243fe6a..0000000 --- a/wallet/src/chain_storage/accounts_store.rs +++ /dev/null @@ -1,113 +0,0 @@ -//TODO: NOT NSSA USER DATA, ACCOUNT -use key_protocol::key_protocol_core::NSSAUserData; -use nssa::Address; -use std::collections::HashMap; - -pub struct WalletAccountsStore { - pub accounts: HashMap, -} - -impl WalletAccountsStore { - pub fn new() -> Self { - Self { - accounts: HashMap::new(), - } - } - - pub fn register_account(&mut self, account: NSSAUserData) { - self.accounts.insert(account.address, account); - } - - pub fn unregister_account(&mut self, account_addr: Address) { - self.accounts.remove(&account_addr); - } -} - -impl Default for WalletAccountsStore { - fn default() -> Self { - Self::new() - } -} - -#[cfg(test)] -mod tests { - use super::*; - use key_protocol::key_protocol_core::NSSAUserData; - /// Helper function to create a sample account - fn create_sample_account(balance: u64) -> NSSAUserData { - NSSAUserData::new_with_balance(balance) - } - - fn pad_to_32(slice: &[u8]) -> [u8; 32] { - let mut padded = [0u8; 32]; - let len = slice.len().min(32); - padded[..len].copy_from_slice(&slice[..len]); - padded - } - - #[test] - fn test_create_empty_store() { - let store = WalletAccountsStore::new(); - assert!(store.accounts.is_empty()); - } - - #[test] - fn test_register_account() { - let mut store = WalletAccountsStore::new(); - - let account = create_sample_account(100); - let account_addr = account.address; - - store.register_account(account); - - assert_eq!(store.accounts.len(), 1); - let stored_account = store.accounts.get(&account_addr).unwrap(); - assert_eq!(stored_account.balance, 100); - } - - #[test] - fn test_unregister_account() { - let mut store = WalletAccountsStore::new(); - - let account = create_sample_account(100); - let account_addr = account.address; - store.register_account(account); - - assert_eq!(store.accounts.len(), 1); - - store.unregister_account(account_addr); - assert!(store.accounts.is_empty()); - } - - #[test] - fn test_unregister_nonexistent_account() { - let mut store = WalletAccountsStore::new(); - - let account_addr: [u8; 32] = pad_to_32("nonexistent".to_string().as_bytes()); - store.unregister_account(Address::new(account_addr)); - - assert!(store.accounts.is_empty()); - } - - #[test] - fn test_register_multiple_accounts() { - let mut store = WalletAccountsStore::new(); - - let account1 = create_sample_account(100); - let account2 = create_sample_account(200); - - let address_1 = account1.address; - let address_2 = account2.address; - - store.register_account(account1); - store.register_account(account2); - - assert_eq!(store.accounts.len(), 2); - - let stored_account1 = store.accounts.get(&address_1).unwrap(); - let stored_account2 = store.accounts.get(&address_2).unwrap(); - - assert_eq!(stored_account1.balance, 100); - assert_eq!(stored_account2.balance, 200); - } -} diff --git a/wallet/src/chain_storage/mod.rs b/wallet/src/chain_storage/mod.rs index 80a14f8..40c9ddc 100644 --- a/wallet/src/chain_storage/mod.rs +++ b/wallet/src/chain_storage/mod.rs @@ -1,55 +1,37 @@ use std::collections::HashMap; -//TODO: NOT USER DATA, ACCOUNT use anyhow::Result; use common::merkle_tree_public::merkle_tree::UTXOCommitmentsMerkleTree; use key_protocol::key_protocol_core::NSSAUserData; -use nssa::Address; -use serde::{Deserialize, Serialize}; use crate::config::WalletConfig; -pub mod accounts_store; - -#[derive(Deserialize, Serialize)] -pub struct AccMap { - pub acc_map: HashMap, -} - -impl From> for AccMap { - fn from(value: HashMap<[u8; 32], NSSAUserData>) -> Self { - AccMap { - acc_map: value - .into_iter() - .map(|(key, val)| (hex::encode(key), val)) - .collect(), - } - } -} - -impl From for HashMap<[u8; 32], NSSAUserData> { - fn from(value: AccMap) -> Self { - value - .acc_map - .into_iter() - .map(|(key, val)| (hex::decode(key).unwrap().try_into().unwrap(), val)) - .collect() - } -} - pub struct WalletChainStore { - pub acc_map: HashMap, + pub user_data: NSSAUserData, pub utxo_commitments_store: UTXOCommitmentsMerkleTree, pub wallet_config: WalletConfig, } impl WalletChainStore { pub fn new(config: WalletConfig) -> Result { - let acc_map = HashMap::new(); + let accounts: HashMap = config + .initial_accounts + .clone() + .into_iter() + .map(|init_acc_data| (init_acc_data.address, init_acc_data.account)) + .collect(); + + let accounts_keys: HashMap = config + .initial_accounts + .clone() + .into_iter() + .map(|init_acc_data| (init_acc_data.address, init_acc_data.pub_sign_key)) + .collect(); + let utxo_commitments_store = UTXOCommitmentsMerkleTree::new(vec![]); Ok(Self { - acc_map, + user_data: NSSAUserData::new_with_accounts(accounts_keys, accounts), utxo_commitments_store, wallet_config: config, }) @@ -58,48 +40,33 @@ impl WalletChainStore { #[cfg(test)] mod tests { + use crate::config::InitialAccountData; + use super::*; - use key_protocol::key_protocol_core::NSSAUserData; use std::path::PathBuf; use tempfile::tempdir; - fn create_initial_accounts() -> Vec { + fn create_initial_accounts() -> Vec { let initial_acc1 = serde_json::from_str(r#"{ "address": "1b84c5567b126440995d3ed5aaba0565d71e1834604819ff9c17f5e9d5dd078f", - "balance": 100, - "nonce": 0, - "key_holder": { - "nullifer_public_key": "03A340BECA9FAAB444CED0140681D72EA1318B5C611704FEE017DA9836B17DB718", - "pub_account_signing_key": [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], - "top_secret_key_holder": { - "secret_spending_key": "7BC46784DB1BC67825D8F029436846712BFDF9B5D79EA3AB11D39A52B9B229D4" - }, - "utxo_secret_key_holder": { - "nullifier_secret_key": "BB54A8D3C9C51B82C431082D1845A74677B0EF829A11B517E1D9885DE3139506", - "viewing_secret_key": "AD923E92F6A5683E30140CEAB2702AFB665330C1EE4EFA70FAF29767B6B52BAF" - }, - "viewing_public_key": "0361220C5D277E7A1709340FD31A52600C1432B9C45B9BCF88A43581D58824A8B6" - }, - "utxos": {} + "pub_sign_key": [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], + "account": { + "program_owner": [0,0,0,0,0,0,0,0], + "balance": 100, + "nonce": 0, + "data": [] + } }"#).unwrap(); let initial_acc2 = serde_json::from_str(r#"{ "address": "4d4b6cd1361032ca9bd2aeb9d900aa4d45d9ead80ac9423374c451a7254d0766", - "balance": 200, - "nonce": 0, - "key_holder": { - "nullifer_public_key": "02172F50274DE67C4087C344F5D58E11DF761D90285B095060E0994FAA6BCDE271", - "pub_account_signing_key": [2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2], - "top_secret_key_holder": { - "secret_spending_key": "80A186737C8D38B4288A03F0F589957D9C040D79C19F3E0CC4BA80F8494E5179" - }, - "utxo_secret_key_holder": { - "nullifier_secret_key": "746928E63F0984F6F4818933493CE9C067562D9CB932FDC06D82C86CDF6D7122", - "viewing_secret_key": "89176CF4BC9E673807643FD52110EF99D4894335AFB10D881AC0B5041FE1FCB7" - }, - "viewing_public_key": "026072A8F83FEC3472E30CDD4767683F30B91661D25B1040AD9A5FC2E01D659F99" - }, - "utxos": {} + "pub_sign_key": [2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2], + "account": { + "program_owner": [0,0,0,0,0,0,0,0], + "balance": 100, + "nonce": 0, + "data": [] + } }"#).unwrap(); let initial_accounts = vec![initial_acc1, initial_acc2]; @@ -126,7 +93,7 @@ mod tests { let store = WalletChainStore::new(config.clone()).unwrap(); - assert!(store.acc_map.is_empty()); + assert_eq!(store.user_data.accounts.len(), 2); assert_eq!( store.utxo_commitments_store.get_root().unwrap_or([0; 32]), [0; 32] diff --git a/wallet/src/config.rs b/wallet/src/config.rs index a6bf634..21bd912 100644 --- a/wallet/src/config.rs +++ b/wallet/src/config.rs @@ -1,7 +1,12 @@ +use serde::{Deserialize, Serialize}; use std::path::PathBuf; -use key_protocol::key_protocol_core::NSSAUserData; -use serde::{Deserialize, Serialize}; +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct InitialAccountData { + pub address: nssa::Address, + pub account: nssa_core::account::Account, + pub pub_sign_key: nssa::PrivateKey, +} #[derive(Debug, Clone, Serialize, Deserialize)] pub struct GasConfig { @@ -32,7 +37,5 @@ pub struct WalletConfig { ///Sequencer polling duration for new blocks in seconds pub seq_poll_timeout_secs: u64, ///Initial accounts for wallet - /// - /// TODO: NOT USRE DATA, ACCOUNT - pub initial_accounts: Vec, + pub initial_accounts: Vec, } diff --git a/wallet/src/helperfunctions.rs b/wallet/src/helperfunctions.rs index 334129b..51b9d9d 100644 --- a/wallet/src/helperfunctions.rs +++ b/wallet/src/helperfunctions.rs @@ -1,10 +1,12 @@ use std::{fs::File, io::BufReader, path::PathBuf, str::FromStr}; use anyhow::Result; -use key_protocol::key_protocol_core::NSSAUserData; use nssa::Address; -use crate::{config::WalletConfig, HOME_DIR_ENV_VAR}; +use crate::{ + config::{InitialAccountData, WalletConfig}, + HOME_DIR_ENV_VAR, +}; ///Get home dir for wallet. Env var `NSSA_WALLET_HOME_DIR` must be set before execution to succeed. pub fn get_home() -> Result { @@ -28,9 +30,7 @@ pub fn produce_account_addr_from_hex(hex_str: String) -> Result
{ ///Fetch list of accounts stored at `NSSA_WALLET_HOME_DIR/curr_accounts.json` /// /// If file not present, it is considered as empty list of persistent accounts -/// -/// ToDo: NOT USER DATA, ACCOUNT -pub fn fetch_persistent_accounts() -> Result> { +pub fn fetch_persistent_accounts() -> Result> { let home = get_home()?; let accs_path = home.join("curr_accounts.json"); diff --git a/wallet/src/lib.rs b/wallet/src/lib.rs index a8ab45f..2c4b711 100644 --- a/wallet/src/lib.rs +++ b/wallet/src/lib.rs @@ -8,7 +8,6 @@ use common::{ use anyhow::Result; use chain_storage::WalletChainStore; use config::WalletConfig; -use key_protocol::key_protocol_core::NSSAUserData; use log::info; use nssa::Address; @@ -27,7 +26,6 @@ pub mod helperfunctions; pub struct WalletCore { pub storage: WalletChainStore, - pub wallet_config: WalletConfig, pub sequencer_client: Arc, } @@ -35,32 +33,23 @@ impl WalletCore { pub async fn start_from_config_update_chain(config: WalletConfig) -> Result { let client = Arc::new(SequencerClient::new(config.sequencer_addr.clone())?); - let mut storage = WalletChainStore::new(config.clone())?; - for acc in config.clone().initial_accounts { - storage.acc_map.insert(acc.address, acc); - } + let mut storage = WalletChainStore::new(config)?; - //Persistent accounts take precedence for initial accounts let persistent_accounts = fetch_persistent_accounts()?; for acc in persistent_accounts { - storage.acc_map.insert(acc.address, acc); + storage + .user_data + .update_account_balance(acc.address, acc.account.balance); } Ok(Self { storage, - wallet_config: config.clone(), sequencer_client: client.clone(), }) } pub async fn create_new_account(&mut self) -> Address { - let account = NSSAUserData::new(); - - let addr = account.address; - - self.storage.acc_map.insert(account.address, account); - - addr + self.storage.user_data.generate_new_account() } pub async fn send_public_native_token_transfer( @@ -70,27 +59,36 @@ impl WalletCore { to: Address, balance_to_move: u128, ) -> Result { - let account = self.storage.acc_map.get(&from); + let account = self.storage.user_data.get_account(&from); if let Some(account) = account { - let addresses = vec![from, to]; - let nonces = vec![nonce]; - let program_id = nssa::program::Program::authenticated_transfer_program().id(); - let message = nssa::public_transaction::Message::try_new( - program_id, - addresses, - nonces, - balance_to_move, - ) - .unwrap(); + if account.balance >= balance_to_move { + let addresses = vec![from, to]; + let nonces = vec![nonce]; + let program_id = nssa::program::Program::authenticated_transfer_program().id(); + let message = nssa::public_transaction::Message::try_new( + program_id, + addresses, + nonces, + balance_to_move, + ) + .unwrap(); - let signing_key = account.key_holder.get_pub_account_signing_key(); - let witness_set = - nssa::public_transaction::WitnessSet::for_message(&message, &[signing_key]); + let signing_key = self.storage.user_data.get_account_signing_key(&from); - let tx = nssa::PublicTransaction::new(message, witness_set); + if let Some(signing_key) = signing_key { + let witness_set = + nssa::public_transaction::WitnessSet::for_message(&message, &[signing_key]); - Ok(self.sequencer_client.send_tx(tx).await?) + let tx = nssa::PublicTransaction::new(message, witness_set); + + Ok(self.sequencer_client.send_tx(tx).await?) + } else { + Err(ExecutionFailureKind::KeyNotFoundError) + } + } else { + Err(ExecutionFailureKind::InsufficientFundsError) + } } else { Err(ExecutionFailureKind::AmountMismatchError) }