From e7d17548b7bee98ef92b15d22778475d53277974 Mon Sep 17 00:00:00 2001 From: /alex/ Date: Tue, 20 Feb 2024 11:55:49 +0100 Subject: [PATCH] Rename wallet data (#1934) * rename wallet data * remove awaits and update signatures * make it compile * rename more wallet data mentions * unmut wallet * remove mut * remove more mut * update save load test * clean up * one more mut * temporarily disable test * review * some nits * revert backup rename; remove a function * revert restore rename * visibility; rename * set alias * ) Co-authored-by: Thoralf-M <46689931+Thoralf-M@users.noreply.github.com> * doc Co-authored-by: Thibault Martinez * doc fixes * fix * doc 2 Co-authored-by: Thibault Martinez --------- Co-authored-by: Thoralf-M <46689931+Thoralf-M@users.noreply.github.com> Co-authored-by: Thibault Martinez --- bindings/core/src/method_handler/wallet.rs | 33 ++-- cli/src/cli.rs | 2 +- cli/src/wallet_cli/mod.rs | 42 ++--- .../how_tos/wallet/consolidate_outputs.rs | 4 +- sdk/examples/how_tos/wallet/create_wallet.rs | 2 +- sdk/examples/how_tos/wallet/list_outputs.rs | 4 +- .../how_tos/wallet/list_transactions.rs | 4 +- sdk/examples/wallet/spammer.rs | 2 +- sdk/examples/wallet/storage.rs | 1 - sdk/examples/wallet/wallet.rs | 5 - sdk/src/wallet/core/builder.rs | 68 +++---- sdk/src/wallet/core/mod.rs | 178 +++++++----------- .../core/operations/address_generation.rs | 2 +- sdk/src/wallet/core/operations/client.rs | 4 +- .../core/operations/stronghold_backup/mod.rs | 98 +++++----- .../stronghold_backup/stronghold_snapshot.rs | 97 +++++++--- sdk/src/wallet/operations/balance.rs | 38 ++-- sdk/src/wallet/operations/block.rs | 2 +- sdk/src/wallet/operations/output_claiming.rs | 40 ++-- .../wallet/operations/output_consolidation.rs | 16 +- .../wallet/operations/participation/mod.rs | 10 +- .../wallet/operations/syncing/foundries.rs | 6 +- sdk/src/wallet/operations/syncing/mod.rs | 2 +- sdk/src/wallet/operations/syncing/outputs.rs | 30 +-- .../wallet/operations/syncing/transactions.rs | 28 +-- .../wallet/operations/transaction/account.rs | 6 +- .../burning_melting/melt_native_token.rs | 2 +- .../transaction/high_level/create_account.rs | 2 +- .../high_level/delegation/create.rs | 2 +- .../high_level/delegation/delay.rs | 2 +- .../high_level/minting/mint_native_token.rs | 8 +- .../transaction/high_level/send_nft.rs | 2 +- .../transaction/high_level/staking/begin.rs | 2 +- .../transaction/high_level/staking/end.rs | 2 +- .../transaction/high_level/staking/extend.rs | 2 +- .../operations/transaction/input_selection.rs | 35 ++-- sdk/src/wallet/operations/transaction/mod.rs | 17 +- .../operations/transaction/prepare_output.rs | 2 +- .../operations/wait_for_tx_acceptance.rs | 2 +- sdk/src/wallet/storage/constants.rs | 8 +- sdk/src/wallet/storage/manager.rs | 62 +++--- sdk/src/wallet/types/mod.rs | 10 +- sdk/src/wallet/update.rs | 100 +++++----- sdk/tests/types/transaction_id.rs | 8 +- sdk/tests/wallet/address_generation.rs | 3 +- sdk/tests/wallet/backup_restore.rs | 24 +-- sdk/tests/wallet/consolidation.rs | 4 +- sdk/tests/wallet/core.rs | 11 +- sdk/tests/wallet/output_preparation.rs | 2 +- sdk/tests/wallet/syncing.rs | 6 +- sdk/tests/wallet/transactions.rs | 22 +-- 51 files changed, 553 insertions(+), 511 deletions(-) diff --git a/bindings/core/src/method_handler/wallet.rs b/bindings/core/src/method_handler/wallet.rs index 17979f26e6..bc53e77ff4 100644 --- a/bindings/core/src/method_handler/wallet.rs +++ b/bindings/core/src/method_handler/wallet.rs @@ -18,7 +18,7 @@ use crate::{method::WalletMethod, response::Response}; /// Call a wallet method. pub(crate) async fn call_wallet_method_internal(wallet: &Wallet, method: WalletMethod) -> crate::Result { let response = match method { - WalletMethod::Accounts => Response::OutputsData(wallet.data().await.accounts().cloned().collect()), + WalletMethod::Accounts => Response::OutputsData(wallet.ledger().await.accounts().cloned().collect()), #[cfg(feature = "stronghold")] WalletMethod::Backup { destination, password } => { wallet.backup(destination, password).await?; @@ -144,17 +144,14 @@ pub(crate) async fn call_wallet_method_internal(wallet: &Wallet, method: WalletM // wallet.deregister_participation_event(&event_id).await?; // Response::Ok // } - WalletMethod::GetAddress => { - let address = wallet.address().await; - Response::Address(address) - } + WalletMethod::GetAddress => Response::Address(wallet.address().await), WalletMethod::GetBalance => Response::Balance(wallet.balance().await?), WalletMethod::GetFoundryOutput { token_id } => { let output = wallet.get_foundry_output(token_id).await?; Response::Output(output) } WalletMethod::GetIncomingTransaction { transaction_id } => wallet - .data() + .ledger() .await .get_incoming_transaction(&transaction_id) .map_or_else( @@ -162,7 +159,7 @@ pub(crate) async fn call_wallet_method_internal(wallet: &Wallet, method: WalletM |transaction| Response::Transaction(Some(Box::new(TransactionWithMetadataDto::from(transaction)))), ), WalletMethod::GetOutput { output_id } => { - Response::OutputData(wallet.data().await.get_output(&output_id).cloned().map(Box::new)) + Response::OutputData(wallet.ledger().await.get_output(&output_id).cloned().map(Box::new)) } // #[cfg(feature = "participation")] // WalletMethod::GetParticipationEvent { event_id } => { @@ -191,7 +188,7 @@ pub(crate) async fn call_wallet_method_internal(wallet: &Wallet, method: WalletM // } WalletMethod::GetTransaction { transaction_id } => Response::Transaction( wallet - .data() + .ledger() .await .get_transaction(&transaction_id) .map(TransactionWithMetadataDto::from) @@ -227,11 +224,11 @@ pub(crate) async fn call_wallet_method_internal(wallet: &Wallet, method: WalletM Response::PreparedTransaction(data) } WalletMethod::ImplicitAccounts => { - Response::OutputsData(wallet.data().await.implicit_accounts().cloned().collect()) + Response::OutputsData(wallet.ledger().await.implicit_accounts().cloned().collect()) } WalletMethod::IncomingTransactions => Response::Transactions( wallet - .data() + .ledger() .await .incoming_transactions() .values() @@ -239,16 +236,16 @@ pub(crate) async fn call_wallet_method_internal(wallet: &Wallet, method: WalletM .collect(), ), WalletMethod::Outputs { filter_options } => { - let wallet_data = wallet.data().await; + let wallet_ledger = wallet.ledger().await; Response::OutputsData(if let Some(filter) = filter_options { - wallet_data.filtered_outputs(filter).cloned().collect() + wallet_ledger.filtered_outputs(filter).cloned().collect() } else { - wallet_data.outputs().values().cloned().collect() + wallet_ledger.outputs().values().cloned().collect() }) } WalletMethod::PendingTransactions => Response::Transactions( wallet - .data() + .ledger() .await .pending_transactions() .map(TransactionWithMetadataDto::from) @@ -444,7 +441,7 @@ pub(crate) async fn call_wallet_method_internal(wallet: &Wallet, method: WalletM WalletMethod::Sync { options } => Response::Balance(wallet.sync(options).await?), WalletMethod::Transactions => Response::Transactions( wallet - .data() + .ledger() .await .transactions() .values() @@ -452,11 +449,11 @@ pub(crate) async fn call_wallet_method_internal(wallet: &Wallet, method: WalletM .collect(), ), WalletMethod::UnspentOutputs { filter_options } => { - let wallet_data = wallet.data().await; + let wallet_ledger = wallet.ledger().await; Response::OutputsData(if let Some(filter) = filter_options { - wallet_data.filtered_unspent_outputs(filter).cloned().collect() + wallet_ledger.filtered_unspent_outputs(filter).cloned().collect() } else { - wallet_data.unspent_outputs().values().cloned().collect() + wallet_ledger.unspent_outputs().values().cloned().collect() }) } }; diff --git a/cli/src/cli.rs b/cli/src/cli.rs index c4a5f6c8c2..3527f10227 100644 --- a/cli/src/cli.rs +++ b/cli/src/cli.rs @@ -497,7 +497,7 @@ pub async fn restore_command_stronghold( if let Err(e) = wallet.restore_backup(backup_path.into(), password, None, None).await { // Clean up the file system after a failed restore (typically produces a wallet without a secret manager). // TODO: a better way would be to not create any files/dirs in the first place when it's not clear yet whether - // the restore will be successful. + // the restore will be successful. https://github.com/iotaledger/iota-sdk/issues/2018 if storage_path.is_dir() && !restore_into_existing_wallet { std::fs::remove_dir_all(storage_path)?; } diff --git a/cli/src/wallet_cli/mod.rs b/cli/src/wallet_cli/mod.rs index 49fdc8853c..5b7ff66755 100644 --- a/cli/src/wallet_cli/mod.rs +++ b/cli/src/wallet_cli/mod.rs @@ -391,8 +391,8 @@ impl FromStr for OutputSelector { // `accounts` command pub async fn accounts_command(wallet: &Wallet) -> Result<(), Error> { - let wallet_data = wallet.data().await; - let accounts = wallet_data.accounts(); + let wallet_ledger = wallet.ledger().await; + let accounts = wallet_ledger.accounts(); let hrp = wallet.client().get_bech32_hrp().await?; println_log_info!("Accounts:\n"); @@ -430,9 +430,9 @@ pub async fn address_command(wallet: &Wallet) -> Result<(), Error> { // `allot-mana` command pub async fn allot_mana_command(wallet: &Wallet, mana: u64, account_id: Option) -> Result<(), Error> { let account_id = { - let wallet_data = wallet.data().await; + let wallet_ledger = wallet.ledger().await; account_id - .or_else(|| wallet_data.first_account_id()) + .or_else(|| wallet_ledger.first_account_id()) .ok_or(WalletError::AccountNotFound)? }; @@ -565,9 +565,9 @@ pub async fn claim_command(wallet: &Wallet, output_id: Option) -> Resu /// `claimable-outputs` command pub async fn claimable_outputs_command(wallet: &Wallet) -> Result<(), Error> { for output_id in wallet.claimable_outputs(OutputsToClaim::All).await? { - let wallet_data = wallet.data().await; + let wallet_ledger = wallet.ledger().await; // Unwrap: for the iterated `OutputId`s this call will always return `Some(...)`. - let output = &wallet_data.get_output(&output_id).unwrap().output; + let output = &wallet_ledger.get_output(&output_id).unwrap().output; let kind = match output { Output::Nft(_) => "Nft", Output::Basic(_) => "Basic", @@ -613,9 +613,9 @@ pub async fn congestion_command( work_score: Option, ) -> Result<(), Error> { let account_id = { - let wallet_data = wallet.data().await; + let wallet_ledger = wallet.ledger().await; account_id - .or_else(|| wallet_data.first_account_id()) + .or_else(|| wallet_ledger.first_account_id()) .ok_or(WalletError::AccountNotFound)? }; @@ -868,8 +868,8 @@ pub async fn implicit_account_transition_command(wallet: &Wallet, output_id: Out // `implicit-accounts` command pub async fn implicit_accounts_command(wallet: &Wallet) -> Result<(), Error> { - let wallet_data = wallet.data().await; - let implicit_accounts = wallet_data.implicit_accounts(); + let wallet_ledger = wallet.ledger().await; + let implicit_accounts = wallet_ledger.implicit_accounts(); let hrp = wallet.client().get_bech32_hrp().await?; println_log_info!("Implicit accounts:\n"); @@ -974,11 +974,11 @@ pub async fn node_info_command(wallet: &Wallet) -> Result<(), Error> { /// `output` command pub async fn output_command(wallet: &Wallet, selector: OutputSelector, metadata: bool) -> Result<(), Error> { - let wallet_data = wallet.data().await; + let wallet_ledger = wallet.ledger().await; let output = match selector { - OutputSelector::Id(id) => wallet_data.get_output(&id), + OutputSelector::Id(id) => wallet_ledger.get_output(&id), OutputSelector::Index(index) => { - let mut outputs = wallet_data.outputs().values().collect::>(); + let mut outputs = wallet_ledger.outputs().values().collect::>(); outputs.sort_unstable_by_key(|o| o.output_id); outputs.into_iter().nth(index) } @@ -999,7 +999,7 @@ pub async fn output_command(wallet: &Wallet, selector: OutputSelector, metadata: /// `outputs` command pub async fn outputs_command(wallet: &Wallet) -> Result<(), Error> { - print_outputs(wallet.data().await.outputs().values().cloned().collect(), "Outputs:") + print_outputs(wallet.ledger().await.outputs().values().cloned().collect(), "Outputs:") } // `send` command @@ -1104,11 +1104,11 @@ pub async fn sync_command(wallet: &Wallet) -> Result<(), Error> { /// `transaction` command pub async fn transaction_command(wallet: &Wallet, selector: TransactionSelector) -> Result<(), Error> { - let wallet_data = wallet.data().await; + let wallet_ledger = wallet.ledger().await; let transaction = match selector { - TransactionSelector::Id(id) => wallet_data.get_transaction(&id), + TransactionSelector::Id(id) => wallet_ledger.get_transaction(&id), TransactionSelector::Index(index) => { - let mut transactions = wallet_data.transactions().values().collect::>(); + let mut transactions = wallet_ledger.transactions().values().collect::>(); transactions.sort_unstable_by(|a, b| b.timestamp.cmp(&a.timestamp)); transactions.into_iter().nth(index) } @@ -1125,8 +1125,8 @@ pub async fn transaction_command(wallet: &Wallet, selector: TransactionSelector) /// `transactions` command pub async fn transactions_command(wallet: &Wallet, show_details: bool) -> Result<(), Error> { - let wallet_data = wallet.data().await; - let mut transactions = wallet_data.transactions().values().collect::>(); + let wallet_ledger = wallet.ledger().await; + let mut transactions = wallet_ledger.transactions().values().collect::>(); transactions.sort_unstable_by(|a, b| b.timestamp.cmp(&a.timestamp)); if transactions.is_empty() { @@ -1150,7 +1150,7 @@ pub async fn transactions_command(wallet: &Wallet, show_details: bool) -> Result /// `unspent-outputs` command pub async fn unspent_outputs_command(wallet: &Wallet) -> Result<(), Error> { print_outputs( - wallet.data().await.unspent_outputs().values().cloned().collect(), + wallet.ledger().await.unspent_outputs().values().cloned().collect(), "Unspent outputs:", ) } @@ -1253,7 +1253,7 @@ async fn print_wallet_address(wallet: &Wallet) -> Result<(), Error> { let mut delegations = Vec::new(); let mut anchors = Vec::new(); - for output_data in wallet.data().await.unspent_outputs().values() { + for output_data in wallet.ledger().await.unspent_outputs().values() { let output_id = output_data.output_id; output_ids.push(output_id); diff --git a/sdk/examples/how_tos/wallet/consolidate_outputs.rs b/sdk/examples/how_tos/wallet/consolidate_outputs.rs index 8b9df45733..f630c99161 100644 --- a/sdk/examples/how_tos/wallet/consolidate_outputs.rs +++ b/sdk/examples/how_tos/wallet/consolidate_outputs.rs @@ -49,7 +49,7 @@ async fn main() -> Result<()> { // output. println!("Outputs BEFORE consolidation:"); wallet - .data() + .ledger() .await .unspent_outputs() .values() @@ -89,7 +89,7 @@ async fn main() -> Result<()> { // Outputs after consolidation println!("Outputs AFTER consolidation:"); wallet - .data() + .ledger() .await .unspent_outputs() .values() diff --git a/sdk/examples/how_tos/wallet/create_wallet.rs b/sdk/examples/how_tos/wallet/create_wallet.rs index 8d9acb5a92..ca14347a08 100644 --- a/sdk/examples/how_tos/wallet/create_wallet.rs +++ b/sdk/examples/how_tos/wallet/create_wallet.rs @@ -48,7 +48,7 @@ async fn main() -> Result<()> { let client_options = ClientOptions::new().with_node(&std::env::var("NODE_URL").unwrap())?; // Create the wallet - let wallet = Wallet::builder() + Wallet::builder() .with_secret_manager(SecretManager::Stronghold(secret_manager)) .with_storage_path(&std::env::var("WALLET_DB_PATH").unwrap()) .with_client_options(client_options) diff --git a/sdk/examples/how_tos/wallet/list_outputs.rs b/sdk/examples/how_tos/wallet/list_outputs.rs index fc5a77bde9..111d3b9989 100644 --- a/sdk/examples/how_tos/wallet/list_outputs.rs +++ b/sdk/examples/how_tos/wallet/list_outputs.rs @@ -29,13 +29,13 @@ async fn main() -> Result<()> { // Print output ids println!("Output ids:"); - for output in wallet.data().await.outputs().values() { + for output in wallet.ledger().await.outputs().values() { println!("{}", output.output_id); } // Print unspent output ids println!("Unspent output ids:"); - for output in wallet.data().await.unspent_outputs().values() { + for output in wallet.ledger().await.unspent_outputs().values() { println!("{}", output.output_id); } diff --git a/sdk/examples/how_tos/wallet/list_transactions.rs b/sdk/examples/how_tos/wallet/list_transactions.rs index fea3b5685c..f2566a7e70 100644 --- a/sdk/examples/how_tos/wallet/list_transactions.rs +++ b/sdk/examples/how_tos/wallet/list_transactions.rs @@ -37,13 +37,13 @@ async fn main() -> Result<()> { // Print transaction ids println!("Sent transactions:"); - for transaction_id in wallet.data().await.transactions().keys() { + for transaction_id in wallet.ledger().await.transactions().keys() { println!("{}", transaction_id); } // Print received transaction ids println!("Received transactions:"); - for transaction_id in wallet.data().await.incoming_transactions().keys() { + for transaction_id in wallet.ledger().await.incoming_transactions().keys() { println!("{}", transaction_id); } diff --git a/sdk/examples/wallet/spammer.rs b/sdk/examples/wallet/spammer.rs index 25f0ece4d4..9accfd388d 100644 --- a/sdk/examples/wallet/spammer.rs +++ b/sdk/examples/wallet/spammer.rs @@ -74,7 +74,7 @@ async fn main() -> Result<()> { // We make sure that for all threads there are always inputs available to // fund the transaction, otherwise we create enough unspent outputs. let num_unspent_basic_outputs_with_send_amount = wallet - .data() + .ledger() .await .filtered_unspent_outputs(FilterOptions { output_types: Some(vec![BasicOutput::KIND]), diff --git a/sdk/examples/wallet/storage.rs b/sdk/examples/wallet/storage.rs index c901a155e3..cd22c6db89 100644 --- a/sdk/examples/wallet/storage.rs +++ b/sdk/examples/wallet/storage.rs @@ -49,7 +49,6 @@ async fn main() -> Result<()> { } async fn sync_print_balance(wallet: &Wallet) -> Result<()> { - let alias = wallet.alias().await; let now = tokio::time::Instant::now(); let balance = wallet.sync(None).await?; println!("Wallet synced in: {:.2?}", now.elapsed()); diff --git a/sdk/examples/wallet/wallet.rs b/sdk/examples/wallet/wallet.rs index a9d8bae81e..87de681822 100644 --- a/sdk/examples/wallet/wallet.rs +++ b/sdk/examples/wallet/wallet.rs @@ -65,11 +65,6 @@ async fn create_wallet() -> Result { .await } -async fn print_address(wallet: &Wallet) -> Result<()> { - println!("Wallet address: {}", wallet.address().await); - Ok(()) -} - async fn sync_print_balance(wallet: &Wallet, full_report: bool) -> Result<()> { let now = tokio::time::Instant::now(); let balance = wallet.sync(None).await?; diff --git a/sdk/src/wallet/core/builder.rs b/sdk/src/wallet/core/builder.rs index ae8df31326..214cde1722 100644 --- a/sdk/src/wallet/core/builder.rs +++ b/sdk/src/wallet/core/builder.rs @@ -19,7 +19,7 @@ use crate::{ client::secret::{GenerateAddressOptions, SecretManage, SecretManager}, types::block::address::{Address, Bech32Address}, wallet::{ - core::{operations::background_syncing::BackgroundSyncStatus, Bip44, WalletData, WalletInner}, + core::{operations::background_syncing::BackgroundSyncStatus, Bip44, WalletInner, WalletLedger}, operations::syncing::SyncOptions, ClientOptions, Wallet, }, @@ -178,9 +178,20 @@ where self.secret_manager = secret_manager; } + let restored_bip_path = loaded_wallet_builder.as_ref().and_then(|builder| builder.bip_path); + // May use a previously stored BIP path if it wasn't provided - if self.bip_path.is_none() { - self.bip_path = loaded_wallet_builder.as_ref().and_then(|builder| builder.bip_path); + if let Some(bip_path) = self.bip_path { + if let Some(restored_bip_path) = restored_bip_path { + if bip_path != restored_bip_path { + return Err(crate::wallet::Error::BipPathMismatch { + new_bip_path: Some(bip_path), + old_bip_path: Some(restored_bip_path), + }); + } + } + } else { + self.bip_path = restored_bip_path; } // May use a previously stored wallet alias if it wasn't provided @@ -205,23 +216,10 @@ where } } // Panic: can be safely unwrapped now - let address = self.address.as_ref().unwrap().clone(); - - #[cfg(feature = "storage")] - let mut wallet_data = storage_manager.load_wallet_data().await?; + let wallet_address = self.address.as_ref().unwrap().clone(); - // The bip path must not change. #[cfg(feature = "storage")] - if let Some(wallet_data) = &wallet_data { - let new_bip_path = self.bip_path; - let old_bip_path = wallet_data.bip_path; - if new_bip_path != old_bip_path { - return Err(crate::wallet::Error::BipPathMismatch { - new_bip_path, - old_bip_path, - }); - } - } + let mut wallet_ledger = storage_manager.load_wallet_ledger().await?; // Store the wallet builder (for convenience reasons) #[cfg(feature = "storage")] @@ -230,8 +228,8 @@ where // It happened that inputs got locked, the transaction failed, but they weren't unlocked again, so we do this // here #[cfg(feature = "storage")] - if let Some(wallet_data) = &mut wallet_data { - unlock_unused_inputs(wallet_data)?; + if let Some(wallet_ledger) = &mut wallet_ledger { + unlock_unused_inputs(wallet_ledger)?; } // Create the node client. @@ -260,24 +258,30 @@ where storage_manager, }; #[cfg(feature = "storage")] - let wallet_data = wallet_data.unwrap_or_else(|| WalletData::new(self.bip_path, address, self.alias.clone())); + let wallet_ledger = wallet_ledger.unwrap_or_default(); #[cfg(not(feature = "storage"))] - let wallet_data = WalletData::new(self.bip_path, address, self.alias.clone()); + let wallet_ledger = WalletLedger::default(); + let wallet = Wallet { + address: Arc::new(RwLock::new(wallet_address)), + bip_path: Arc::new(RwLock::new(self.bip_path)), + alias: Arc::new(RwLock::new(self.alias)), inner: Arc::new(wallet_inner), - data: Arc::new(RwLock::new(wallet_data)), + ledger: Arc::new(RwLock::new(wallet_ledger)), }; // If the wallet builder is not set, it means the user provided it and we need to update the addresses. // In the other case it was loaded from the database and addresses are up to date. if provided_client_options { - wallet.update_bech32_hrp().await?; + wallet.update_address_hrp().await?; } Ok(wallet) } /// Generate the wallet address. + /// + /// Note: make sure to only call it after `self.secret_manager` and `self.bip_path` has been set. pub(crate) async fn create_default_wallet_address(&self) -> crate::wallet::Result { let bech32_hrp = self .client_options @@ -313,8 +317,8 @@ where #[cfg(feature = "storage")] pub(crate) async fn from_wallet(wallet: &Wallet) -> Self { Self { - bip_path: wallet.bip_path().await, address: Some(wallet.address().await), + bip_path: wallet.bip_path().await, alias: wallet.alias().await, client_options: Some(wallet.client_options().await), storage_options: Some(wallet.storage_options.clone()), @@ -326,17 +330,17 @@ where // Check if any of the locked inputs is not used in a transaction and unlock them, so they get available for new // transactions #[cfg(feature = "storage")] -fn unlock_unused_inputs(wallet_data: &mut WalletData) -> crate::wallet::Result<()> { +fn unlock_unused_inputs(wallet_ledger: &mut WalletLedger) -> crate::wallet::Result<()> { log::debug!("[unlock_unused_inputs]"); let mut used_inputs = HashSet::new(); - for transaction_id in &wallet_data.pending_transactions { - if let Some(tx) = wallet_data.transactions.get(transaction_id) { + for transaction_id in &wallet_ledger.pending_transactions { + if let Some(tx) = wallet_ledger.transactions.get(transaction_id) { for input in &tx.inputs { used_inputs.insert(*input.metadata.output_id()); } } } - wallet_data.locked_outputs.retain(|input| { + wallet_ledger.locked_outputs.retain(|input| { let used = used_inputs.contains(input); if !used { log::debug!("unlocking unused input {input}"); @@ -357,11 +361,11 @@ pub(crate) mod dto { #[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] pub struct WalletBuilderDto { - #[serde(default, skip_serializing_if = "Option::is_none")] - pub(crate) bip_path: Option, #[serde(default, skip_serializing_if = "Option::is_none")] pub(crate) address: Option, #[serde(default, skip_serializing_if = "Option::is_none")] + pub(crate) bip_path: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] pub(crate) alias: Option, #[serde(default, skip_serializing_if = "Option::is_none")] pub(crate) client_options: Option, @@ -373,8 +377,8 @@ pub(crate) mod dto { impl From for WalletBuilder { fn from(value: WalletBuilderDto) -> Self { Self { - bip_path: value.bip_path, address: value.address, + bip_path: value.bip_path, alias: value.alias, client_options: value.client_options, #[cfg(feature = "storage")] diff --git a/sdk/src/wallet/core/mod.rs b/sdk/src/wallet/core/mod.rs index 59f5b92e1d..30414fb3da 100644 --- a/sdk/src/wallet/core/mod.rs +++ b/sdk/src/wallet/core/mod.rs @@ -46,15 +46,21 @@ use crate::{ /// The stateful wallet used to interact with an IOTA network. #[derive(Debug)] pub struct Wallet { + pub(crate) address: Arc>, + pub(crate) bip_path: Arc>>, + pub(crate) alias: Arc>>, pub(crate) inner: Arc>, - pub(crate) data: Arc>, + pub(crate) ledger: Arc>, } impl Clone for Wallet { fn clone(&self) -> Self { Self { + address: self.address.clone(), + bip_path: self.bip_path.clone(), + alias: self.alias.clone(), inner: self.inner.clone(), - data: self.data.clone(), + ledger: self.ledger.clone(), } } } @@ -100,15 +106,9 @@ pub struct WalletInner { pub(crate) storage_manager: StorageManager, } -/// Wallet data. -#[derive(Clone, Debug, Eq, PartialEq)] -pub struct WalletData { - /// The wallet BIP44 path. - pub(crate) bip_path: Option, - /// The wallet address. - pub(crate) address: Bech32Address, - /// The wallet alias. - pub(crate) alias: Option, +/// Wallet ledger. +#[derive(Clone, Debug, Default, Eq, PartialEq)] +pub struct WalletLedger { /// Outputs // stored separated from the wallet for performance? pub(crate) outputs: HashMap, @@ -137,23 +137,7 @@ pub struct WalletData { pub(crate) native_token_foundries: HashMap, } -impl WalletData { - pub(crate) fn new(bip_path: Option, address: Bech32Address, alias: Option) -> Self { - Self { - bip_path, - address, - alias, - outputs: HashMap::new(), - locked_outputs: HashSet::new(), - unspent_outputs: HashMap::new(), - transactions: HashMap::new(), - pending_transactions: HashSet::new(), - incoming_transactions: HashMap::new(), - inaccessible_incoming_transactions: HashSet::new(), - native_token_foundries: HashMap::new(), - } - } - +impl WalletLedger { fn filter_outputs<'a>( outputs: impl Iterator, filter: FilterOptions, @@ -360,37 +344,12 @@ where crate::wallet::Error: From, crate::client::Error: From, { - /// Create a new wallet. - pub(crate) async fn new(inner: Arc>, data: WalletData) -> Result { - #[cfg(feature = "storage")] - let default_sync_options = inner - .storage_manager - .get_default_sync_options() - .await? - .unwrap_or_default(); - #[cfg(not(feature = "storage"))] - let default_sync_options = Default::default(); - - // TODO: maybe move this into a `reset` fn or smth to avoid this kinda-weird block. - { - let mut last_synced = inner.last_synced.lock().await; - *last_synced = Default::default(); - let mut sync_options = inner.default_sync_options.lock().await; - *sync_options = default_sync_options; - } - - Ok(Self { - inner, - data: Arc::new(RwLock::new(data)), - }) - } - /// Get the [`Output`] that minted a native token by the token ID. First try to get it /// from the wallet, if it isn't in the wallet try to get it from the node pub async fn get_foundry_output(&self, native_token_id: TokenId) -> Result { let foundry_id = FoundryId::from(native_token_id); - for output_data in self.data.read().await.outputs.values() { + for output_data in self.ledger.read().await.outputs.values() { if let Output::Foundry(foundry_output) = &output_data.output { if foundry_output.id() == foundry_id { return Ok(output_data.output.clone()); @@ -410,32 +369,55 @@ where self.inner.emit(wallet_event).await } - pub async fn data(&self) -> tokio::sync::RwLockReadGuard<'_, WalletData> { - self.data.read().await + /// Get the wallet address. + pub async fn address(&self) -> Bech32Address { + self.address.read().await.clone() } - pub(crate) async fn data_mut(&self) -> tokio::sync::RwLockWriteGuard<'_, WalletData> { - self.data.write().await + pub(crate) async fn address_mut(&self) -> tokio::sync::RwLockWriteGuard<'_, Bech32Address> { + self.address.write().await } - #[cfg(feature = "storage")] - pub(crate) fn storage_manager(&self) -> &StorageManager { - &self.storage_manager + /// Get the wallet's Bech32 HRP. + pub async fn bech32_hrp(&self) -> Hrp { + self.address.read().await.hrp + } + + /// Get the wallet's bip path. + pub async fn bip_path(&self) -> Option { + *self.bip_path.read().await + } + + pub(crate) async fn bip_path_mut(&self) -> tokio::sync::RwLockWriteGuard<'_, Option> { + self.bip_path.write().await } /// Get the alias of the wallet if one was set. pub async fn alias(&self) -> Option { - self.data().await.alias.clone() + self.alias.read().await.clone() } - /// Get the wallet address. - pub async fn address(&self) -> Bech32Address { - self.data().await.address.clone() + pub(crate) async fn alias_mut(&self) -> tokio::sync::RwLockWriteGuard<'_, Option> { + self.alias.write().await + } + + /// Get the wallet's ledger state. + pub async fn ledger(&self) -> tokio::sync::RwLockReadGuard<'_, WalletLedger> { + self.ledger.read().await + } + + pub(crate) async fn ledger_mut(&self) -> tokio::sync::RwLockWriteGuard<'_, WalletLedger> { + self.ledger.write().await + } + + #[cfg(feature = "storage")] + pub(crate) fn storage_manager(&self) -> &StorageManager { + &self.storage_manager } /// Returns the implicit account creation address of the wallet if it is Ed25519 based. pub async fn implicit_account_creation_address(&self) -> Result { - let bech32_address = &self.data().await.address; + let bech32_address = &self.address().await; if let Address::Ed25519(address) = bech32_address.inner() { Ok(Bech32Address::new( @@ -446,16 +428,6 @@ where Err(Error::NonEd25519Address) } } - - /// Get the wallet's configured Bech32 HRP. - pub async fn bech32_hrp(&self) -> Hrp { - self.data().await.address.hrp - } - - /// Get the wallet's configured bip path. - pub async fn bip_path(&self) -> Option { - self.data().await.bip_path - } } impl WalletInner { @@ -523,13 +495,10 @@ impl Drop for WalletInner { } } -/// Dto for the wallet data. +/// Dto for the wallet ledger. #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct WalletDataDto { - pub bip_path: Option, - pub address: Bech32Address, - pub alias: Option, +pub struct WalletLedgerDto { pub outputs: HashMap, pub locked_outputs: HashSet, pub unspent_outputs: HashMap, @@ -540,17 +509,14 @@ pub struct WalletDataDto { pub native_token_foundries: HashMap, } -impl TryFromDto for WalletData { +impl TryFromDto for WalletLedger { type Error = crate::wallet::Error; fn try_from_dto_with_params_inner( - dto: WalletDataDto, + dto: WalletLedgerDto, params: Option<&ProtocolParameters>, ) -> core::result::Result { Ok(Self { - bip_path: dto.bip_path, - address: dto.address, - alias: dto.alias, outputs: dto.outputs, locked_outputs: dto.locked_outputs, unspent_outputs: dto.unspent_outputs, @@ -571,12 +537,9 @@ impl TryFromDto for WalletData { } } -impl From<&WalletData> for WalletDataDto { - fn from(value: &WalletData) -> Self { +impl From<&WalletLedger> for WalletLedgerDto { + fn from(value: &WalletLedger) -> Self { Self { - bip_path: value.bip_path, - address: value.address.clone(), - alias: value.alias.clone(), outputs: value.outputs.clone(), locked_outputs: value.locked_outputs.clone(), unspent_outputs: value.unspent_outputs.clone(), @@ -686,13 +649,7 @@ mod test { incoming_transaction, ); - let wallet_data = WalletData { - bip_path: Some(Bip44::new(4218)), - address: crate::types::block::address::Bech32Address::from_str( - "rms1qpllaj0pyveqfkwxmnngz2c488hfdtmfrj3wfkgxtk4gtyrax0jaxzt70zy", - ) - .unwrap(), - alias: Some("Alice".to_string()), + let wallet_ledger = WalletLedger { outputs: HashMap::new(), locked_outputs: HashSet::new(), unspent_outputs: HashMap::new(), @@ -703,29 +660,22 @@ mod test { native_token_foundries: HashMap::new(), }; - let deser_wallet_data = WalletData::try_from_dto( - serde_json::from_str::(&serde_json::to_string(&WalletDataDto::from(&wallet_data)).unwrap()) - .unwrap(), + let deser_wallet_ledger = WalletLedger::try_from_dto( + serde_json::from_str::( + &serde_json::to_string(&WalletLedgerDto::from(&wallet_ledger)).unwrap(), + ) + .unwrap(), ) .unwrap(); - assert_eq!(wallet_data, deser_wallet_data); + assert_eq!(wallet_ledger, deser_wallet_ledger); } - impl WalletData { - /// Returns a mock of this type with the following values: - /// index: 0, coin_type: 4218, alias: "Alice", address: - /// rms1qpllaj0pyveqfkwxmnngz2c488hfdtmfrj3wfkgxtk4gtyrax0jaxzt70zy, all other fields are set to their Rust - /// defaults. + impl WalletLedger { + // TODO: use something non-empty #[cfg(feature = "storage")] - pub(crate) fn mock() -> Self { + pub(crate) fn test_instance() -> Self { Self { - bip_path: Some(Bip44::new(4218)), - address: crate::types::block::address::Bech32Address::from_str( - "rms1qpllaj0pyveqfkwxmnngz2c488hfdtmfrj3wfkgxtk4gtyrax0jaxzt70zy", - ) - .unwrap(), - alias: Some("Alice".to_string()), outputs: HashMap::new(), locked_outputs: HashSet::new(), unspent_outputs: HashMap::new(), diff --git a/sdk/src/wallet/core/operations/address_generation.rs b/sdk/src/wallet/core/operations/address_generation.rs index 50427dd324..42e51294fa 100644 --- a/sdk/src/wallet/core/operations/address_generation.rs +++ b/sdk/src/wallet/core/operations/address_generation.rs @@ -25,7 +25,7 @@ impl Wallet { address_index: u32, options: impl Into> + Send, ) -> crate::wallet::Result { - // TODO #1279: not sure yet whether we also should allow this method to generate addresses for different bip + // TODO: not sure yet whether we also should allow this method to generate addresses for different bip // paths. let coin_type = self.bip_path().await.ok_or(Error::MissingBipPath)?.coin_type; diff --git a/sdk/src/wallet/core/operations/client.rs b/sdk/src/wallet/core/operations/client.rs index 1955b3e7f5..2f6d513b10 100644 --- a/sdk/src/wallet/core/operations/client.rs +++ b/sdk/src/wallet/core/operations/client.rs @@ -67,7 +67,7 @@ where } *self.client.network_info.write().await = network_info; - self.update_bech32_hrp().await?; + self.update_address_hrp().await?; } #[cfg(feature = "storage")] @@ -139,7 +139,7 @@ where .update_node_manager(node_manager_builder.build(HashSet::new())) .await?; - self.update_bech32_hrp().await?; + self.update_address_hrp().await?; Ok(()) } diff --git a/sdk/src/wallet/core/operations/stronghold_backup/mod.rs b/sdk/src/wallet/core/operations/stronghold_backup/mod.rs index 3435397b5e..acbf5b8366 100644 --- a/sdk/src/wallet/core/operations/stronghold_backup/mod.rs +++ b/sdk/src/wallet/core/operations/stronghold_backup/mod.rs @@ -5,7 +5,7 @@ pub(crate) mod stronghold_snapshot; use std::{fs, path::PathBuf}; -use self::stronghold_snapshot::read_wallet_data_from_stronghold_snapshot; +use self::stronghold_snapshot::restore_from_stronghold_snapshot; #[cfg(feature = "storage")] use crate::wallet::WalletBuilder; use crate::{ @@ -14,11 +14,12 @@ use crate::{ utils::Password, }, types::block::address::Hrp, - wallet::Wallet, + wallet::{core::WalletLedgerDto, Wallet}, }; impl Wallet { - /// Backup the wallet data in a Stronghold file. + /// Backup the wallet in a Stronghold snapshot file. + /// /// `stronghold_password` must be the current one when Stronghold is used as SecretManager. pub async fn backup( &self, @@ -34,7 +35,7 @@ impl Wallet { // Backup with existing stronghold SecretManager::Stronghold(stronghold) => { stronghold.set_password(stronghold_password).await?; - self.store_data_to_stronghold(stronghold).await?; + self.backup_to_stronghold_snapshot(stronghold).await?; // Write snapshot to backup path stronghold.write_stronghold_snapshot(Some(&backup_path)).await?; } @@ -45,7 +46,7 @@ impl Wallet { .password(stronghold_password) .build(backup_path)?; - self.store_data_to_stronghold(&backup_stronghold).await?; + self.backup_to_stronghold_snapshot(&backup_stronghold).await?; // Write snapshot to backup path backup_stronghold.write_stronghold_snapshot(None).await?; @@ -55,7 +56,8 @@ impl Wallet { Ok(()) } - /// Restore a backup from a Stronghold file + /// Restore a backup from a Stronghold snapshot file. + /// /// Replaces client_options, bip_path, secret_manager and wallet. Returns an error if the wallet was already /// created If Stronghold is used as secret_manager, the existing Stronghold file will be overwritten. If a /// mnemonic was stored, it will be gone. @@ -79,34 +81,32 @@ impl Wallet { return Err(crate::wallet::Error::Backup("backup path doesn't exist")); } - let wallet_data = self.data().await; + let wallet_ledger = self.ledger().await; // We don't want to overwrite a possible existing wallet - if !wallet_data.outputs.is_empty() { + if !wallet_ledger.outputs.is_empty() { return Err(crate::wallet::Error::Backup( "can't restore backup when there is already a wallet", )); } - let curr_bip_path = wallet_data.bip_path; + let curr_bip_path = self.bip_path().await; // Explicitly drop the data to avoid contention - drop(wallet_data); + drop(wallet_ledger); // We'll create a new stronghold to load the backup let new_stronghold = StrongholdSecretManager::builder() .password(stronghold_password.clone()) .build(backup_path.clone())?; - let (read_client_options, read_secret_manager, read_wallet_data) = - read_wallet_data_from_stronghold_snapshot::(&new_stronghold).await?; - - let read_bip_path = read_wallet_data.as_ref().and_then(|data| data.bip_path); + let (read_address, read_bip_path, read_alias, read_client_options, read_secret_manager, read_wallet_ledger) = + restore_from_stronghold_snapshot::(&new_stronghold).await?; // If the bip path is not matching the current one, we may ignore the backup let ignore_backup_values = ignore_if_bip_path_mismatch.map_or(false, |ignore| { if ignore { - // TODO: #1279 okay that if both are none we always load the backup values? + // TODO: is it okay that if both are none we always load the backup values? curr_bip_path != read_bip_path } else { false @@ -114,7 +114,7 @@ impl Wallet { }); if !ignore_backup_values { - self.data_mut().await.bip_path = read_bip_path; + *self.bip_path_mut().await = read_bip_path; } // Get the current snapshot path if set @@ -154,14 +154,17 @@ impl Wallet { } if !ignore_backup_values { - if let Some(read_wallet_data) = read_wallet_data { + if let Some(read_wallet_ledger) = read_wallet_ledger { let restore_wallet = ignore_if_bech32_hrp_mismatch.map_or(true, |expected_bech32_hrp| { // Only restore if bech32 hrps match - read_wallet_data.address.hrp() == &expected_bech32_hrp + read_address.hrp() == &expected_bech32_hrp }); if restore_wallet { - *self.data_mut().await = read_wallet_data; + *self.address_mut().await = read_address; + *self.bip_path_mut().await = read_bip_path; + *self.alias_mut().await = read_alias; + *self.ledger_mut().await = read_wallet_ledger; } } } @@ -182,12 +185,16 @@ impl Wallet { .expect("can't convert os string"), ) .with_client_options(self.client_options().await) - .with_bip_path(self.data().await.bip_path); + .with_address(self.address().await) + .with_bip_path(self.bip_path().await) + .with_alias(self.alias().await); wallet_builder.save(self.storage_manager()).await?; - // also save wallet data to db - self.storage_manager().save_wallet_data(&*self.data().await).await?; + // also save wallet ledger to db + self.storage_manager() + .save_wallet_ledger(&WalletLedgerDto::from(&*self.ledger().await)) + .await?; } Ok(()) @@ -195,8 +202,9 @@ impl Wallet { } impl Wallet { - /// Backup the wallet data in a Stronghold file - /// stronghold_password must be the current one when Stronghold is used as SecretManager. + /// Backup the wallet in a Stronghold snapshot file. + /// + /// `stronghold_password` must be the current one when Stronghold is used as SecretManager. pub async fn backup( &self, backup_path: PathBuf, @@ -207,7 +215,7 @@ impl Wallet { secret_manager.set_password(stronghold_password).await?; - self.store_data_to_stronghold(&secret_manager).await?; + self.backup_to_stronghold_snapshot(&secret_manager).await?; // Write snapshot to backup path secret_manager.write_stronghold_snapshot(Some(&backup_path)).await?; @@ -215,7 +223,8 @@ impl Wallet { Ok(()) } - /// Restore a backup from a Stronghold file + /// Restore a backup from a Stronghold file. + /// /// Replaces client_options, bip path, secret_manager and wallet. Returns an error if the wallet was already /// created If Stronghold is used as secret_manager, the existing Stronghold file will be overwritten. If a /// mnemonic was stored, it will be gone. @@ -239,34 +248,32 @@ impl Wallet { return Err(crate::wallet::Error::Backup("backup path doesn't exist")); } - let wallet_data = self.data().await; + let wallet_ledger = self.ledger().await; // We don't want to overwrite a possible existing wallet - if !wallet_data.outputs.is_empty() { + if !wallet_ledger.outputs.is_empty() { return Err(crate::wallet::Error::Backup( "can't restore backup when there is already a wallet", )); } - let curr_bip_path = wallet_data.bip_path; + let curr_bip_path = self.bip_path().await; // Explicitly drop the data to avoid contention - drop(wallet_data); + drop(wallet_ledger); // We'll create a new stronghold to load the backup let new_stronghold = StrongholdSecretManager::builder() .password(stronghold_password.clone()) .build(backup_path.clone())?; - let (read_client_options, read_secret_manager, read_wallet_data) = - read_wallet_data_from_stronghold_snapshot::(&new_stronghold).await?; - - let read_bip_path = read_wallet_data.as_ref().and_then(|data| data.bip_path); + let (read_address, read_bip_path, read_alias, read_client_options, read_secret_manager, read_wallet_ledger) = + restore_from_stronghold_snapshot::(&new_stronghold).await?; // If the bip path is not matching the current one, we may ignore the backup let ignore_backup_values = ignore_if_bip_path_mismatch.map_or(false, |ignore| { if ignore { - // TODO: #1279 okay that if both are none we always load the backup values? + // TODO: is it okay that if both are none we always load the backup values? curr_bip_path != read_bip_path } else { false @@ -274,7 +281,7 @@ impl Wallet { }); if !ignore_backup_values { - self.data_mut().await.bip_path = read_bip_path; + *self.bip_path_mut().await = read_bip_path; } if let Some(mut read_secret_manager) = read_secret_manager { @@ -303,14 +310,17 @@ impl Wallet { } if !ignore_backup_values { - if let Some(read_wallet_data) = read_wallet_data { + if let Some(read_wallet_ledger) = read_wallet_ledger { let restore_wallet = ignore_if_bech32_hrp_mismatch.map_or(true, |expected_bech32_hrp| { // Only restore if bech32 hrps match - read_wallet_data.address.hrp() == &expected_bech32_hrp + read_address.hrp() == &expected_bech32_hrp }); if restore_wallet { - *self.data_mut().await = read_wallet_data; + *self.address_mut().await = read_address; + *self.bip_path_mut().await = read_bip_path; + *self.alias_mut().await = read_alias; + *self.ledger_mut().await = read_wallet_ledger; } } } @@ -331,12 +341,16 @@ impl Wallet { .expect("can't convert os string"), ) .with_client_options(self.client_options().await) - .with_bip_path(self.data().await.bip_path); + .with_address(self.address().await) + .with_bip_path(self.bip_path().await) + .with_alias(self.alias().await); wallet_builder.save(self.storage_manager()).await?; - // also save wallet data to db - self.storage_manager().save_wallet_data(&*self.data().await).await?; + // also save wallet ledger to db + self.storage_manager() + .save_wallet_ledger(&WalletLedgerDto::from(&*self.ledger().await)) + .await?; } Ok(()) diff --git a/sdk/src/wallet/core/operations/stronghold_backup/stronghold_snapshot.rs b/sdk/src/wallet/core/operations/stronghold_backup/stronghold_snapshot.rs index 83632aaca1..3d40b556be 100644 --- a/sdk/src/wallet/core/operations/stronghold_backup/stronghold_snapshot.rs +++ b/sdk/src/wallet/core/operations/stronghold_backup/stronghold_snapshot.rs @@ -1,11 +1,14 @@ // Copyright 2022 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 +use crypto::keys::bip44::Bip44; + use crate::{ client::{secret::SecretManagerConfig, storage::StorageAdapter, stronghold::StrongholdAdapter}, - types::TryFromDto, + types::{block::address::Bech32Address, TryFromDto}, wallet::{ - core::{WalletData, WalletDataDto}, + self, + core::{WalletLedger, WalletLedgerDto}, migration::{latest_backup_migration_version, migrate, MIGRATION_VERSION_KEY}, ClientOptions, Wallet, }, @@ -13,61 +16,95 @@ use crate::{ pub(crate) const CLIENT_OPTIONS_KEY: &str = "client_options"; pub(crate) const SECRET_MANAGER_KEY: &str = "secret_manager"; -pub(crate) const WALLET_DATA_KEY: &str = "wallet_data"; +pub(crate) const WALLET_LEDGER_KEY: &str = "wallet_ledger"; +pub(crate) const WALLET_ADDRESS_KEY: &str = "wallet_address"; +pub(crate) const WALLET_BIP_PATH_KEY: &str = "wallet_bip_path"; +pub(crate) const WALLET_ALIAS_KEY: &str = "wallet_alias"; -impl Wallet { - pub(crate) async fn store_data_to_stronghold(&self, stronghold: &StrongholdAdapter) -> crate::wallet::Result<()> { +impl Wallet +where + crate::wallet::Error: From, + crate::client::Error: From, +{ + pub(crate) async fn backup_to_stronghold_snapshot( + &self, + stronghold: &StrongholdAdapter, + ) -> crate::wallet::Result<()> { // Set migration version stronghold .set(MIGRATION_VERSION_KEY, &latest_backup_migration_version()) .await?; + // Store the client options let client_options = self.client_options().await; stronghold.set(CLIENT_OPTIONS_KEY, &client_options).await?; + // Store the secret manager if let Some(secret_manager_dto) = self.secret_manager.read().await.to_config() { stronghold.set(SECRET_MANAGER_KEY, &secret_manager_dto).await?; } - let serialized_wallet_data = serde_json::to_value(&WalletDataDto::from(&*self.data.read().await))?; - stronghold.set(WALLET_DATA_KEY, &serialized_wallet_data).await?; + // Store the wallet address + stronghold + .set(WALLET_ADDRESS_KEY, self.address().await.as_ref()) + .await?; + + // Store the wallet bip path + stronghold.set(WALLET_BIP_PATH_KEY, &self.bip_path().await).await?; + + // Store the wallet alias + stronghold.set(WALLET_ALIAS_KEY, &self.alias().await).await?; + + let serialized_wallet_ledger = serde_json::to_value(&WalletLedgerDto::from(&*self.ledger.read().await))?; + stronghold.set(WALLET_LEDGER_KEY, &serialized_wallet_ledger).await?; Ok(()) } } -pub(crate) async fn read_wallet_data_from_stronghold_snapshot( +pub(crate) async fn restore_from_stronghold_snapshot( stronghold: &StrongholdAdapter, -) -> crate::wallet::Result<(Option, Option, Option)> { +) -> crate::wallet::Result<( + Bech32Address, + Option, + Option, + Option, + Option, + Option, +)> { migrate(stronghold).await?; // Get client_options let client_options = stronghold.get(CLIENT_OPTIONS_KEY).await?; - // TODO #1279: remove - // // Get coin_type - // let coin_type_bytes = stronghold.get_bytes(COIN_TYPE_KEY).await?; - // let coin_type = if let Some(coin_type_bytes) = coin_type_bytes { - // let coin_type = u32::from_le_bytes( - // coin_type_bytes - // .try_into() - // .map_err(|_| WalletError::Backup("invalid coin_type"))?, - // ); - // log::debug!("[restore_backup] restored coin_type: {coin_type}"); - // Some(coin_type) - // } else { - // None - // }; - // Get secret_manager - let restored_secret_manager = stronghold.get(SECRET_MANAGER_KEY).await?; + let secret_manager = stronghold.get(SECRET_MANAGER_KEY).await?; + + // Get the wallet address + let wallet_address = stronghold + .get(WALLET_ADDRESS_KEY) + .await? + .ok_or(wallet::Error::Backup("missing non-optional wallet address"))?; + + // Get the wallet bip path + let wallet_bip_path = stronghold.get(WALLET_BIP_PATH_KEY).await?; + + // Get the wallet alias + let wallet_alias = stronghold.get(WALLET_ALIAS_KEY).await?; - // Get wallet data - let restored_wallet_data = stronghold - .get::(WALLET_DATA_KEY) + // Get wallet ledger + let wallet_ledger = stronghold + .get::(WALLET_LEDGER_KEY) .await? - .map(WalletData::try_from_dto) + .map(WalletLedger::try_from_dto) .transpose()?; - Ok((client_options, restored_secret_manager, restored_wallet_data)) + Ok(( + wallet_address, + wallet_bip_path, + wallet_alias, + client_options, + secret_manager, + wallet_ledger, + )) } diff --git a/sdk/src/wallet/operations/balance.rs b/sdk/src/wallet/operations/balance.rs index e9efdbfd80..400eacd08a 100644 --- a/sdk/src/wallet/operations/balance.rs +++ b/sdk/src/wallet/operations/balance.rs @@ -27,7 +27,8 @@ where let protocol_parameters = self.client().get_protocol_parameters().await?; let slot_index = self.client().get_slot_index().await?; - let wallet_data = self.data().await.clone(); + let wallet_address = self.address().await; + let wallet_ledger = self.ledger().await.clone(); let network_id = protocol_parameters.network_id(); let storage_score_params = protocol_parameters.storage_score_parameters(); @@ -36,20 +37,21 @@ where let mut total_native_tokens = NativeTokensBuilder::default(); #[cfg(feature = "participation")] - let voting_output = wallet_data.get_voting_output()?; + let voting_output = wallet_ledger.get_voting_output()?; - let claimable_outputs = wallet_data.claimable_outputs(OutputsToClaim::All, slot_index, &protocol_parameters)?; + let claimable_outputs = + wallet_ledger.claimable_outputs(&wallet_address, OutputsToClaim::All, slot_index, &protocol_parameters)?; #[cfg(feature = "participation")] { if let Some(voting_output) = &voting_output { - if voting_output.output.as_basic().address() == wallet_data.address.inner() { + if voting_output.output.as_basic().address() == wallet_address.inner() { balance.base_coin.voting_power = voting_output.output.amount(); } } } - for (output_id, output_data) in &wallet_data.unspent_outputs { + for (output_id, output_data) in &wallet_ledger.unspent_outputs { // Check if output is from the network we're currently connected to if output_data.network_id != network_id { continue; @@ -76,7 +78,7 @@ where // Add storage deposit balance.required_storage_deposit.account += storage_cost; - if !wallet_data.locked_outputs.contains(output_id) { + if !wallet_ledger.locked_outputs.contains(output_id) { total_storage_cost += storage_cost; } @@ -88,7 +90,7 @@ where balance.base_coin.total += foundry.amount(); // Add storage deposit balance.required_storage_deposit.foundry += storage_cost; - if !wallet_data.locked_outputs.contains(output_id) { + if !wallet_ledger.locked_outputs.contains(output_id) { total_storage_cost += storage_cost; } @@ -108,7 +110,7 @@ where } // Add storage deposit balance.required_storage_deposit.delegation += storage_cost; - if !wallet_data.locked_outputs.contains(output_id) { + if !wallet_ledger.locked_outputs.contains(output_id) { total_storage_cost += storage_cost; } @@ -141,12 +143,12 @@ where // Add storage deposit if output.is_basic() { balance.required_storage_deposit.basic += storage_cost; - if output.native_token().is_some() && !wallet_data.locked_outputs.contains(output_id) { + if output.native_token().is_some() && !wallet_ledger.locked_outputs.contains(output_id) { total_storage_cost += storage_cost; } } else if output.is_nft() { balance.required_storage_deposit.nft += storage_cost; - if !wallet_data.locked_outputs.contains(output_id) { + if !wallet_ledger.locked_outputs.contains(output_id) { total_storage_cost += storage_cost; } } @@ -172,7 +174,7 @@ where // We use the addresses with unspent outputs, because other addresses of // the account without unspent // outputs can't be related to this output - wallet_data.address.inner(), + wallet_address.inner(), output, slot_index, protocol_parameters.committable_age_range(), @@ -188,7 +190,7 @@ where .map_or_else( || output.amount(), |sdr| { - if wallet_data.address.inner() == sdr.return_address() { + if wallet_address.inner() == sdr.return_address() { // sending to ourself, we get the full amount output.amount() } else { @@ -219,13 +221,13 @@ where // Amount for basic outputs isn't added to total storage cost if there aren't native // tokens, since we can spend it without burning. if output.native_token().is_some() - && !wallet_data.locked_outputs.contains(output_id) + && !wallet_ledger.locked_outputs.contains(output_id) { total_storage_cost += storage_cost; } } else if output.is_nft() { balance.required_storage_deposit.nft += storage_cost; - if !wallet_data.locked_outputs.contains(output_id) { + if !wallet_ledger.locked_outputs.contains(output_id) { total_storage_cost += storage_cost; } } @@ -259,18 +261,18 @@ where } // for `available` get locked_outputs, sum outputs amount and subtract from total_amount - log::debug!("[BALANCE] locked outputs: {:#?}", wallet_data.locked_outputs); + log::debug!("[BALANCE] locked outputs: {:#?}", wallet_ledger.locked_outputs); let mut locked_amount = 0; let mut locked_mana = DecayedMana::default(); let mut locked_native_tokens = NativeTokensBuilder::default(); - for locked_output in &wallet_data.locked_outputs { + for locked_output in &wallet_ledger.locked_outputs { // Skip potentially_locked_outputs, as their amounts aren't added to the balance if balance.potentially_locked_outputs.contains_key(locked_output) { continue; } - if let Some(output_data) = wallet_data.unspent_outputs.get(locked_output) { + if let Some(output_data) = wallet_ledger.unspent_outputs.get(locked_output) { // Only check outputs that are in this network if output_data.network_id == network_id { locked_amount += output_data.output.amount(); @@ -309,7 +311,7 @@ where } }); - let metadata = wallet_data + let metadata = wallet_ledger .native_token_foundries .get(&FoundryId::from(*native_token.token_id())) .and_then(|foundry| foundry.immutable_features().metadata()) diff --git a/sdk/src/wallet/operations/block.rs b/sdk/src/wallet/operations/block.rs index 491bd78ae6..20273a047e 100644 --- a/sdk/src/wallet/operations/block.rs +++ b/sdk/src/wallet/operations/block.rs @@ -22,7 +22,7 @@ where // If an issuer ID is provided, use it; otherwise, use the first available account or implicit account. let issuer_id = match issuer_id.into() { Some(id) => id, - None => self.data().await.first_account_id().ok_or(Error::AccountNotFound)?, + None => self.ledger().await.first_account_id().ok_or(Error::AccountNotFound)?, }; let unsigned_block = self.client().build_basic_block(issuer_id, payload).await?; diff --git a/sdk/src/wallet/operations/output_claiming.rs b/sdk/src/wallet/operations/output_claiming.rs index eaec534a7c..7d063c7e88 100644 --- a/sdk/src/wallet/operations/output_claiming.rs +++ b/sdk/src/wallet/operations/output_claiming.rs @@ -8,7 +8,7 @@ use serde::{Deserialize, Serialize}; use crate::{ client::{api::PreparedTransactionData, secret::SecretManage}, types::block::{ - address::{Address, Ed25519Address}, + address::{Address, Bech32Address, Ed25519Address}, output::{ unlock_condition::AddressUnlockCondition, BasicOutput, NftOutputBuilder, Output, OutputId, UnlockCondition, }, @@ -16,7 +16,7 @@ use crate::{ slot::SlotIndex, }, wallet::{ - core::WalletData, + core::WalletLedger, operations::{helpers::time::can_output_be_unlocked_now, transaction::TransactionOptions}, types::{OutputData, TransactionWithMetadata}, Wallet, @@ -34,7 +34,7 @@ pub enum OutputsToClaim { All, } -impl WalletData { +impl WalletLedger { /// Get basic and nft outputs that have /// [`ExpirationUnlockCondition`](crate::types::block::output::unlock_condition::ExpirationUnlockCondition), /// [`StorageDepositReturnUnlockCondition`](crate::types::block::output::unlock_condition::StorageDepositReturnUnlockCondition) or @@ -43,6 +43,7 @@ impl WalletData { /// additional inputs pub(crate) fn claimable_outputs( &self, + wallet_address: &Bech32Address, outputs_to_claim: OutputsToClaim, slot_index: SlotIndex, protocol_parameters: &ProtocolParameters, @@ -66,7 +67,7 @@ impl WalletData { && can_output_be_unlocked_now( // We use the addresses with unspent outputs, because other addresses of the // account without unspent outputs can't be related to this output - self.address.inner(), + wallet_address.inner(), output_data, slot_index, protocol_parameters.committable_age_range(), @@ -142,12 +143,17 @@ where /// unlocked now and also get basic outputs with only an [`AddressUnlockCondition`] unlock condition, for /// additional inputs pub async fn claimable_outputs(&self, outputs_to_claim: OutputsToClaim) -> crate::wallet::Result> { - let wallet_data = self.data().await; + let wallet_ledger = self.ledger().await; let slot_index = self.client().get_slot_index().await?; let protocol_parameters = self.client().get_protocol_parameters().await?; - wallet_data.claimable_outputs(outputs_to_claim, slot_index, &protocol_parameters) + wallet_ledger.claimable_outputs( + &self.address().await, + outputs_to_claim, + slot_index, + &protocol_parameters, + ) } /// Get basic outputs that have only one unlock condition which is [AddressUnlockCondition], so they can be used as @@ -156,11 +162,11 @@ where log::debug!("[OUTPUT_CLAIMING] get_basic_outputs_for_additional_inputs"); #[cfg(feature = "participation")] let voting_output = self.get_voting_output().await?; - let wallet_data = self.data().await; + let wallet_ledger = self.ledger().await; // Get basic outputs only with AddressUnlockCondition and no other unlock condition let mut basic_outputs: Vec = Vec::new(); - for (output_id, output_data) in &wallet_data.unspent_outputs { + for (output_id, output_data) in &wallet_ledger.unspent_outputs { #[cfg(feature = "participation")] if let Some(ref voting_output) = voting_output { // Remove voting output from inputs, because we don't want to spent it to claim something else. @@ -169,8 +175,8 @@ where } } // Don't use outputs that are locked for other transactions - if !wallet_data.locked_outputs.contains(output_id) { - if let Some(output) = wallet_data.outputs.get(output_id) { + if !wallet_ledger.locked_outputs.contains(output_id) { + if let Some(output) = wallet_ledger.outputs.get(output_id) { if let Output::Basic(basic_output) = &output.output { if let [UnlockCondition::Address(a)] = basic_output.unlock_conditions().as_ref() { // Implicit accounts can't be used @@ -239,12 +245,12 @@ where let storage_score_params = self.client().get_storage_score_parameters().await?; - let wallet_data = self.data().await; + let wallet_ledger = self.ledger().await; let mut outputs_to_claim = Vec::new(); for output_id in output_ids_to_claim { - if let Some(output_data) = wallet_data.unspent_outputs.get(&output_id) { - if !wallet_data.locked_outputs.contains(&output_id) { + if let Some(output_data) = wallet_ledger.unspent_outputs.get(&output_id) { + if !wallet_ledger.locked_outputs.contains(&output_id) { outputs_to_claim.push(output_data.clone()); } } @@ -256,8 +262,8 @@ where )); } - let wallet_address = wallet_data.address.clone(); - drop(wallet_data); + let wallet_address = self.address().await; + drop(wallet_ledger); let mut nft_outputs_to_send = Vec::new(); @@ -279,13 +285,13 @@ where // deposit for the remaining amount and possible native tokens NftOutputBuilder::from(nft_output) .with_nft_id(nft_output.nft_id_non_null(&output_data.output_id)) - .with_unlock_conditions([AddressUnlockCondition::new(wallet_address.clone())]) + .with_unlock_conditions([AddressUnlockCondition::new(&wallet_address)]) .finish_output()? } else { NftOutputBuilder::from(nft_output) .with_minimum_amount(storage_score_params) .with_nft_id(nft_output.nft_id_non_null(&output_data.output_id)) - .with_unlock_conditions([AddressUnlockCondition::new(wallet_address.clone())]) + .with_unlock_conditions([AddressUnlockCondition::new(&wallet_address)]) .finish_output()? }; diff --git a/sdk/src/wallet/operations/output_consolidation.rs b/sdk/src/wallet/operations/output_consolidation.rs index e4fe56c5ed..e9e970fcd1 100644 --- a/sdk/src/wallet/operations/output_consolidation.rs +++ b/sdk/src/wallet/operations/output_consolidation.rs @@ -91,7 +91,7 @@ where /// Prepares the transaction for [Wallet::consolidate_outputs()]. pub async fn prepare_consolidate_outputs(&self, params: ConsolidationParams) -> Result { log::debug!("[OUTPUT_CONSOLIDATION] prepare consolidating outputs if needed"); - let wallet_address = self.data().await.address.clone(); + let wallet_address = self.address().await; let outputs_to_consolidate = self.get_outputs_to_consolidate(¶ms).await?; @@ -160,24 +160,24 @@ where // let voting_output = self.get_voting_output().await?; let slot_index = self.client().get_slot_index().await?; let storage_score_parameters = self.client().get_protocol_parameters().await?.storage_score_parameters; - let wallet_data = self.data().await; - let wallet_address = wallet_data.address.clone(); + let wallet_ledger = self.ledger().await; + let wallet_address = self.address().await; let mut outputs_to_consolidate = Vec::new(); let mut native_token_inputs = HashMap::new(); - for (output_id, output_data) in &wallet_data.unspent_outputs { + for (output_id, output_data) in &wallet_ledger.unspent_outputs { // #[cfg(feature = "participation")] // if let Some(ref voting_output) = voting_output { // // Remove voting output from inputs, because we want to keep its features and not consolidate it. // if output_data.output_id == voting_output.output_id { // continue; - // } + // }.await // } - let is_locked_output = wallet_data.locked_outputs.contains(output_id); + let is_locked_output = wallet_ledger.locked_outputs.contains(output_id); let should_consolidate_output = self - .should_consolidate_output(output_data, slot_index, &wallet_address) + .should_consolidate_output(output_data, slot_index, wallet_address.as_ref()) .await?; if !is_locked_output && should_consolidate_output { outputs_to_consolidate.push(output_data.clone()); @@ -210,7 +210,7 @@ where }) }); - drop(wallet_data); + drop(wallet_ledger); let output_threshold = self.get_output_consolidation_threshold(params).await?; diff --git a/sdk/src/wallet/operations/participation/mod.rs b/sdk/src/wallet/operations/participation/mod.rs index 4b83fa1b69..bd05b35560 100644 --- a/sdk/src/wallet/operations/participation/mod.rs +++ b/sdk/src/wallet/operations/participation/mod.rs @@ -25,7 +25,7 @@ use crate::{ }, block::output::{unlock_condition::UnlockCondition, Output, OutputId}, }, - wallet::{core::WalletData, types::OutputData, Result, Wallet}, + wallet::{core::WalletLedger, types::OutputData, Result, Wallet}, }; /// An object containing an account's entire participation overview. @@ -66,8 +66,8 @@ where // "[get_participation_overview] restored_spent_cached_outputs_len: {}", // restored_spent_cached_outputs_len // ); - // let wallet_data = self.data().await; - // let participation_outputs = wallet_data.outputs().values().filter(|output_data| { + // let wallet_ledger = self.data().await; + // let participation_outputs = wallet_ledger.outputs().values().filter(|output_data| { // is_valid_participation_output(&output_data.output) // // Check that the metadata exists, because otherwise we aren't participating for anything // && output_data.output.features().and_then(|f| f.metadata()).is_some() @@ -218,7 +218,7 @@ where /// /// If multiple outputs with this tag exist, the one with the largest amount will be returned. pub async fn get_voting_output(&self) -> Result> { - self.data().await.get_voting_output() + self.ledger().await.get_voting_output() } // /// Gets client for an event. @@ -271,7 +271,7 @@ where // } } -impl WalletData { +impl WalletLedger { /// Returns the voting output ("PARTICIPATION" tag). /// /// If multiple outputs with this tag exist, the one with the largest amount will be returned. diff --git a/sdk/src/wallet/operations/syncing/foundries.rs b/sdk/src/wallet/operations/syncing/foundries.rs index 278ceca9a9..ce72321869 100644 --- a/sdk/src/wallet/operations/syncing/foundries.rs +++ b/sdk/src/wallet/operations/syncing/foundries.rs @@ -20,7 +20,7 @@ where ) -> crate::wallet::Result<()> { log::debug!("[SYNC] request_and_store_foundry_outputs"); - let mut foundries = self.data().await.native_token_foundries.clone(); + let mut foundries = self.ledger().await.native_token_foundries.clone(); let results = futures::future::try_join_all(foundry_ids.into_iter().filter(|f| !foundries.contains_key(f)).map( |foundry_id| { @@ -46,8 +46,8 @@ where } } - let mut wallet_data = self.data_mut().await; - wallet_data.native_token_foundries = foundries; + let mut wallet_ledger = self.ledger_mut().await; + wallet_ledger.native_token_foundries = foundries; Ok(()) } diff --git a/sdk/src/wallet/operations/syncing/mod.rs b/sdk/src/wallet/operations/syncing/mod.rs index 4dde220f37..22e51a81f1 100644 --- a/sdk/src/wallet/operations/syncing/mod.rs +++ b/sdk/src/wallet/operations/syncing/mod.rs @@ -96,7 +96,7 @@ where let wallet_address_with_unspent_outputs = AddressWithUnspentOutputs { address: self.address().await, - output_ids: self.data().await.unspent_outputs().keys().copied().collect(), + output_ids: self.ledger().await.unspent_outputs().keys().copied().collect(), internal: false, key_index: 0, }; diff --git a/sdk/src/wallet/operations/syncing/outputs.rs b/sdk/src/wallet/operations/syncing/outputs.rs index c317953024..f6832c6ba1 100644 --- a/sdk/src/wallet/operations/syncing/outputs.rs +++ b/sdk/src/wallet/operations/syncing/outputs.rs @@ -30,14 +30,14 @@ where log::debug!("[SYNC] convert output_responses"); // store outputs with network_id let network_id = self.client().get_network_id().await?; - let wallet_data = self.data().await; + let wallet_ledger = self.ledger().await; Ok(outputs_with_meta .into_iter() .map(|output_with_meta| { // check if we know the transaction that created this output and if we created it (if we store incoming // transactions separated, then this check wouldn't be required) - let remainder = wallet_data + let remainder = wallet_ledger .transactions .get(output_with_meta.metadata().output_id().transaction_id()) .map_or(false, |tx| !tx.incoming); @@ -65,10 +65,10 @@ where let mut outputs = Vec::new(); let mut unknown_outputs = Vec::new(); let mut unspent_outputs = Vec::new(); - let mut wallet_data = self.data_mut().await; + let mut wallet_ledger = self.ledger_mut().await; for output_id in output_ids { - match wallet_data.outputs.get_mut(&output_id) { + match wallet_ledger.outputs.get_mut(&output_id) { // set unspent if not already Some(output_data) => { if output_data.is_spent() { @@ -88,10 +88,10 @@ where // known output is unspent, so insert it to the unspent outputs again, because if it was an // account/nft/foundry output it could have been removed when syncing without them for (output_id, output_data) in unspent_outputs { - wallet_data.unspent_outputs.insert(output_id, output_data); + wallet_ledger.unspent_outputs.insert(output_id, output_data); } - drop(wallet_data); + drop(wallet_ledger); if !unknown_outputs.is_empty() { outputs.extend(self.client().get_outputs_with_metadata(&unknown_outputs).await?); @@ -114,13 +114,15 @@ where ) -> crate::wallet::Result<()> { log::debug!("[SYNC] request_incoming_transaction_data"); - let wallet_data = self.data().await; + let wallet_ledger = self.ledger().await; transaction_ids.retain(|transaction_id| { - !(wallet_data.transactions.contains_key(transaction_id) - || wallet_data.incoming_transactions.contains_key(transaction_id) - || wallet_data.inaccessible_incoming_transactions.contains(transaction_id)) + !(wallet_ledger.transactions.contains_key(transaction_id) + || wallet_ledger.incoming_transactions.contains_key(transaction_id) + || wallet_ledger + .inaccessible_incoming_transactions + .contains(transaction_id)) }); - drop(wallet_data); + drop(wallet_ledger); // Limit parallel requests to 100, to avoid timeouts let results = @@ -176,15 +178,15 @@ where .await?; // Update account with new transactions - let mut wallet_data = self.data_mut().await; + let mut wallet_ledger = self.ledger_mut().await; for (transaction_id, txn) in results.into_iter().flatten() { if let Some(transaction) = txn { - wallet_data.incoming_transactions.insert(transaction_id, transaction); + wallet_ledger.incoming_transactions.insert(transaction_id, transaction); } else { log::debug!("[SYNC] adding {transaction_id} to inaccessible_incoming_transactions"); // Save transactions that weren't found by the node to avoid requesting them endlessly. // Will be cleared when new client options are provided. - wallet_data.inaccessible_incoming_transactions.insert(transaction_id); + wallet_ledger.inaccessible_incoming_transactions.insert(transaction_id); } } diff --git a/sdk/src/wallet/operations/syncing/transactions.rs b/sdk/src/wallet/operations/syncing/transactions.rs index b5ab3a80ee..947da1d539 100644 --- a/sdk/src/wallet/operations/syncing/transactions.rs +++ b/sdk/src/wallet/operations/syncing/transactions.rs @@ -8,7 +8,7 @@ use crate::{ block::{input::Input, output::OutputId, BlockId}, }, wallet::{ - core::WalletData, + core::WalletLedger, types::{InclusionState, TransactionWithMetadata}, Wallet, }, @@ -30,13 +30,13 @@ where /// be synced again pub(crate) async fn sync_pending_transactions(&self) -> crate::wallet::Result { log::debug!("[SYNC] sync pending transactions"); - let wallet_data = self.data().await; + let wallet_ledger = self.ledger().await; // only set to true if a transaction got confirmed for which we don't have an output // (transaction_output.is_none()) let mut confirmed_unknown_output = false; - if wallet_data.pending_transactions.is_empty() { + if wallet_ledger.pending_transactions.is_empty() { return Ok(confirmed_unknown_output); } @@ -48,9 +48,9 @@ where // are available again let mut output_ids_to_unlock = Vec::new(); - for transaction_id in &wallet_data.pending_transactions { + for transaction_id in &wallet_ledger.pending_transactions { log::debug!("[SYNC] sync pending transaction {transaction_id}"); - let mut transaction = wallet_data + let mut transaction = wallet_ledger .transactions .get(transaction_id) // panic during development to easier detect if something is wrong, should be handled different later @@ -64,14 +64,14 @@ where // check if we have an output (remainder, if not sending to an own address) that got created by this // transaction, if that's the case, then the transaction got confirmed - let transaction_output = wallet_data + let transaction_output = wallet_ledger .outputs .keys() .find(|o| o.transaction_id() == transaction_id); if let Some(transaction_output) = transaction_output { // Save to unwrap, we just got the output - let confirmed_output_data = wallet_data.outputs.get(transaction_output).expect("output exists"); + let confirmed_output_data = wallet_ledger.outputs.get(transaction_output).expect("output exists"); log::debug!( "[SYNC] confirmed transaction {transaction_id} in block {}", confirmed_output_data.metadata.block_id() @@ -90,7 +90,7 @@ where let mut input_got_spent = false; for input in transaction.payload.transaction().inputs() { let Input::Utxo(input) = input; - if let Some(input) = wallet_data.outputs.get(input.output_id()) { + if let Some(input) = wallet_ledger.outputs.get(input.output_id()) { if input.metadata.is_spent() { input_got_spent = true; } @@ -152,7 +152,7 @@ where } } else if input_got_spent { process_transaction_with_unknown_state( - &wallet_data, + &wallet_ledger, transaction, &mut updated_transactions, &mut output_ids_to_unlock, @@ -162,7 +162,7 @@ where Err(crate::client::Error::Node(crate::client::node_api::error::Error::NotFound(_))) => { if input_got_spent { process_transaction_with_unknown_state( - &wallet_data, + &wallet_ledger, transaction, &mut updated_transactions, &mut output_ids_to_unlock, @@ -173,7 +173,7 @@ where } } else if input_got_spent { process_transaction_with_unknown_state( - &wallet_data, + &wallet_ledger, transaction, &mut updated_transactions, &mut output_ids_to_unlock, @@ -188,7 +188,7 @@ where updated_transactions.push(transaction); } } - drop(wallet_data); + drop(wallet_ledger); // updates account with balances, output ids, outputs self.update_with_transactions(updated_transactions, spent_output_ids, output_ids_to_unlock) @@ -219,7 +219,7 @@ fn updated_transaction_and_outputs( // When a transaction got pruned, the inputs and outputs are also not available, then this could mean that it was // confirmed and the created outputs got also already spent and pruned or the inputs got spent in another transaction fn process_transaction_with_unknown_state( - wallet_data: &WalletData, + wallet_ledger: &WalletLedger, mut transaction: TransactionWithMetadata, updated_transactions: &mut Vec, output_ids_to_unlock: &mut Vec, @@ -227,7 +227,7 @@ fn process_transaction_with_unknown_state( let mut all_inputs_spent = true; for input in transaction.payload.transaction().inputs() { let Input::Utxo(input) = input; - if let Some(output_data) = wallet_data.outputs.get(input.output_id()) { + if let Some(output_data) = wallet_ledger.outputs.get(input.output_id()) { if !output_data.is_spent() { // unspent output needs to be made available again output_ids_to_unlock.push(*input.output_id()); diff --git a/sdk/src/wallet/operations/transaction/account.rs b/sdk/src/wallet/operations/transaction/account.rs index b7b6c5b048..cf19e07c7a 100644 --- a/sdk/src/wallet/operations/transaction/account.rs +++ b/sdk/src/wallet/operations/transaction/account.rs @@ -52,8 +52,8 @@ where where crate::wallet::Error: From, { - let wallet_data = self.data().await; - let implicit_account_data = wallet_data + let wallet_ledger = self.ledger().await; + let implicit_account_data = wallet_ledger .unspent_outputs .get(output_id) .ok_or(Error::ImplicitAccountNotFound)?; @@ -95,7 +95,7 @@ where )?]) .finish_output()?; - drop(wallet_data); + drop(wallet_ledger); let transaction_options = TransactionOptions { required_inputs: [*output_id].into(), diff --git a/sdk/src/wallet/operations/transaction/high_level/burning_melting/melt_native_token.rs b/sdk/src/wallet/operations/transaction/high_level/burning_melting/melt_native_token.rs index c09dbb8d57..3bdbe8f11b 100644 --- a/sdk/src/wallet/operations/transaction/high_level/burning_melting/melt_native_token.rs +++ b/sdk/src/wallet/operations/transaction/high_level/burning_melting/melt_native_token.rs @@ -93,7 +93,7 @@ where let mut existing_account_output_data = None; let mut existing_foundry_output = None; - for (output_id, output_data) in self.data().await.unspent_outputs.iter() { + for (output_id, output_data) in self.ledger().await.unspent_outputs.iter() { match &output_data.output { Output::Account(output) => { if output.account_id_non_null(output_id) == account_id { diff --git a/sdk/src/wallet/operations/transaction/high_level/create_account.rs b/sdk/src/wallet/operations/transaction/high_level/create_account.rs index b57d5db157..652da2da0d 100644 --- a/sdk/src/wallet/operations/transaction/high_level/create_account.rs +++ b/sdk/src/wallet/operations/transaction/high_level/create_account.rs @@ -110,7 +110,7 @@ where ) -> Option<(AccountId, OutputData)> { log::debug!("[get_account_output]"); let account_id = account_id.into(); - self.data() + self.ledger() .await .unspent_outputs .values() diff --git a/sdk/src/wallet/operations/transaction/high_level/delegation/create.rs b/sdk/src/wallet/operations/transaction/high_level/delegation/create.rs index 625336b6bf..46aa8eace6 100644 --- a/sdk/src/wallet/operations/transaction/high_level/delegation/create.rs +++ b/sdk/src/wallet/operations/transaction/high_level/delegation/create.rs @@ -91,7 +91,7 @@ where self.client().bech32_hrp_matches(bech32_address.hrp()).await?; bech32_address.inner().clone() } - None => self.address().await.inner().clone(), + None => self.address().await.into_inner(), }; let output = DelegationOutputBuilder::new_with_amount( diff --git a/sdk/src/wallet/operations/transaction/high_level/delegation/delay.rs b/sdk/src/wallet/operations/transaction/high_level/delegation/delay.rs index 83ba6f38b7..ca4f9ea80a 100644 --- a/sdk/src/wallet/operations/transaction/high_level/delegation/delay.rs +++ b/sdk/src/wallet/operations/transaction/high_level/delegation/delay.rs @@ -39,7 +39,7 @@ where reclaim_excess: bool, ) -> crate::wallet::Result { let delegation_output = self - .data() + .ledger() .await .unspent_delegation_output(&delegation_id) .ok_or(crate::wallet::Error::MissingDelegation(delegation_id))? diff --git a/sdk/src/wallet/operations/transaction/high_level/minting/mint_native_token.rs b/sdk/src/wallet/operations/transaction/high_level/minting/mint_native_token.rs index 6f94e1e941..99b3a23df0 100644 --- a/sdk/src/wallet/operations/transaction/high_level/minting/mint_native_token.rs +++ b/sdk/src/wallet/operations/transaction/high_level/minting/mint_native_token.rs @@ -56,8 +56,8 @@ where log::debug!("[TRANSACTION] mint_native_token"); let mint_amount = mint_amount.into(); - let wallet_data = self.data().await; - let existing_foundry_output = wallet_data.unspent_outputs.values().find(|output_data| { + let wallet_ledger = self.ledger().await; + let existing_foundry_output = wallet_ledger.unspent_outputs.values().find(|output_data| { if let Output::Foundry(output) = &output_data.output { TokenId::new(*output.id()) == token_id } else { @@ -80,7 +80,7 @@ where } // Get the account output that controls the foundry output - let existing_account_output = wallet_data.unspent_outputs.values().find(|output_data| { + let existing_account_output = wallet_ledger.unspent_outputs.values().find(|output_data| { if let Output::Account(output) = &output_data.output { output.account_id_non_null(&output_data.output_id) == **foundry_output.account_address() } else { @@ -94,7 +94,7 @@ where return Err(Error::MintingFailed("account output is not available".to_string())); }; - drop(wallet_data); + drop(wallet_ledger); let account_output = if let Output::Account(account_output) = existing_account_output.output { account_output diff --git a/sdk/src/wallet/operations/transaction/high_level/send_nft.rs b/sdk/src/wallet/operations/transaction/high_level/send_nft.rs index 8675a8659b..ffc86ba9ae 100644 --- a/sdk/src/wallet/operations/transaction/high_level/send_nft.rs +++ b/sdk/src/wallet/operations/transaction/high_level/send_nft.rs @@ -96,7 +96,7 @@ where self.client().bech32_hrp_matches(address.hrp()).await?; // Find nft output from the inputs - if let Some(nft_output_data) = self.data().await.unspent_nft_output(&nft_id) { + if let Some(nft_output_data) = self.ledger().await.unspent_nft_output(&nft_id) { if let Output::Nft(nft_output) = &nft_output_data.output { // Set the nft id and new address unlock condition let nft_builder = NftOutputBuilder::from(nft_output) diff --git a/sdk/src/wallet/operations/transaction/high_level/staking/begin.rs b/sdk/src/wallet/operations/transaction/high_level/staking/begin.rs index 1b7b844dc0..e8f70dc5a0 100644 --- a/sdk/src/wallet/operations/transaction/high_level/staking/begin.rs +++ b/sdk/src/wallet/operations/transaction/high_level/staking/begin.rs @@ -52,7 +52,7 @@ where let account_id = params.account_id; let account_output_data = self - .data() + .ledger() .await .unspent_account_output(&account_id) .cloned() diff --git a/sdk/src/wallet/operations/transaction/high_level/staking/end.rs b/sdk/src/wallet/operations/transaction/high_level/staking/end.rs index fd991ae8fa..cd8cfad959 100644 --- a/sdk/src/wallet/operations/transaction/high_level/staking/end.rs +++ b/sdk/src/wallet/operations/transaction/high_level/staking/end.rs @@ -23,7 +23,7 @@ where log::debug!("[TRANSACTION] prepare_end_staking"); let account_output_data = self - .data() + .ledger() .await .unspent_account_output(&account_id) .cloned() diff --git a/sdk/src/wallet/operations/transaction/high_level/staking/extend.rs b/sdk/src/wallet/operations/transaction/high_level/staking/extend.rs index 535a3778c5..4b6c1efb54 100644 --- a/sdk/src/wallet/operations/transaction/high_level/staking/extend.rs +++ b/sdk/src/wallet/operations/transaction/high_level/staking/extend.rs @@ -31,7 +31,7 @@ where log::debug!("[TRANSACTION] prepare_extend_staking"); let account_output_data = self - .data() + .ledger() .await .unspent_account_output(&account_id) .cloned() diff --git a/sdk/src/wallet/operations/transaction/input_selection.rs b/sdk/src/wallet/operations/transaction/input_selection.rs index b7cdd895b2..6a42a2cb6f 100644 --- a/sdk/src/wallet/operations/transaction/input_selection.rs +++ b/sdk/src/wallet/operations/transaction/input_selection.rs @@ -4,6 +4,8 @@ use alloc::collections::BTreeSet; use std::collections::HashMap; +use crypto::keys::bip44::Bip44; + #[cfg(feature = "events")] use crate::wallet::events::types::{TransactionProgressEvent, WalletEvent}; use crate::{ @@ -12,12 +14,13 @@ use crate::{ secret::{types::InputSigningData, SecretManage}, }, types::block::{ + address::Bech32Address, output::{Output, OutputId}, protocol::CommittableAgeRange, slot::SlotIndex, }, wallet::{ - core::WalletData, operations::helpers::time::can_output_be_unlocked_forever_from_now_on, types::OutputData, + operations::helpers::time::can_output_be_unlocked_forever_from_now_on, types::OutputData, RemainderValueStrategy, TransactionOptions, Wallet, }, }; @@ -41,7 +44,7 @@ where let creation_slot = self.client().get_slot_index().await?; let slot_commitment_id = self.client().get_issuance().await?.latest_commitment.id(); if options.issuer_id.is_none() { - options.issuer_id = self.data().await.first_account_id(); + options.issuer_id = self.ledger().await.first_account_id(); } let reference_mana_cost = if let Some(issuer_id) = options.issuer_id { Some( @@ -58,7 +61,7 @@ where RemainderValueStrategy::CustomAddress(address) => Some(address), }; // lock so the same inputs can't be selected in multiple transactions - let mut wallet_data = self.data_mut().await; + let mut wallet_ledger = self.ledger_mut().await; #[cfg(feature = "events")] self.emit(WalletEvent::TransactionProgress( @@ -67,7 +70,7 @@ where .await; #[allow(unused_mut)] - let mut forbidden_inputs = wallet_data.locked_outputs.clone(); + let mut forbidden_inputs = wallet_ledger.locked_outputs.clone(); // Prevent consuming the voting output if not actually wanted #[cfg(feature = "participation")] @@ -80,8 +83,9 @@ where // Filter inputs to not include inputs that require additional outputs for storage deposit return or could be // still locked. let available_outputs_signing_data = filter_inputs( - &wallet_data, - wallet_data.unspent_outputs.values(), + &self.address().await, + self.bip_path().await, + wallet_ledger.unspent_outputs.values(), creation_slot, protocol_parameters.committable_age_range(), &options.required_inputs, @@ -91,7 +95,7 @@ where if let Some(burn) = &options.burn { for delegation_id in burn.delegations() { - if let Some(output) = wallet_data.unspent_delegation_output(delegation_id) { + if let Some(output) = wallet_ledger.unspent_delegation_output(delegation_id) { mana_rewards.insert( output.output_id, self.client() @@ -105,12 +109,12 @@ where // Check that no input got already locked for output_id in &options.required_inputs { - if wallet_data.locked_outputs.contains(output_id) { + if wallet_ledger.locked_outputs.contains(output_id) { return Err(crate::wallet::Error::CustomInput(format!( "provided custom input {output_id} is already used in another transaction", ))); } - if let Some(input) = wallet_data.outputs.get(output_id) { + if let Some(input) = wallet_ledger.outputs.get(output_id) { if input.output.can_claim_rewards(outputs.iter().find(|o| { input .output @@ -132,7 +136,7 @@ where let mut input_selection = InputSelection::new( available_outputs_signing_data, outputs, - Some(wallet_data.address.clone().into_inner()), + Some(self.address().await.into_inner()), creation_slot, slot_commitment_id, protocol_parameters.clone(), @@ -165,7 +169,7 @@ where // lock outputs so they don't get used by another transaction for output in &prepared_transaction_data.inputs_data { log::debug!("[TRANSACTION] locking: {}", output.output_id()); - wallet_data.locked_outputs.insert(*output.output_id()); + wallet_ledger.locked_outputs.insert(*output.output_id()); } Ok(prepared_transaction_data) @@ -177,7 +181,8 @@ where /// `claim_outputs` or providing their OutputId's in the custom_inputs #[allow(clippy::too_many_arguments)] fn filter_inputs<'a>( - wallet_data: &WalletData, + wallet_address: &Bech32Address, + wallet_bip_path: Option, available_outputs: impl IntoIterator, slot_index: impl Into + Copy, committable_age_range: CommittableAgeRange, @@ -190,7 +195,7 @@ fn filter_inputs<'a>( let output_can_be_unlocked_now_and_in_future = can_output_be_unlocked_forever_from_now_on( // We use the addresses with unspent outputs, because other addresses of the // account without unspent outputs can't be related to this output - &wallet_data.address.inner, + wallet_address.inner(), &output_data.output, slot_index, committable_age_range, @@ -202,7 +207,9 @@ fn filter_inputs<'a>( } } - if let Some(available_input) = output_data.input_signing_data(wallet_data, slot_index, committable_age_range)? { + if let Some(available_input) = + output_data.input_signing_data(wallet_address, wallet_bip_path, slot_index, committable_age_range)? + { available_outputs_signing_data.push(available_input); } } diff --git a/sdk/src/wallet/operations/transaction/mod.rs b/sdk/src/wallet/operations/transaction/mod.rs index f75de0f89b..57688f25f2 100644 --- a/sdk/src/wallet/operations/transaction/mod.rs +++ b/sdk/src/wallet/operations/transaction/mod.rs @@ -22,6 +22,7 @@ use crate::{ block::{output::Output, payload::signed_transaction::SignedTransactionPayload}, }, wallet::{ + core::WalletLedgerDto, types::{InclusionState, TransactionWithMetadata}, Wallet, }, @@ -172,16 +173,18 @@ where inputs, }; - let mut wallet_data = self.data_mut().await; + let mut wallet_ledger = self.ledger_mut().await; - wallet_data.transactions.insert(transaction_id, transaction.clone()); - wallet_data.pending_transactions.insert(transaction_id); + wallet_ledger.transactions.insert(transaction_id, transaction.clone()); + wallet_ledger.pending_transactions.insert(transaction_id); #[cfg(feature = "storage")] { // TODO: maybe better to use the wallet address as identifier now? - log::debug!("[TRANSACTION] storing wallet"); - self.storage_manager().save_wallet_data(&wallet_data).await?; + log::debug!("[TRANSACTION] storing wallet ledger"); + self.storage_manager() + .save_wallet_ledger(&WalletLedgerDto::from(&*wallet_ledger)) + .await?; } Ok(transaction) @@ -189,10 +192,10 @@ where // unlock outputs async fn unlock_inputs(&self, inputs: &[InputSigningData]) -> crate::wallet::Result<()> { - let mut wallet_data = self.data_mut().await; + let mut wallet_ledger = self.ledger_mut().await; for input_signing_data in inputs { let output_id = input_signing_data.output_id(); - wallet_data.locked_outputs.remove(output_id); + wallet_ledger.locked_outputs.remove(output_id); log::debug!( "[TRANSACTION] Unlocked output {} because of transaction error", output_id diff --git a/sdk/src/wallet/operations/transaction/prepare_output.rs b/sdk/src/wallet/operations/transaction/prepare_output.rs index 75da29a3a7..2d08f60fea 100644 --- a/sdk/src/wallet/operations/transaction/prepare_output.rs +++ b/sdk/src/wallet/operations/transaction/prepare_output.rs @@ -243,7 +243,7 @@ where ) } else { // Transition an existing NFT output - let unspent_nft_output = self.data().await.unspent_nft_output(nft_id).cloned(); + let unspent_nft_output = self.ledger().await.unspent_nft_output(nft_id).cloned(); // Find nft output from the inputs let mut first_output_builder = if let Some(nft_output_data) = &unspent_nft_output { diff --git a/sdk/src/wallet/operations/wait_for_tx_acceptance.rs b/sdk/src/wallet/operations/wait_for_tx_acceptance.rs index 8cd17ac7f8..7f7dc055a8 100644 --- a/sdk/src/wallet/operations/wait_for_tx_acceptance.rs +++ b/sdk/src/wallet/operations/wait_for_tx_acceptance.rs @@ -31,7 +31,7 @@ where log::debug!("[wait_for_transaction_acceptance]"); let transaction = self - .data() + .ledger() .await .transactions .get(transaction_id) diff --git a/sdk/src/wallet/storage/constants.rs b/sdk/src/wallet/storage/constants.rs index 3891fecf14..c30d664764 100644 --- a/sdk/src/wallet/storage/constants.rs +++ b/sdk/src/wallet/storage/constants.rs @@ -15,15 +15,17 @@ pub const fn default_storage_path() -> &'static str { DEFAULT_STORAGE_PATH } +// wallet db schema pub(crate) const DATABASE_SCHEMA_VERSION: u8 = 1; pub(crate) const DATABASE_SCHEMA_VERSION_KEY: &str = "database-schema-version"; -pub(crate) const WALLET_DATA_KEY: &str = "wallet-data"; +// wallet db keys +pub(crate) const WALLET_LEDGER_KEY: &str = "wallet-ledger"; pub(crate) const WALLET_BUILDER_KEY: &str = "wallet-builder"; -pub(crate) const WALLET_SYNC_OPTIONS: &str = "wallet-sync-options"; - pub(crate) const SECRET_MANAGER_KEY: &str = "secret-manager"; +pub(crate) const WALLET_SYNC_OPTIONS: &str = "wallet-sync-options"; + // #[cfg(feature = "participation")] // pub(crate) const PARTICIPATION_EVENTS: &str = "participation-events"; // #[cfg(feature = "participation")] diff --git a/sdk/src/wallet/storage/manager.rs b/sdk/src/wallet/storage/manager.rs index ec217e5365..e733144457 100644 --- a/sdk/src/wallet/storage/manager.rs +++ b/sdk/src/wallet/storage/manager.rs @@ -7,7 +7,7 @@ use crate::{ client::storage::StorageAdapter, types::TryFromDto, wallet::{ - core::{WalletData, WalletDataDto}, + core::{WalletLedger, WalletLedgerDto}, migration::migrate, operations::syncing::SyncOptions, storage::{constants::*, DynStorageAdapter, Storage}, @@ -49,25 +49,28 @@ impl StorageManager { Ok(storage_manager) } - pub(crate) async fn load_wallet_data(&self) -> crate::wallet::Result> { - if let Some(dto) = self.get::(WALLET_DATA_KEY).await? { - Ok(Some(WalletData::try_from_dto(dto)?)) + pub(crate) async fn load_wallet_ledger(&self) -> crate::wallet::Result> { + if let Some(dto) = self.get::(WALLET_LEDGER_KEY).await? { + Ok(Some(WalletLedger::try_from_dto(dto)?)) } else { Ok(None) } } - pub(crate) async fn save_wallet_data(&self, wallet_data: &WalletData) -> crate::wallet::Result<()> { - self.set(WALLET_DATA_KEY, &WalletDataDto::from(wallet_data)).await + pub(crate) async fn save_wallet_ledger(&self, wallet_ledger: &WalletLedgerDto) -> crate::wallet::Result<()> { + self.set(WALLET_LEDGER_KEY, wallet_ledger).await?; + Ok(()) } pub(crate) async fn set_default_sync_options(&self, sync_options: &SyncOptions) -> crate::wallet::Result<()> { - let key = format!("{WALLET_DATA_KEY}-{WALLET_SYNC_OPTIONS}"); + let key = format!("{WALLET_LEDGER_KEY}-{WALLET_SYNC_OPTIONS}"); self.set(&key, &sync_options).await } + // TODO: call this method in wallet builder: https://github.com/iotaledger/iota-sdk/issues/2022 + #[allow(dead_code)] pub(crate) async fn get_default_sync_options(&self) -> crate::wallet::Result> { - let key = format!("{WALLET_DATA_KEY}-{WALLET_SYNC_OPTIONS}"); + let key = format!("{WALLET_LEDGER_KEY}-{WALLET_SYNC_OPTIONS}"); self.get(&key).await } } @@ -91,12 +94,14 @@ impl StorageAdapter for StorageManager { #[cfg(test)] mod tests { + use crypto::keys::bip44::Bip44; use pretty_assertions::assert_eq; use serde::{Deserialize, Serialize}; use super::*; use crate::{ - client::secret::SecretManager, + client::{constants::SHIMMER_COIN_TYPE, secret::SecretManager}, + types::block::address::{Bech32Address, Ed25519Address}, wallet::{core::operations::storage::SaveLoadWallet, storage::adapter::memory::Memory, WalletBuilder}, }; @@ -122,15 +127,18 @@ mod tests { } #[tokio::test] - async fn save_load_wallet_data() { + async fn save_load_wallet_ledger() { let storage_manager = StorageManager::new(Memory::default(), None).await.unwrap(); - assert!(storage_manager.load_wallet_data().await.unwrap().is_none()); + assert!(storage_manager.load_wallet_ledger().await.unwrap().is_none()); - let wallet_data = WalletData::mock(); + let wallet_ledger = WalletLedger::test_instance(); - storage_manager.save_wallet_data(&wallet_data).await.unwrap(); - let wallet = storage_manager.load_wallet_data().await.unwrap(); - assert!(matches!(wallet, Some(data) if data.alias == Some("Alice".to_string()))); + storage_manager + .save_wallet_ledger(&WalletLedgerDto::from(&wallet_ledger)) + .await + .unwrap(); + + assert_eq!(storage_manager.load_wallet_ledger().await.unwrap(), Some(wallet_ledger)); } #[tokio::test] @@ -143,14 +151,24 @@ mod tests { .is_none() ); - let wallet_builder = WalletBuilder::::new(); + let wallet_address = Bech32Address::new("rms".parse().unwrap(), Ed25519Address::null()); + let wallet_bip_path = Bip44::new(SHIMMER_COIN_TYPE); + let wallet_alias = "savings".to_string(); + + let wallet_builder = WalletBuilder::::new() + .with_address(wallet_address.clone()) + .with_bip_path(wallet_bip_path) + .with_alias(wallet_alias.clone()); + wallet_builder.save(&storage_manager).await.unwrap(); - assert!( - WalletBuilder::::load(&storage_manager) - .await - .unwrap() - .is_some() - ); + let restored_wallet_builder = WalletBuilder::::load(&storage_manager) + .await + .unwrap() + .unwrap(); + + assert_eq!(restored_wallet_builder.address, Some(wallet_address)); + assert_eq!(restored_wallet_builder.bip_path, Some(wallet_bip_path)); + assert_eq!(restored_wallet_builder.alias, Some(wallet_alias)); } } diff --git a/sdk/src/wallet/types/mod.rs b/sdk/src/wallet/types/mod.rs index 55a1343151..70e0fcc1fb 100644 --- a/sdk/src/wallet/types/mod.rs +++ b/sdk/src/wallet/types/mod.rs @@ -9,6 +9,7 @@ pub mod participation; use std::str::FromStr; +use crypto::keys::bip44::Bip44; use serde::{Deserialize, Serialize}; pub use self::{ @@ -20,6 +21,7 @@ use crate::{ types::{ api::core::OutputWithMetadataResponse, block::{ + address::Bech32Address, output::{Output, OutputId, OutputIdProof, OutputMetadata}, payload::signed_transaction::{dto::SignedTransactionPayloadDto, SignedTransactionPayload, TransactionId}, protocol::{CommittableAgeRange, ProtocolParameters}, @@ -28,7 +30,6 @@ use crate::{ }, TryFromDto, }, - wallet::core::WalletData, }; /// An output with metadata @@ -55,7 +56,8 @@ impl OutputData { pub fn input_signing_data( &self, - wallet_data: &WalletData, + wallet_address: &Bech32Address, + wallet_bip_path: Option, commitment_slot_index: impl Into, committable_age_range: CommittableAgeRange, ) -> crate::wallet::Result> { @@ -65,9 +67,9 @@ impl OutputData { .ok_or(crate::client::Error::ExpirationDeadzone)?; let chain = if let Some(required_ed25519) = required_address.backing_ed25519() { - if let Some(backing_ed25519) = wallet_data.address.inner().backing_ed25519() { + if let Some(backing_ed25519) = wallet_address.inner().backing_ed25519() { if required_ed25519 == backing_ed25519 { - wallet_data.bip_path + wallet_bip_path } else { // Different ed25519 chain than the wallet one. None diff --git a/sdk/src/wallet/update.rs b/sdk/src/wallet/update.rs index 54a2fe8df1..7ea338e679 100644 --- a/sdk/src/wallet/update.rs +++ b/sdk/src/wallet/update.rs @@ -10,6 +10,7 @@ use crate::{ payload::signed_transaction::TransactionId, }, wallet::{ + core::WalletLedgerDto, types::{InclusionState, OutputData, TransactionWithMetadata}, Wallet, }, @@ -25,12 +26,35 @@ where crate::wallet::Error: From, crate::client::Error: From, { - /// Set the alias for the wallet. - pub async fn set_alias(&self, alias: &str) -> crate::wallet::Result<()> { - let mut wallet_data = self.data_mut().await; - wallet_data.alias = Some(alias.to_string()); + /// Update the wallet address with a possible new Bech32 HRP and clear the inaccessible incoming transactions. + pub(crate) async fn update_address_hrp(&self) -> crate::wallet::Result<()> { + let bech32_hrp = self.client().get_bech32_hrp().await?; + log::debug!("updating wallet with new bech32 hrp: {}", bech32_hrp); + + self.address_mut().await.hrp = bech32_hrp; + + let mut wallet_ledger = self.ledger_mut().await; + wallet_ledger.inaccessible_incoming_transactions.clear(); + #[cfg(feature = "storage")] - self.storage_manager().save_wallet_data(&wallet_data).await?; + { + log::debug!("[save] wallet ledger with updated bech32 hrp",); + self.storage_manager() + .save_wallet_ledger(&WalletLedgerDto::from(&*wallet_ledger)) + .await?; + } + + drop(wallet_ledger); + + Ok(()) + } + + /// Set the wallet alias. + pub async fn set_alias(&self, alias: &str) -> crate::wallet::Result<()> { + log::debug!("setting wallet alias to: {}", alias); + + *self.alias_mut().await = Some(alias.to_string()); + Ok(()) } @@ -40,18 +64,18 @@ where unspent_outputs: Vec, spent_or_unsynced_output_metadata_map: HashMap>, ) -> crate::wallet::Result<()> { - log::debug!("[SYNC] Update wallet with new synced transactions"); + log::debug!("[SYNC] Update wallet ledger with new synced transactions"); let network_id = self.client().get_network_id().await?; - let mut wallet_data = self.data_mut().await; + let mut wallet_ledger = self.ledger_mut().await; // Update spent outputs for (output_id, output_metadata_response_opt) in spent_or_unsynced_output_metadata_map { // If we got the output response and it's still unspent, skip it if let Some(output_metadata_response) = output_metadata_response_opt { if output_metadata_response.is_spent() { - wallet_data.unspent_outputs.remove(&output_id); - if let Some(output_data) = wallet_data.outputs.get_mut(&output_id) { + wallet_ledger.unspent_outputs.remove(&output_id); + if let Some(output_data) = wallet_ledger.outputs.get_mut(&output_id) { output_data.metadata = output_metadata_response; } } else { @@ -60,14 +84,14 @@ where } } - if let Some(output) = wallet_data.outputs.get(&output_id) { + if let Some(output) = wallet_ledger.outputs.get(&output_id) { // Could also be outputs from other networks after we switched the node, so we check that first if output.network_id == network_id { log::debug!("[SYNC] Spent output {}", output_id); - wallet_data.locked_outputs.remove(&output_id); - wallet_data.unspent_outputs.remove(&output_id); + wallet_ledger.locked_outputs.remove(&output_id); + wallet_ledger.unspent_outputs.remove(&output_id); // Update spent data fields - if let Some(output_data) = wallet_data.outputs.get_mut(&output_id) { + if let Some(output_data) = wallet_ledger.outputs.get_mut(&output_id) { if !output_data.is_spent() { log::warn!( "[SYNC] Setting output {} as spent without having the OutputConsumptionMetadata", @@ -97,14 +121,14 @@ where // Add new synced outputs for output_data in unspent_outputs { // Insert output, if it's unknown emit the NewOutputEvent - if wallet_data + if wallet_ledger .outputs .insert(output_data.output_id, output_data.clone()) .is_none() { #[cfg(feature = "events")] { - let transaction = wallet_data + let transaction = wallet_ledger .incoming_transactions .get(output_data.output_id.transaction_id()); self.emit(WalletEvent::NewOutput(Box::new(NewOutputEvent { @@ -118,14 +142,16 @@ where } }; if !output_data.is_spent() { - wallet_data.unspent_outputs.insert(output_data.output_id, output_data); + wallet_ledger.unspent_outputs.insert(output_data.output_id, output_data); } } #[cfg(feature = "storage")] { - log::debug!("[SYNC] storing wallet with new synced data"); - self.storage_manager().save_wallet_data(&wallet_data).await?; + log::debug!("[SYNC] storing wallet ledger with new synced data"); + self.storage_manager() + .save_wallet_ledger(&WalletLedgerDto::from(&*wallet_ledger)) + .await?; } Ok(()) } @@ -139,13 +165,13 @@ where ) -> crate::wallet::Result<()> { log::debug!("[SYNC] Update wallet with new synced transactions"); - let mut wallet_data = self.data_mut().await; + let mut wallet_ledger = self.ledger_mut().await; for transaction in updated_transactions { match transaction.inclusion_state { InclusionState::Confirmed | InclusionState::Conflicting | InclusionState::UnknownPruned => { let transaction_id = transaction.payload.transaction().id(); - wallet_data.pending_transactions.remove(&transaction_id); + wallet_ledger.pending_transactions.remove(&transaction_id); log::debug!( "[SYNC] inclusion_state of {transaction_id} changed to {:?}", transaction.inclusion_state @@ -161,13 +187,13 @@ where } _ => {} } - wallet_data + wallet_ledger .transactions .insert(transaction.payload.transaction().id(), transaction.clone()); } for output_to_unlock in &spent_output_ids { - if let Some(output_data) = wallet_data.outputs.get_mut(output_to_unlock) { + if let Some(output_data) = wallet_ledger.outputs.get_mut(output_to_unlock) { if !output_data.is_spent() { log::warn!( "[SYNC] Setting output {} as spent without having the OutputConsumptionMetadata", @@ -182,13 +208,13 @@ where )); } } - wallet_data.locked_outputs.remove(output_to_unlock); - wallet_data.unspent_outputs.remove(output_to_unlock); + wallet_ledger.locked_outputs.remove(output_to_unlock); + wallet_ledger.unspent_outputs.remove(output_to_unlock); log::debug!("[SYNC] Unlocked spent output {}", output_to_unlock); } for output_to_unlock in &output_ids_to_unlock { - wallet_data.locked_outputs.remove(output_to_unlock); + wallet_ledger.locked_outputs.remove(output_to_unlock); log::debug!( "[SYNC] Unlocked unspent output {} because of a conflicting transaction", output_to_unlock @@ -197,27 +223,11 @@ where #[cfg(feature = "storage")] { - log::debug!("[SYNC] storing wallet with new synced transactions"); - self.storage_manager().save_wallet_data(&wallet_data).await?; - } - Ok(()) - } - - /// Update the wallet address with a possible new Bech32 HRP and clear the inaccessible incoming transactions. - pub(crate) async fn update_bech32_hrp(&self) -> crate::wallet::Result<()> { - let bech32_hrp = self.client().get_bech32_hrp().await?; - log::debug!("updating wallet data with new bech32 hrp: {}", bech32_hrp); - let mut wallet_data = self.data_mut().await; - - wallet_data.address.hrp = bech32_hrp; - wallet_data.inaccessible_incoming_transactions.clear(); - - #[cfg(feature = "storage")] - { - log::debug!("[save] wallet data with updated bech32 hrp",); - self.storage_manager().save_wallet_data(&wallet_data).await?; + log::debug!("[SYNC] storing wallet ledger with new synced transactions"); + self.storage_manager() + .save_wallet_ledger(&WalletLedgerDto::from(&*wallet_ledger)) + .await?; } - Ok(()) } } diff --git a/sdk/tests/types/transaction_id.rs b/sdk/tests/types/transaction_id.rs index a6b6190b64..4ac3265b21 100644 --- a/sdk/tests/types/transaction_id.rs +++ b/sdk/tests/types/transaction_id.rs @@ -3,13 +3,7 @@ use core::str::FromStr; -use iota_sdk::types::{ - block::payload::{ - signed_transaction::{dto::SignedTransactionPayloadDto, SignedTransactionPayload, TransactionId}, - Payload, - }, - TryFromDto, -}; +use iota_sdk::types::block::payload::signed_transaction::TransactionId; use packable::PackableExt; use pretty_assertions::assert_eq; diff --git a/sdk/tests/wallet/address_generation.rs b/sdk/tests/wallet/address_generation.rs index eccd3aa430..51fa73587c 100644 --- a/sdk/tests/wallet/address_generation.rs +++ b/sdk/tests/wallet/address_generation.rs @@ -14,10 +14,9 @@ use iota_sdk::{ client::{ constants::IOTA_COIN_TYPE, secret::{mnemonic::MnemonicSecretManager, SecretManager}, - Error as ClientError, }, types::block::address::ToBech32Ext, - wallet::{ClientOptions, Error, Result, Wallet}, + wallet::{ClientOptions, Result, Wallet}, }; use pretty_assertions::assert_eq; diff --git a/sdk/tests/wallet/backup_restore.rs b/sdk/tests/wallet/backup_restore.rs index 7fd0ceec97..45690860d3 100644 --- a/sdk/tests/wallet/backup_restore.rs +++ b/sdk/tests/wallet/backup_restore.rs @@ -98,7 +98,7 @@ // let node_dto = NodeDto::Node(Node::from(Url::parse(NODE_LOCAL).unwrap())); // assert!(client_options.node_manager_builder.nodes.contains(&node_dto)); -// assert_eq!(wallet.address().await, restored_wallet.address().await); +// assert_eq!(wallet.address().clone(), restored_wallet.address().clone()); // // secret manager is the same // assert_eq!( @@ -196,7 +196,7 @@ // // Get wallet // let recovered_wallet = restore_wallet; -// assert_eq!(wallet.address().await, recovered_wallet.address().await); +// assert_eq!(wallet.address().clone(), recovered_wallet.address().clone()); // // secret manager is the same // assert_eq!( @@ -269,14 +269,14 @@ // // Validate restored data // // No wallet restored, because the coin type was different -// assert!(restore_wallet.get_wallet_data().await?.is_empty()); +// assert!(restore_wallet.get_wallet_ledger().await?.is_empty()); // // Restored coin type is not used and it's still the same one // let new_wallet = restore_wallet; // assert_eq!(new_wallet.data().await.coin_type(), &IOTA_COIN_TYPE); // // secret manager is the same // assert_eq!( -// new_wallet.address().await, +// new_wallet.address().clone(), // "smr1qrpwecegav7eh0z363ca69laxej64rrt4e3u0rtycyuh0mam3vq3ulygj9p" // ); @@ -352,13 +352,13 @@ // // Validate restored data // // The wallet is restored, because the coin type is the same -// let restored_wallet = restore_wallet.get_wallet_data().await?; +// let restored_wallet = restore_wallet.get_wallet_ledger().await?; // assert!(restored_wallet.is_some()); // // addresses are still there // assert_eq!( -// restored_wallet.address().await, -// wallet_before_backup.address().await +// restored_wallet.address().clone(), +// wallet_before_backup.address().clone() // ); // // compare client options, they are not restored @@ -431,10 +431,10 @@ // // Validate restored data // // No wallet restored, because the coin type was different -// let restored_wallet = restore_wallet.get_wallet_data().await?; +// let restored_wallet = restore_wallet.get_wallet_ledger().await?; // assert_eq!( -// wallet.address().await, -// restored_wallet.address().await, +// wallet.address().clone(), +// restored_wallet.address().clone(), // ); // // TODO: Restored coin type is used @@ -442,7 +442,7 @@ // assert_eq!(new_wallet.data().await.coin_type(), &SHIMMER_COIN_TYPE); // // secret manager is restored // assert_eq!( -// new_wallet.address().await, +// new_wallet.address().clone(), // "smr1qzvjvjyqxgfx4f0m3xhn2rj24e03dwsmjz082735y3wx88v2gudu2afedhu" // ); @@ -520,7 +520,7 @@ // assert!(client_options.node_manager_builder.nodes.contains(&node_dto)); // // No restored wallet because the bech32 hrp was different -// let restored_wallet = restore_wallet.get_wallet_data().await?; +// let restored_wallet = restore_wallet.get_wallet_ledger().await?; // assert!(restored_wallet.is_empty()); // // Restored coin type is used diff --git a/sdk/tests/wallet/consolidation.rs b/sdk/tests/wallet/consolidation.rs index cb0341dcd6..2ada555aba 100644 --- a/sdk/tests/wallet/consolidation.rs +++ b/sdk/tests/wallet/consolidation.rs @@ -31,7 +31,7 @@ async fn consolidation() -> Result<()> { let balance = wallet_1.sync(None).await.unwrap(); assert_eq!(balance.base_coin().available(), 10 * amount); - assert_eq!(wallet_1.data().await.unspent_outputs().len(), 10); + assert_eq!(wallet_1.ledger().await.unspent_outputs().len(), 10); let tx = wallet_1 .consolidate_outputs(ConsolidationParams::new().with_force(true)) @@ -44,7 +44,7 @@ async fn consolidation() -> Result<()> { // Balance still the same assert_eq!(balance.base_coin().available(), 10 * amount); // Only one unspent output - assert_eq!(wallet_1.data().await.unspent_outputs().len(), 1); + assert_eq!(wallet_1.ledger().await.unspent_outputs().len(), 1); tear_down(storage_path_0)?; tear_down(storage_path_1)?; diff --git a/sdk/tests/wallet/core.rs b/sdk/tests/wallet/core.rs index 8723afe641..be49f758aa 100644 --- a/sdk/tests/wallet/core.rs +++ b/sdk/tests/wallet/core.rs @@ -88,7 +88,7 @@ async fn changed_bip_path() -> Result<()> { drop(wallet); - let err = Wallet::builder() + let result = Wallet::builder() .with_secret_manager(SecretManager::Mnemonic(MnemonicSecretManager::try_from_mnemonic( mnemonic.clone(), )?)) @@ -97,13 +97,14 @@ async fn changed_bip_path() -> Result<()> { .finish() .await; - // Building the wallet with another coin type needs to return an error, because a different coin type was used in - // the existing account - let mismatch_err: Result = Err(Error::BipPathMismatch { + let _mismatch_err: Result = Err(Error::BipPathMismatch { new_bip_path: Some(Bip44::new(IOTA_COIN_TYPE)), old_bip_path: Some(Bip44::new(SHIMMER_COIN_TYPE)), }); - assert!(matches!(err, mismatch_err)); + + // Building the wallet with another coin type needs to return an error, because a different coin type was used in + // the existing account + assert!(matches!(result, _mismatch_err)); // Building the wallet with the same coin type still works assert!( diff --git a/sdk/tests/wallet/output_preparation.rs b/sdk/tests/wallet/output_preparation.rs index d4343fb8c2..a95f4262c2 100644 --- a/sdk/tests/wallet/output_preparation.rs +++ b/sdk/tests/wallet/output_preparation.rs @@ -782,7 +782,7 @@ async fn prepare_output_only_single_nft() -> Result<()> { assert_eq!(balance.nfts().len(), 1); let nft_amount = wallet_1 - .data() + .ledger() .await .unspent_outputs() .values() diff --git a/sdk/tests/wallet/syncing.rs b/sdk/tests/wallet/syncing.rs index cbafe71189..78dc36b486 100644 --- a/sdk/tests/wallet/syncing.rs +++ b/sdk/tests/wallet/syncing.rs @@ -51,7 +51,7 @@ // let wallet_0 = create_wallet_with_funds(storage_path_0, None, None, 1).await?; // let wallet_1 = make_wallet(storage_path_1, None, None).await?; -// let wallet_1_address = wallet_1.address().await; +// let wallet_1_address = wallet_1.address().clone(); // let token_supply = wallet_0.client().get_token_supply().await?; // // Only one basic output without further unlock conditions @@ -154,7 +154,7 @@ // let wallet_0 = create_wallet_with_funds(storage_path_0, None, None, 1).await?; // let wallet_1 = make_wallet(storage_path_1, None, None).await?; -// let wallet_1_address = wallet_1.address().await; +// let wallet_1_address = wallet_1.address().clone(); // let token_supply = wallet_0.client().get_token_supply().await?; @@ -203,7 +203,7 @@ // iota_sdk::client::request_funds_from_faucet( // crate::wallet::common::FAUCET_URL, -// &wallet.address().await, +// &wallet.address().clone(), // ) // .await?; diff --git a/sdk/tests/wallet/transactions.rs b/sdk/tests/wallet/transactions.rs index 30c7943c2b..8091b0e9cc 100644 --- a/sdk/tests/wallet/transactions.rs +++ b/sdk/tests/wallet/transactions.rs @@ -1,10 +1,10 @@ // Copyright 2023 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -use iota_sdk::wallet::{MintNftParams, Result, SendNftParams, SendParams, TransactionOptions}; -use pretty_assertions::assert_eq; +// use iota_sdk::wallet::{MintNftParams, Result, SendNftParams, SendParams, TransactionOptions}; +// use pretty_assertions::assert_eq; -use crate::wallet::common::{make_wallet, request_funds, setup, tear_down}; +// use crate::wallet::common::{make_wallet, request_funds, setup, tear_down}; // #[ignore] // #[tokio::test] @@ -21,7 +21,7 @@ use crate::wallet::common::{make_wallet, request_funds, setup, tear_down}; // let amount = 1_000_000; // let tx = wallet_0 -// .send_with_params([SendParams::new(amount, wallet_1.address().await)?], None) +// .send_with_params([SendParams::new(amount, wallet_1.address().clone())?], None) // .await?; // wallet_0 @@ -53,7 +53,7 @@ use crate::wallet::common::{make_wallet, request_funds, setup, tear_down}; // vec![ // SendParams::new( // amount, -// wallet_1.address().await, +// wallet_1.address().clone(), // )?; // // Only 127, because we need one remainder // 127 @@ -133,7 +133,7 @@ use crate::wallet::common::{make_wallet, request_funds, setup, tear_down}; // let wallet_1 = make_wallet(storage_path_1, None, None).await?; // let nft_options = [MintNftParams::new() -// .with_address(wallet_0.address().await) +// .with_address(wallet_0.address().clone()) // .with_metadata(b"some nft metadata".to_vec()) // .with_immutable_metadata(b"some immutable nft metadata".to_vec())]; @@ -146,7 +146,7 @@ use crate::wallet::common::{make_wallet, request_funds, setup, tear_down}; // // Send to wallet 1 // let transaction = wallet_0 // .send_nft( -// [SendNftParams::new(wallet_1.address().await, nft_id)?], +// [SendNftParams::new(wallet_1.address().clone(), nft_id)?], // None, // ) // .await @@ -178,7 +178,7 @@ use crate::wallet::common::{make_wallet, request_funds, setup, tear_down}; // let amount = 1_000_000; // let tx = wallet_0 // .send_with_params( -// [SendParams::new(amount, wallet_1.address().await)?], +// [SendParams::new(amount, wallet_1.address().clone())?], // Some(TransactionOptions { // note: Some(String::from("send_with_note")), // ..Default::default() @@ -213,7 +213,7 @@ use crate::wallet::common::{make_wallet, request_funds, setup, tear_down}; // .send_with_params( // [SendParams::new( // 1_000_000, -// wallet_0.address().await, +// wallet_0.address().clone(), // )?], // None, // ) @@ -228,7 +228,7 @@ use crate::wallet::common::{make_wallet, request_funds, setup, tear_down}; // // Something in the transaction must be different than in the first one, otherwise it will be the // same // one // 2_000_000, -// wallet_0.address().await, +// wallet_0.address().clone(), // )?], // None, // ) @@ -297,7 +297,7 @@ use crate::wallet::common::{make_wallet, request_funds, setup, tear_down}; // .await; // let tx = wallet_0 -// .send_with_params([SendParams::new(amount, wallet_1.address().await)?], None) +// .send_with_params([SendParams::new(amount, wallet_1.address().clone())?], None) // .await?; // let data = receiver.recv().await.expect("never received event");