From 6c3e3ecc19c4e35b307eb33213e98bfc7c656c5c Mon Sep 17 00:00:00 2001 From: Lucas Nogueira Date: Tue, 15 Dec 2020 14:55:01 -0300 Subject: [PATCH 01/29] feat(manager): initial work on account guard --- src/account/mod.rs | 21 ++-------- src/account_manager.rs | 88 ++++++++++++++++++++++++++---------------- src/lib.rs | 5 ++- 3 files changed, 63 insertions(+), 51 deletions(-) diff --git a/src/account/mod.rs b/src/account/mod.rs index 0e71a72c6..b7c50f7e5 100644 --- a/src/account/mod.rs +++ b/src/account/mod.rs @@ -75,7 +75,6 @@ pub struct AccountInitialiser<'a> { messages: Vec, addresses: Vec
, client_options: ClientOptions, - skip_persistance: bool, storage_path: &'a PathBuf, signer_type: Option, } @@ -90,7 +89,6 @@ impl<'a> AccountInitialiser<'a> { messages: vec![], addresses: vec![], client_options, - skip_persistance: false, storage_path, #[cfg(feature = "stronghold")] signer_type: Some(SignerType::Stronghold), @@ -138,11 +136,6 @@ impl<'a> AccountInitialiser<'a> { self } - pub(crate) fn skip_persistance(mut self) -> Self { - self.skip_persistance = true; - self - } - /// Initialises the account. pub fn initialise(self) -> crate::Result { let accounts = crate::storage::with_adapter(self.storage_path, |storage| storage.get_all())?; @@ -163,13 +156,10 @@ impl<'a> AccountInitialiser<'a> { } } - // check for empty latest account only when not skipping persistance (account discovery process) - if !self.skip_persistance { - if let Some(latest_account) = accounts.last() { - let latest_account: Account = serde_json::from_str(&latest_account)?; - if latest_account.messages().is_empty() && latest_account.total_balance() == 0 { - return Err(crate::WalletError::LatestAccountIsEmpty); - } + if let Some(latest_account) = accounts.last() { + let latest_account: Account = serde_json::from_str(&latest_account)?; + if latest_account.messages().is_empty() && latest_account.total_balance() == 0 { + return Err(crate::WalletError::LatestAccountIsEmpty); } } @@ -189,9 +179,6 @@ impl<'a> AccountInitialiser<'a> { let id = with_signer(&signer_type, |signer| signer.init_account(&account, mnemonic))?; account.set_id(id.into()); - if !self.skip_persistance { - account.save()?; - } Ok(account) } } diff --git a/src/account_manager.rs b/src/account_manager.rs index efaedcb99..a15aa8b9d 100644 --- a/src/account_manager.rs +++ b/src/account_manager.rs @@ -11,13 +11,16 @@ use crate::{ message::{Message, MessageType, Transfer}, signing::SignerType, storage::StorageAdapter, + AccountGuard, }; use std::{ + collections::HashMap, convert::TryInto, fs, panic::AssertUnwindSafe, path::{Path, PathBuf}, + sync::{Arc, RwLock}, thread, time::Duration, }; @@ -30,6 +33,8 @@ use stronghold::Stronghold; /// The default storage path. pub const DEFAULT_STORAGE_PATH: &str = "./example-database"; +type AccountStore = Arc>>; + /// The account manager. /// /// Used to manage multiple accounts. @@ -42,6 +47,7 @@ pub struct AccountManager { #[getset(get = "pub", set = "pub")] polling_interval: Duration, started_monitoring: bool, + accounts: AccountStore, } /// Internal transfer response metadata. @@ -71,20 +77,26 @@ impl AccountManager { storage_path: impl AsRef, adapter: S, ) -> crate::Result { + let accounts = adapter.get_all()?; + let accounts = crate::storage::parse_accounts(&storage_path.as_ref().to_path_buf(), &accounts)? + .into_iter() + .map(|account| (account.id().clone(), Arc::new(RwLock::new(account)))) + .collect(); + crate::storage::set_adapter(&storage_path, adapter); let instance = Self { storage_path: storage_path.as_ref().to_path_buf(), polling_interval: Duration::from_millis(30_000), started_monitoring: false, + accounts: Arc::new(RwLock::new(accounts)), }; Ok(instance) } /// Starts monitoring the accounts with the node's mqtt topics. fn start_monitoring(&self) -> crate::Result<()> { - let accounts = crate::storage::with_adapter(&self.storage_path, |storage| storage.get_all())?; - let accounts = crate::storage::parse_accounts(&self.storage_path, &accounts)?; - for account in accounts { + for account in self.accounts.read().unwrap().values() { + let account = account.read().unwrap(); crate::monitor::monitor_account_addresses_balance(&account)?; crate::monitor::monitor_unconfirmed_messages(&account)?; } @@ -118,11 +130,13 @@ impl AccountManager { fn start_polling(&self, is_monitoring_disabled: bool) -> thread::JoinHandle<()> { let storage_path = self.storage_path.clone(); let interval = self.polling_interval; + let accounts = self.accounts.clone(); thread::spawn(move || { loop { let storage_path_ = storage_path.clone(); + let accounts = accounts.clone(); crate::block_on(async move { - if let Err(panic) = AssertUnwindSafe(poll(storage_path_, is_monitoring_disabled)) + if let Err(panic) = AssertUnwindSafe(poll(accounts, storage_path_, is_monitoring_disabled)) .catch_unwind() .await { @@ -160,9 +174,7 @@ impl AccountManager { /// Syncs all accounts. pub async fn sync_accounts(&self) -> crate::Result> { - let accounts = crate::storage::with_adapter(&self.storage_path, |storage| storage.get_all())?; - let mut accounts = crate::storage::parse_accounts(&self.storage_path, &accounts)?; - sync_accounts(&self.storage_path, None, &mut accounts).await + sync_accounts(self.accounts.clone(), &self.storage_path, None).await } /// Transfers an amount from an account to another. @@ -301,11 +313,13 @@ impl AccountManager { } } -async fn poll(storage_path: PathBuf, syncing: bool) -> crate::Result<()> { +async fn poll(accounts: AccountStore, storage_path: PathBuf, syncing: bool) -> crate::Result<()> { let retried = if syncing { - let accounts_before_sync = crate::storage::with_adapter(&storage_path, |storage| storage.get_all())?; - let mut accounts_before_sync = crate::storage::parse_accounts(&storage_path, &accounts_before_sync)?; - let synced_accounts = sync_accounts(&storage_path, Some(0), &mut accounts_before_sync).await?; + let mut accounts_before_sync = Vec::new(); + for account in accounts.read().unwrap().values() { + accounts_before_sync.push(account.read().unwrap().clone()); + } + let synced_accounts = sync_accounts(accounts, &storage_path, Some(0)).await?; let accounts_after_sync = crate::storage::with_adapter(&storage_path, |storage| storage.get_all())?; let mut accounts_after_sync = crate::storage::parse_accounts(&storage_path, &accounts_after_sync)?; @@ -394,60 +408,68 @@ async fn discover_accounts( storage_path: &PathBuf, client_options: &ClientOptions, signer_type: Option, -) -> crate::Result> { +) -> crate::Result> { let mut synced_accounts = vec![]; loop { - let mut account_initialiser = AccountInitialiser::new(client_options.clone(), &storage_path).skip_persistance(); + let mut account_initialiser = AccountInitialiser::new(client_options.clone(), &storage_path); if let Some(signer_type) = &signer_type { account_initialiser = account_initialiser.signer_type(signer_type.clone()); } let mut account = account_initialiser.initialise()?; - let synced_account = account.sync().skip_persistance().execute().await?; + let synced_account = account.sync().execute().await?; let is_empty = *synced_account.is_empty(); if is_empty { break; } else { - synced_accounts.push(synced_account); - account.save()?; + synced_accounts.push((account, synced_account)); } } Ok(synced_accounts) } async fn sync_accounts<'a>( + accounts: AccountStore, storage_path: &PathBuf, address_index: Option, - accounts: &mut Vec, ) -> crate::Result> { let mut synced_accounts = vec![]; let mut last_account = None; - for account in accounts { - let mut sync = account.sync(); - if let Some(index) = address_index { - sync = sync.address_index(index); + + { + let accounts = accounts.read().unwrap(); + for account in accounts.values() { + let mut account = account.write().unwrap(); + let mut sync = account.sync(); + if let Some(index) = address_index { + sync = sync.address_index(index); + } + let synced_account = sync.execute().await?; + last_account = Some(( + account.messages().is_empty() || account.addresses().iter().all(|addr| *addr.balance() == 0), + account.client_options().clone(), + account.signer_type().clone(), + )); + synced_accounts.push(synced_account); } - let synced_account = sync.execute().await?; - last_account = Some(account); - synced_accounts.push(synced_account); } let discovered_accounts_res = match last_account { - Some(account) => { - if account.messages().is_empty() || account.addresses().iter().all(|addr| *addr.balance() == 0) { - discover_accounts( - &storage_path, - account.client_options(), - Some(account.signer_type().clone()), - ) - .await + Some((is_empty, client_options, signer_type)) => { + if is_empty { + discover_accounts(&storage_path, &client_options, Some(signer_type)).await } else { Ok(vec![]) } } None => discover_accounts(&storage_path, &ClientOptions::default(), None).await, }; + if let Ok(discovered_accounts) = discovered_accounts_res { - synced_accounts.extend(discovered_accounts.into_iter()); + let mut accounts = accounts.write().unwrap(); + for (account, synced_account) in discovered_accounts { + accounts.insert(account.id().clone(), Arc::new(RwLock::new(account))); + synced_accounts.push(synced_account); + } } Ok(synced_accounts) diff --git a/src/lib.rs b/src/lib.rs index eba136956..cbf70e4d1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -35,11 +35,14 @@ use once_cell::sync::OnceCell; use std::{ collections::HashMap, path::PathBuf, - sync::{Arc, Mutex}, + sync::{Arc, Mutex, RwLock}, }; use stronghold::Stronghold; use tokio::runtime::Runtime; +/// A thread guard over an account. +pub type AccountGuard = Arc>; + static STRONGHOLD_INSTANCE: OnceCell>>> = OnceCell::new(); /// The wallet error type. From eef503930d5c23f56950fe85c199125b52aaf8b2 Mon Sep 17 00:00:00 2001 From: Lucas Nogueira Date: Tue, 15 Dec 2020 15:04:33 -0300 Subject: [PATCH 02/29] feat(manager): adjust `get_account` to use the store --- src/account_manager.rs | 32 +++++++++++++++++++++----------- src/actor/mod.rs | 11 ++++++++--- 2 files changed, 29 insertions(+), 14 deletions(-) diff --git a/src/account_manager.rs b/src/account_manager.rs index a15aa8b9d..804515240 100644 --- a/src/account_manager.rs +++ b/src/account_manager.rs @@ -57,7 +57,7 @@ pub struct InternalTransferMetadata { /// Source account with new message and addresses attached. pub from_account: Account, /// Destination account with new message attached. - pub to_account: Account, + pub to_account: AccountGuard, } impl AccountManager { @@ -184,8 +184,12 @@ impl AccountManager { to_account_id: &AccountIdentifier, amount: u64, ) -> crate::Result { - let mut from_account = self.get_account(from_account_id)?; - let to_account = self.get_account(to_account_id)?; + let from_account_guard = self.get_account(from_account_id)?; + let to_account_guard = self.get_account(to_account_id)?; + + let mut from_account = from_account_guard.write().unwrap(); + let to_account = to_account_guard.read().unwrap(); + let to_address = to_account .latest_address() .ok_or_else(|| anyhow::anyhow!("destination account address list empty"))? @@ -194,8 +198,9 @@ impl AccountManager { let metadata = from_synchronized .transfer(Transfer::new(to_address.address().clone(), amount)) .await?; + Ok(InternalTransferMetadata { - to_account, + to_account: to_account_guard.clone(), from_account: metadata.account, message: metadata.message, }) @@ -271,10 +276,12 @@ impl AccountManager { } /// Gets the account associated with the given identifier. - pub fn get_account(&self, account_id: &AccountIdentifier) -> crate::Result { - let mut account = crate::storage::get_account(&self.storage_path, &account_id)?; - account.set_storage_path(self.storage_path.clone()); - Ok(account) + pub fn get_account(&self, account_id: &AccountIdentifier) -> crate::Result { + let accounts = self.accounts.read().unwrap(); + accounts + .get(account_id) + .cloned() + .ok_or(crate::WalletError::AccountNotFound) } /// Gets the account associated with the given alias (case insensitive). @@ -296,19 +303,22 @@ impl AccountManager { /// Reattaches an unconfirmed transaction. pub async fn reattach(&self, account_id: &AccountIdentifier, message_id: &MessageId) -> crate::Result { - let mut account = self.get_account(account_id)?; + let account = self.get_account(account_id)?; + let mut account = account.write().unwrap(); account.sync().execute().await?.reattach(message_id).await } /// Promotes an unconfirmed transaction. pub async fn promote(&self, account_id: &AccountIdentifier, message_id: &MessageId) -> crate::Result { - let mut account = self.get_account(account_id)?; + let account = self.get_account(account_id)?; + let mut account = account.write().unwrap(); account.sync().execute().await?.promote(message_id).await } /// Retries an unconfirmed transaction. pub async fn retry(&self, account_id: &AccountIdentifier, message_id: &MessageId) -> crate::Result { - let mut account = self.get_account(account_id)?; + let account = self.get_account(account_id)?; + let mut account = account.write().unwrap(); account.sync().execute().await?.retry(message_id).await } } diff --git a/src/actor/mod.rs b/src/actor/mod.rs index c47d0aca4..9865dd7b6 100644 --- a/src/actor/mod.rs +++ b/src/actor/mod.rs @@ -153,7 +153,9 @@ impl WalletMessageHandler { account_id: &AccountIdentifier, method: &AccountMethod, ) -> Result { - let mut account = self.account_manager.get_account(account_id)?; + let account = self.account_manager.get_account(account_id)?; + let mut account = account.write().unwrap(); + match method { AccountMethod::GenerateAddress => { let address = account.generate_address()?; @@ -231,7 +233,8 @@ impl WalletMessageHandler { fn get_account(&self, account_id: &AccountIdentifier) -> Result { let account = self.account_manager.get_account(&account_id)?; - Ok(ResponseType::ReadAccount(account)) + let account = account.read().unwrap(); + Ok(ResponseType::ReadAccount(account.clone())) } fn get_accounts(&self) -> Result { @@ -245,7 +248,9 @@ impl WalletMessageHandler { } async fn send_transfer(&self, account_id: &AccountIdentifier, transfer: &Transfer) -> Result { - let mut account = self.account_manager.get_account(account_id)?; + let account = self.account_manager.get_account(account_id)?; + let mut account = account.write().unwrap(); + let synced = account.sync().execute().await?; let message = synced.transfer(transfer.clone()).await?.message; Ok(ResponseType::SentTransfer(message)) From 3790e30f8d08d14cc9a65cd9b2fcd1ed971fdf72 Mon Sep 17 00:00:00 2001 From: Lucas Nogueira Date: Tue, 15 Dec 2020 16:25:22 -0300 Subject: [PATCH 03/29] refactor(lib): use AccountGuard instead of Account --- src/account/mod.rs | 41 +++++--- src/account/sync/mod.rs | 205 ++++++++++++++++++++-------------------- src/account_manager.rs | 99 ++++++++++--------- src/actor/mod.rs | 41 +++++--- src/lib.rs | 5 +- src/monitor.rs | 162 ++++++++++++++----------------- 6 files changed, 285 insertions(+), 268 deletions(-) diff --git a/src/account/mod.rs b/src/account/mod.rs index b7c50f7e5..8b852bcf2 100644 --- a/src/account/mod.rs +++ b/src/account/mod.rs @@ -17,8 +17,9 @@ use serde::{Deserialize, Serialize}; use std::{ collections::HashMap, convert::TryInto, + ops::Deref, path::PathBuf, - sync::{Arc, Mutex}, + sync::{Arc, Mutex, RwLock}, }; mod sync; @@ -137,7 +138,7 @@ impl<'a> AccountInitialiser<'a> { } /// Initialises the account. - pub fn initialise(self) -> crate::Result { + pub fn initialise(self) -> crate::Result { let accounts = crate::storage::with_adapter(self.storage_path, |storage| storage.get_all())?; let alias = self.alias.unwrap_or_else(|| format!("Account {}", accounts.len())); let signer_type = self @@ -179,7 +180,7 @@ impl<'a> AccountInitialiser<'a> { let id = with_signer(&signer_type, |signer| signer.init_account(&account, mnemonic))?; account.set_id(id.into()); - Ok(account) + Ok(account.into()) } } @@ -229,6 +230,30 @@ pub struct Account { has_pending_changes: bool, } +/// A thread guard over an account. +#[derive(Debug, Clone)] +pub struct AccountGuard(Arc>); + +impl From for AccountGuard { + fn from(account: Account) -> Self { + Self(Arc::new(RwLock::new(account))) + } +} + +impl Deref for AccountGuard { + type Target = RwLock; + fn deref(&self) -> &Self::Target { + &self.0.deref() + } +} + +impl AccountGuard { + /// Returns the builder to setup the process to synchronize this account with the Tangle. + pub fn sync(&self) -> AccountSynchronizer { + AccountSynchronizer::new(self.clone()) + } +} + impl Account { /// Returns the most recent address of the account. pub fn latest_address(&self) -> Option<&Address> { @@ -238,11 +263,6 @@ impl Account { .max_by_key(|a| a.key_index()) } - /// Returns the builder to setup the process to synchronize this account with the Tangle. - pub fn sync(&'_ mut self) -> AccountSynchronizer<'_> { - AccountSynchronizer::new(self, self.storage_path.clone()) - } - /// Gets the account's total balance. /// It's read directly from the storage. To read the latest account balance, you should `sync` first. pub fn total_balance(&self) -> u64 { @@ -376,10 +396,9 @@ impl Account { let address = crate::address::get_new_address(&self)?; self.addresses.push(address.clone()); - self.save()?; - // ignore errors because we fallback to the polling system - let _ = crate::monitor::monitor_address_balance(&self, address.address()); + // TODO let _ = crate::monitor::monitor_address_balance(&self, address.address()); + Ok(address) } diff --git a/src/account/sync/mod.rs b/src/account/sync/mod.rs index b9a3ec811..f5843ae3f 100644 --- a/src/account/sync/mod.rs +++ b/src/account/sync/mod.rs @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 use crate::{ - account::{get_account_addresses_lock, Account, AccountIdentifier}, + account::{get_account_addresses_lock, Account, AccountGuard}, address::{Address, AddressBuilder, AddressOutput, IotaAddress}, client::get_client, message::{Message, RemainderValueStrategy, Transfer}, @@ -16,13 +16,12 @@ use iota::{ }, ClientMiner, }; -use serde::{Deserialize, Serialize}; +use serde::{ser::Serializer, Serialize}; use slip10::BIP32Path; use std::{ convert::TryInto, num::NonZeroU64, - path::PathBuf, sync::{mpsc::channel, Arc, Mutex, MutexGuard}, thread, time::Duration, @@ -49,7 +48,6 @@ const OUTPUT_LOCK_TIMEOUT: Duration = Duration::from_secs(30); /// Returns a (addresses, messages) tuples representing the address history up to latest unused address, /// and the messages associated with the addresses. async fn sync_addresses( - storage_path: &PathBuf, account: &'_ Account, address_index: usize, gap_limit: usize, @@ -230,13 +228,8 @@ async fn update_account_messages<'a>( Ok(()) } -async fn perform_sync( - mut account: &mut Account, - storage_path: &PathBuf, - address_index: usize, - gap_limit: usize, -) -> crate::Result { - let (found_addresses, found_messages) = sync_addresses(&storage_path, &account, address_index, gap_limit).await?; +async fn perform_sync(mut account: &mut Account, address_index: usize, gap_limit: usize) -> crate::Result { + let (found_addresses, found_messages) = sync_addresses(&account, address_index, gap_limit).await?; let mut new_messages = vec![]; for (found_message_id, confirmed, found_message) in found_messages { @@ -298,25 +291,26 @@ async fn perform_sync( } /// Account sync helper. -pub struct AccountSynchronizer<'a> { - account: &'a mut Account, +pub struct AccountSynchronizer { + account: AccountGuard, address_index: usize, gap_limit: usize, skip_persistance: bool, - storage_path: PathBuf, } -impl<'a> AccountSynchronizer<'a> { +impl AccountSynchronizer { /// Initialises a new instance of the sync helper. - pub(super) fn new(account: &'a mut Account, storage_path: PathBuf) -> Self { - let address_index = account.addresses().len(); + pub(super) fn new(account: AccountGuard) -> Self { + let address_index = { + let account_ = account.read().unwrap(); + account_.addresses().len() + }; Self { account, // by default we synchronize from the latest address (supposedly unspent) address_index: if address_index == 0 { 0 } else { address_index - 1 }, gap_limit: if address_index == 0 { 10 } else { 1 }, skip_persistance: false, - storage_path, } } @@ -342,48 +336,55 @@ impl<'a> AccountSynchronizer<'a> { /// The account syncing process ensures that the latest metadata (balance, transactions) /// associated with an account is fetched from the tangle and is stored locally. pub async fn execute(self) -> crate::Result { - let options = self.account.client_options().clone(); - let client = get_client(&options); + let mut account_ref = self.account.write().unwrap(); - let _ = crate::monitor::unsubscribe(&self.account); + let options = account_ref.client_options().clone(); + let client = get_client(&options); - let mut account_ = self.account.clone(); - let return_value = - match perform_sync(&mut account_, &self.storage_path, self.address_index, self.gap_limit).await { - Ok(is_empty) => { - self.account.set_addresses(account_.addresses().to_vec()); - self.account.set_messages(account_.messages().to_vec()); - if !self.skip_persistance { - self.account.save()?; - } + let _ = crate::monitor::unsubscribe(self.account.clone()); - let synced_account = SyncedAccount { - account_id: self.account.id().clone(), - deposit_address: self.account.latest_address().unwrap().clone(), - is_empty, - storage_path: self.storage_path, - addresses: self.account.addresses().clone(), - messages: self.account.messages().clone(), - }; - Ok(synced_account) + let mut account_ = account_ref.clone(); + let return_value = match perform_sync(&mut account_, self.address_index, self.gap_limit).await { + Ok(is_empty) => { + if !self.skip_persistance { + account_ref.set_addresses(account_.addresses().to_vec()); + account_ref.set_messages(account_.messages().to_vec()); } - Err(e) => Err(e), - }; - let _ = crate::monitor::monitor_account_addresses_balance(&self.account); - let _ = crate::monitor::monitor_unconfirmed_messages(&self.account); + let synced_account = SyncedAccount { + account: self.account.clone(), + deposit_address: account_ref.latest_address().unwrap().clone(), + is_empty, + addresses: account_ref.addresses().clone(), + messages: account_ref.messages().clone(), + }; + Ok(synced_account) + } + Err(e) => Err(e), + }; + + let _ = crate::monitor::monitor_account_addresses_balance(self.account.clone()); + let _ = crate::monitor::monitor_unconfirmed_messages(self.account.clone()); return_value } } +fn serialize_as_id(x: &AccountGuard, s: S) -> Result +where + S: Serializer, +{ + let account = x.read().unwrap(); + account.id().serialize(s) +} + /// Data returned from account synchronization. -#[derive(Debug, Clone, PartialEq, Getters, Serialize, Deserialize)] +#[derive(Debug, Clone, Getters, Serialize)] pub struct SyncedAccount { /// The associated account identifier. - #[serde(rename = "accountId")] + #[serde(rename = "accountId", serialize_with = "serialize_as_id")] #[getset(get = "pub")] - account_id: AccountIdentifier, + account: AccountGuard, /// The account's deposit address. #[serde(rename = "depositAddress")] #[getset(get = "pub")] @@ -398,8 +399,6 @@ pub struct SyncedAccount { /// The account addresses. #[getset(get = "pub")] addresses: Vec
, - #[serde(rename = "storagePath")] - storage_path: PathBuf, } /// Transfer response metadata. @@ -408,7 +407,7 @@ pub struct TransferMetadata { /// The transfer message. pub message: Message, /// The transfer source account with new message and addresses attached. - pub account: Account, + pub account: AccountGuard, } impl SyncedAccount { @@ -467,61 +466,63 @@ impl SyncedAccount { return Err(crate::WalletError::ZeroAmount); } + let account_ = self.account.read().unwrap(); + // lock the transfer process until we select the input addresses // we do this to prevent multiple threads trying to transfer at the same time // so it doesn't consume the same addresses multiple times, which leads to a conflict state - let account_addresses_locker = get_account_addresses_lock(&self.account_id); + let account_addresses_locker = get_account_addresses_lock(account_.id()); let mut locked_addresses = account_addresses_locker.lock().unwrap(); // prepare the transfer getting some needed objects and values let value: u64 = transfer_obj.amount; - let mut account = crate::storage::get_account(&self.storage_path, &self.account_id)?; let mut addresses_to_watch = vec![]; - if value > account.total_balance() { + if value > account_.total_balance() { return Err(crate::WalletError::InsufficientFunds); } + let available_balance = account_.available_balance(); + drop(account_); + // if the transfer value exceeds the account's available balance, // wait for an account update or sync it with the tangle - if value > account.available_balance() { + if value > available_balance { let (tx, rx) = channel(); let tx = Arc::new(Mutex::new(tx)); - let storage_path = self.storage_path.clone(); - let account_id = self.account_id.clone(); + let account = self.account.clone(); thread::spawn(move || { let tx = tx.lock().unwrap(); for _ in 1..30 { thread::sleep(OUTPUT_LOCK_TIMEOUT / 30); - if let Ok(account) = crate::storage::get_account(&storage_path, &account_id) { - // the account received an update and now the balance is sufficient - if value <= account.available_balance() { - let _ = tx.send(account); - break; - } + let account_ = account.read().unwrap(); + // the account received an update and now the balance is sufficient + if value <= account_.available_balance() { + let _ = tx.send(()); + break; } } }); match rx.recv_timeout(OUTPUT_LOCK_TIMEOUT) { - Ok(acc) => { - account = acc; - } + Ok(_) => {} Err(_) => { // if we got a timeout waiting for the account update, we try to sync it - account.sync().execute().await?; + self.account.sync().execute().await?; } } } - let client = crate::client::get_client(account.client_options()); + let mut account_ = self.account.write().unwrap(); + + let client = crate::client::get_client(account_.client_options()); let client = client.read().unwrap(); if let RemainderValueStrategy::AccountAddress(ref remainder_target_address) = transfer_obj.remainder_value_strategy { - if !account + if !account_ .addresses() .iter() .any(|addr| addr.address() == remainder_target_address) @@ -534,7 +535,7 @@ impl SyncedAccount { let (input_addresses, remainder_address) = self.select_inputs( &mut locked_addresses, transfer_obj.amount, - &mut account, + &mut account_, &transfer_obj.address, )?; @@ -545,7 +546,7 @@ impl SyncedAccount { let mut address_index_recorders = vec![]; for input_address in &input_addresses { - let account_address = account + let account_address = account_ .addresses() .iter() .find(|a| a.address() == &input_address.address) @@ -554,13 +555,13 @@ impl SyncedAccount { let mut outputs = vec![]; let address_path = BIP32Path::from_str(&format!( "m/44H/4218H/{}H/{}H/{}H", - *account.index(), + *account_.index(), *account_address.internal() as u32, *account_address.key_index() )) .unwrap(); - for (offset, address_output) in account_address.available_outputs(&account).iter().enumerate() { + for (offset, address_output) in account_address.available_outputs(&account_).iter().enumerate() { outputs.push(( (*address_output).clone(), *account_address.key_index(), @@ -616,7 +617,7 @@ impl SyncedAccount { if remainder_value > 0 { let remainder_address = remainder_address.ok_or_else(|| anyhow::anyhow!("remainder address not defined"))?; - let remainder_address = account + let remainder_address = account_ .addresses() .iter() .find(|a| a.address() == &remainder_address.address) @@ -628,12 +629,12 @@ impl SyncedAccount { // generate a new change address to send the remainder value RemainderValueStrategy::ChangeAddress => { if *remainder_address.internal() { - let deposit_address = account.latest_address().unwrap().address().clone(); + let deposit_address = account_.latest_address().unwrap().address().clone(); deposit_address } else { - let change_address = crate::address::get_new_change_address(&account, &remainder_address)?; + let change_address = crate::address::get_new_change_address(&account_, &remainder_address)?; let addr = change_address.address().clone(); - account.append_addresses(vec![change_address]); + account_.append_addresses(vec![change_address]); addresses_to_watch.push(addr.clone()); addr } @@ -657,8 +658,8 @@ impl SyncedAccount { .finish() .map_err(|e| anyhow::anyhow!(format!("{:?}", e)))?; - let unlock_blocks = crate::signing::with_signer(account.signer_type(), |signer| { - signer.sign_message(&account, &essence, &mut address_index_recorders) + let unlock_blocks = crate::signing::with_signer(account_.signer_type(), |signer| { + signer.sign_message(&account_, &essence, &mut address_index_recorders) })?; let mut tx_builder = Transaction::builder().with_essence(essence); for unlock_block in unlock_blocks { @@ -679,32 +680,22 @@ impl SyncedAccount { // if this is a transfer to the account's latest address or we used the latest as deposit of the remainder // value, we generate a new one to keep the latest address unused - let latest_address = account.latest_address().unwrap().address(); + let latest_address = account_.latest_address().unwrap().address(); if latest_address == &transfer_obj.address || (remainder_value_deposit_address.is_some() && &remainder_value_deposit_address.unwrap() == latest_address) { - let addr = crate::address::get_new_address(&account)?; + let addr = crate::address::get_new_address(&account_)?; addresses_to_watch.push(addr.address().clone()); - account.append_addresses(vec![addr]); + account_.append_addresses(vec![addr]); } let message = client.get_message().data(&message_id).await?; - // drop the client ref so it doesn't lock the monitor system - std::mem::drop(client); - - for address in addresses_to_watch { - // ignore errors because we fallback to the polling system - let _ = crate::monitor::monitor_address_balance(&account, &address); - } - - let message = Message::from_iota_message(message_id, account.addresses(), &message, None)?; - account.append_messages(vec![message.clone()]); - - account.save()?; + let message = Message::from_iota_message(message_id, account_.addresses(), &message, None)?; + account_.append_messages(vec![message.clone()]); - let account_addresses_locker = get_account_addresses_lock(&self.account_id); + let account_addresses_locker = get_account_addresses_lock(account_.id()); let mut locked_addresses = account_addresses_locker.lock().unwrap(); for input_address in &input_addresses { let index = locked_addresses @@ -714,25 +705,37 @@ impl SyncedAccount { locked_addresses.remove(index); } + // drop the client and account_ refs so it doesn't lock the monitor system + drop(account_); + drop(client); + + for address in addresses_to_watch { + // ignore errors because we fallback to the polling system + let _ = crate::monitor::monitor_address_balance(self.account.clone(), &address); + } + // ignore errors because we fallback to the polling system - let _ = crate::monitor::monitor_confirmation_state_change(&account, &message_id); + let _ = crate::monitor::monitor_confirmation_state_change(self.account.clone(), &message_id); - Ok(TransferMetadata { message, account }) + Ok(TransferMetadata { + message, + account: self.account.clone(), + }) } /// Retry message. pub async fn retry(&self, message_id: &MessageId) -> crate::Result { - repost_message(&self.account_id, &self.storage_path, message_id, RepostAction::Retry).await + repost_message(self.account.clone(), message_id, RepostAction::Retry).await } /// Promote message. pub async fn promote(&self, message_id: &MessageId) -> crate::Result { - repost_message(&self.account_id, &self.storage_path, message_id, RepostAction::Promote).await + repost_message(self.account.clone(), message_id, RepostAction::Promote).await } /// Reattach message. pub async fn reattach(&self, message_id: &MessageId) -> crate::Result { - repost_message(&self.account_id, &self.storage_path, message_id, RepostAction::Reattach).await + repost_message(self.account.clone(), message_id, RepostAction::Reattach).await } } @@ -743,12 +746,12 @@ pub(crate) enum RepostAction { } pub(crate) async fn repost_message( - account_id: &AccountIdentifier, - storage_path: &PathBuf, + account: AccountGuard, message_id: &MessageId, action: RepostAction, ) -> crate::Result { - let mut account: Account = crate::storage::get_account(&storage_path, account_id)?; + let mut account = account.write().unwrap(); + let message = match account.get_message(message_id) { Some(message_to_repost) => { // get the latest reattachment of the message we want to promote/rettry/reattach diff --git a/src/account_manager.rs b/src/account_manager.rs index 804515240..a92ab57e0 100644 --- a/src/account_manager.rs +++ b/src/account_manager.rs @@ -3,15 +3,14 @@ use crate::{ account::{ - account_id_to_stronghold_record_id, repost_message, Account, AccountIdentifier, AccountInitialiser, - RepostAction, SyncedAccount, + account_id_to_stronghold_record_id, repost_message, Account, AccountGuard, AccountIdentifier, + AccountInitialiser, RepostAction, SyncedAccount, }, client::ClientOptions, event::{emit_balance_change, emit_confirmation_state_change, emit_transaction_event, TransactionEventType}, message::{Message, MessageType, Transfer}, signing::SignerType, storage::StorageAdapter, - AccountGuard, }; use std::{ @@ -27,7 +26,7 @@ use std::{ use futures::FutureExt; use getset::{Getters, Setters}; -use iota::message::prelude::MessageId; +use iota::{MessageId, Payload}; use stronghold::Stronghold; /// The default storage path. @@ -55,7 +54,7 @@ pub struct InternalTransferMetadata { /// Transfer message. pub message: Message, /// Source account with new message and addresses attached. - pub from_account: Account, + pub from_account: AccountGuard, /// Destination account with new message attached. pub to_account: AccountGuard, } @@ -80,7 +79,7 @@ impl AccountManager { let accounts = adapter.get_all()?; let accounts = crate::storage::parse_accounts(&storage_path.as_ref().to_path_buf(), &accounts)? .into_iter() - .map(|account| (account.id().clone(), Arc::new(RwLock::new(account)))) + .map(|account| (account.id().clone(), account.into())) .collect(); crate::storage::set_adapter(&storage_path, adapter); @@ -96,9 +95,8 @@ impl AccountManager { /// Starts monitoring the accounts with the node's mqtt topics. fn start_monitoring(&self) -> crate::Result<()> { for account in self.accounts.read().unwrap().values() { - let account = account.read().unwrap(); - crate::monitor::monitor_account_addresses_balance(&account)?; - crate::monitor::monitor_unconfirmed_messages(&account)?; + crate::monitor::monitor_account_addresses_balance(account.clone())?; + crate::monitor::monitor_unconfirmed_messages(account.clone())?; } Ok(()) } @@ -187,14 +185,15 @@ impl AccountManager { let from_account_guard = self.get_account(from_account_id)?; let to_account_guard = self.get_account(to_account_id)?; - let mut from_account = from_account_guard.write().unwrap(); - let to_account = to_account_guard.read().unwrap(); + let to_address = { + let to_account = to_account_guard.read().unwrap(); + to_account + .latest_address() + .ok_or_else(|| anyhow::anyhow!("destination account address list empty"))? + .clone() + }; - let to_address = to_account - .latest_address() - .ok_or_else(|| anyhow::anyhow!("destination account address list empty"))? - .clone(); - let from_synchronized = from_account.sync().execute().await?; + let from_synchronized = from_account_guard.sync().execute().await?; let metadata = from_synchronized .transfer(Transfer::new(to_address.address().clone(), amount)) .await?; @@ -285,40 +284,35 @@ impl AccountManager { } /// Gets the account associated with the given alias (case insensitive). - pub fn get_account_by_alias>(&self, alias: S) -> Option { + pub fn get_account_by_alias>(&self, alias: S) -> Option { let alias = alias.into().to_lowercase(); - if let Ok(accounts) = self.get_accounts() { - accounts.into_iter().find(|acc| acc.alias().to_lowercase() == alias) - } else { - None - } + self.get_accounts().into_iter().find(|acc| { + let acc = acc.read().unwrap(); + acc.alias().to_lowercase() == alias + }) } /// Gets all accounts from storage. - pub fn get_accounts(&self) -> crate::Result> { - crate::storage::with_adapter(&self.storage_path, |storage| { - crate::storage::parse_accounts(&self.storage_path, &storage.get_all()?) - }) + pub fn get_accounts(&self) -> Vec { + let accounts = self.accounts.read().unwrap(); + accounts.values().cloned().collect() } /// Reattaches an unconfirmed transaction. pub async fn reattach(&self, account_id: &AccountIdentifier, message_id: &MessageId) -> crate::Result { let account = self.get_account(account_id)?; - let mut account = account.write().unwrap(); account.sync().execute().await?.reattach(message_id).await } /// Promotes an unconfirmed transaction. pub async fn promote(&self, account_id: &AccountIdentifier, message_id: &MessageId) -> crate::Result { let account = self.get_account(account_id)?; - let mut account = account.write().unwrap(); account.sync().execute().await?.promote(message_id).await } /// Retries an unconfirmed transaction. pub async fn retry(&self, account_id: &AccountIdentifier, message_id: &MessageId) -> crate::Result { let account = self.get_account(account_id)?; - let mut account = account.write().unwrap(); account.sync().execute().await?.retry(message_id).await } } @@ -378,18 +372,24 @@ async fn poll(accounts: AccountStore, storage_path: PathBuf, syncing: bool) -> c } retry_unconfirmed_transactions(synced_accounts.iter().zip(accounts_after_sync.iter()).collect()).await? } else { - let accounts = crate::storage::with_adapter(&storage_path, |storage| storage.get_all())?; let mut retried_messages = vec![]; - for account in crate::storage::parse_accounts(&storage_path, &accounts)? { - let unconfirmed_messages = - account.list_messages(account.messages().len(), 0, Some(MessageType::Unconfirmed)); + for account in accounts.read().unwrap().values() { + let (account_id, unconfirmed_messages): (AccountIdentifier, Vec<(MessageId, Payload)>) = { + let account = account.read().unwrap(); + let account_id = account.id().clone(); + let unconfirmed_messages = account + .list_messages(account.messages().len(), 0, Some(MessageType::Unconfirmed)) + .iter() + .map(|m| (*m.id(), m.payload().clone())) + .collect(); + (account_id, unconfirmed_messages) + }; let mut promotions = vec![]; let mut reattachments = vec![]; - for message in unconfirmed_messages { - let new_message = - repost_message(account.id(), &storage_path, message.id(), RepostAction::Retry).await?; - if new_message.payload() == message.payload() { + for (message_id, payload) in unconfirmed_messages { + let new_message = repost_message(account.clone(), &message_id, RepostAction::Retry).await?; + if new_message.payload() == &payload { reattachments.push(new_message); } else { promotions.push(new_message); @@ -399,7 +399,7 @@ async fn poll(accounts: AccountStore, storage_path: PathBuf, syncing: bool) -> c retried_messages.push(RetriedData { promoted: promotions, reattached: reattachments, - account_id: account.id().clone(), + account_id, }); } @@ -418,14 +418,14 @@ async fn discover_accounts( storage_path: &PathBuf, client_options: &ClientOptions, signer_type: Option, -) -> crate::Result> { +) -> crate::Result> { let mut synced_accounts = vec![]; loop { let mut account_initialiser = AccountInitialiser::new(client_options.clone(), &storage_path); if let Some(signer_type) = &signer_type { account_initialiser = account_initialiser.signer_type(signer_type.clone()); } - let mut account = account_initialiser.initialise()?; + let account = account_initialiser.initialise()?; let synced_account = account.sync().execute().await?; let is_empty = *synced_account.is_empty(); if is_empty { @@ -446,18 +446,19 @@ async fn sync_accounts<'a>( let mut last_account = None; { - let accounts = accounts.read().unwrap(); - for account in accounts.values() { - let mut account = account.write().unwrap(); + let mut accounts = accounts.write().unwrap(); + for account in accounts.values_mut() { let mut sync = account.sync(); if let Some(index) = address_index { sync = sync.address_index(index); } let synced_account = sync.execute().await?; + + let account_ = account.read().unwrap(); last_account = Some(( - account.messages().is_empty() || account.addresses().iter().all(|addr| *addr.balance() == 0), - account.client_options().clone(), - account.signer_type().clone(), + account_.messages().is_empty() || account_.addresses().iter().all(|addr| *addr.balance() == 0), + account_.client_options().clone(), + account_.signer_type().clone(), )); synced_accounts.push(synced_account); } @@ -477,7 +478,11 @@ async fn sync_accounts<'a>( if let Ok(discovered_accounts) = discovered_accounts_res { let mut accounts = accounts.write().unwrap(); for (account, synced_account) in discovered_accounts { - accounts.insert(account.id().clone(), Arc::new(RwLock::new(account))); + let account_id = { + let account_ = account.read().unwrap(); + account_.id().clone() + }; + accounts.insert(account_id, account); synced_accounts.push(synced_account); } } diff --git a/src/actor/mod.rs b/src/actor/mod.rs index 9865dd7b6..714c8ad19 100644 --- a/src/actor/mod.rs +++ b/src/actor/mod.rs @@ -154,11 +154,11 @@ impl WalletMessageHandler { method: &AccountMethod, ) -> Result { let account = self.account_manager.get_account(account_id)?; - let mut account = account.write().unwrap(); match method { AccountMethod::GenerateAddress => { - let address = account.generate_address()?; + let mut account_ = account.write().unwrap(); + let address = account_.generate_address()?; Ok(ResponseType::GeneratedAddress(address)) } AccountMethod::ListMessages { @@ -166,7 +166,8 @@ impl WalletMessageHandler { from, message_type, } => { - let messages: Vec = account + let account_ = account.read().unwrap(); + let messages: Vec = account_ .list_messages(*count, *from, message_type.clone()) .into_iter() .cloned() @@ -174,12 +175,22 @@ impl WalletMessageHandler { Ok(ResponseType::Messages(messages)) } AccountMethod::ListAddresses { unspent } => { - let addresses = account.list_addresses(*unspent).into_iter().cloned().collect(); + let account_ = account.read().unwrap(); + let addresses = account_.list_addresses(*unspent).into_iter().cloned().collect(); Ok(ResponseType::Addresses(addresses)) } - AccountMethod::GetAvailableBalance => Ok(ResponseType::AvailableBalance(account.available_balance())), - AccountMethod::GetTotalBalance => Ok(ResponseType::TotalBalance(account.total_balance())), - AccountMethod::GetLatestAddress => Ok(ResponseType::LatestAddress(account.latest_address().cloned())), + AccountMethod::GetAvailableBalance => { + let account_ = account.read().unwrap(); + Ok(ResponseType::AvailableBalance(account_.available_balance())) + } + AccountMethod::GetTotalBalance => { + let account_ = account.read().unwrap(); + Ok(ResponseType::TotalBalance(account_.total_balance())) + } + AccountMethod::GetLatestAddress => { + let account_ = account.read().unwrap(); + Ok(ResponseType::LatestAddress(account_.latest_address().cloned())) + } AccountMethod::SyncAccount { address_index, gap_limit, @@ -228,7 +239,10 @@ impl WalletMessageHandler { ); } - builder.initialise().map(ResponseType::CreatedAccount) + builder.initialise().map(|account| { + let account = account.read().unwrap(); + ResponseType::CreatedAccount(account.clone()) + }) } fn get_account(&self, account_id: &AccountIdentifier) -> Result { @@ -238,8 +252,13 @@ impl WalletMessageHandler { } fn get_accounts(&self) -> Result { - let accounts = self.account_manager.get_accounts()?; - Ok(ResponseType::ReadAccounts(accounts)) + let accounts = self.account_manager.get_accounts(); + let mut accounts_ = Vec::new(); + for account in accounts { + let account_ = account.read().unwrap(); + accounts_.push(account_.clone()); + } + Ok(ResponseType::ReadAccounts(accounts_)) } fn set_stronghold_password(&mut self, password: &str) -> Result { @@ -249,8 +268,6 @@ impl WalletMessageHandler { async fn send_transfer(&self, account_id: &AccountIdentifier, transfer: &Transfer) -> Result { let account = self.account_manager.get_account(account_id)?; - let mut account = account.write().unwrap(); - let synced = account.sync().execute().await?; let message = synced.transfer(transfer.clone()).await?.message; Ok(ResponseType::SentTransfer(message)) diff --git a/src/lib.rs b/src/lib.rs index cbf70e4d1..eba136956 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -35,14 +35,11 @@ use once_cell::sync::OnceCell; use std::{ collections::HashMap, path::PathBuf, - sync::{Arc, Mutex, RwLock}, + sync::{Arc, Mutex}, }; use stronghold::Stronghold; use tokio::runtime::Runtime; -/// A thread guard over an account. -pub type AccountGuard = Arc>; - static STRONGHOLD_INSTANCE: OnceCell>>> = OnceCell::new(); /// The wallet error type. diff --git a/src/monitor.rs b/src/monitor.rs index 671271a12..3b7a25a24 100644 --- a/src/monitor.rs +++ b/src/monitor.rs @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 use crate::{ - account::{Account, AccountIdentifier}, + account::AccountGuard, address::{AddressOutput, IotaAddress}, client::ClientOptions, message::{Message, MessageType}, @@ -11,7 +11,7 @@ use crate::{ use iota::{message::prelude::MessageId, MessageMetadata, OutputMetadata, Topic, TopicEvent}; use serde::Deserialize; -use std::{convert::TryInto, path::PathBuf}; +use std::convert::TryInto; #[derive(Deserialize)] struct AddressOutputPayload { @@ -42,24 +42,14 @@ struct AddressOutputPayloadAddress { } /// Unsubscribe from all topics associated with the account. -pub fn unsubscribe(account: &Account) -> crate::Result<()> { - let client = crate::client::get_client(account.client_options()); +pub fn unsubscribe(account: AccountGuard) -> crate::Result<()> { + let account_ = account.read().unwrap(); + let client = crate::client::get_client(account_.client_options()); let mut client = client.write().unwrap(); client.subscriber().unsubscribe()?; Ok(()) } -fn mutate_account( - account_id: &AccountIdentifier, - storage_path: &PathBuf, - cb: F, -) -> crate::Result<()> { - let mut account = crate::storage::get_account(&storage_path, &account_id)?; - cb(&mut account); - account.save()?; - Ok(()) -} - fn subscribe_to_topic( client_options: &ClientOptions, topic: String, @@ -72,41 +62,34 @@ fn subscribe_to_topic( } /// Monitor account addresses for balance changes. -pub fn monitor_account_addresses_balance(account: &Account) -> crate::Result<()> { - for address in account.addresses() { - monitor_address_balance(&account, address.address())?; +pub fn monitor_account_addresses_balance(account: AccountGuard) -> crate::Result<()> { + let account_ = account.read().unwrap(); + for address in account_.addresses() { + monitor_address_balance(account.clone(), address.address())?; } Ok(()) } /// Monitor address for balance changes. -pub fn monitor_address_balance(account: &Account, address: &IotaAddress) -> crate::Result<()> { - let account_id = account.id().clone(); - let storage_path = account.storage_path().clone(); - let client_options = account.client_options().clone(); +pub fn monitor_address_balance(account: AccountGuard, address: &IotaAddress) -> crate::Result<()> { + let client_options = { + let account_ = account.read().unwrap(); + account_.client_options().clone() + }; let address = address.clone(); - let address_bech32 = address.to_bech32(); subscribe_to_topic( - account.client_options(), - format!("addresses/{}/outputs", address_bech32), + &client_options.clone(), + format!("addresses/{}/outputs", address.to_bech32()), move |topic_event| { let topic_event = topic_event.clone(); let address = address.clone(); let client_options = client_options.clone(); - let storage_path = storage_path.clone(); - let account_id = account_id.clone(); + let account = account.clone(); std::thread::spawn(move || { crate::block_on(async { - let _ = process_output( - topic_event.payload.clone(), - account_id, - address, - client_options, - storage_path, - ) - .await; + let _ = process_output(topic_event.payload.clone(), account, address, client_options).await; }); }); }, @@ -117,10 +100,9 @@ pub fn monitor_address_balance(account: &Account, address: &IotaAddress) -> crat async fn process_output( payload: String, - account_id: AccountIdentifier, + account: AccountGuard, address: IotaAddress, client_options: ClientOptions, - storage_path: PathBuf, ) -> crate::Result<()> { let output: AddressOutputPayload = serde_json::from_str(&payload)?; let metadata = OutputMetadata { @@ -143,67 +125,64 @@ async fn process_output( client.get_message().data(&message_id_).await? }; + let mut account = account.write().unwrap(); + let account_id = account.id().clone(); + let message_id_ = *message_id; - mutate_account(&account_id, &storage_path, |account| { - { - let addresses = account.addresses_mut(); - let address_to_update = addresses.iter_mut().find(|a| a.address() == &address).unwrap(); - address_to_update.handle_new_output(address_output); - crate::event::emit_balance_change(&account_id, &address_to_update, *address_to_update.balance()); - } + { + let addresses = account.addresses_mut(); + let address_to_update = addresses.iter_mut().find(|a| a.address() == &address).unwrap(); + address_to_update.handle_new_output(address_output); + crate::event::emit_balance_change(&account_id, &address_to_update, *address_to_update.balance()); + } - match account.messages_mut().iter().position(|m| m.id() == &message_id_) { - Some(message_index) => { - let message = &mut account.messages_mut()[message_index]; - message.set_confirmed(Some(true)); - } - None => { - let message = - Message::from_iota_message(message_id_, account.addresses(), &message, Some(true)).unwrap(); - crate::event::emit_transaction_event( - crate::event::TransactionEventType::NewTransaction, - &account_id, - &message, - ); - account.messages_mut().push(message); - } + match account.messages_mut().iter().position(|m| m.id() == &message_id_) { + Some(message_index) => { + let message = &mut account.messages_mut()[message_index]; + message.set_confirmed(Some(true)); + } + None => { + let message = Message::from_iota_message(message_id_, account.addresses(), &message, Some(true)).unwrap(); + crate::event::emit_transaction_event( + crate::event::TransactionEventType::NewTransaction, + account.id(), + &message, + ); + account.messages_mut().push(message); } - })?; + } Ok(()) } /// Monitor the account's unconfirmed messages for confirmation state change. -pub fn monitor_unconfirmed_messages(account: &Account) -> crate::Result<()> { - for message in account.list_messages(0, 0, Some(MessageType::Unconfirmed)) { - monitor_confirmation_state_change(&account, message.id())?; +pub fn monitor_unconfirmed_messages(account: AccountGuard) -> crate::Result<()> { + let account_ = account.read().unwrap(); + for message in account_.list_messages(0, 0, Some(MessageType::Unconfirmed)) { + monitor_confirmation_state_change(account.clone(), message.id())?; } Ok(()) } /// Monitor message for confirmation state. -pub fn monitor_confirmation_state_change(account: &Account, message_id: &MessageId) -> crate::Result<()> { - let account_id = account.id().clone(); - let storage_path = account.storage_path().clone(); - let message = account - .messages() - .iter() - .find(|message| message.id() == message_id) - .unwrap() - .clone(); +pub fn monitor_confirmation_state_change(account: AccountGuard, message_id: &MessageId) -> crate::Result<()> { + let (message, client_options) = { + let account_ = account.read().unwrap(); + let message = account_ + .messages() + .iter() + .find(|message| message.id() == message_id) + .unwrap() + .clone(); + (message, account_.client_options().clone()) + }; let message_id = *message_id; subscribe_to_topic( - account.client_options(), + &client_options, format!("messages/{}/metadata", message_id.to_string()), move |topic_event| { - let account_id = account_id.clone(); - let _ = process_metadata( - topic_event.payload.clone(), - account_id, - message_id, - &message, - &storage_path, - ); + let account = account.clone(); + let _ = process_metadata(topic_event.payload.clone(), account, message_id, &message); }, )?; Ok(()) @@ -211,26 +190,23 @@ pub fn monitor_confirmation_state_change(account: &Account, message_id: &Message fn process_metadata( payload: String, - account_id: AccountIdentifier, + account: AccountGuard, message_id: MessageId, message: &Message, - storage_path: &PathBuf, ) -> crate::Result<()> { let metadata: MessageMetadata = serde_json::from_str(&payload)?; if let Some(inclusion_state) = metadata.ledger_inclusion_state { let confirmed = inclusion_state == "included"; if message.confirmed().is_none() || confirmed != message.confirmed().unwrap() { - mutate_account(&account_id, &storage_path, |account| { - let message_id = { - let messages = account.messages_mut(); - let message = messages.iter_mut().find(|m| m.id() == &message_id).unwrap(); - message.set_confirmed(Some(confirmed)); - *message.id() - }; - - crate::event::emit_confirmation_state_change(&account_id, &message, confirmed); - })?; + let mut account = account.write().unwrap(); + { + let messages = account.messages_mut(); + let message = messages.iter_mut().find(|m| m.id() == &message_id).unwrap(); + message.set_confirmed(Some(confirmed)); + } + + crate::event::emit_confirmation_state_change(account.id(), &message, confirmed); } } Ok(()) From 9a7685a11feb6996a37ad4c6bd4d90925b36e909 Mon Sep 17 00:00:00 2001 From: Lucas Nogueira Date: Tue, 15 Dec 2020 17:08:24 -0300 Subject: [PATCH 04/29] fix(lib): tests and examples compilation --- examples/account_operations.rs | 3 ++- examples/backup_and_restore.rs | 6 ++++-- examples/transfer.rs | 2 ++ src/account/mod.rs | 33 +++++++++++++++++++------------ src/account_manager.rs | 36 ++++++++++++++++++++++++---------- 5 files changed, 54 insertions(+), 26 deletions(-) diff --git a/examples/account_operations.rs b/examples/account_operations.rs index afc719df1..906265e01 100644 --- a/examples/account_operations.rs +++ b/examples/account_operations.rs @@ -10,7 +10,8 @@ async fn main() -> iota_wallet::Result<()> { // first we'll create an example account and store it let client_options = ClientOptionsBuilder::node("https://nodes.devnet.iota.org:443")?.build(); - let mut account = manager.create_account(client_options).alias("alias").initialise()?; + let account = manager.create_account(client_options).alias("alias").initialise()?; + let mut account = account.write().unwrap(); // update alias account.set_alias("the new alias"); diff --git a/examples/backup_and_restore.rs b/examples/backup_and_restore.rs index 1221994a6..90a55b7bf 100644 --- a/examples/backup_and_restore.rs +++ b/examples/backup_and_restore.rs @@ -10,7 +10,8 @@ fn main() -> iota_wallet::Result<()> { // first we'll create an example account let client_options = ClientOptionsBuilder::node("https://nodes.devnet.iota.org:443")?.build(); let account = manager.create_account(client_options).alias("alias").initialise()?; - let id = account.id(); + let account_ = account.read().unwrap(); + let id = account_.id(); // backup the stored accounts to ./backup/${backup_name} let backup_path = manager.backup("./backup")?; @@ -21,7 +22,8 @@ fn main() -> iota_wallet::Result<()> { // import the accounts from the backup and assert that it's the same manager.import_accounts(backup_path)?; let imported_account = manager.get_account(id)?; - assert_eq!(account, imported_account); + let imported_account_ = imported_account.read().unwrap(); + assert_eq!(*account_, *imported_account_); Ok(()) } diff --git a/examples/transfer.rs b/examples/transfer.rs index 46afa9916..f71d6f1a5 100644 --- a/examples/transfer.rs +++ b/examples/transfer.rs @@ -15,6 +15,8 @@ async fn main() -> iota_wallet::Result<()> { // we need to synchronize with the Tangle first let sync_accounts = manager.sync_accounts().await?; let sync_account = sync_accounts.first().unwrap(); + + let account = account.read().unwrap(); sync_account .transfer(Transfer::new(account.latest_address().unwrap().address().clone(), 150)) .await?; diff --git a/src/account/mod.rs b/src/account/mod.rs index 8b852bcf2..fd21d784a 100644 --- a/src/account/mod.rs +++ b/src/account/mod.rs @@ -476,23 +476,30 @@ mod tests { .build(); let account_id = { - let mut account = manager - .create_account(client_options) - .alias("alias") - .initialise() - .expect("failed to add account"); + let account = manager + .create_account(client_options) + .alias("alias") + .initialise() + .expect("failed to add account"); + let mut account = account.write().unwrap(); account.set_alias(updated_alias); - account.id().clone() + let id = account.id().clone(); + account.save().unwrap(); + id }; - let account_in_storage = manager - .get_account(&account_id) - .expect("failed to get account from storage"); - assert_eq!( - account_in_storage.alias().to_string(), - updated_alias.to_string() - ); + std::thread::spawn(move || { + std::thread::sleep(std::time::Duration::from_secs(3)); + let account_in_storage = manager + .get_account(&account_id) + .expect("failed to get account from storage"); + let account_in_storage = account_in_storage.read().unwrap(); + assert_eq!( + account_in_storage.alias().to_string(), + updated_alias.to_string() + ); + }); } } } diff --git a/src/account_manager.rs b/src/account_manager.rs index a92ab57e0..6725f9ace 100644 --- a/src/account_manager.rs +++ b/src/account_manager.rs @@ -76,22 +76,33 @@ impl AccountManager { storage_path: impl AsRef, adapter: S, ) -> crate::Result { - let accounts = adapter.get_all()?; - let accounts = crate::storage::parse_accounts(&storage_path.as_ref().to_path_buf(), &accounts)? - .into_iter() - .map(|account| (account.id().clone(), account.into())) - .collect(); - crate::storage::set_adapter(&storage_path, adapter); let instance = Self { storage_path: storage_path.as_ref().to_path_buf(), polling_interval: Duration::from_millis(30_000), started_monitoring: false, - accounts: Arc::new(RwLock::new(accounts)), + accounts: Default::default(), }; Ok(instance) } + /// Loads the account from the storage into the manager instance. + pub fn load_accounts(&mut self) -> crate::Result<()> { + let empty_accounts = { + let accounts = self.accounts.read().unwrap(); + accounts.is_empty() + }; + if empty_accounts { + let accounts = crate::storage::with_adapter(&self.storage_path, |storage| storage.get_all())?; + let accounts = crate::storage::parse_accounts(&self.storage_path, &accounts)? + .into_iter() + .map(|account| (account.id().clone(), account.into())) + .collect(); + self.accounts = Arc::new(RwLock::new(accounts)); + } + Ok(()) + } + /// Starts monitoring the accounts with the node's mqtt topics. fn start_monitoring(&self) -> crate::Result<()> { for account in self.accounts.read().unwrap().values() { @@ -121,6 +132,8 @@ impl AccountManager { )?; crate::init_stronghold(&self.storage_path, stronghold); self.start_background_sync(); + self.load_accounts()?; + Ok(()) } @@ -578,9 +591,10 @@ mod tests { .alias("alias") .initialise() .expect("failed to add account"); + let account_ = account.read().unwrap(); manager - .remove_account(account.id()) + .remove_account(account_.id()) .expect("failed to remove account"); } } @@ -607,8 +621,9 @@ mod tests { .finish() .unwrap(), None).unwrap()]) .initialise().unwrap(); + let account_ = account.read().unwrap(); - let remove_response = manager.remove_account(account.id()); + let remove_response = manager.remove_account(account_.id()); assert!(remove_response.is_err()); } } @@ -633,8 +648,9 @@ mod tests { .unwrap()]) .initialise() .unwrap(); + let account_ = account.read().unwrap(); - let remove_response = manager.remove_account(account.id()); + let remove_response = manager.remove_account(account_.id()); assert!(remove_response.is_err()); } } From e6ce170b9124f3b96f4f5ad139f490b51d2f90e1 Mon Sep 17 00:00:00 2001 From: Lucas Nogueira Date: Tue, 15 Dec 2020 18:42:55 -0300 Subject: [PATCH 05/29] fix(lib): tests --- examples/actor.rs | 13 +++- src/account/mod.rs | 89 +++++++++++++--------- src/account/sync/mod.rs | 2 + src/account_manager.rs | 158 +++++++++++++++++++++++++++++----------- src/actor/message.rs | 2 +- src/actor/mod.rs | 23 ++++-- src/client.rs | 2 +- src/lib.rs | 38 +++++++++- 8 files changed, 238 insertions(+), 89 deletions(-) diff --git a/examples/actor.rs b/examples/actor.rs index cd9b48620..62da7c181 100644 --- a/examples/actor.rs +++ b/examples/actor.rs @@ -1,7 +1,10 @@ // Copyright 2020 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -use iota_wallet::actor::{AccountToCreate, Message, MessageType, Response, ResponseType, WalletMessageHandler}; +use iota_wallet::{ + actor::{AccountToCreate, Message, MessageType, Response, ResponseType, WalletMessageHandler}, + client::ClientOptionsBuilder, +}; use tokio::sync::mpsc::{unbounded_channel, UnboundedReceiver, UnboundedSender}; /// The Wallet actor. @@ -45,7 +48,13 @@ async fn send_message(tx: &UnboundedSender, message_type: MessageType) async fn main() { let tx = spawn_actor(); - let account = AccountToCreate::default(); + let account = AccountToCreate { + client_options: ClientOptionsBuilder::node("http://node.iota").unwrap().build(), + mnemonic: None, + alias: None, + created_at: None, + }; + send_message(&tx, MessageType::SetStrongholdPassword("password".to_string())).await; let response = send_message(&tx, MessageType::CreateAccount(account)).await; match response.response() { diff --git a/src/account/mod.rs b/src/account/mod.rs index fd21d784a..fd9984b7f 100644 --- a/src/account/mod.rs +++ b/src/account/mod.rs @@ -2,6 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 use crate::{ + account_manager::AccountStore, address::{Address, IotaAddress}, client::ClientOptions, message::{Message, MessageType}, @@ -69,32 +70,36 @@ impl From for AccountIdentifier { } /// Account initialiser. -pub struct AccountInitialiser<'a> { +pub struct AccountInitialiser { + accounts: AccountStore, + storage_path: PathBuf, mnemonic: Option, alias: Option, created_at: Option>, messages: Vec, addresses: Vec
, client_options: ClientOptions, - storage_path: &'a PathBuf, signer_type: Option, + skip_persistance: bool, } -impl<'a> AccountInitialiser<'a> { +impl AccountInitialiser { /// Initialises the account builder. - pub(crate) fn new(client_options: ClientOptions, storage_path: &'a PathBuf) -> Self { + pub(crate) fn new(client_options: ClientOptions, accounts: AccountStore, storage_path: PathBuf) -> Self { Self { + accounts, + storage_path, mnemonic: None, alias: None, created_at: None, messages: vec![], addresses: vec![], client_options, - storage_path, #[cfg(feature = "stronghold")] signer_type: Some(SignerType::Stronghold), #[cfg(not(feature = "stronghold"))] signer_type: None, + skip_persistance: false, } } @@ -137,9 +142,15 @@ impl<'a> AccountInitialiser<'a> { self } + pub(crate) fn skip_persistance(mut self) -> Self { + self.skip_persistance = true; + self + } + /// Initialises the account. pub fn initialise(self) -> crate::Result { - let accounts = crate::storage::with_adapter(self.storage_path, |storage| storage.get_all())?; + let mut accounts = self.accounts.write().unwrap(); + let alias = self.alias.unwrap_or_else(|| format!("Account {}", accounts.len())); let signer_type = self .signer_type @@ -157,8 +168,8 @@ impl<'a> AccountInitialiser<'a> { } } - if let Some(latest_account) = accounts.last() { - let latest_account: Account = serde_json::from_str(&latest_account)?; + if let Some(latest_account) = accounts.values().last() { + let latest_account = latest_account.read().unwrap(); if latest_account.messages().is_empty() && latest_account.total_balance() == 0 { return Err(crate::WalletError::LatestAccountIsEmpty); } @@ -173,14 +184,23 @@ impl<'a> AccountInitialiser<'a> { messages: self.messages, addresses: self.addresses, client_options: self.client_options, - storage_path: self.storage_path.clone(), - has_pending_changes: false, + storage_path: self.storage_path, + has_pending_changes: true, }; let id = with_signer(&signer_type, |signer| signer.init_account(&account, mnemonic))?; account.set_id(id.into()); - Ok(account.into()) + let guard = if self.skip_persistance { + account.into() + } else { + let account_id = account.id().clone(); + let guard: AccountGuard = account.into(); + accounts.insert(account_id, guard.clone()); + guard + }; + + Ok(guard) } } @@ -299,21 +319,14 @@ impl Account { self.client_options = options; } - /// Saves the pending changes on the account. - /// This is automatically performed when the account goes out of scope. - pub fn save_pending_changes(&mut self) -> crate::Result<()> { + pub(crate) fn save(&mut self) -> crate::Result<()> { if self.has_pending_changes { - self.save()?; - self.has_pending_changes = false; + let storage_path = self.storage_path.clone(); + crate::storage::save_account(&storage_path, self)?; } Ok(()) } - pub(crate) fn save(&mut self) -> crate::Result<()> { - let storage_path = self.storage_path.clone(); - crate::storage::save_account(&storage_path, self) - } - /// Gets a list of transactions on this account. /// It's fetched from the storage. To ensure the database is updated with the latest transactions, /// `sync` should be called first. @@ -337,10 +350,11 @@ impl Account { /// let mut manager = AccountManager::new().unwrap(); /// # let mut manager = AccountManager::with_storage_path(storage_path).unwrap(); /// manager.set_stronghold_password("password").unwrap(); - /// let mut account = manager + /// let account = manager /// .create_account(client_options) /// .initialise() /// .expect("failed to add account"); + /// let account = account.read().unwrap(); /// account.list_messages(10, 5, Some(MessageType::Received)); /// ``` pub fn list_messages(&self, count: usize, from: usize, message_type: Option) -> Vec<&Message> { @@ -396,6 +410,8 @@ impl Account { let address = crate::address::get_new_address(&self)?; self.addresses.push(address.clone()); + self.has_pending_changes = true; + // ignore errors because we fallback to the polling system // TODO let _ = crate::monitor::monitor_address_balance(&self, address.address()); @@ -405,6 +421,7 @@ impl Account { #[doc(hidden)] pub fn append_messages(&mut self, messages: Vec) { self.messages.extend(messages); + self.has_pending_changes = true; } pub(crate) fn append_addresses(&mut self, addresses: Vec
) { @@ -418,15 +435,18 @@ impl Account { self.addresses.push(address); } }); + self.has_pending_changes = true; } #[doc(hidden)] pub fn addresses_mut(&mut self) -> &mut Vec
{ + self.has_pending_changes = true; &mut self.addresses } #[doc(hidden)] pub fn messages_mut(&mut self) -> &mut Vec { + self.has_pending_changes = true; &mut self.messages } @@ -438,7 +458,7 @@ impl Account { impl Drop for Account { fn drop(&mut self) { - let _ = self.save_pending_changes(); + let _ = self.save(); } } @@ -469,6 +489,7 @@ mod tests { #[test] fn set_alias() { let manager = crate::test_utils::get_account_manager(); + let mut manager = manager.lock().unwrap(); let updated_alias = "updated alias"; let client_options = ClientOptionsBuilder::node("https://nodes.devnet.iota.org:443") @@ -485,21 +506,19 @@ mod tests { account.set_alias(updated_alias); let id = account.id().clone(); - account.save().unwrap(); id }; - std::thread::spawn(move || { - std::thread::sleep(std::time::Duration::from_secs(3)); - let account_in_storage = manager - .get_account(&account_id) - .expect("failed to get account from storage"); - let account_in_storage = account_in_storage.read().unwrap(); - assert_eq!( - account_in_storage.alias().to_string(), - updated_alias.to_string() - ); - }); + manager.stop_background_sync().unwrap(); + + let account_in_storage = manager + .get_account(&account_id) + .expect("failed to get account from storage"); + let account_in_storage = account_in_storage.read().unwrap(); + assert_eq!( + account_in_storage.alias().to_string(), + updated_alias.to_string() + ); } } } diff --git a/src/account/sync/mod.rs b/src/account/sync/mod.rs index f5843ae3f..37260c79f 100644 --- a/src/account/sync/mod.rs +++ b/src/account/sync/mod.rs @@ -816,6 +816,8 @@ mod tests { fn account_sync() { crate::block_on(async move { let manager = crate::test_utils::get_account_manager(); + let manager = manager.lock().unwrap(); + let client_options = ClientOptionsBuilder::node("https://nodes.devnet.iota.org:443") .unwrap() .build(); diff --git a/src/account_manager.rs b/src/account_manager.rs index 6725f9ace..062e2d8c0 100644 --- a/src/account_manager.rs +++ b/src/account_manager.rs @@ -3,8 +3,8 @@ use crate::{ account::{ - account_id_to_stronghold_record_id, repost_message, Account, AccountGuard, AccountIdentifier, - AccountInitialiser, RepostAction, SyncedAccount, + account_id_to_stronghold_record_id, repost_message, AccountGuard, AccountIdentifier, AccountInitialiser, + RepostAction, SyncedAccount, }, client::ClientOptions, event::{emit_balance_change, emit_confirmation_state_change, emit_transaction_event, TransactionEventType}, @@ -19,8 +19,11 @@ use std::{ fs, panic::AssertUnwindSafe, path::{Path, PathBuf}, - sync::{Arc, RwLock}, - thread, + sync::{ + atomic::{AtomicBool, Ordering}, + Arc, RwLock, + }, + thread::{self, JoinHandle}, time::Duration, }; @@ -32,7 +35,7 @@ use stronghold::Stronghold; /// The default storage path. pub const DEFAULT_STORAGE_PATH: &str = "./example-database"; -type AccountStore = Arc>>; +pub(crate) type AccountStore = Arc>>; /// The account manager. /// @@ -47,6 +50,8 @@ pub struct AccountManager { polling_interval: Duration, started_monitoring: bool, accounts: AccountStore, + stop_polling: Arc, + polling_handle: Option>, } /// Internal transfer response metadata. @@ -59,6 +64,12 @@ pub struct InternalTransferMetadata { pub to_account: AccountGuard, } +impl Drop for AccountManager { + fn drop(&mut self) { + let _ = self.stop_background_sync(); + } +} + impl AccountManager { /// Initialises a new instance of the account manager with the default storage adapter. pub fn new() -> crate::Result { @@ -82,6 +93,8 @@ impl AccountManager { polling_interval: Duration::from_millis(30_000), started_monitoring: false, accounts: Default::default(), + stop_polling: Arc::new(AtomicBool::new(false)), + polling_handle: None, }; Ok(instance) } @@ -116,11 +129,28 @@ impl AccountManager { pub fn start_background_sync(&mut self) { if !self.started_monitoring { let monitoring_disabled = self.start_monitoring().is_err(); - self.start_polling(monitoring_disabled); + self.polling_handle = Some(self.start_polling(monitoring_disabled)); self.started_monitoring = true; } } + /// Stops the background polling and MQTT monitoring. + pub fn stop_background_sync(&mut self) -> crate::Result<()> { + { + let accounts = self.accounts.read().unwrap(); + for account in accounts.values() { + let _ = crate::monitor::unsubscribe(account.clone()); + } + } + + if let Some(handle) = self.polling_handle.take() { + self.stop_polling.store(true, Ordering::Relaxed); + handle.join().expect("failed to stop polling thread"); + } + + Ok(()) + } + /// Sets the stronghold password. pub fn set_stronghold_password>(&mut self, password: P) -> crate::Result<()> { let stronghold_path = self.storage_path.join(crate::storage::stronghold_snapshot_filename()); @@ -138,16 +168,18 @@ impl AccountManager { } /// Starts the polling mechanism. - fn start_polling(&self, is_monitoring_disabled: bool) -> thread::JoinHandle<()> { + fn start_polling(&self, is_monitoring_disabled: bool) -> JoinHandle<()> { let storage_path = self.storage_path.clone(); let interval = self.polling_interval; let accounts = self.accounts.clone(); + let stop = self.stop_polling.clone(); thread::spawn(move || { - loop { + let sleep_duration = interval / 2; + while !stop.load(Ordering::Relaxed) { let storage_path_ = storage_path.clone(); - let accounts = accounts.clone(); + let accounts_ = accounts.clone(); crate::block_on(async move { - if let Err(panic) = AssertUnwindSafe(poll(accounts, storage_path_, is_monitoring_disabled)) + if let Err(panic) = AssertUnwindSafe(poll(accounts_, storage_path_, is_monitoring_disabled)) .catch_unwind() .await { @@ -162,24 +194,48 @@ impl AccountManager { // when the error is dropped, the on_error event will be triggered } }); - thread::sleep(interval); + + thread::sleep(sleep_duration); + + let accounts_ = accounts.read().unwrap(); + for account in accounts_.values() { + let mut account = account.write().unwrap(); + let _ = account.save(); + } + + thread::sleep(sleep_duration); } }) } /// Adds a new account. - pub fn create_account(&self, client_options: ClientOptions) -> AccountInitialiser<'_> { - AccountInitialiser::new(client_options, &self.storage_path) + pub fn create_account(&self, client_options: ClientOptions) -> AccountInitialiser { + AccountInitialiser::new(client_options, self.accounts.clone(), self.storage_path.clone()) } /// Deletes an account. pub fn remove_account(&self, account_id: &AccountIdentifier) -> crate::Result<()> { - let account_str = crate::storage::with_adapter(&self.storage_path, |storage| storage.get(&account_id))?; - let account: Account = serde_json::from_str(&account_str)?; - if !(account.messages().is_empty() && account.total_balance() == 0) { - return Err(crate::WalletError::MessageNotEmpty); + let mut accounts = self.accounts.write().unwrap(); + + { + let account = accounts.get(&account_id).ok_or(crate::WalletError::AccountNotFound)?; + let account = account.read().unwrap(); + + if !(account.messages().is_empty() && account.total_balance() == 0) { + return Err(crate::WalletError::MessageNotEmpty); + } } - crate::storage::with_adapter(&self.storage_path, |storage| storage.remove(&account_id))?; + + accounts.remove(account_id); + + if let Err(e) = crate::storage::with_adapter(&self.storage_path, |storage| storage.remove(&account_id)) { + match e { + // if we got an "AccountNotFound" error, that means we didn't save the cached account yet + crate::WalletError::AccountNotFound => {} + _ => return Err(e), + } + } + Ok(()) } @@ -336,16 +392,17 @@ async fn poll(accounts: AccountStore, storage_path: PathBuf, syncing: bool) -> c for account in accounts.read().unwrap().values() { accounts_before_sync.push(account.read().unwrap().clone()); } - let synced_accounts = sync_accounts(accounts, &storage_path, Some(0)).await?; - let accounts_after_sync = crate::storage::with_adapter(&storage_path, |storage| storage.get_all())?; - let mut accounts_after_sync = crate::storage::parse_accounts(&storage_path, &accounts_after_sync)?; + let synced_accounts = sync_accounts(accounts.clone(), &storage_path, Some(0)).await?; + let accounts_after_sync = accounts.read().unwrap(); // compare accounts to check for balance changes and new messages for account_before_sync in &accounts_before_sync { let account_after_sync = accounts_after_sync - .iter_mut() - .find(|account| account.id() == account_before_sync.id()) - .unwrap(); + .iter() + .find(|(id, _)| id == &account_before_sync.id()) + .unwrap() + .1; + let account_after_sync = account_after_sync.read().unwrap(); // balance event for address_before_sync in account_before_sync.addresses() { @@ -383,7 +440,7 @@ async fn poll(accounts: AccountStore, storage_path: PathBuf, syncing: bool) -> c } } } - retry_unconfirmed_transactions(synced_accounts.iter().zip(accounts_after_sync.iter()).collect()).await? + retry_unconfirmed_transactions(synced_accounts).await? } else { let mut retried_messages = vec![]; for account in accounts.read().unwrap().values() { @@ -428,13 +485,15 @@ async fn poll(accounts: AccountStore, storage_path: PathBuf, syncing: bool) -> c } async fn discover_accounts( + accounts: AccountStore, storage_path: &PathBuf, client_options: &ClientOptions, signer_type: Option, ) -> crate::Result> { let mut synced_accounts = vec![]; loop { - let mut account_initialiser = AccountInitialiser::new(client_options.clone(), &storage_path); + let mut account_initialiser = + AccountInitialiser::new(client_options.clone(), accounts.clone(), storage_path.clone()).skip_persistance(); if let Some(signer_type) = &signer_type { account_initialiser = account_initialiser.signer_type(signer_type.clone()); } @@ -480,12 +539,12 @@ async fn sync_accounts<'a>( let discovered_accounts_res = match last_account { Some((is_empty, client_options, signer_type)) => { if is_empty { - discover_accounts(&storage_path, &client_options, Some(signer_type)).await + discover_accounts(accounts.clone(), &storage_path, &client_options, Some(signer_type)).await } else { Ok(vec![]) } } - None => discover_accounts(&storage_path, &ClientOptions::default(), None).await, + None => Ok(vec![]), // None => discover_accounts(accounts.clone(), &storage_path, &ClientOptions::default(), None).await, }; if let Ok(discovered_accounts) = discovered_accounts_res { @@ -509,9 +568,11 @@ struct RetriedData { account_id: AccountIdentifier, } -async fn retry_unconfirmed_transactions(accounts: Vec<(&SyncedAccount, &Account)>) -> crate::Result> { +async fn retry_unconfirmed_transactions(synced_accounts: Vec) -> crate::Result> { let mut retried_messages = vec![]; - for (synced, account) in accounts { + for synced in synced_accounts { + let account = synced.account().read().unwrap(); + let unconfirmed_messages = account.list_messages(account.messages().len(), 0, Some(MessageType::Unconfirmed)); let mut reattachments = vec![]; let mut promotions = vec![]; @@ -574,13 +635,14 @@ mod tests { client::ClientOptionsBuilder, message::Message, }; - use iota::message::prelude::{Ed25519Address, Indexation, Message as IotaMessage, MessageId, Payload}; + use iota::{Ed25519Address, Indexation, MessageBuilder, MessageId, Payload}; use rusty_fork::rusty_fork_test; rusty_fork_test! { #[test] fn store_accounts() { let manager = crate::test_utils::get_account_manager(); + let mut manager = manager.lock().unwrap(); let client_options = ClientOptionsBuilder::node("https://nodes.devnet.iota.org:443") .expect("invalid node URL") @@ -593,6 +655,8 @@ mod tests { .expect("failed to add account"); let account_ = account.read().unwrap(); + manager.stop_background_sync().unwrap(); + manager .remove_account(account_.id()) .expect("failed to remove account"); @@ -603,26 +667,36 @@ mod tests { #[test] fn remove_account_with_message_history() { let manager = crate::test_utils::get_account_manager(); + let manager = manager.lock().unwrap(); let client_options = ClientOptionsBuilder::node("https://nodes.devnet.iota.org:443") .expect("invalid node URL") .build(); - let account = manager - .create_account(client_options) - .messages(vec![Message::from_iota_message(MessageId::new([0; 32]), &[], &IotaMessage::builder() + let messages = vec![Message::from_iota_message( + MessageId::new([0; 32]), + &[], + &MessageBuilder::::new() .with_parent1(MessageId::new([0; 32])) .with_parent2(MessageId::new([0; 32])) - .with_payload(Payload::Indexation(Box::new(Indexation::new( - "index".to_string(), - &[0; 16], - ).unwrap()))) + .with_payload(Payload::Indexation(Box::new( + Indexation::new("index".to_string(), &[0; 16]).unwrap(), + ))) .with_network_id(0) + .with_nonce_provider(crate::test_utils::NoopNonceProvider {}, 0f64) .finish() - .unwrap(), None).unwrap()]) - .initialise().unwrap(); - let account_ = account.read().unwrap(); + .unwrap(), + None, + ) + .unwrap()]; + let account = manager + .create_account(client_options) + .messages(messages) + .initialise() + .unwrap(); + + let account_ = account.read().unwrap(); let remove_response = manager.remove_account(account_.id()); assert!(remove_response.is_err()); } @@ -632,6 +706,7 @@ mod tests { #[test] fn remove_account_with_balance() { let manager = crate::test_utils::get_account_manager(); + let manager = manager.lock().unwrap(); let client_options = ClientOptionsBuilder::node("https://nodes.devnet.iota.org:443") .expect("invalid node URL") @@ -659,6 +734,7 @@ mod tests { #[test] fn create_account_with_latest_without_history() { let manager = crate::test_utils::get_account_manager(); + let manager = manager.lock().unwrap(); let client_options = ClientOptionsBuilder::node("https://nodes.devnet.iota.org:443") .expect("invalid node URL") diff --git a/src/actor/message.rs b/src/actor/message.rs index ca0e189b6..0defe2c06 100644 --- a/src/actor/message.rs +++ b/src/actor/message.rs @@ -12,7 +12,7 @@ use serde::{ser::Serializer, Deserialize, Serialize}; use tokio::sync::mpsc::UnboundedSender; /// An account to create. -#[derive(Clone, Debug, Deserialize, Default)] +#[derive(Clone, Debug, Deserialize)] pub struct AccountToCreate { /// The node options. #[serde(rename = "clientOptions")] diff --git a/src/actor/mod.rs b/src/actor/mod.rs index 714c8ad19..6da023dd2 100644 --- a/src/actor/mod.rs +++ b/src/actor/mod.rs @@ -291,6 +291,7 @@ impl WalletMessageHandler { #[cfg(test)] mod tests { use super::{AccountToCreate, Message, MessageType, Response, ResponseType, WalletMessageHandler}; + use crate::client::ClientOptionsBuilder; use tokio::sync::mpsc::{unbounded_channel, UnboundedReceiver, UnboundedSender}; /// The wallet actor builder. @@ -322,7 +323,9 @@ mod tests { pub fn build(self) -> Wallet { Wallet { rx: self.rx.expect("rx is required"), - message_handler: WalletMessageHandler::new().expect("failed to initialise account manager"), + message_handler: self + .message_handler + .unwrap_or_else(|| WalletMessageHandler::new().expect("failed to initialise account manager")), } } } @@ -366,14 +369,24 @@ mod tests { let tx = spawn_actor(); // create an account - let account = AccountToCreate::default(); + let account = AccountToCreate { + client_options: ClientOptionsBuilder::node("http://node.iota").unwrap().build(), + mnemonic: None, + alias: None, + created_at: None, + }; send_message(&tx, MessageType::SetStrongholdPassword("password".to_string())).await; let response = send_message(&tx, MessageType::CreateAccount(account)).await; match response.response() { ResponseType::CreatedAccount(created_account) => { - // remove the created account - let response = send_message(&tx, MessageType::RemoveAccount(created_account.id().clone())).await; - assert!(matches!(response.response(), ResponseType::RemovedAccount(_))); + let id = created_account.id().clone(); + std::thread::spawn(move || { + std::thread::sleep(std::time::Duration::from_secs(6)); + // remove the created account + let response = + crate::block_on(async move { send_message(&tx, MessageType::RemoveAccount(id)).await }); + assert!(matches!(response.response(), ResponseType::RemovedAccount(_))); + }); } _ => panic!("unexpected response"), } diff --git a/src/client.rs b/src/client.rs index 6a3a54dee..ac414a570 100644 --- a/src/client.rs +++ b/src/client.rs @@ -243,7 +243,7 @@ impl ClientOptionsBuilder { } /// The client options type. -#[derive(Default, Serialize, Deserialize, Clone, Debug, PartialEq, Eq, Hash, Getters)] +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, Hash, Getters)] #[getset(get = "pub(crate)")] pub struct ClientOptions { node: Option, diff --git a/src/lib.rs b/src/lib.rs index eba136956..07dcf2ac7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -161,19 +161,49 @@ pub(crate) fn block_on(cb: C) -> C::Output { #[cfg(test)] mod test_utils { use super::account_manager::AccountManager; + use iota::pow::providers::{Provider as PowProvider, ProviderBuilder as PowProviderBuilder}; use once_cell::sync::OnceCell; use rand::{thread_rng, Rng}; - use std::path::PathBuf; + use std::{path::PathBuf, sync::Mutex, time::Duration}; - static MANAGER_INSTANCE: OnceCell = OnceCell::new(); - pub fn get_account_manager() -> &'static AccountManager { + static MANAGER_INSTANCE: OnceCell> = OnceCell::new(); + pub fn get_account_manager() -> &'static Mutex { MANAGER_INSTANCE.get_or_init(|| { let storage_path: String = thread_rng().gen_ascii_chars().take(10).collect(); let storage_path = PathBuf::from(format!("./example-database/{}", storage_path)); let mut manager = AccountManager::with_storage_path(storage_path).unwrap(); + manager.set_polling_interval(Duration::from_secs(4)); manager.set_stronghold_password("password").unwrap(); - manager + Mutex::new(manager) }) } + + /// The miner builder. + #[derive(Default)] + pub struct NoopNonceProviderBuilder; + + impl PowProviderBuilder for NoopNonceProviderBuilder { + type Provider = NoopNonceProvider; + + fn new() -> Self { + Self::default() + } + + fn finish(self) -> NoopNonceProvider { + NoopNonceProvider {} + } + } + + /// The miner used for PoW + pub struct NoopNonceProvider; + + impl PowProvider for NoopNonceProvider { + type Builder = NoopNonceProviderBuilder; + type Error = crate::WalletError; + + fn nonce(&self, bytes: &[u8], target_score: f64) -> std::result::Result { + Ok(0) + } + } } From ab1fb18d054e2742e85ecdf8ace468cda0786013 Mon Sep 17 00:00:00 2001 From: Lucas Nogueira Date: Tue, 15 Dec 2020 22:28:58 -0300 Subject: [PATCH 06/29] chore(transfer): return message instead of wrapper --- src/account/mod.rs | 2 +- src/account/sync/mod.rs | 16 ++-------------- src/account_manager.rs | 20 +++----------------- src/actor/mod.rs | 5 ++--- 4 files changed, 8 insertions(+), 35 deletions(-) diff --git a/src/account/mod.rs b/src/account/mod.rs index fd9984b7f..595dc1ea3 100644 --- a/src/account/mod.rs +++ b/src/account/mod.rs @@ -25,7 +25,7 @@ use std::{ mod sync; pub(crate) use sync::{repost_message, RepostAction}; -pub use sync::{AccountSynchronizer, SyncedAccount, TransferMetadata}; +pub use sync::{AccountSynchronizer, SyncedAccount}; type AddressesLock = Arc>>; type AccountAddressesLock = Arc>>; diff --git a/src/account/sync/mod.rs b/src/account/sync/mod.rs index 37260c79f..777ec080b 100644 --- a/src/account/sync/mod.rs +++ b/src/account/sync/mod.rs @@ -401,15 +401,6 @@ pub struct SyncedAccount { addresses: Vec
, } -/// Transfer response metadata. -#[derive(Debug)] -pub struct TransferMetadata { - /// The transfer message. - pub message: Message, - /// The transfer source account with new message and addresses attached. - pub account: AccountGuard, -} - impl SyncedAccount { /// Selects input addresses for a value transaction. /// The method ensures that the recipient address doesn’t match any of the selected inputs or the remainder address. @@ -460,7 +451,7 @@ impl SyncedAccount { } /// Send messages. - pub async fn transfer(&self, transfer_obj: Transfer) -> crate::Result { + pub async fn transfer(&self, transfer_obj: Transfer) -> crate::Result { // validate the transfer if transfer_obj.amount == 0 { return Err(crate::WalletError::ZeroAmount); @@ -717,10 +708,7 @@ impl SyncedAccount { // ignore errors because we fallback to the polling system let _ = crate::monitor::monitor_confirmation_state_change(self.account.clone(), &message_id); - Ok(TransferMetadata { - message, - account: self.account.clone(), - }) + Ok(message) } /// Retry message. diff --git a/src/account_manager.rs b/src/account_manager.rs index 062e2d8c0..e285ccff8 100644 --- a/src/account_manager.rs +++ b/src/account_manager.rs @@ -54,16 +54,6 @@ pub struct AccountManager { polling_handle: Option>, } -/// Internal transfer response metadata. -pub struct InternalTransferMetadata { - /// Transfer message. - pub message: Message, - /// Source account with new message and addresses attached. - pub from_account: AccountGuard, - /// Destination account with new message attached. - pub to_account: AccountGuard, -} - impl Drop for AccountManager { fn drop(&mut self) { let _ = self.stop_background_sync(); @@ -250,7 +240,7 @@ impl AccountManager { from_account_id: &AccountIdentifier, to_account_id: &AccountIdentifier, amount: u64, - ) -> crate::Result { + ) -> crate::Result { let from_account_guard = self.get_account(from_account_id)?; let to_account_guard = self.get_account(to_account_id)?; @@ -263,15 +253,11 @@ impl AccountManager { }; let from_synchronized = from_account_guard.sync().execute().await?; - let metadata = from_synchronized + let message = from_synchronized .transfer(Transfer::new(to_address.address().clone(), amount)) .await?; - Ok(InternalTransferMetadata { - to_account: to_account_guard.clone(), - from_account: metadata.account, - message: metadata.message, - }) + Ok(message) } /// Backups the accounts to the given destination diff --git a/src/actor/mod.rs b/src/actor/mod.rs index 6da023dd2..280b21aac 100644 --- a/src/actor/mod.rs +++ b/src/actor/mod.rs @@ -269,7 +269,7 @@ impl WalletMessageHandler { async fn send_transfer(&self, account_id: &AccountIdentifier, transfer: &Transfer) -> Result { let account = self.account_manager.get_account(account_id)?; let synced = account.sync().execute().await?; - let message = synced.transfer(transfer.clone()).await?.message; + let message = synced.transfer(transfer.clone()).await?; Ok(ResponseType::SentTransfer(message)) } @@ -282,8 +282,7 @@ impl WalletMessageHandler { let message = self .account_manager .internal_transfer(from_account_id, to_account_id, amount) - .await? - .message; + .await?; Ok(ResponseType::SentTransfer(message)) } } From ca93f151eba996cf11ec929189b030be70aa7d9a Mon Sep 17 00:00:00 2001 From: Lucas Nogueira Date: Tue, 15 Dec 2020 23:01:11 -0300 Subject: [PATCH 07/29] refactor(lib): update nodejs binding with latest account refactor --- .../node/native/src/classes/account/mod.rs | 15 +-- .../node/native/src/classes/account/sync.rs | 17 +-- .../account_manager/internal_transfer.rs | 21 +-- .../native/src/classes/account_manager/mod.rs | 29 +++-- .../src/classes/account_manager/sync.rs | 8 +- .../native/src/classes/synced_account/mod.rs | 40 +++--- .../src/classes/synced_account/repost.rs | 14 +- .../native/src/classes/synced_account/send.rs | 14 +- bindings/node/native/src/lib.rs | 123 +++++++----------- src/monitor.rs | 3 +- 10 files changed, 111 insertions(+), 173 deletions(-) diff --git a/bindings/node/native/src/classes/account/mod.rs b/bindings/node/native/src/classes/account/mod.rs index 3c078648f..82d03d01c 100644 --- a/bindings/node/native/src/classes/account/mod.rs +++ b/bindings/node/native/src/classes/account/mod.rs @@ -3,15 +3,12 @@ use std::str::FromStr; -use iota_wallet::{ - account::{Account, AccountIdentifier}, - message::MessageId, -}; +use iota_wallet::{account::AccountIdentifier, message::MessageId}; use neon::prelude::*; mod sync; -pub struct AccountWrapper(pub String); +pub struct AccountWrapper(pub AccountIdentifier); impl Drop for AccountWrapper { fn drop(&mut self) { @@ -22,10 +19,8 @@ impl Drop for AccountWrapper { declare_types! { pub class JsAccount for AccountWrapper { init(mut cx) { - let account = cx.argument::(0)?.value(); - let account: Account = serde_json::from_str(&account).expect("invalid account JSON"); - let id = crate::store_account(account); - Ok(AccountWrapper(id)) + let account_id = cx.argument::(0)?.value(); + Ok(AccountWrapper(serde_json::from_str(&account_id).expect("invalid account identifier"))) } method id(mut cx) { @@ -156,7 +151,6 @@ declare_types! { let account = crate::get_account(id); let mut account = account.write().unwrap(); account.set_alias(alias); - account.save_pending_changes().expect("failed to save account"); } Ok(cx.undefined().upcast()) } @@ -171,7 +165,6 @@ declare_types! { let account = crate::get_account(id); let mut account = account.write().unwrap(); account.set_client_options(client_options); - account.save_pending_changes().expect("failed to save account"); } Ok(cx.undefined().upcast()) } diff --git a/bindings/node/native/src/classes/account/sync.rs b/bindings/node/native/src/classes/account/sync.rs index 98cbc6184..263d5aab1 100644 --- a/bindings/node/native/src/classes/account/sync.rs +++ b/bindings/node/native/src/classes/account/sync.rs @@ -1,7 +1,10 @@ // Copyright 2020 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -use iota_wallet::{account::SyncedAccount, WalletError}; +use iota_wallet::{ + account::{AccountIdentifier, SyncedAccount}, + WalletError, +}; use neon::prelude::*; use serde::Deserialize; @@ -16,7 +19,7 @@ pub struct SyncOptions { } pub struct SyncTask { - pub account_id: String, + pub account_id: AccountIdentifier, pub options: SyncOptions, } @@ -27,8 +30,7 @@ impl Task for SyncTask { fn perform(&self) -> Result { let account = crate::get_account(&self.account_id); - let mut acc = account.write().unwrap(); - let mut synchronizer = acc.sync(); + let mut synchronizer = account.sync(); if let Some(address_index) = self.options.address_index { synchronizer = synchronizer.address_index(address_index); } @@ -46,10 +48,9 @@ impl Task for SyncTask { fn complete(self, mut cx: TaskContext, value: Result) -> JsResult { match value { Ok(val) => { - let synced = serde_json::to_string(&val).unwrap(); - let synced = cx.string(synced); - let account_id = cx.string(&self.account_id); - Ok(crate::JsSyncedAccount::new(&mut cx, vec![synced, account_id])?.upcast()) + let id = crate::store_synced_account(val); + let id = cx.string(id); + Ok(crate::JsSyncedAccount::new(&mut cx, vec![id])?.upcast()) } Err(e) => cx.throw_error(e.to_string()), } diff --git a/bindings/node/native/src/classes/account_manager/internal_transfer.rs b/bindings/node/native/src/classes/account_manager/internal_transfer.rs index 44c98a5f1..f73461ad6 100644 --- a/bindings/node/native/src/classes/account_manager/internal_transfer.rs +++ b/bindings/node/native/src/classes/account_manager/internal_transfer.rs @@ -3,13 +3,13 @@ use std::sync::{Arc, RwLock}; -use iota_wallet::{account_manager::AccountManager, message::Message, WalletError}; +use iota_wallet::{account::AccountIdentifier, account_manager::AccountManager, message::Message, WalletError}; use neon::prelude::*; pub struct InternalTransferTask { pub manager: Arc>, - pub from_account_id: String, - pub to_account_id: String, + pub from_account_id: AccountIdentifier, + pub to_account_id: AccountIdentifier, pub amount: u64, } @@ -21,18 +21,9 @@ impl Task for InternalTransferTask { fn perform(&self) -> Result { let manager = self.manager.read().unwrap(); crate::block_on(crate::convert_async_panics(|| async { - let from_account = crate::get_account(&self.from_account_id); - let from_account = from_account.read().unwrap(); - let to_account = crate::get_account(&self.to_account_id); - let to_account = to_account.read().unwrap(); - let res = manager - .internal_transfer(from_account.id(), to_account.id(), self.amount) - .await?; - - crate::update_account(&self.from_account_id, res.from_account); - crate::update_account(&self.to_account_id, res.to_account); - - Ok(res.message) + manager + .internal_transfer(&self.from_account_id, &self.to_account_id, self.amount) + .await })) } diff --git a/bindings/node/native/src/classes/account_manager/mod.rs b/bindings/node/native/src/classes/account_manager/mod.rs index 368b9468b..132ab80e8 100644 --- a/bindings/node/native/src/classes/account_manager/mod.rs +++ b/bindings/node/native/src/classes/account_manager/mod.rs @@ -161,10 +161,11 @@ declare_types! { } builder.initialise().expect("error creating account") }; - let account = serde_json::to_string(&account).unwrap(); - let account = cx.string(account); - Ok(JsAccount::new(&mut cx, vec![account])?.upcast()) + let id = crate::store_account(account); + let id = cx.string(serde_json::to_string(&id).unwrap()); + + Ok(JsAccount::new(&mut cx, vec![id])?.upcast()) } method getAccount(mut cx) { @@ -179,9 +180,9 @@ declare_types! { }; match account { Ok(acc) => { - let account = serde_json::to_string(&acc).unwrap(); - let account = cx.string(account); - Ok(JsAccount::new(&mut cx, vec![account])?.upcast()) + let id = crate::store_account(acc); + let id = cx.string(serde_json::to_string(&id).unwrap()); + Ok(JsAccount::new(&mut cx, vec![id])?.upcast()) }, Err(_) => Ok(cx.undefined().upcast()) } @@ -198,9 +199,9 @@ declare_types! { }; match account { Some(acc) => { - let account = serde_json::to_string(&acc).unwrap(); - let account = cx.string(account); - Ok(JsAccount::new(&mut cx, vec![account])?.upcast()) + let id = crate::store_account(acc); + let id = cx.string(serde_json::to_string(&id).unwrap()); + Ok(JsAccount::new(&mut cx, vec![id])?.upcast()) }, None => Ok(cx.undefined().upcast()) } @@ -212,14 +213,14 @@ declare_types! { let guard = cx.lock(); let ref_ = &this.borrow(&guard).0; let manager = ref_.read().unwrap(); - manager.get_accounts().expect("failed to get accounts") + manager.get_accounts() }; let js_array = JsArray::new(&mut cx, accounts.len() as u32); - for (index, account) in accounts.iter().enumerate() { - let account = serde_json::to_string(&account).unwrap(); - let account = cx.string(account); - let js_account = JsAccount::new(&mut cx, vec![account])?; + for (index, account) in accounts.into_iter().enumerate() { + let id = crate::store_account(account); + let id = cx.string(serde_json::to_string(&id).unwrap()); + let js_account = JsAccount::new(&mut cx, vec![id])?; js_array.set(&mut cx, index as u32, js_account)?; } diff --git a/bindings/node/native/src/classes/account_manager/sync.rs b/bindings/node/native/src/classes/account_manager/sync.rs index b97909df6..e50f5a003 100644 --- a/bindings/node/native/src/classes/account_manager/sync.rs +++ b/bindings/node/native/src/classes/account_manager/sync.rs @@ -24,10 +24,10 @@ impl Task for SyncTask { match value { Ok(synced_accounts) => { let js_array = JsArray::new(&mut cx, synced_accounts.len() as u32); - for (index, synced_account) in synced_accounts.iter().enumerate() { - let synced = serde_json::to_string(&synced_account).unwrap(); - let synced = cx.string(synced); - let synced_instance = crate::JsSyncedAccount::new(&mut cx, vec![synced])?; + for (index, synced_account) in synced_accounts.into_iter().enumerate() { + let id = crate::store_synced_account(synced_account); + let id = cx.string(id); + let synced_instance = crate::JsSyncedAccount::new(&mut cx, vec![id])?; js_array.set(&mut cx, index as u32, synced_instance)?; } diff --git a/bindings/node/native/src/classes/synced_account/mod.rs b/bindings/node/native/src/classes/synced_account/mod.rs index e93efbf78..75ddd768c 100644 --- a/bindings/node/native/src/classes/synced_account/mod.rs +++ b/bindings/node/native/src/classes/synced_account/mod.rs @@ -1,13 +1,9 @@ // Copyright 2020 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -use std::{ - str::FromStr, - sync::{Arc, RwLock}, -}; +use std::str::FromStr; use iota_wallet::{ - account::SyncedAccount, address::parse as parse_address, message::{MessageId, RemainderValueStrategy, Transfer}, }; @@ -17,15 +13,19 @@ mod repost; mod send; #[derive(Clone)] -pub struct SyncedAccountWrapper(Arc>, String); +pub struct SyncedAccountWrapper(pub String); + +impl Drop for SyncedAccountWrapper { + fn drop(&mut self) { + crate::remove_synced_account(&self.0); + } +} declare_types! { pub class JsSyncedAccount for SyncedAccountWrapper { init(mut cx) { - let synced = cx.argument::(0)?.value(); - let account_id = cx.argument::(1)?.value(); - let synced: SyncedAccount = serde_json::from_str(&synced).expect("invalid synced account JSON"); - Ok(SyncedAccountWrapper(Arc::new(RwLock::new(synced)), account_id)) + let synced_account_id = cx.argument::(0)?.value(); + Ok(SyncedAccountWrapper(synced_account_id)) } method send(mut cx) { @@ -45,10 +45,9 @@ declare_types! { .remainder_value_strategy(remainder_value_strategy); let this = cx.this(); - let instance = cx.borrow(&this, |r| r.clone()); + let synced_account_id = cx.borrow(&this, |r| r.0.clone()); let task = send::SendTask { - synced: instance.0, - account_id: instance.1, + synced_account_id, transfer, }; task.schedule(cb); @@ -60,10 +59,9 @@ declare_types! { let cb = cx.argument::(1)?; let this = cx.this(); - let instance = cx.borrow(&this, |r| r.clone()); + let synced_account_id = cx.borrow(&this, |r| r.0.clone()); let task = repost::RepostTask { - synced: instance.0, - account_id: instance.1, + synced_account_id, message_id, action: repost::RepostAction::Retry, }; @@ -76,10 +74,9 @@ declare_types! { let cb = cx.argument::(1)?; let this = cx.this(); - let instance = cx.borrow(&this, |r| r.clone()); + let synced_account_id = cx.borrow(&this, |r| r.0.clone()); let task = repost::RepostTask { - synced: instance.0, - account_id: instance.1, + synced_account_id, message_id, action: repost::RepostAction::Reattach, }; @@ -92,10 +89,9 @@ declare_types! { let cb = cx.argument::(1)?; let this = cx.this(); - let instance = cx.borrow(&this, |r| r.clone()); + let synced_account_id = cx.borrow(&this, |r| r.0.clone()); let task = repost::RepostTask { - synced: instance.0, - account_id: instance.1, + synced_account_id, message_id, action: repost::RepostAction::Promote, }; diff --git a/bindings/node/native/src/classes/synced_account/repost.rs b/bindings/node/native/src/classes/synced_account/repost.rs index f410ecb70..79c48f8f4 100644 --- a/bindings/node/native/src/classes/synced_account/repost.rs +++ b/bindings/node/native/src/classes/synced_account/repost.rs @@ -1,10 +1,7 @@ // Copyright 2020 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -use std::sync::{Arc, RwLock}; - use iota_wallet::{ - account::SyncedAccount, message::{Message, MessageId}, WalletError, }; @@ -17,8 +14,7 @@ pub enum RepostAction { } pub struct RepostTask { - pub synced: Arc>, - pub account_id: String, + pub synced_account_id: String, pub message_id: MessageId, pub action: RepostAction, } @@ -29,7 +25,9 @@ impl Task for RepostTask { type JsEvent = JsValue; fn perform(&self) -> Result { - let synced = self.synced.read().unwrap(); + let synced = crate::get_synced_account(&self.synced_account_id); + let synced = synced.read().unwrap(); + crate::block_on(crate::convert_async_panics(|| async { let message = match self.action { RepostAction::Retry => synced.retry(&self.message_id).await?, @@ -37,10 +35,6 @@ impl Task for RepostTask { RepostAction::Promote => synced.promote(&self.message_id).await?, }; - let account = crate::get_account(&self.account_id); - let mut account = account.write().unwrap(); - account.append_messages(vec![message.clone()]); - Ok(message) })) } diff --git a/bindings/node/native/src/classes/synced_account/send.rs b/bindings/node/native/src/classes/synced_account/send.rs index 5dfc2f1d0..10f0947fe 100644 --- a/bindings/node/native/src/classes/synced_account/send.rs +++ b/bindings/node/native/src/classes/synced_account/send.rs @@ -1,18 +1,14 @@ // Copyright 2020 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -use std::sync::{Arc, RwLock}; - use iota_wallet::{ - account::SyncedAccount, message::{Message, Transfer}, WalletError, }; use neon::prelude::*; pub struct SendTask { - pub synced: Arc>, - pub account_id: String, + pub synced_account_id: String, pub transfer: Transfer, } @@ -22,11 +18,11 @@ impl Task for SendTask { type JsEvent = JsValue; fn perform(&self) -> Result { - let synced = self.synced.read().unwrap(); + let synced = crate::get_synced_account(&self.synced_account_id); + let synced = synced.read().unwrap(); + crate::block_on(crate::convert_async_panics(|| async { - let res = synced.transfer(self.transfer.clone()).await?; - crate::update_account(&self.account_id, res.account); - Ok(res.message) + synced.transfer(self.transfer.clone()).await })) } diff --git a/bindings/node/native/src/lib.rs b/bindings/node/native/src/lib.rs index 59b340508..43b1778d3 100644 --- a/bindings/node/native/src/lib.rs +++ b/bindings/node/native/src/lib.rs @@ -6,12 +6,11 @@ use std::{ collections::HashMap, panic::AssertUnwindSafe, sync::{Arc, Mutex, RwLock}, - thread, }; use futures::{Future, FutureExt}; use iota_wallet::{ - account::{Account, AccountIdentifier}, + account::{AccountGuard, AccountIdentifier, SyncedAccount}, WalletError, }; use neon::prelude::*; @@ -22,102 +21,68 @@ use tokio::runtime::Runtime; mod classes; use classes::*; -type AccountInstanceMap = Arc>>>>; - -/// check if the account instance is loaded on the JS side (AccountInstanceMap) and update it by running a callback -fn mutate_account_if_exists(account_id: &AccountIdentifier, cb: F) { - let account_id = account_id.clone(); - thread::spawn(move || { - let map = instances() - .read() - .expect("failed to lock read on account instances: mutate_account_if_exists()"); - - for account in map.values() { - let account_ = account.read().unwrap(); - if account_.id() == &account_id { - std::mem::drop(account_); - let mut account = account.write().unwrap(); - cb(&mut account); - break; - } - } - }); -} +type AccountInstanceMap = Arc>>; +type SyncedAccountGuard = Arc>; +type SyncedAccountInstanceMap = Arc>>; /// Gets the account instances map. -fn instances() -> &'static AccountInstanceMap { - static INSTANCES: Lazy = Lazy::new(|| { - iota_wallet::event::on_balance_change(|event| { - let address = event.cloned_address(); - let balance = *event.balance(); - mutate_account_if_exists(event.account_id(), move |account| { - let addresses = account.addresses_mut(); - if let Some(address) = addresses.iter_mut().find(|a| a == &&address) { - address.set_balance(balance); - } - }); - }); - iota_wallet::event::on_new_transaction(|event| { - let message = event.cloned_message(); - mutate_account_if_exists(event.account_id(), move |account| { - account.append_messages(vec![message]); - }); - }); - iota_wallet::event::on_confirmation_state_change(|event| { - let message = event.cloned_message(); - let confirmed = *event.confirmed(); - mutate_account_if_exists(event.account_id(), move |account| { - if let Some(message) = account.messages_mut().iter_mut().find(|m| m == &&message) { - message.set_confirmed(Some(confirmed)); - } - }); - }); - iota_wallet::event::on_reattachment(|event| { - let message = event.cloned_message(); - mutate_account_if_exists(event.account_id(), move |account| { - account.append_messages(vec![message]); - }); - }); - iota_wallet::event::on_broadcast(|event| { - let message = event.cloned_message(); - mutate_account_if_exists(event.account_id(), move |account| { - if let Some(message) = account.messages_mut().iter_mut().find(|m| m == &&message) { - message.set_broadcasted(true); - } - }); - }); - Default::default() - }); +fn account_instances() -> &'static AccountInstanceMap { + static INSTANCES: Lazy = Lazy::new(Default::default); &INSTANCES } -pub(crate) fn get_account(id: &str) -> Arc> { - let map = instances() +pub(crate) fn get_account(id: &AccountIdentifier) -> AccountGuard { + let map = account_instances() .read() .expect("failed to lock account instances: get_account()"); map.get(id).expect("account dropped or not initialised").clone() } -pub(crate) fn store_account(account: Account) -> String { - let mut map = instances() +pub(crate) fn store_account(account: AccountGuard) -> AccountIdentifier { + let mut map = account_instances() .write() .expect("failed to lock account instances: store_account()"); - let id: String = thread_rng().sample_iter(&Alphanumeric).take(10).collect(); - map.insert(id.clone(), Arc::new(RwLock::new(account))); + let id = { + let account_ = account.read().unwrap(); + account_.id().clone() + }; + map.insert(id.clone(), account); id } -pub(crate) fn update_account(id: &str, account: Account) { - let mut map = instances() +pub(crate) fn remove_account(id: &AccountIdentifier) { + let mut map = account_instances() .write() - .expect("failed to lock account instances: store_account()"); - map.insert(id.to_string(), Arc::new(RwLock::new(account))); + .expect("failed to lock account instances: remove_account()"); + map.remove(id); +} + +/// Gets the synced account instances map. +fn synced_account_instances() -> &'static SyncedAccountInstanceMap { + static INSTANCES: Lazy = Lazy::new(Default::default); + &INSTANCES } -pub(crate) fn remove_account(id: &str) { - let mut map = instances() +pub(crate) fn get_synced_account(id: &str) -> SyncedAccountGuard { + let map = synced_account_instances() + .read() + .expect("failed to lock synced account instances: get_synced_account()"); + map.get(id).expect("synced account dropped or not initialised").clone() +} + +pub(crate) fn store_synced_account(synced_account: SyncedAccount) -> String { + let mut map = synced_account_instances() .write() - .expect("failed to lock account instances: remove_account()"); + .expect("failed to lock synced account instances: store_synced_account()"); + let id: String = thread_rng().sample_iter(&Alphanumeric).take(10).collect(); + map.insert(id.clone(), Arc::new(RwLock::new(synced_account))); + id +} + +pub(crate) fn remove_synced_account(id: &str) { + let mut map = synced_account_instances() + .write() + .expect("failed to lock synced account instances: remove_synced_account()"); map.remove(id); } diff --git a/src/monitor.rs b/src/monitor.rs index 3b7a25a24..2aceb7798 100644 --- a/src/monitor.rs +++ b/src/monitor.rs @@ -76,10 +76,11 @@ pub fn monitor_address_balance(account: AccountGuard, address: &IotaAddress) -> let account_ = account.read().unwrap(); account_.client_options().clone() }; + let client_options_ = client_options.clone(); let address = address.clone(); subscribe_to_topic( - &client_options.clone(), + &client_options_, format!("addresses/{}/outputs", address.to_bech32()), move |topic_event| { let topic_event = topic_event.clone(); From 184d2d57432cc60411a34796ef973e3f243a4ea4 Mon Sep 17 00:00:00 2001 From: Lucas Nogueira Date: Tue, 15 Dec 2020 23:43:11 -0300 Subject: [PATCH 08/29] feat(account): add bridge methods to the guard --- src/account/mod.rs | 90 ++++++++++++++++++++++++++++++++++++++++++ src/account_manager.rs | 3 +- 2 files changed, 92 insertions(+), 1 deletion(-) diff --git a/src/account/mod.rs b/src/account/mod.rs index 595dc1ea3..2aada0664 100644 --- a/src/account/mod.rs +++ b/src/account/mod.rs @@ -267,11 +267,101 @@ impl Deref for AccountGuard { } } +macro_rules! guard_field_getters { + ($ty:ident, $(#[$attr:meta] => $x:ident => $ret:ty),*) => { + impl $ty { + $( + #[$attr] + pub fn $x(&self) -> $ret { + let account = self.read().unwrap(); + account.$x().clone() + } + )* + } + } +} + +guard_field_getters!( + AccountGuard, + #[doc = "Bridge to [Account#id](struct.Account.html#method.id)."] => id => AccountIdentifier, + #[doc = "Bridge to [Account#signer_type](struct.Account.html#method.signer_type)."] => signer_type => SignerType, + #[doc = "Bridge to [Account#index](struct.Account.html#method.index)."] => index => usize, + #[doc = "Bridge to [Account#alias](struct.Account.html#method.alias)."] => alias => String, + #[doc = "Bridge to [Account#created_at](struct.Account.html#method.created_at)."] => created_at => DateTime, + #[doc = "Bridge to [Account#messages](struct.Account.html#method.messages). + This method clones the addresses so prefer the using the `read` method to access the account instance."] => messages => Vec, + #[doc = "Bridge to [Account#addresses](struct.Account.html#method.addresses). + This method clones the addresses so prefer the using the `read` method to access the account instance."] => addresses => Vec
, + #[doc = "Bridge to [Account#client_options](struct.Account.html#method.client_options)."] => client_options => ClientOptions +); + impl AccountGuard { /// Returns the builder to setup the process to synchronize this account with the Tangle. pub fn sync(&self) -> AccountSynchronizer { AccountSynchronizer::new(self.clone()) } + + /// Bridge to [Account#latest_address](struct.Account.html#method.latest_address). + pub fn latest_address(&self) -> Option
{ + let account = self.0.read().unwrap(); + account.latest_address().cloned() + } + + /// Bridge to [Account#total_balance](struct.Account.html#method.total_balance). + pub fn total_balance(&self) -> u64 { + let account = self.0.read().unwrap(); + account.total_balance() + } + + /// Bridge to [Account#available_balance](struct.Account.html#method.available_balance). + pub fn available_balance(&self) -> u64 { + let account = self.0.read().unwrap(); + account.available_balance() + } + + /// Bridge to [Account#set_alias](struct.Account.html#method.set_alias). + pub fn set_alias(&mut self, alias: impl AsRef) { + let mut account = self.0.write().unwrap(); + account.set_alias(alias); + } + + /// Bridge to [Account#set_client_options](struct.Account.html#method.set_client_options). + pub fn set_client_options(&mut self, options: ClientOptions) { + let mut account = self.0.write().unwrap(); + account.set_client_options(options); + } + + /// Bridge to [Account#list_messages](struct.Account.html#method.list_messages). + /// This method clones the account's messages so when querying a large list of messages + /// prefer using the `read` method to access the account instance. + pub fn list_messages(&self, count: usize, from: usize, message_type: Option) -> Vec { + let account = self.0.read().unwrap(); + account + .list_messages(count, from, message_type) + .into_iter() + .cloned() + .collect() + } + + /// Bridge to [Account#list_addresses](struct.Account.html#method.list_addresses). + /// This method clones the account's addresses so when querying a large list of addresses + /// prefer using the `read` method to access the account instance. + pub fn list_addresses(&self, unspent: bool) -> Vec
{ + let account = self.0.read().unwrap(); + account.list_addresses(unspent).into_iter().cloned().collect() + } + + /// Bridge to [Account#generate_address](struct.Account.html#method.generate_address). + pub fn generate_address(&mut self) -> crate::Result
{ + let mut account = self.0.write().unwrap(); + account.generate_address() + } + + /// Bridge to [Account#get_message](struct.Account.html#method.get_message). + pub fn get_message(&self, message_id: &MessageId) -> Option { + let account = self.0.read().unwrap(); + account.get_message(message_id).cloned() + } } impl Account { diff --git a/src/account_manager.rs b/src/account_manager.rs index e285ccff8..9267180de 100644 --- a/src/account_manager.rs +++ b/src/account_manager.rs @@ -530,7 +530,8 @@ async fn sync_accounts<'a>( Ok(vec![]) } } - None => Ok(vec![]), // None => discover_accounts(accounts.clone(), &storage_path, &ClientOptions::default(), None).await, + None => Ok(vec![]), /* None => discover_accounts(accounts.clone(), &storage_path, &ClientOptions::default(), + * None).await, */ }; if let Ok(discovered_accounts) = discovered_accounts_res { From b11b3f819876fe09bf1d76da8f7b3dc39d3c3710 Mon Sep 17 00:00:00 2001 From: Lucas Nogueira Date: Wed, 16 Dec 2020 10:42:45 -0300 Subject: [PATCH 09/29] fix(sync): deadlocks --- src/account/sync/mod.rs | 33 +++++++++++++++++++++++++++------ 1 file changed, 27 insertions(+), 6 deletions(-) diff --git a/src/account/sync/mod.rs b/src/account/sync/mod.rs index 777ec080b..1cc06f227 100644 --- a/src/account/sync/mod.rs +++ b/src/account/sync/mod.rs @@ -336,27 +336,48 @@ impl AccountSynchronizer { /// The account syncing process ensures that the latest metadata (balance, transactions) /// associated with an account is fetched from the tangle and is stored locally. pub async fn execute(self) -> crate::Result { - let mut account_ref = self.account.write().unwrap(); - - let options = account_ref.client_options().clone(); + let options = self.account.client_options(); let client = get_client(&options); let _ = crate::monitor::unsubscribe(self.account.clone()); - let mut account_ = account_ref.clone(); + let mut account_ = { + let account_ref = self.account.read().unwrap(); + account_ref.clone() + }; + let message_ids_before_sync: Vec = account_.messages().iter().map(|m| *m.id()).collect(); + let addresses_before_sync: Vec = account_.addresses().iter().map(|a| a.address().to_bech32()).collect(); + let return_value = match perform_sync(&mut account_, self.address_index, self.gap_limit).await { Ok(is_empty) => { if !self.skip_persistance { + let mut account_ref = self.account.write().unwrap(); account_ref.set_addresses(account_.addresses().to_vec()); account_ref.set_messages(account_.messages().to_vec()); } + let account_ref = self.account.read().unwrap(); + let synced_account = SyncedAccount { account: self.account.clone(), deposit_address: account_ref.latest_address().unwrap().clone(), is_empty, - addresses: account_ref.addresses().clone(), - messages: account_ref.messages().clone(), + addresses: account_ref + .addresses() + .iter() + .filter(|a| { + !addresses_before_sync + .iter() + .any(|addr| addr == &a.address().to_bech32()) + }) + .cloned() + .collect(), + messages: account_ref + .messages() + .iter() + .filter(|m| !message_ids_before_sync.iter().any(|id| id == m.id())) + .cloned() + .collect(), }; Ok(synced_account) } From 6919b14e17f0a33470011682563855a2fb72ffd0 Mon Sep 17 00:00:00 2001 From: Lucas Nogueira Date: Wed, 16 Dec 2020 10:42:53 -0300 Subject: [PATCH 10/29] feat(bindings): add loadAccounts method --- .../node/native/src/classes/account_manager/mod.rs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/bindings/node/native/src/classes/account_manager/mod.rs b/bindings/node/native/src/classes/account_manager/mod.rs index 132ab80e8..b528942c0 100644 --- a/bindings/node/native/src/classes/account_manager/mod.rs +++ b/bindings/node/native/src/classes/account_manager/mod.rs @@ -131,6 +131,17 @@ declare_types! { Ok(cx.undefined().upcast()) } + method loadAccounts(mut cx) { + { + let this = cx.this(); + let guard = cx.lock(); + let ref_ = &this.borrow(&guard).0; + let mut manager = ref_.write().unwrap(); + manager.load_accounts().expect("failed to load accounts"); + } + Ok(cx.undefined().upcast()) + } + method createAccount(mut cx) { let account = { let account_to_create = cx.argument::(0)?; From 3cfb6a30419785cf3fd6215725b3d1d81e75aa6e Mon Sep 17 00:00:00 2001 From: Lucas Nogueira Date: Wed, 16 Dec 2020 12:42:16 -0300 Subject: [PATCH 11/29] chore(lib): cleanup --- .../node/native/src/classes/account/mod.rs | 1 - examples/account_operations.rs | 1 - examples/backup_and_restore.rs | 7 ++-- examples/transfer.rs | 1 - src/account/mod.rs | 36 ++++++++----------- src/actor/mod.rs | 3 +- 6 files changed, 20 insertions(+), 29 deletions(-) diff --git a/bindings/node/native/src/classes/account/mod.rs b/bindings/node/native/src/classes/account/mod.rs index 82d03d01c..3dc849d9c 100644 --- a/bindings/node/native/src/classes/account/mod.rs +++ b/bindings/node/native/src/classes/account/mod.rs @@ -188,7 +188,6 @@ declare_types! { let guard = cx.lock(); let id = &this.borrow(&guard).0; let account = crate::get_account(id); - let mut account = account.write().unwrap(); account.generate_address().expect("error generating address") }; Ok(neon_serde::to_value(&mut cx, &address)?) diff --git a/examples/account_operations.rs b/examples/account_operations.rs index 906265e01..49cdfd264 100644 --- a/examples/account_operations.rs +++ b/examples/account_operations.rs @@ -11,7 +11,6 @@ async fn main() -> iota_wallet::Result<()> { // first we'll create an example account and store it let client_options = ClientOptionsBuilder::node("https://nodes.devnet.iota.org:443")?.build(); let account = manager.create_account(client_options).alias("alias").initialise()?; - let mut account = account.write().unwrap(); // update alias account.set_alias("the new alias"); diff --git a/examples/backup_and_restore.rs b/examples/backup_and_restore.rs index 90a55b7bf..f9d3bcb16 100644 --- a/examples/backup_and_restore.rs +++ b/examples/backup_and_restore.rs @@ -10,8 +10,7 @@ fn main() -> iota_wallet::Result<()> { // first we'll create an example account let client_options = ClientOptionsBuilder::node("https://nodes.devnet.iota.org:443")?.build(); let account = manager.create_account(client_options).alias("alias").initialise()?; - let account_ = account.read().unwrap(); - let id = account_.id(); + let id = account.id(); // backup the stored accounts to ./backup/${backup_name} let backup_path = manager.backup("./backup")?; @@ -21,7 +20,9 @@ fn main() -> iota_wallet::Result<()> { // import the accounts from the backup and assert that it's the same manager.import_accounts(backup_path)?; - let imported_account = manager.get_account(id)?; + let imported_account = manager.get_account(&id)?; + + let account_ = account.read().unwrap(); let imported_account_ = imported_account.read().unwrap(); assert_eq!(*account_, *imported_account_); diff --git a/examples/transfer.rs b/examples/transfer.rs index f71d6f1a5..633e19bca 100644 --- a/examples/transfer.rs +++ b/examples/transfer.rs @@ -16,7 +16,6 @@ async fn main() -> iota_wallet::Result<()> { let sync_accounts = manager.sync_accounts().await?; let sync_account = sync_accounts.first().unwrap(); - let account = account.read().unwrap(); sync_account .transfer(Transfer::new(account.latest_address().unwrap().address().clone(), 150)) .await?; diff --git a/src/account/mod.rs b/src/account/mod.rs index 2aada0664..1b6bd6b71 100644 --- a/src/account/mod.rs +++ b/src/account/mod.rs @@ -301,6 +301,19 @@ impl AccountGuard { AccountSynchronizer::new(self.clone()) } + /// Gets a new unused address and links it to this account. + pub fn generate_address(&self) -> crate::Result
{ + let mut account = self.0.write().unwrap(); + let address = crate::address::get_new_address(&account)?; + account.addresses.push(address.clone()); + + account.has_pending_changes = true; + + let _ = crate::monitor::monitor_address_balance(self.clone(), address.address()); + + Ok(address) + } + /// Bridge to [Account#latest_address](struct.Account.html#method.latest_address). pub fn latest_address(&self) -> Option
{ let account = self.0.read().unwrap(); @@ -320,13 +333,13 @@ impl AccountGuard { } /// Bridge to [Account#set_alias](struct.Account.html#method.set_alias). - pub fn set_alias(&mut self, alias: impl AsRef) { + pub fn set_alias(&self, alias: impl AsRef) { let mut account = self.0.write().unwrap(); account.set_alias(alias); } /// Bridge to [Account#set_client_options](struct.Account.html#method.set_client_options). - pub fn set_client_options(&mut self, options: ClientOptions) { + pub fn set_client_options(&self, options: ClientOptions) { let mut account = self.0.write().unwrap(); account.set_client_options(options); } @@ -351,12 +364,6 @@ impl AccountGuard { account.list_addresses(unspent).into_iter().cloned().collect() } - /// Bridge to [Account#generate_address](struct.Account.html#method.generate_address). - pub fn generate_address(&mut self) -> crate::Result
{ - let mut account = self.0.write().unwrap(); - account.generate_address() - } - /// Bridge to [Account#get_message](struct.Account.html#method.get_message). pub fn get_message(&self, message_id: &MessageId) -> Option { let account = self.0.read().unwrap(); @@ -495,19 +502,6 @@ impl Account { .collect() } - /// Gets a new unused address and links it to this account. - pub fn generate_address(&mut self) -> crate::Result
{ - let address = crate::address::get_new_address(&self)?; - self.addresses.push(address.clone()); - - self.has_pending_changes = true; - - // ignore errors because we fallback to the polling system - // TODO let _ = crate::monitor::monitor_address_balance(&self, address.address()); - - Ok(address) - } - #[doc(hidden)] pub fn append_messages(&mut self, messages: Vec) { self.messages.extend(messages); diff --git a/src/actor/mod.rs b/src/actor/mod.rs index 280b21aac..cb7b9d043 100644 --- a/src/actor/mod.rs +++ b/src/actor/mod.rs @@ -157,8 +157,7 @@ impl WalletMessageHandler { match method { AccountMethod::GenerateAddress => { - let mut account_ = account.write().unwrap(); - let address = account_.generate_address()?; + let address = account.generate_address()?; Ok(ResponseType::GeneratedAddress(address)) } AccountMethod::ListMessages { From f8aa72c7a5993f85b09cd56ce8c7c41c7b865d4f Mon Sep 17 00:00:00 2001 From: Lucas Nogueira Date: Wed, 16 Dec 2020 12:54:33 -0300 Subject: [PATCH 12/29] chore(transfer): lock write only when needed --- src/account/sync/mod.rs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/account/sync/mod.rs b/src/account/sync/mod.rs index 1cc06f227..5cf342544 100644 --- a/src/account/sync/mod.rs +++ b/src/account/sync/mod.rs @@ -439,7 +439,7 @@ impl SyncedAccount { &self, locked_addresses: &'a mut MutexGuard<'_, Vec>, threshold: u64, - account: &'a mut Account, + account: &'a Account, address: &'a IotaAddress, ) -> crate::Result<(Vec, Option)> { let mut available_addresses: Vec = account @@ -526,7 +526,7 @@ impl SyncedAccount { } } - let mut account_ = self.account.write().unwrap(); + let account_ = self.account.read().unwrap(); let client = crate::client::get_client(account_.client_options()); let client = client.read().unwrap(); @@ -547,7 +547,7 @@ impl SyncedAccount { let (input_addresses, remainder_address) = self.select_inputs( &mut locked_addresses, transfer_obj.amount, - &mut account_, + &account_, &transfer_obj.address, )?; @@ -624,6 +624,9 @@ impl SyncedAccount { } } + drop(account_); + let mut account_ = self.account.write().unwrap(); + // if there's remainder value, we check the strategy defined in the transfer let mut remainder_value_deposit_address = None; if remainder_value > 0 { From 6aa04304d2eae5357746e26c5a6a55ae25fd3824 Mon Sep 17 00:00:00 2001 From: Lucas Nogueira Date: Wed, 16 Dec 2020 15:53:23 -0300 Subject: [PATCH 13/29] chore(account): rename AccountGuard to AccountHandle --- bindings/node/native/src/lib.rs | 14 +++++++------- src/account/mod.rs | 14 +++++++------- src/account/sync/mod.rs | 12 ++++++------ src/account_manager.rs | 12 ++++++------ src/monitor.rs | 16 ++++++++-------- 5 files changed, 34 insertions(+), 34 deletions(-) diff --git a/bindings/node/native/src/lib.rs b/bindings/node/native/src/lib.rs index 43b1778d3..0ca3fdf6f 100644 --- a/bindings/node/native/src/lib.rs +++ b/bindings/node/native/src/lib.rs @@ -10,7 +10,7 @@ use std::{ use futures::{Future, FutureExt}; use iota_wallet::{ - account::{AccountGuard, AccountIdentifier, SyncedAccount}, + account::{AccountHandle, AccountIdentifier, SyncedAccount}, WalletError, }; use neon::prelude::*; @@ -21,9 +21,9 @@ use tokio::runtime::Runtime; mod classes; use classes::*; -type AccountInstanceMap = Arc>>; -type SyncedAccountGuard = Arc>; -type SyncedAccountInstanceMap = Arc>>; +type AccountInstanceMap = Arc>>; +type SyncedAccountHandle = Arc>; +type SyncedAccountInstanceMap = Arc>>; /// Gets the account instances map. fn account_instances() -> &'static AccountInstanceMap { @@ -31,14 +31,14 @@ fn account_instances() -> &'static AccountInstanceMap { &INSTANCES } -pub(crate) fn get_account(id: &AccountIdentifier) -> AccountGuard { +pub(crate) fn get_account(id: &AccountIdentifier) -> AccountHandle { let map = account_instances() .read() .expect("failed to lock account instances: get_account()"); map.get(id).expect("account dropped or not initialised").clone() } -pub(crate) fn store_account(account: AccountGuard) -> AccountIdentifier { +pub(crate) fn store_account(account: AccountHandle) -> AccountIdentifier { let mut map = account_instances() .write() .expect("failed to lock account instances: store_account()"); @@ -63,7 +63,7 @@ fn synced_account_instances() -> &'static SyncedAccountInstanceMap { &INSTANCES } -pub(crate) fn get_synced_account(id: &str) -> SyncedAccountGuard { +pub(crate) fn get_synced_account(id: &str) -> SyncedAccountHandle { let map = synced_account_instances() .read() .expect("failed to lock synced account instances: get_synced_account()"); diff --git a/src/account/mod.rs b/src/account/mod.rs index 1b6bd6b71..8b07243ca 100644 --- a/src/account/mod.rs +++ b/src/account/mod.rs @@ -148,7 +148,7 @@ impl AccountInitialiser { } /// Initialises the account. - pub fn initialise(self) -> crate::Result { + pub fn initialise(self) -> crate::Result { let mut accounts = self.accounts.write().unwrap(); let alias = self.alias.unwrap_or_else(|| format!("Account {}", accounts.len())); @@ -195,7 +195,7 @@ impl AccountInitialiser { account.into() } else { let account_id = account.id().clone(); - let guard: AccountGuard = account.into(); + let guard: AccountHandle = account.into(); accounts.insert(account_id, guard.clone()); guard }; @@ -252,15 +252,15 @@ pub struct Account { /// A thread guard over an account. #[derive(Debug, Clone)] -pub struct AccountGuard(Arc>); +pub struct AccountHandle(Arc>); -impl From for AccountGuard { +impl From for AccountHandle { fn from(account: Account) -> Self { Self(Arc::new(RwLock::new(account))) } } -impl Deref for AccountGuard { +impl Deref for AccountHandle { type Target = RwLock; fn deref(&self) -> &Self::Target { &self.0.deref() @@ -282,7 +282,7 @@ macro_rules! guard_field_getters { } guard_field_getters!( - AccountGuard, + AccountHandle, #[doc = "Bridge to [Account#id](struct.Account.html#method.id)."] => id => AccountIdentifier, #[doc = "Bridge to [Account#signer_type](struct.Account.html#method.signer_type)."] => signer_type => SignerType, #[doc = "Bridge to [Account#index](struct.Account.html#method.index)."] => index => usize, @@ -295,7 +295,7 @@ guard_field_getters!( #[doc = "Bridge to [Account#client_options](struct.Account.html#method.client_options)."] => client_options => ClientOptions ); -impl AccountGuard { +impl AccountHandle { /// Returns the builder to setup the process to synchronize this account with the Tangle. pub fn sync(&self) -> AccountSynchronizer { AccountSynchronizer::new(self.clone()) diff --git a/src/account/sync/mod.rs b/src/account/sync/mod.rs index 5cf342544..fab9f2523 100644 --- a/src/account/sync/mod.rs +++ b/src/account/sync/mod.rs @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 use crate::{ - account::{get_account_addresses_lock, Account, AccountGuard}, + account::{get_account_addresses_lock, Account, AccountHandle}, address::{Address, AddressBuilder, AddressOutput, IotaAddress}, client::get_client, message::{Message, RemainderValueStrategy, Transfer}, @@ -292,7 +292,7 @@ async fn perform_sync(mut account: &mut Account, address_index: usize, gap_limit /// Account sync helper. pub struct AccountSynchronizer { - account: AccountGuard, + account: AccountHandle, address_index: usize, gap_limit: usize, skip_persistance: bool, @@ -300,7 +300,7 @@ pub struct AccountSynchronizer { impl AccountSynchronizer { /// Initialises a new instance of the sync helper. - pub(super) fn new(account: AccountGuard) -> Self { + pub(super) fn new(account: AccountHandle) -> Self { let address_index = { let account_ = account.read().unwrap(); account_.addresses().len() @@ -391,7 +391,7 @@ impl AccountSynchronizer { } } -fn serialize_as_id(x: &AccountGuard, s: S) -> Result +fn serialize_as_id(x: &AccountHandle, s: S) -> Result where S: Serializer, { @@ -405,7 +405,7 @@ pub struct SyncedAccount { /// The associated account identifier. #[serde(rename = "accountId", serialize_with = "serialize_as_id")] #[getset(get = "pub")] - account: AccountGuard, + account: AccountHandle, /// The account's deposit address. #[serde(rename = "depositAddress")] #[getset(get = "pub")] @@ -758,7 +758,7 @@ pub(crate) enum RepostAction { } pub(crate) async fn repost_message( - account: AccountGuard, + account: AccountHandle, message_id: &MessageId, action: RepostAction, ) -> crate::Result { diff --git a/src/account_manager.rs b/src/account_manager.rs index 9267180de..bbc7633c4 100644 --- a/src/account_manager.rs +++ b/src/account_manager.rs @@ -3,7 +3,7 @@ use crate::{ account::{ - account_id_to_stronghold_record_id, repost_message, AccountGuard, AccountIdentifier, AccountInitialiser, + account_id_to_stronghold_record_id, repost_message, AccountHandle, AccountIdentifier, AccountInitialiser, RepostAction, SyncedAccount, }, client::ClientOptions, @@ -35,7 +35,7 @@ use stronghold::Stronghold; /// The default storage path. pub const DEFAULT_STORAGE_PATH: &str = "./example-database"; -pub(crate) type AccountStore = Arc>>; +pub(crate) type AccountStore = Arc>>; /// The account manager. /// @@ -330,7 +330,7 @@ impl AccountManager { } /// Gets the account associated with the given identifier. - pub fn get_account(&self, account_id: &AccountIdentifier) -> crate::Result { + pub fn get_account(&self, account_id: &AccountIdentifier) -> crate::Result { let accounts = self.accounts.read().unwrap(); accounts .get(account_id) @@ -339,7 +339,7 @@ impl AccountManager { } /// Gets the account associated with the given alias (case insensitive). - pub fn get_account_by_alias>(&self, alias: S) -> Option { + pub fn get_account_by_alias>(&self, alias: S) -> Option { let alias = alias.into().to_lowercase(); self.get_accounts().into_iter().find(|acc| { let acc = acc.read().unwrap(); @@ -348,7 +348,7 @@ impl AccountManager { } /// Gets all accounts from storage. - pub fn get_accounts(&self) -> Vec { + pub fn get_accounts(&self) -> Vec { let accounts = self.accounts.read().unwrap(); accounts.values().cloned().collect() } @@ -475,7 +475,7 @@ async fn discover_accounts( storage_path: &PathBuf, client_options: &ClientOptions, signer_type: Option, -) -> crate::Result> { +) -> crate::Result> { let mut synced_accounts = vec![]; loop { let mut account_initialiser = diff --git a/src/monitor.rs b/src/monitor.rs index 2aceb7798..bdb8d7c04 100644 --- a/src/monitor.rs +++ b/src/monitor.rs @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 use crate::{ - account::AccountGuard, + account::AccountHandle, address::{AddressOutput, IotaAddress}, client::ClientOptions, message::{Message, MessageType}, @@ -42,7 +42,7 @@ struct AddressOutputPayloadAddress { } /// Unsubscribe from all topics associated with the account. -pub fn unsubscribe(account: AccountGuard) -> crate::Result<()> { +pub fn unsubscribe(account: AccountHandle) -> crate::Result<()> { let account_ = account.read().unwrap(); let client = crate::client::get_client(account_.client_options()); let mut client = client.write().unwrap(); @@ -62,7 +62,7 @@ fn subscribe_to_topic( } /// Monitor account addresses for balance changes. -pub fn monitor_account_addresses_balance(account: AccountGuard) -> crate::Result<()> { +pub fn monitor_account_addresses_balance(account: AccountHandle) -> crate::Result<()> { let account_ = account.read().unwrap(); for address in account_.addresses() { monitor_address_balance(account.clone(), address.address())?; @@ -71,7 +71,7 @@ pub fn monitor_account_addresses_balance(account: AccountGuard) -> crate::Result } /// Monitor address for balance changes. -pub fn monitor_address_balance(account: AccountGuard, address: &IotaAddress) -> crate::Result<()> { +pub fn monitor_address_balance(account: AccountHandle, address: &IotaAddress) -> crate::Result<()> { let client_options = { let account_ = account.read().unwrap(); account_.client_options().clone() @@ -101,7 +101,7 @@ pub fn monitor_address_balance(account: AccountGuard, address: &IotaAddress) -> async fn process_output( payload: String, - account: AccountGuard, + account: AccountHandle, address: IotaAddress, client_options: ClientOptions, ) -> crate::Result<()> { @@ -156,7 +156,7 @@ async fn process_output( } /// Monitor the account's unconfirmed messages for confirmation state change. -pub fn monitor_unconfirmed_messages(account: AccountGuard) -> crate::Result<()> { +pub fn monitor_unconfirmed_messages(account: AccountHandle) -> crate::Result<()> { let account_ = account.read().unwrap(); for message in account_.list_messages(0, 0, Some(MessageType::Unconfirmed)) { monitor_confirmation_state_change(account.clone(), message.id())?; @@ -165,7 +165,7 @@ pub fn monitor_unconfirmed_messages(account: AccountGuard) -> crate::Result<()> } /// Monitor message for confirmation state. -pub fn monitor_confirmation_state_change(account: AccountGuard, message_id: &MessageId) -> crate::Result<()> { +pub fn monitor_confirmation_state_change(account: AccountHandle, message_id: &MessageId) -> crate::Result<()> { let (message, client_options) = { let account_ = account.read().unwrap(); let message = account_ @@ -191,7 +191,7 @@ pub fn monitor_confirmation_state_change(account: AccountGuard, message_id: &Mes fn process_metadata( payload: String, - account: AccountGuard, + account: AccountHandle, message_id: MessageId, message: &Message, ) -> crate::Result<()> { From 92f6d952fe8c68d95da1f05ff556a75f3f412fea Mon Sep 17 00:00:00 2001 From: Lucas Nogueira Date: Wed, 16 Dec 2020 16:02:25 -0300 Subject: [PATCH 14/29] chore(lib): inline usage of account handle where appropriate --- bindings/node/native/src/lib.rs | 5 +---- src/account/mod.rs | 35 ++++++++++++++--------------- src/account_manager.rs | 40 ++++++++++++--------------------- src/monitor.rs | 21 ++++++++--------- 4 files changed, 41 insertions(+), 60 deletions(-) diff --git a/bindings/node/native/src/lib.rs b/bindings/node/native/src/lib.rs index 0ca3fdf6f..556e5b306 100644 --- a/bindings/node/native/src/lib.rs +++ b/bindings/node/native/src/lib.rs @@ -42,10 +42,7 @@ pub(crate) fn store_account(account: AccountHandle) -> AccountIdentifier { let mut map = account_instances() .write() .expect("failed to lock account instances: store_account()"); - let id = { - let account_ = account.read().unwrap(); - account_.id().clone() - }; + let id = account.id(); map.insert(id.clone(), account); id } diff --git a/src/account/mod.rs b/src/account/mod.rs index 8b07243ca..552de9ce1 100644 --- a/src/account/mod.rs +++ b/src/account/mod.rs @@ -273,8 +273,7 @@ macro_rules! guard_field_getters { $( #[$attr] pub fn $x(&self) -> $ret { - let account = self.read().unwrap(); - account.$x().clone() + self.0.read().unwrap().$x().clone() } )* } @@ -316,40 +315,36 @@ impl AccountHandle { /// Bridge to [Account#latest_address](struct.Account.html#method.latest_address). pub fn latest_address(&self) -> Option
{ - let account = self.0.read().unwrap(); - account.latest_address().cloned() + self.0.read().unwrap().latest_address().cloned() } /// Bridge to [Account#total_balance](struct.Account.html#method.total_balance). pub fn total_balance(&self) -> u64 { - let account = self.0.read().unwrap(); - account.total_balance() + self.0.read().unwrap().total_balance() } /// Bridge to [Account#available_balance](struct.Account.html#method.available_balance). pub fn available_balance(&self) -> u64 { - let account = self.0.read().unwrap(); - account.available_balance() + self.0.read().unwrap().available_balance() } /// Bridge to [Account#set_alias](struct.Account.html#method.set_alias). pub fn set_alias(&self, alias: impl AsRef) { - let mut account = self.0.write().unwrap(); - account.set_alias(alias); + self.0.write().unwrap().set_alias(alias); } /// Bridge to [Account#set_client_options](struct.Account.html#method.set_client_options). pub fn set_client_options(&self, options: ClientOptions) { - let mut account = self.0.write().unwrap(); - account.set_client_options(options); + self.0.write().unwrap().set_client_options(options); } /// Bridge to [Account#list_messages](struct.Account.html#method.list_messages). /// This method clones the account's messages so when querying a large list of messages /// prefer using the `read` method to access the account instance. pub fn list_messages(&self, count: usize, from: usize, message_type: Option) -> Vec { - let account = self.0.read().unwrap(); - account + self.0 + .read() + .unwrap() .list_messages(count, from, message_type) .into_iter() .cloned() @@ -360,14 +355,18 @@ impl AccountHandle { /// This method clones the account's addresses so when querying a large list of addresses /// prefer using the `read` method to access the account instance. pub fn list_addresses(&self, unspent: bool) -> Vec
{ - let account = self.0.read().unwrap(); - account.list_addresses(unspent).into_iter().cloned().collect() + self.0 + .read() + .unwrap() + .list_addresses(unspent) + .into_iter() + .cloned() + .collect() } /// Bridge to [Account#get_message](struct.Account.html#method.get_message). pub fn get_message(&self, message_id: &MessageId) -> Option { - let account = self.0.read().unwrap(); - account.get_message(message_id).cloned() + self.0.read().unwrap().get_message(message_id).cloned() } } diff --git a/src/account_manager.rs b/src/account_manager.rs index bbc7633c4..8e592454e 100644 --- a/src/account_manager.rs +++ b/src/account_manager.rs @@ -91,11 +91,7 @@ impl AccountManager { /// Loads the account from the storage into the manager instance. pub fn load_accounts(&mut self) -> crate::Result<()> { - let empty_accounts = { - let accounts = self.accounts.read().unwrap(); - accounts.is_empty() - }; - if empty_accounts { + if self.accounts.read().unwrap().is_empty() { let accounts = crate::storage::with_adapter(&self.storage_path, |storage| storage.get_all())?; let accounts = crate::storage::parse_accounts(&self.storage_path, &accounts)? .into_iter() @@ -126,11 +122,8 @@ impl AccountManager { /// Stops the background polling and MQTT monitoring. pub fn stop_background_sync(&mut self) -> crate::Result<()> { - { - let accounts = self.accounts.read().unwrap(); - for account in accounts.values() { - let _ = crate::monitor::unsubscribe(account.clone()); - } + for account in self.accounts.read().unwrap().values() { + let _ = crate::monitor::unsubscribe(account.clone()); } if let Some(handle) = self.polling_handle.take() { @@ -241,23 +234,18 @@ impl AccountManager { to_account_id: &AccountIdentifier, amount: u64, ) -> crate::Result { - let from_account_guard = self.get_account(from_account_id)?; - let to_account_guard = self.get_account(to_account_id)?; - - let to_address = { - let to_account = to_account_guard.read().unwrap(); - to_account - .latest_address() - .ok_or_else(|| anyhow::anyhow!("destination account address list empty"))? - .clone() - }; - - let from_synchronized = from_account_guard.sync().execute().await?; - let message = from_synchronized + let to_address = self + .get_account(to_account_id)? + .read() + .unwrap() + .latest_address() + .ok_or_else(|| anyhow::anyhow!("destination account address list empty"))? + .clone(); + + let from_synchronized = self.get_account(from_account_id)?.sync().execute().await?; + from_synchronized .transfer(Transfer::new(to_address.address().clone(), amount)) - .await?; - - Ok(message) + .await } /// Backups the accounts to the given destination diff --git a/src/monitor.rs b/src/monitor.rs index bdb8d7c04..69596f56c 100644 --- a/src/monitor.rs +++ b/src/monitor.rs @@ -128,14 +128,12 @@ async fn process_output( let mut account = account.write().unwrap(); let account_id = account.id().clone(); - let message_id_ = *message_id; - { - let addresses = account.addresses_mut(); - let address_to_update = addresses.iter_mut().find(|a| a.address() == &address).unwrap(); - address_to_update.handle_new_output(address_output); - crate::event::emit_balance_change(&account_id, &address_to_update, *address_to_update.balance()); - } + + let addresses = account.addresses_mut(); + let address_to_update = addresses.iter_mut().find(|a| a.address() == &address).unwrap(); + address_to_update.handle_new_output(address_output); + crate::event::emit_balance_change(&account_id, &address_to_update, *address_to_update.balance()); match account.messages_mut().iter().position(|m| m.id() == &message_id_) { Some(message_index) => { @@ -201,11 +199,10 @@ fn process_metadata( let confirmed = inclusion_state == "included"; if message.confirmed().is_none() || confirmed != message.confirmed().unwrap() { let mut account = account.write().unwrap(); - { - let messages = account.messages_mut(); - let message = messages.iter_mut().find(|m| m.id() == &message_id).unwrap(); - message.set_confirmed(Some(confirmed)); - } + + let messages = account.messages_mut(); + let account_message = messages.iter_mut().find(|m| m.id() == &message_id).unwrap(); + account_message.set_confirmed(Some(confirmed)); crate::event::emit_confirmation_state_change(account.id(), &message, confirmed); } From 1d9052942581fd9de0679274f6d8a80a2abc3280 Mon Sep 17 00:00:00 2001 From: Lucas Nogueira Date: Wed, 16 Dec 2020 16:16:12 -0300 Subject: [PATCH 15/29] chore(lib): rename AccountHandle variables to account_handle --- .../node/native/src/classes/account/mod.rs | 48 +++++++-------- bindings/node/native/src/lib.rs | 6 +- examples/backup_and_restore.rs | 12 ++-- src/account/mod.rs | 14 ++--- src/account/sync/mod.rs | 57 +++++++++--------- src/account_manager.rs | 58 +++++++++---------- src/actor/mod.rs | 39 ++++++------- src/monitor.rs | 53 ++++++++--------- 8 files changed, 137 insertions(+), 150 deletions(-) diff --git a/bindings/node/native/src/classes/account/mod.rs b/bindings/node/native/src/classes/account/mod.rs index 3dc849d9c..77a38290a 100644 --- a/bindings/node/native/src/classes/account/mod.rs +++ b/bindings/node/native/src/classes/account/mod.rs @@ -28,8 +28,8 @@ declare_types! { let this = cx.this(); let guard = cx.lock(); let id = &this.borrow(&guard).0; - let account = crate::get_account(id); - let account = account.read().unwrap(); + let account_handle = crate::get_account(id); + let account = account_handle.read().unwrap(); account.id().clone() }; @@ -44,8 +44,8 @@ declare_types! { let this = cx.this(); let guard = cx.lock(); let id = &this.borrow(&guard).0; - let account = crate::get_account(id); - let account = account.read().unwrap(); + let account_handle = crate::get_account(id); + let account = account_handle.read().unwrap(); *account.index() }; @@ -57,8 +57,8 @@ declare_types! { let this = cx.this(); let guard = cx.lock(); let id = &this.borrow(&guard).0; - let account = crate::get_account(id); - let account = account.read().unwrap(); + let account_handle = crate::get_account(id); + let account = account_handle.read().unwrap(); account.alias().clone() }; @@ -70,8 +70,8 @@ declare_types! { let this = cx.this(); let guard = cx.lock(); let id = &this.borrow(&guard).0; - let account = crate::get_account(id); - let account = account.read().unwrap(); + let account_handle = crate::get_account(id); + let account = account_handle.read().unwrap(); account.available_balance() }; Ok(cx.number(balance as f64).upcast()) @@ -82,8 +82,8 @@ declare_types! { let this = cx.this(); let guard = cx.lock(); let id = &this.borrow(&guard).0; - let account = crate::get_account(id); - let account = account.read().unwrap(); + let account_handle = crate::get_account(id); + let account = account_handle.read().unwrap(); account.total_balance() }; Ok(cx.number(balance as f64).upcast()) @@ -108,8 +108,8 @@ declare_types! { let this = cx.this(); let id = cx.borrow(&this, |r| r.0.clone()); - let account = crate::get_account(&id); - let account = account.read().unwrap(); + let account_handle = crate::get_account(&id); + let account = account_handle.read().unwrap(); let messages = account.list_messages(count, from, filter); let js_array = JsArray::new(&mut cx, messages.len() as u32); @@ -129,8 +129,8 @@ declare_types! { let this = cx.this(); let id = cx.borrow(&this, |r| r.0.clone()); - let account = crate::get_account(&id); - let account = account.read().unwrap(); + let account_handle = crate::get_account(&id); + let account = account_handle.read().unwrap(); let addresses = account.list_addresses(unspent); let js_array = JsArray::new(&mut cx, addresses.len() as u32); @@ -148,8 +148,8 @@ declare_types! { let this = cx.this(); let guard = cx.lock(); let id = &this.borrow(&guard).0; - let account = crate::get_account(id); - let mut account = account.write().unwrap(); + let account_handle = crate::get_account(id); + let mut account = account_handle.write().unwrap(); account.set_alias(alias); } Ok(cx.undefined().upcast()) @@ -162,8 +162,8 @@ declare_types! { let this = cx.this(); let guard = cx.lock(); let id = &this.borrow(&guard).0; - let account = crate::get_account(id); - let mut account = account.write().unwrap(); + let account_handle = crate::get_account(id); + let mut account = account_handle.write().unwrap(); account.set_client_options(client_options); } Ok(cx.undefined().upcast()) @@ -173,8 +173,8 @@ declare_types! { let message_id = MessageId::from_str(cx.argument::(0)?.value().as_str()).expect("invalid message id length"); let this = cx.this(); let id = cx.borrow(&this, |r| r.0.clone()); - let account = crate::get_account(&id); - let account = account.read().unwrap(); + let account_handle = crate::get_account(&id); + let account = account_handle.read().unwrap(); let message = account.get_message(&message_id); match message { Some(m) => Ok(neon_serde::to_value(&mut cx, &m)?), @@ -187,8 +187,8 @@ declare_types! { let this = cx.this(); let guard = cx.lock(); let id = &this.borrow(&guard).0; - let account = crate::get_account(id); - account.generate_address().expect("error generating address") + let account_handle = crate::get_account(id); + account_handle.generate_address().expect("error generating address") }; Ok(neon_serde::to_value(&mut cx, &address)?) } @@ -196,8 +196,8 @@ declare_types! { method latestAddress(mut cx) { let this = cx.this(); let id = cx.borrow(&this, |r| r.0.clone()); - let account = crate::get_account(&id); - let account = account.read().unwrap(); + let account_handle = crate::get_account(&id); + let account = account_handle.read().unwrap(); let address = account.latest_address(); match address { Some(a) => Ok(neon_serde::to_value(&mut cx, &a)?), diff --git a/bindings/node/native/src/lib.rs b/bindings/node/native/src/lib.rs index 556e5b306..88c42a6fd 100644 --- a/bindings/node/native/src/lib.rs +++ b/bindings/node/native/src/lib.rs @@ -38,12 +38,12 @@ pub(crate) fn get_account(id: &AccountIdentifier) -> AccountHandle { map.get(id).expect("account dropped or not initialised").clone() } -pub(crate) fn store_account(account: AccountHandle) -> AccountIdentifier { +pub(crate) fn store_account(account_handle: AccountHandle) -> AccountIdentifier { let mut map = account_instances() .write() .expect("failed to lock account instances: store_account()"); - let id = account.id(); - map.insert(id.clone(), account); + let id = account_handle.id(); + map.insert(id.clone(), account_handle); id } diff --git a/examples/backup_and_restore.rs b/examples/backup_and_restore.rs index f9d3bcb16..c85330006 100644 --- a/examples/backup_and_restore.rs +++ b/examples/backup_and_restore.rs @@ -9,8 +9,8 @@ fn main() -> iota_wallet::Result<()> { // first we'll create an example account let client_options = ClientOptionsBuilder::node("https://nodes.devnet.iota.org:443")?.build(); - let account = manager.create_account(client_options).alias("alias").initialise()?; - let id = account.id(); + let account_handle = manager.create_account(client_options).alias("alias").initialise()?; + let id = account_handle.id(); // backup the stored accounts to ./backup/${backup_name} let backup_path = manager.backup("./backup")?; @@ -20,11 +20,11 @@ fn main() -> iota_wallet::Result<()> { // import the accounts from the backup and assert that it's the same manager.import_accounts(backup_path)?; - let imported_account = manager.get_account(&id)?; + let imported_account_handle = manager.get_account(&id)?; - let account_ = account.read().unwrap(); - let imported_account_ = imported_account.read().unwrap(); - assert_eq!(*account_, *imported_account_); + let account = account_handle.read().unwrap(); + let imported_account = imported_account_handle.read().unwrap(); + assert_eq!(*account, *imported_account); Ok(()) } diff --git a/src/account/mod.rs b/src/account/mod.rs index 552de9ce1..71331cc03 100644 --- a/src/account/mod.rs +++ b/src/account/mod.rs @@ -168,8 +168,8 @@ impl AccountInitialiser { } } - if let Some(latest_account) = accounts.values().last() { - let latest_account = latest_account.read().unwrap(); + if let Some(latest_account_handle) = accounts.values().last() { + let latest_account = latest_account_handle.read().unwrap(); if latest_account.messages().is_empty() && latest_account.total_balance() == 0 { return Err(crate::WalletError::LatestAccountIsEmpty); } @@ -450,7 +450,7 @@ impl Account { /// .create_account(client_options) /// .initialise() /// .expect("failed to add account"); - /// let account = account.read().unwrap(); + /// let account = account_handle.read().unwrap(); /// account.list_messages(10, 5, Some(MessageType::Received)); /// ``` pub fn list_messages(&self, count: usize, from: usize, message_type: Option) -> Vec<&Message> { @@ -580,16 +580,14 @@ mod tests { .build(); let account_id = { - let account = manager + let account_handle = manager .create_account(client_options) .alias("alias") .initialise() .expect("failed to add account"); - let mut account = account.write().unwrap(); - account.set_alias(updated_alias); - let id = account.id().clone(); - id + account_handle.set_alias(updated_alias); + account_handle.id() }; manager.stop_background_sync().unwrap(); diff --git a/src/account/sync/mod.rs b/src/account/sync/mod.rs index fab9f2523..eef010141 100644 --- a/src/account/sync/mod.rs +++ b/src/account/sync/mod.rs @@ -292,7 +292,7 @@ async fn perform_sync(mut account: &mut Account, address_index: usize, gap_limit /// Account sync helper. pub struct AccountSynchronizer { - account: AccountHandle, + account_handle: AccountHandle, address_index: usize, gap_limit: usize, skip_persistance: bool, @@ -300,13 +300,10 @@ pub struct AccountSynchronizer { impl AccountSynchronizer { /// Initialises a new instance of the sync helper. - pub(super) fn new(account: AccountHandle) -> Self { - let address_index = { - let account_ = account.read().unwrap(); - account_.addresses().len() - }; + pub(super) fn new(account_handle: AccountHandle) -> Self { + let address_index = account_handle.read().unwrap().addresses().len(); Self { - account, + account_handle, // by default we synchronize from the latest address (supposedly unspent) address_index: if address_index == 0 { 0 } else { address_index - 1 }, gap_limit: if address_index == 0 { 10 } else { 1 }, @@ -336,13 +333,13 @@ impl AccountSynchronizer { /// The account syncing process ensures that the latest metadata (balance, transactions) /// associated with an account is fetched from the tangle and is stored locally. pub async fn execute(self) -> crate::Result { - let options = self.account.client_options(); + let options = self.account_handle.client_options(); let client = get_client(&options); - let _ = crate::monitor::unsubscribe(self.account.clone()); + let _ = crate::monitor::unsubscribe(self.account_handle.clone()); let mut account_ = { - let account_ref = self.account.read().unwrap(); + let account_ref = self.account_handle.read().unwrap(); account_ref.clone() }; let message_ids_before_sync: Vec = account_.messages().iter().map(|m| *m.id()).collect(); @@ -351,15 +348,15 @@ impl AccountSynchronizer { let return_value = match perform_sync(&mut account_, self.address_index, self.gap_limit).await { Ok(is_empty) => { if !self.skip_persistance { - let mut account_ref = self.account.write().unwrap(); + let mut account_ref = self.account_handle.write().unwrap(); account_ref.set_addresses(account_.addresses().to_vec()); account_ref.set_messages(account_.messages().to_vec()); } - let account_ref = self.account.read().unwrap(); + let account_ref = self.account_handle.read().unwrap(); let synced_account = SyncedAccount { - account: self.account.clone(), + account_handle: self.account_handle.clone(), deposit_address: account_ref.latest_address().unwrap().clone(), is_empty, addresses: account_ref @@ -384,8 +381,8 @@ impl AccountSynchronizer { Err(e) => Err(e), }; - let _ = crate::monitor::monitor_account_addresses_balance(self.account.clone()); - let _ = crate::monitor::monitor_unconfirmed_messages(self.account.clone()); + let _ = crate::monitor::monitor_account_addresses_balance(self.account_handle.clone()); + let _ = crate::monitor::monitor_unconfirmed_messages(self.account_handle.clone()); return_value } @@ -405,7 +402,7 @@ pub struct SyncedAccount { /// The associated account identifier. #[serde(rename = "accountId", serialize_with = "serialize_as_id")] #[getset(get = "pub")] - account: AccountHandle, + account_handle: AccountHandle, /// The account's deposit address. #[serde(rename = "depositAddress")] #[getset(get = "pub")] @@ -478,7 +475,7 @@ impl SyncedAccount { return Err(crate::WalletError::ZeroAmount); } - let account_ = self.account.read().unwrap(); + let account_ = self.account_handle.read().unwrap(); // lock the transfer process until we select the input addresses // we do this to prevent multiple threads trying to transfer at the same time @@ -503,14 +500,14 @@ impl SyncedAccount { let (tx, rx) = channel(); let tx = Arc::new(Mutex::new(tx)); - let account = self.account.clone(); + let account_handle = self.account_handle.clone(); thread::spawn(move || { let tx = tx.lock().unwrap(); for _ in 1..30 { thread::sleep(OUTPUT_LOCK_TIMEOUT / 30); - let account_ = account.read().unwrap(); + let account = account_handle.read().unwrap(); // the account received an update and now the balance is sufficient - if value <= account_.available_balance() { + if value <= account.available_balance() { let _ = tx.send(()); break; } @@ -521,12 +518,12 @@ impl SyncedAccount { Ok(_) => {} Err(_) => { // if we got a timeout waiting for the account update, we try to sync it - self.account.sync().execute().await?; + self.account_handle.sync().execute().await?; } } } - let account_ = self.account.read().unwrap(); + let account_ = self.account_handle.read().unwrap(); let client = crate::client::get_client(account_.client_options()); let client = client.read().unwrap(); @@ -625,7 +622,7 @@ impl SyncedAccount { } drop(account_); - let mut account_ = self.account.write().unwrap(); + let mut account_ = self.account_handle.write().unwrap(); // if there's remainder value, we check the strategy defined in the transfer let mut remainder_value_deposit_address = None; @@ -726,28 +723,28 @@ impl SyncedAccount { for address in addresses_to_watch { // ignore errors because we fallback to the polling system - let _ = crate::monitor::monitor_address_balance(self.account.clone(), &address); + let _ = crate::monitor::monitor_address_balance(self.account_handle.clone(), &address); } // ignore errors because we fallback to the polling system - let _ = crate::monitor::monitor_confirmation_state_change(self.account.clone(), &message_id); + let _ = crate::monitor::monitor_confirmation_state_change(self.account_handle.clone(), &message_id); Ok(message) } /// Retry message. pub async fn retry(&self, message_id: &MessageId) -> crate::Result { - repost_message(self.account.clone(), message_id, RepostAction::Retry).await + repost_message(self.account_handle.clone(), message_id, RepostAction::Retry).await } /// Promote message. pub async fn promote(&self, message_id: &MessageId) -> crate::Result { - repost_message(self.account.clone(), message_id, RepostAction::Promote).await + repost_message(self.account_handle.clone(), message_id, RepostAction::Promote).await } /// Reattach message. pub async fn reattach(&self, message_id: &MessageId) -> crate::Result { - repost_message(self.account.clone(), message_id, RepostAction::Reattach).await + repost_message(self.account_handle.clone(), message_id, RepostAction::Reattach).await } } @@ -758,11 +755,11 @@ pub(crate) enum RepostAction { } pub(crate) async fn repost_message( - account: AccountHandle, + account_handle: AccountHandle, message_id: &MessageId, action: RepostAction, ) -> crate::Result { - let mut account = account.write().unwrap(); + let mut account = account_handle.write().unwrap(); let message = match account.get_message(message_id) { Some(message_to_repost) => { diff --git a/src/account_manager.rs b/src/account_manager.rs index 8e592454e..72760b56c 100644 --- a/src/account_manager.rs +++ b/src/account_manager.rs @@ -181,8 +181,8 @@ impl AccountManager { thread::sleep(sleep_duration); let accounts_ = accounts.read().unwrap(); - for account in accounts_.values() { - let mut account = account.write().unwrap(); + for account_handle in accounts_.values() { + let mut account = account_handle.write().unwrap(); let _ = account.save(); } @@ -201,8 +201,8 @@ impl AccountManager { let mut accounts = self.accounts.write().unwrap(); { - let account = accounts.get(&account_id).ok_or(crate::WalletError::AccountNotFound)?; - let account = account.read().unwrap(); + let account_handle = accounts.get(&account_id).ok_or(crate::WalletError::AccountNotFound)?; + let account = account_handle.read().unwrap(); if !(account.messages().is_empty() && account.total_balance() == 0) { return Err(crate::WalletError::MessageNotEmpty); @@ -363,8 +363,8 @@ impl AccountManager { async fn poll(accounts: AccountStore, storage_path: PathBuf, syncing: bool) -> crate::Result<()> { let retried = if syncing { let mut accounts_before_sync = Vec::new(); - for account in accounts.read().unwrap().values() { - accounts_before_sync.push(account.read().unwrap().clone()); + for account_handle in accounts.read().unwrap().values() { + accounts_before_sync.push(account_handle.read().unwrap().clone()); } let synced_accounts = sync_accounts(accounts.clone(), &storage_path, Some(0)).await?; let accounts_after_sync = accounts.read().unwrap(); @@ -417,9 +417,9 @@ async fn poll(accounts: AccountStore, storage_path: PathBuf, syncing: bool) -> c retry_unconfirmed_transactions(synced_accounts).await? } else { let mut retried_messages = vec![]; - for account in accounts.read().unwrap().values() { + for account_handle in accounts.read().unwrap().values() { let (account_id, unconfirmed_messages): (AccountIdentifier, Vec<(MessageId, Payload)>) = { - let account = account.read().unwrap(); + let account = account_handle.read().unwrap(); let account_id = account.id().clone(); let unconfirmed_messages = account .list_messages(account.messages().len(), 0, Some(MessageType::Unconfirmed)) @@ -432,7 +432,7 @@ async fn poll(accounts: AccountStore, storage_path: PathBuf, syncing: bool) -> c let mut promotions = vec![]; let mut reattachments = vec![]; for (message_id, payload) in unconfirmed_messages { - let new_message = repost_message(account.clone(), &message_id, RepostAction::Retry).await?; + let new_message = repost_message(account_handle.clone(), &message_id, RepostAction::Retry).await?; if new_message.payload() == &payload { reattachments.push(new_message); } else { @@ -493,18 +493,18 @@ async fn sync_accounts<'a>( { let mut accounts = accounts.write().unwrap(); - for account in accounts.values_mut() { - let mut sync = account.sync(); + for account_handle in accounts.values_mut() { + let mut sync = account_handle.sync(); if let Some(index) = address_index { sync = sync.address_index(index); } let synced_account = sync.execute().await?; - let account_ = account.read().unwrap(); + let account = account_handle.read().unwrap(); last_account = Some(( - account_.messages().is_empty() || account_.addresses().iter().all(|addr| *addr.balance() == 0), - account_.client_options().clone(), - account_.signer_type().clone(), + account.messages().is_empty() || account.addresses().iter().all(|addr| *addr.balance() == 0), + account.client_options().clone(), + account.signer_type().clone(), )); synced_accounts.push(synced_account); } @@ -524,12 +524,8 @@ async fn sync_accounts<'a>( if let Ok(discovered_accounts) = discovered_accounts_res { let mut accounts = accounts.write().unwrap(); - for (account, synced_account) in discovered_accounts { - let account_id = { - let account_ = account.read().unwrap(); - account_.id().clone() - }; - accounts.insert(account_id, account); + for (account_handle, synced_account) in discovered_accounts { + accounts.insert(account_handle.id(), account_handle); synced_accounts.push(synced_account); } } @@ -546,7 +542,7 @@ struct RetriedData { async fn retry_unconfirmed_transactions(synced_accounts: Vec) -> crate::Result> { let mut retried_messages = vec![]; for synced in synced_accounts { - let account = synced.account().read().unwrap(); + let account = synced.account_handle().read().unwrap(); let unconfirmed_messages = account.list_messages(account.messages().len(), 0, Some(MessageType::Unconfirmed)); let mut reattachments = vec![]; @@ -623,17 +619,17 @@ mod tests { .expect("invalid node URL") .build(); - let account = manager + let account_handle = manager .create_account(client_options) .alias("alias") .initialise() .expect("failed to add account"); - let account_ = account.read().unwrap(); + let account = account_handle.read().unwrap(); manager.stop_background_sync().unwrap(); manager - .remove_account(account_.id()) + .remove_account(account.id()) .expect("failed to remove account"); } } @@ -665,14 +661,14 @@ mod tests { ) .unwrap()]; - let account = manager + let account_handle = manager .create_account(client_options) .messages(messages) .initialise() .unwrap(); - let account_ = account.read().unwrap(); - let remove_response = manager.remove_account(account_.id()); + let account = account_handle.read().unwrap(); + let remove_response = manager.remove_account(account.id()); assert!(remove_response.is_err()); } } @@ -687,7 +683,7 @@ mod tests { .expect("invalid node URL") .build(); - let account = manager + let account_handle = manager .create_account(client_options) .addresses(vec![AddressBuilder::new() .balance(5) @@ -698,9 +694,9 @@ mod tests { .unwrap()]) .initialise() .unwrap(); - let account_ = account.read().unwrap(); + let account = account_handle.read().unwrap(); - let remove_response = manager.remove_account(account_.id()); + let remove_response = manager.remove_account(account.id()); assert!(remove_response.is_err()); } } diff --git a/src/actor/mod.rs b/src/actor/mod.rs index cb7b9d043..2417e9e6b 100644 --- a/src/actor/mod.rs +++ b/src/actor/mod.rs @@ -153,11 +153,11 @@ impl WalletMessageHandler { account_id: &AccountIdentifier, method: &AccountMethod, ) -> Result { - let account = self.account_manager.get_account(account_id)?; + let account_handle = self.account_manager.get_account(account_id)?; match method { AccountMethod::GenerateAddress => { - let address = account.generate_address()?; + let address = account_handle.generate_address()?; Ok(ResponseType::GeneratedAddress(address)) } AccountMethod::ListMessages { @@ -165,8 +165,8 @@ impl WalletMessageHandler { from, message_type, } => { - let account_ = account.read().unwrap(); - let messages: Vec = account_ + let account = account_handle.read().unwrap(); + let messages: Vec = account .list_messages(*count, *from, message_type.clone()) .into_iter() .cloned() @@ -174,28 +174,28 @@ impl WalletMessageHandler { Ok(ResponseType::Messages(messages)) } AccountMethod::ListAddresses { unspent } => { - let account_ = account.read().unwrap(); - let addresses = account_.list_addresses(*unspent).into_iter().cloned().collect(); + let account = account_handle.read().unwrap(); + let addresses = account.list_addresses(*unspent).into_iter().cloned().collect(); Ok(ResponseType::Addresses(addresses)) } AccountMethod::GetAvailableBalance => { - let account_ = account.read().unwrap(); - Ok(ResponseType::AvailableBalance(account_.available_balance())) + let account = account_handle.read().unwrap(); + Ok(ResponseType::AvailableBalance(account.available_balance())) } AccountMethod::GetTotalBalance => { - let account_ = account.read().unwrap(); - Ok(ResponseType::TotalBalance(account_.total_balance())) + let account = account_handle.read().unwrap(); + Ok(ResponseType::TotalBalance(account.total_balance())) } AccountMethod::GetLatestAddress => { - let account_ = account.read().unwrap(); - Ok(ResponseType::LatestAddress(account_.latest_address().cloned())) + let account = account_handle.read().unwrap(); + Ok(ResponseType::LatestAddress(account.latest_address().cloned())) } AccountMethod::SyncAccount { address_index, gap_limit, skip_persistance, } => { - let mut synchronizer = account.sync(); + let mut synchronizer = account_handle.sync(); if let Some(address_index) = address_index { synchronizer = synchronizer.address_index(*address_index); } @@ -238,24 +238,23 @@ impl WalletMessageHandler { ); } - builder.initialise().map(|account| { - let account = account.read().unwrap(); + builder.initialise().map(|account_handle| { + let account = account_handle.read().unwrap(); ResponseType::CreatedAccount(account.clone()) }) } fn get_account(&self, account_id: &AccountIdentifier) -> Result { - let account = self.account_manager.get_account(&account_id)?; - let account = account.read().unwrap(); + let account_handle = self.account_manager.get_account(&account_id)?; + let account = account_handle.read().unwrap(); Ok(ResponseType::ReadAccount(account.clone())) } fn get_accounts(&self) -> Result { let accounts = self.account_manager.get_accounts(); let mut accounts_ = Vec::new(); - for account in accounts { - let account_ = account.read().unwrap(); - accounts_.push(account_.clone()); + for account_handle in accounts { + accounts_.push(account_handle.read().unwrap().clone()); } Ok(ResponseType::ReadAccounts(accounts_)) } diff --git a/src/monitor.rs b/src/monitor.rs index 69596f56c..49955535c 100644 --- a/src/monitor.rs +++ b/src/monitor.rs @@ -42,9 +42,9 @@ struct AddressOutputPayloadAddress { } /// Unsubscribe from all topics associated with the account. -pub fn unsubscribe(account: AccountHandle) -> crate::Result<()> { - let account_ = account.read().unwrap(); - let client = crate::client::get_client(account_.client_options()); +pub fn unsubscribe(account_handle: AccountHandle) -> crate::Result<()> { + let account = account_handle.read().unwrap(); + let client = crate::client::get_client(account.client_options()); let mut client = client.write().unwrap(); client.subscriber().unsubscribe()?; Ok(()) @@ -62,20 +62,17 @@ fn subscribe_to_topic( } /// Monitor account addresses for balance changes. -pub fn monitor_account_addresses_balance(account: AccountHandle) -> crate::Result<()> { - let account_ = account.read().unwrap(); - for address in account_.addresses() { - monitor_address_balance(account.clone(), address.address())?; +pub fn monitor_account_addresses_balance(account_handle: AccountHandle) -> crate::Result<()> { + let account = account_handle.read().unwrap(); + for address in account.addresses() { + monitor_address_balance(account_handle.clone(), address.address())?; } Ok(()) } /// Monitor address for balance changes. -pub fn monitor_address_balance(account: AccountHandle, address: &IotaAddress) -> crate::Result<()> { - let client_options = { - let account_ = account.read().unwrap(); - account_.client_options().clone() - }; +pub fn monitor_address_balance(account_handle: AccountHandle, address: &IotaAddress) -> crate::Result<()> { + let client_options = account_handle.client_options(); let client_options_ = client_options.clone(); let address = address.clone(); @@ -86,11 +83,11 @@ pub fn monitor_address_balance(account: AccountHandle, address: &IotaAddress) -> let topic_event = topic_event.clone(); let address = address.clone(); let client_options = client_options.clone(); - let account = account.clone(); + let account_handle = account_handle.clone(); std::thread::spawn(move || { crate::block_on(async { - let _ = process_output(topic_event.payload.clone(), account, address, client_options).await; + let _ = process_output(topic_event.payload.clone(), account_handle, address, client_options).await; }); }); }, @@ -101,7 +98,7 @@ pub fn monitor_address_balance(account: AccountHandle, address: &IotaAddress) -> async fn process_output( payload: String, - account: AccountHandle, + account_handle: AccountHandle, address: IotaAddress, client_options: ClientOptions, ) -> crate::Result<()> { @@ -126,7 +123,7 @@ async fn process_output( client.get_message().data(&message_id_).await? }; - let mut account = account.write().unwrap(); + let mut account = account_handle.write().unwrap(); let account_id = account.id().clone(); let message_id_ = *message_id; @@ -154,25 +151,25 @@ async fn process_output( } /// Monitor the account's unconfirmed messages for confirmation state change. -pub fn monitor_unconfirmed_messages(account: AccountHandle) -> crate::Result<()> { - let account_ = account.read().unwrap(); - for message in account_.list_messages(0, 0, Some(MessageType::Unconfirmed)) { - monitor_confirmation_state_change(account.clone(), message.id())?; +pub fn monitor_unconfirmed_messages(account_handle: AccountHandle) -> crate::Result<()> { + let account = account_handle.read().unwrap(); + for message in account.list_messages(0, 0, Some(MessageType::Unconfirmed)) { + monitor_confirmation_state_change(account_handle.clone(), message.id())?; } Ok(()) } /// Monitor message for confirmation state. -pub fn monitor_confirmation_state_change(account: AccountHandle, message_id: &MessageId) -> crate::Result<()> { +pub fn monitor_confirmation_state_change(account_handle: AccountHandle, message_id: &MessageId) -> crate::Result<()> { let (message, client_options) = { - let account_ = account.read().unwrap(); - let message = account_ + let account = account_handle.read().unwrap(); + let message = account .messages() .iter() .find(|message| message.id() == message_id) .unwrap() .clone(); - (message, account_.client_options().clone()) + (message, account.client_options().clone()) }; let message_id = *message_id; @@ -180,8 +177,8 @@ pub fn monitor_confirmation_state_change(account: AccountHandle, message_id: &Me &client_options, format!("messages/{}/metadata", message_id.to_string()), move |topic_event| { - let account = account.clone(); - let _ = process_metadata(topic_event.payload.clone(), account, message_id, &message); + let account_handle = account_handle.clone(); + let _ = process_metadata(topic_event.payload.clone(), account_handle, message_id, &message); }, )?; Ok(()) @@ -189,7 +186,7 @@ pub fn monitor_confirmation_state_change(account: AccountHandle, message_id: &Me fn process_metadata( payload: String, - account: AccountHandle, + account_handle: AccountHandle, message_id: MessageId, message: &Message, ) -> crate::Result<()> { @@ -198,7 +195,7 @@ fn process_metadata( if let Some(inclusion_state) = metadata.ledger_inclusion_state { let confirmed = inclusion_state == "included"; if message.confirmed().is_none() || confirmed != message.confirmed().unwrap() { - let mut account = account.write().unwrap(); + let mut account = account_handle.write().unwrap(); let messages = account.messages_mut(); let account_message = messages.iter_mut().find(|m| m.id() == &message_id).unwrap(); From 9f5ed378dcfee2bddc7a0266b28a73da9d44553d Mon Sep 17 00:00:00 2001 From: Lucas Nogueira Date: Wed, 16 Dec 2020 16:20:19 -0300 Subject: [PATCH 16/29] perf(manager): get accounts by alias method --- src/account_manager.rs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/account_manager.rs b/src/account_manager.rs index 72760b56c..8a6e3993e 100644 --- a/src/account_manager.rs +++ b/src/account_manager.rs @@ -329,10 +329,12 @@ impl AccountManager { /// Gets the account associated with the given alias (case insensitive). pub fn get_account_by_alias>(&self, alias: S) -> Option { let alias = alias.into().to_lowercase(); - self.get_accounts().into_iter().find(|acc| { - let acc = acc.read().unwrap(); - acc.alias().to_lowercase() == alias - }) + self.accounts + .read() + .unwrap() + .values() + .find(|a| a.alias().to_lowercase().chars().zip(alias.chars()).all(|(x, y)| x == y)) + .cloned() } /// Gets all accounts from storage. From b9c51b6f8803dac4d68052335fe48a36716b7069 Mon Sep 17 00:00:00 2001 From: Lucas Nogueira Date: Wed, 16 Dec 2020 17:29:36 -0300 Subject: [PATCH 17/29] refactor(lib): use tokio's RwLock on AccountHandle --- README.md | 4 +- .../node/native/src/classes/account/mod.rs | 100 +++++----- .../node/native/src/classes/account/sync.rs | 28 +-- .../native/src/classes/account_manager/mod.rs | 14 +- bindings/node/native/src/lib.rs | 5 +- examples/account_operations.rs | 12 +- examples/backup_and_restore.rs | 19 +- examples/custom_storage.rs | 11 +- examples/transfer.rs | 13 +- src/account/mod.rs | 98 +++++----- src/account/sync/mod.rs | 35 ++-- src/account_manager.rs | 185 ++++++++++-------- src/actor/mod.rs | 62 +++--- src/lib.rs | 16 +- src/monitor.rs | 43 ++-- 15 files changed, 359 insertions(+), 286 deletions(-) diff --git a/README.md b/README.md index 2d26a8b3b..7bacea5f2 100644 --- a/README.md +++ b/README.md @@ -80,6 +80,7 @@ use iota_wallet::{ storage::sqlite::SqliteStorageAdapter, }; use std::path::PathBuf; + #[tokio::main] async fn main() -> iota_wallet::Result<()> { let storage_folder: PathBuf = "./my-db".into(); @@ -89,7 +90,8 @@ async fn main() -> iota_wallet::Result<()> { let account = manager .create_account(client_options) .signer_type(SignerType::EnvMnemonic) - .initialise()?; + .initialise() + .await?; Ok(()) } ``` diff --git a/bindings/node/native/src/classes/account/mod.rs b/bindings/node/native/src/classes/account/mod.rs index 77a38290a..28a9c5c3c 100644 --- a/bindings/node/native/src/classes/account/mod.rs +++ b/bindings/node/native/src/classes/account/mod.rs @@ -28,9 +28,7 @@ declare_types! { let this = cx.this(); let guard = cx.lock(); let id = &this.borrow(&guard).0; - let account_handle = crate::get_account(id); - let account = account_handle.read().unwrap(); - account.id().clone() + id.clone() }; match id { @@ -45,8 +43,7 @@ declare_types! { let guard = cx.lock(); let id = &this.borrow(&guard).0; let account_handle = crate::get_account(id); - let account = account_handle.read().unwrap(); - *account.index() + crate::block_on(async move { account_handle.index().await }) }; Ok(cx.number(index as f64).upcast()) @@ -58,8 +55,7 @@ declare_types! { let guard = cx.lock(); let id = &this.borrow(&guard).0; let account_handle = crate::get_account(id); - let account = account_handle.read().unwrap(); - account.alias().clone() + crate::block_on(async move { account_handle.alias().await }) }; Ok(cx.string(alias).upcast()) @@ -71,8 +67,7 @@ declare_types! { let guard = cx.lock(); let id = &this.borrow(&guard).0; let account_handle = crate::get_account(id); - let account = account_handle.read().unwrap(); - account.available_balance() + crate::block_on(async move { account_handle.available_balance().await }) }; Ok(cx.number(balance as f64).upcast()) } @@ -83,8 +78,7 @@ declare_types! { let guard = cx.lock(); let id = &this.borrow(&guard).0; let account_handle = crate::get_account(id); - let account = account_handle.read().unwrap(); - account.total_balance() + crate::block_on(async move { account_handle.total_balance().await }) }; Ok(cx.number(balance as f64).upcast()) } @@ -109,16 +103,18 @@ declare_types! { let this = cx.this(); let id = cx.borrow(&this, |r| r.0.clone()); let account_handle = crate::get_account(&id); - let account = account_handle.read().unwrap(); - let messages = account.list_messages(count, from, filter); - - let js_array = JsArray::new(&mut cx, messages.len() as u32); - for (index, message) in messages.iter().enumerate() { - let value = neon_serde::to_value(&mut cx, &message)?; - js_array.set(&mut cx, index as u32, value)?; - } + crate::block_on(async move { + let account = account_handle.read().await; + let messages = account.list_messages(count, from, filter); + + let js_array = JsArray::new(&mut cx, messages.len() as u32); + for (index, message) in messages.iter().enumerate() { + let value = neon_serde::to_value(&mut cx, &message)?; + js_array.set(&mut cx, index as u32, value)?; + } - Ok(js_array.upcast()) + Ok(js_array.upcast()) + }) } method listAddresses(mut cx) { @@ -130,16 +126,18 @@ declare_types! { let this = cx.this(); let id = cx.borrow(&this, |r| r.0.clone()); let account_handle = crate::get_account(&id); - let account = account_handle.read().unwrap(); - let addresses = account.list_addresses(unspent); - - let js_array = JsArray::new(&mut cx, addresses.len() as u32); - for (index, address) in addresses.iter().enumerate() { - let value = neon_serde::to_value(&mut cx, &address)?; - js_array.set(&mut cx, index as u32, value)?; - } + crate::block_on(async move { + let account = account_handle.read().await; + let addresses = account.list_addresses(unspent); + + let js_array = JsArray::new(&mut cx, addresses.len() as u32); + for (index, address) in addresses.iter().enumerate() { + let value = neon_serde::to_value(&mut cx, &address)?; + js_array.set(&mut cx, index as u32, value)?; + } - Ok(js_array.upcast()) + Ok(js_array.upcast()) + }) } method setAlias(mut cx) { @@ -149,8 +147,7 @@ declare_types! { let guard = cx.lock(); let id = &this.borrow(&guard).0; let account_handle = crate::get_account(id); - let mut account = account_handle.write().unwrap(); - account.set_alias(alias); + crate::block_on(async move { account_handle.set_alias(alias).await; }); } Ok(cx.undefined().upcast()) } @@ -163,8 +160,7 @@ declare_types! { let guard = cx.lock(); let id = &this.borrow(&guard).0; let account_handle = crate::get_account(id); - let mut account = account_handle.write().unwrap(); - account.set_client_options(client_options); + crate::block_on(async move { account_handle.set_client_options(client_options).await; }); } Ok(cx.undefined().upcast()) } @@ -173,13 +169,15 @@ declare_types! { let message_id = MessageId::from_str(cx.argument::(0)?.value().as_str()).expect("invalid message id length"); let this = cx.this(); let id = cx.borrow(&this, |r| r.0.clone()); - let account_handle = crate::get_account(&id); - let account = account_handle.read().unwrap(); - let message = account.get_message(&message_id); - match message { - Some(m) => Ok(neon_serde::to_value(&mut cx, &m)?), - None => Ok(cx.undefined().upcast()) - } + crate::block_on(async move { + let account_handle = crate::get_account(&id); + let account = account_handle.read().await; + let message = account.get_message(&message_id); + match message { + Some(m) => Ok(neon_serde::to_value(&mut cx, &m)?), + None => Ok(cx.undefined().upcast()) + } + }) } method generateAddress(mut cx) { @@ -187,8 +185,10 @@ declare_types! { let this = cx.this(); let guard = cx.lock(); let id = &this.borrow(&guard).0; - let account_handle = crate::get_account(id); - account_handle.generate_address().expect("error generating address") + crate::block_on(async move { + let account_handle = crate::get_account(id); + account_handle.generate_address().await.expect("error generating address") + }) }; Ok(neon_serde::to_value(&mut cx, &address)?) } @@ -196,13 +196,15 @@ declare_types! { method latestAddress(mut cx) { let this = cx.this(); let id = cx.borrow(&this, |r| r.0.clone()); - let account_handle = crate::get_account(&id); - let account = account_handle.read().unwrap(); - let address = account.latest_address(); - match address { - Some(a) => Ok(neon_serde::to_value(&mut cx, &a)?), - None => Ok(cx.undefined().upcast()) - } + crate::block_on(async move { + let account_handle = crate::get_account(&id); + let account = account_handle.read().await; + let address = account.latest_address(); + match address { + Some(a) => Ok(neon_serde::to_value(&mut cx, &a)?), + None => Ok(cx.undefined().upcast()) + } + }) } method sync(mut cx) { diff --git a/bindings/node/native/src/classes/account/sync.rs b/bindings/node/native/src/classes/account/sync.rs index 263d5aab1..cc5fc0a98 100644 --- a/bindings/node/native/src/classes/account/sync.rs +++ b/bindings/node/native/src/classes/account/sync.rs @@ -29,20 +29,22 @@ impl Task for SyncTask { type JsEvent = JsValue; fn perform(&self) -> Result { - let account = crate::get_account(&self.account_id); - let mut synchronizer = account.sync(); - if let Some(address_index) = self.options.address_index { - synchronizer = synchronizer.address_index(address_index); - } - if let Some(gap_limit) = self.options.gap_limit { - synchronizer = synchronizer.gap_limit(gap_limit); - } - if let Some(skip_persistance) = self.options.skip_persistance { - if skip_persistance { - synchronizer = synchronizer.skip_persistance(); + crate::block_on(async move { + let account = crate::get_account(&self.account_id); + let mut synchronizer = account.sync().await; + if let Some(address_index) = self.options.address_index { + synchronizer = synchronizer.address_index(address_index); } - } - crate::block_on(crate::convert_async_panics(|| async { synchronizer.execute().await })) + if let Some(gap_limit) = self.options.gap_limit { + synchronizer = synchronizer.gap_limit(gap_limit); + } + if let Some(skip_persistance) = self.options.skip_persistance { + if skip_persistance { + synchronizer = synchronizer.skip_persistance(); + } + } + synchronizer.execute().await + }) } fn complete(self, mut cx: TaskContext, value: Result) -> JsResult { diff --git a/bindings/node/native/src/classes/account_manager/mod.rs b/bindings/node/native/src/classes/account_manager/mod.rs index b528942c0..f73f85774 100644 --- a/bindings/node/native/src/classes/account_manager/mod.rs +++ b/bindings/node/native/src/classes/account_manager/mod.rs @@ -115,7 +115,7 @@ declare_types! { let guard = cx.lock(); let ref_ = &this.borrow(&guard).0; let mut manager = ref_.write().unwrap(); - manager.set_stronghold_password(password).expect("error setting stronghold password"); + crate::block_on(async move { manager.set_stronghold_password(password).await }).expect("error setting stronghold password"); } Ok(cx.undefined().upcast()) } @@ -126,7 +126,7 @@ declare_types! { let guard = cx.lock(); let ref_ = &this.borrow(&guard).0; let mut manager = ref_.write().unwrap(); - manager.start_background_sync(); + crate::block_on(async move { manager.start_background_sync().await }); } Ok(cx.undefined().upcast()) } @@ -170,7 +170,7 @@ declare_types! { .expect("invalid account created at format"), ); } - builder.initialise().expect("error creating account") + crate::block_on(async move { builder.initialise().await }).expect("error creating account") }; let id = crate::store_account(account); @@ -206,11 +206,11 @@ declare_types! { let guard = cx.lock(); let ref_ = &this.borrow(&guard).0; let manager = ref_.read().unwrap(); - manager.get_account_by_alias(alias) + crate::block_on(async move { manager.get_account_by_alias(alias).await }) }; match account { - Some(acc) => { - let id = crate::store_account(acc); + Some(account) => { + let id = crate::store_account(account); let id = cx.string(serde_json::to_string(&id).unwrap()); Ok(JsAccount::new(&mut cx, vec![id])?.upcast()) }, @@ -246,7 +246,7 @@ declare_types! { let guard = cx.lock(); let ref_ = &this.borrow(&guard).0; let manager = ref_.read().unwrap(); - manager.remove_account(&id).expect("error removing account") + crate::block_on(async move { manager.remove_account(&id).await }).expect("error removing account") }; Ok(cx.undefined().upcast()) } diff --git a/bindings/node/native/src/lib.rs b/bindings/node/native/src/lib.rs index 88c42a6fd..560ec1dd2 100644 --- a/bindings/node/native/src/lib.rs +++ b/bindings/node/native/src/lib.rs @@ -42,7 +42,10 @@ pub(crate) fn store_account(account_handle: AccountHandle) -> AccountIdentifier let mut map = account_instances() .write() .expect("failed to lock account instances: store_account()"); - let id = account_handle.id(); + + let handle = account_handle.clone(); + let id = block_on(async move { handle.id().await }); + map.insert(id.clone(), account_handle); id } diff --git a/examples/account_operations.rs b/examples/account_operations.rs index 49cdfd264..c27542b7a 100644 --- a/examples/account_operations.rs +++ b/examples/account_operations.rs @@ -6,21 +6,25 @@ use iota_wallet::{account_manager::AccountManager, client::ClientOptionsBuilder, #[tokio::main] async fn main() -> iota_wallet::Result<()> { let mut manager = AccountManager::new().unwrap(); - manager.set_stronghold_password("password").unwrap(); + manager.set_stronghold_password("password").await.unwrap(); // first we'll create an example account and store it let client_options = ClientOptionsBuilder::node("https://nodes.devnet.iota.org:443")?.build(); - let account = manager.create_account(client_options).alias("alias").initialise()?; + let account = manager + .create_account(client_options) + .alias("alias") + .initialise() + .await?; // update alias - account.set_alias("the new alias"); + account.set_alias("the new alias").await; // list unspent addresses let _ = account.list_addresses(false); // list spent addresses let _ = account.list_addresses(true); // generate a new unused address - let _ = account.generate_address()?; + let _ = account.generate_address().await?; // list messages let _ = account.list_messages(5, 0, Some(MessageType::Failed)); diff --git a/examples/backup_and_restore.rs b/examples/backup_and_restore.rs index c85330006..914ea35bc 100644 --- a/examples/backup_and_restore.rs +++ b/examples/backup_and_restore.rs @@ -3,27 +3,32 @@ use iota_wallet::{account_manager::AccountManager, client::ClientOptionsBuilder}; -fn main() -> iota_wallet::Result<()> { +#[tokio::main] +async fn main() -> iota_wallet::Result<()> { let mut manager = AccountManager::new().unwrap(); - manager.set_stronghold_password("password").unwrap(); + manager.set_stronghold_password("password").await.unwrap(); // first we'll create an example account let client_options = ClientOptionsBuilder::node("https://nodes.devnet.iota.org:443")?.build(); - let account_handle = manager.create_account(client_options).alias("alias").initialise()?; - let id = account_handle.id(); + let account_handle = manager + .create_account(client_options) + .alias("alias") + .initialise() + .await?; + let id = account_handle.id().await; // backup the stored accounts to ./backup/${backup_name} let backup_path = manager.backup("./backup")?; // delete the account on the current storage - manager.remove_account(&id)?; + manager.remove_account(&id).await?; // import the accounts from the backup and assert that it's the same manager.import_accounts(backup_path)?; let imported_account_handle = manager.get_account(&id)?; - let account = account_handle.read().unwrap(); - let imported_account = imported_account_handle.read().unwrap(); + let account = account_handle.read().await; + let imported_account = imported_account_handle.read().await; assert_eq!(*account, *imported_account); Ok(()) diff --git a/examples/custom_storage.rs b/examples/custom_storage.rs index f5a400da3..1d70cc0bd 100644 --- a/examples/custom_storage.rs +++ b/examples/custom_storage.rs @@ -59,15 +59,20 @@ impl StorageAdapter for MyStorage { } } -fn main() -> iota_wallet::Result<()> { +#[tokio::main] +async fn main() -> iota_wallet::Result<()> { let mut manager = AccountManager::with_storage_adapter("./example-database/sled", MyStorage::new("./example-database/sled")?) .unwrap(); - manager.set_stronghold_password("password").unwrap(); + manager.set_stronghold_password("password").await.unwrap(); // first we'll create an example account let client_options = ClientOptionsBuilder::node("https://nodes.devnet.iota.org:443")?.build(); - manager.create_account(client_options).alias("alias").initialise()?; + manager + .create_account(client_options) + .alias("alias") + .initialise() + .await?; Ok(()) } diff --git a/examples/transfer.rs b/examples/transfer.rs index 633e19bca..6f435c999 100644 --- a/examples/transfer.rs +++ b/examples/transfer.rs @@ -6,18 +6,25 @@ use iota_wallet::{account_manager::AccountManager, client::ClientOptionsBuilder, #[tokio::main] async fn main() -> iota_wallet::Result<()> { let mut manager = AccountManager::new().unwrap(); - manager.set_stronghold_password("password").unwrap(); + manager.set_stronghold_password("password").await.unwrap(); // first we'll create an example account and store it let client_options = ClientOptionsBuilder::node("https://nodes.devnet.iota.org:443")?.build(); - let account = manager.create_account(client_options).alias("alias").initialise()?; + let account = manager + .create_account(client_options) + .alias("alias") + .initialise() + .await?; // we need to synchronize with the Tangle first let sync_accounts = manager.sync_accounts().await?; let sync_account = sync_accounts.first().unwrap(); sync_account - .transfer(Transfer::new(account.latest_address().unwrap().address().clone(), 150)) + .transfer(Transfer::new( + account.latest_address().await.unwrap().address().clone(), + 150, + )) .await?; Ok(()) diff --git a/src/account/mod.rs b/src/account/mod.rs index 71331cc03..6323c95a3 100644 --- a/src/account/mod.rs +++ b/src/account/mod.rs @@ -14,13 +14,14 @@ use getset::{Getters, Setters}; use iota::message::prelude::MessageId; use once_cell::sync::OnceCell; use serde::{Deserialize, Serialize}; +use tokio::sync::RwLock; use std::{ collections::HashMap, convert::TryInto, ops::Deref, path::PathBuf, - sync::{Arc, Mutex, RwLock}, + sync::{Arc, Mutex}, }; mod sync; @@ -148,7 +149,7 @@ impl AccountInitialiser { } /// Initialises the account. - pub fn initialise(self) -> crate::Result { + pub async fn initialise(self) -> crate::Result { let mut accounts = self.accounts.write().unwrap(); let alias = self.alias.unwrap_or_else(|| format!("Account {}", accounts.len())); @@ -169,7 +170,7 @@ impl AccountInitialiser { } if let Some(latest_account_handle) = accounts.values().last() { - let latest_account = latest_account_handle.read().unwrap(); + let latest_account = latest_account_handle.read().await; if latest_account.messages().is_empty() && latest_account.total_balance() == 0 { return Err(crate::WalletError::LatestAccountIsEmpty); } @@ -272,8 +273,8 @@ macro_rules! guard_field_getters { impl $ty { $( #[$attr] - pub fn $x(&self) -> $ret { - self.0.read().unwrap().$x().clone() + pub async fn $x(&self) -> $ret { + self.0.read().await.$x().clone() } )* } @@ -296,13 +297,13 @@ guard_field_getters!( impl AccountHandle { /// Returns the builder to setup the process to synchronize this account with the Tangle. - pub fn sync(&self) -> AccountSynchronizer { - AccountSynchronizer::new(self.clone()) + pub async fn sync(&self) -> AccountSynchronizer { + AccountSynchronizer::new(self.clone()).await } /// Gets a new unused address and links it to this account. - pub fn generate_address(&self) -> crate::Result
{ - let mut account = self.0.write().unwrap(); + pub async fn generate_address(&self) -> crate::Result
{ + let mut account = self.0.write().await; let address = crate::address::get_new_address(&account)?; account.addresses.push(address.clone()); @@ -314,37 +315,37 @@ impl AccountHandle { } /// Bridge to [Account#latest_address](struct.Account.html#method.latest_address). - pub fn latest_address(&self) -> Option
{ - self.0.read().unwrap().latest_address().cloned() + pub async fn latest_address(&self) -> Option
{ + self.0.read().await.latest_address().cloned() } /// Bridge to [Account#total_balance](struct.Account.html#method.total_balance). - pub fn total_balance(&self) -> u64 { - self.0.read().unwrap().total_balance() + pub async fn total_balance(&self) -> u64 { + self.0.read().await.total_balance() } /// Bridge to [Account#available_balance](struct.Account.html#method.available_balance). - pub fn available_balance(&self) -> u64 { - self.0.read().unwrap().available_balance() + pub async fn available_balance(&self) -> u64 { + self.0.read().await.available_balance() } /// Bridge to [Account#set_alias](struct.Account.html#method.set_alias). - pub fn set_alias(&self, alias: impl AsRef) { - self.0.write().unwrap().set_alias(alias); + pub async fn set_alias(&self, alias: impl AsRef) { + self.0.write().await.set_alias(alias); } /// Bridge to [Account#set_client_options](struct.Account.html#method.set_client_options). - pub fn set_client_options(&self, options: ClientOptions) { - self.0.write().unwrap().set_client_options(options); + pub async fn set_client_options(&self, options: ClientOptions) { + self.0.write().await.set_client_options(options); } /// Bridge to [Account#list_messages](struct.Account.html#method.list_messages). /// This method clones the account's messages so when querying a large list of messages /// prefer using the `read` method to access the account instance. - pub fn list_messages(&self, count: usize, from: usize, message_type: Option) -> Vec { + pub async fn list_messages(&self, count: usize, from: usize, message_type: Option) -> Vec { self.0 .read() - .unwrap() + .await .list_messages(count, from, message_type) .into_iter() .cloned() @@ -354,10 +355,10 @@ impl AccountHandle { /// Bridge to [Account#list_addresses](struct.Account.html#method.list_addresses). /// This method clones the account's addresses so when querying a large list of addresses /// prefer using the `read` method to access the account instance. - pub fn list_addresses(&self, unspent: bool) -> Vec
{ + pub async fn list_addresses(&self, unspent: bool) -> Vec
{ self.0 .read() - .unwrap() + .await .list_addresses(unspent) .into_iter() .cloned() @@ -365,8 +366,8 @@ impl AccountHandle { } /// Bridge to [Account#get_message](struct.Account.html#method.get_message). - pub fn get_message(&self, message_id: &MessageId) -> Option { - self.0.read().unwrap().get_message(message_id).cloned() + pub async fn get_message(&self, message_id: &MessageId) -> Option { + self.0.read().await.get_message(message_id).cloned() } } @@ -450,7 +451,7 @@ impl Account { /// .create_account(client_options) /// .initialise() /// .expect("failed to add account"); - /// let account = account_handle.read().unwrap(); + /// let account = account_handle.read().await; /// account.list_messages(10, 5, Some(MessageType::Received)); /// ``` pub fn list_messages(&self, count: usize, from: usize, message_type: Option) -> Vec<&Message> { @@ -579,27 +580,30 @@ mod tests { .expect("invalid node URL") .build(); - let account_id = { - let account_handle = manager - .create_account(client_options) - .alias("alias") - .initialise() - .expect("failed to add account"); - - account_handle.set_alias(updated_alias); - account_handle.id() - }; - - manager.stop_background_sync().unwrap(); - - let account_in_storage = manager - .get_account(&account_id) - .expect("failed to get account from storage"); - let account_in_storage = account_in_storage.read().unwrap(); - assert_eq!( - account_in_storage.alias().to_string(), - updated_alias.to_string() - ); + crate::block_on(async move { + let account_id = { + let account_handle = manager + .create_account(client_options) + .alias("alias") + .initialise() + .await + .expect("failed to add account"); + + account_handle.set_alias(updated_alias).await; + account_handle.id().await + }; + + manager.stop_background_sync().unwrap(); + + let account_in_storage = manager + .get_account(&account_id) + .expect("failed to get account from storage"); + let account_in_storage = account_in_storage.read().await; + assert_eq!( + account_in_storage.alias().to_string(), + updated_alias.to_string() + ); + }); } } } diff --git a/src/account/sync/mod.rs b/src/account/sync/mod.rs index eef010141..949c674db 100644 --- a/src/account/sync/mod.rs +++ b/src/account/sync/mod.rs @@ -300,8 +300,8 @@ pub struct AccountSynchronizer { impl AccountSynchronizer { /// Initialises a new instance of the sync helper. - pub(super) fn new(account_handle: AccountHandle) -> Self { - let address_index = account_handle.read().unwrap().addresses().len(); + pub(super) async fn new(account_handle: AccountHandle) -> Self { + let address_index = account_handle.read().await.addresses().len(); Self { account_handle, // by default we synchronize from the latest address (supposedly unspent) @@ -333,13 +333,13 @@ impl AccountSynchronizer { /// The account syncing process ensures that the latest metadata (balance, transactions) /// associated with an account is fetched from the tangle and is stored locally. pub async fn execute(self) -> crate::Result { - let options = self.account_handle.client_options(); + let options = self.account_handle.client_options().await; let client = get_client(&options); let _ = crate::monitor::unsubscribe(self.account_handle.clone()); let mut account_ = { - let account_ref = self.account_handle.read().unwrap(); + let account_ref = self.account_handle.read().await; account_ref.clone() }; let message_ids_before_sync: Vec = account_.messages().iter().map(|m| *m.id()).collect(); @@ -348,12 +348,12 @@ impl AccountSynchronizer { let return_value = match perform_sync(&mut account_, self.address_index, self.gap_limit).await { Ok(is_empty) => { if !self.skip_persistance { - let mut account_ref = self.account_handle.write().unwrap(); + let mut account_ref = self.account_handle.write().await; account_ref.set_addresses(account_.addresses().to_vec()); account_ref.set_messages(account_.messages().to_vec()); } - let account_ref = self.account_handle.read().unwrap(); + let account_ref = self.account_handle.read().await; let synced_account = SyncedAccount { account_handle: self.account_handle.clone(), @@ -392,8 +392,10 @@ fn serialize_as_id(x: &AccountHandle, s: S) -> Result where S: Serializer, { - let account = x.read().unwrap(); - account.id().serialize(s) + crate::block_on(async move { + let account = x.read().await; + account.id().serialize(s) + }) } /// Data returned from account synchronization. @@ -475,7 +477,7 @@ impl SyncedAccount { return Err(crate::WalletError::ZeroAmount); } - let account_ = self.account_handle.read().unwrap(); + let account_ = self.account_handle.read().await; // lock the transfer process until we select the input addresses // we do this to prevent multiple threads trying to transfer at the same time @@ -505,7 +507,7 @@ impl SyncedAccount { let tx = tx.lock().unwrap(); for _ in 1..30 { thread::sleep(OUTPUT_LOCK_TIMEOUT / 30); - let account = account_handle.read().unwrap(); + let account = crate::block_on(async { account_handle.read().await }); // the account received an update and now the balance is sufficient if value <= account.available_balance() { let _ = tx.send(()); @@ -518,12 +520,12 @@ impl SyncedAccount { Ok(_) => {} Err(_) => { // if we got a timeout waiting for the account update, we try to sync it - self.account_handle.sync().execute().await?; + self.account_handle.sync().await.execute().await?; } } } - let account_ = self.account_handle.read().unwrap(); + let account_ = self.account_handle.read().await; let client = crate::client::get_client(account_.client_options()); let client = client.read().unwrap(); @@ -622,7 +624,7 @@ impl SyncedAccount { } drop(account_); - let mut account_ = self.account_handle.write().unwrap(); + let mut account_ = self.account_handle.write().await; // if there's remainder value, we check the strategy defined in the transfer let mut remainder_value_deposit_address = None; @@ -759,7 +761,7 @@ pub(crate) async fn repost_message( message_id: &MessageId, action: RepostAction, ) -> crate::Result { - let mut account = account_handle.write().unwrap(); + let mut account = account_handle.write().await; let message = match account.get_message(message_id) { Some(message_to_repost) => { @@ -830,11 +832,14 @@ mod tests { let client_options = ClientOptionsBuilder::node("https://nodes.devnet.iota.org:443") .unwrap() .build(); - let account = manager + crate::block_on(async move { + let account = manager .create_account(client_options) .alias("alias") .initialise() + .await .unwrap(); + }); // let synced_accounts = account.sync().execute().await.unwrap(); // TODO improve test when the node API is ready to use diff --git a/src/account_manager.rs b/src/account_manager.rs index 8a6e3993e..b68ff6f28 100644 --- a/src/account_manager.rs +++ b/src/account_manager.rs @@ -103,18 +103,18 @@ impl AccountManager { } /// Starts monitoring the accounts with the node's mqtt topics. - fn start_monitoring(&self) -> crate::Result<()> { + async fn start_monitoring(&self) -> crate::Result<()> { for account in self.accounts.read().unwrap().values() { - crate::monitor::monitor_account_addresses_balance(account.clone())?; - crate::monitor::monitor_unconfirmed_messages(account.clone())?; + crate::monitor::monitor_account_addresses_balance(account.clone()).await?; + crate::monitor::monitor_unconfirmed_messages(account.clone()).await?; } Ok(()) } /// Initialises the background polling and MQTT monitoring. - pub fn start_background_sync(&mut self) { + pub async fn start_background_sync(&mut self) { if !self.started_monitoring { - let monitoring_disabled = self.start_monitoring().is_err(); + let monitoring_disabled = self.start_monitoring().await.is_err(); self.polling_handle = Some(self.start_polling(monitoring_disabled)); self.started_monitoring = true; } @@ -135,7 +135,7 @@ impl AccountManager { } /// Sets the stronghold password. - pub fn set_stronghold_password>(&mut self, password: P) -> crate::Result<()> { + pub async fn set_stronghold_password>(&mut self, password: P) -> crate::Result<()> { let stronghold_path = self.storage_path.join(crate::storage::stronghold_snapshot_filename()); let stronghold = Stronghold::new( &stronghold_path, @@ -144,7 +144,7 @@ impl AccountManager { None, )?; crate::init_stronghold(&self.storage_path, stronghold); - self.start_background_sync(); + self.start_background_sync().await; self.load_accounts()?; Ok(()) @@ -157,12 +157,11 @@ impl AccountManager { let accounts = self.accounts.clone(); let stop = self.stop_polling.clone(); thread::spawn(move || { - let sleep_duration = interval / 2; while !stop.load(Ordering::Relaxed) { let storage_path_ = storage_path.clone(); - let accounts_ = accounts.clone(); + let accounts = accounts.clone(); crate::block_on(async move { - if let Err(panic) = AssertUnwindSafe(poll(accounts_, storage_path_, is_monitoring_disabled)) + if let Err(panic) = AssertUnwindSafe(poll(accounts.clone(), storage_path_, is_monitoring_disabled)) .catch_unwind() .await { @@ -176,17 +175,15 @@ impl AccountManager { let _error = crate::WalletError::UnknownError(msg); // when the error is dropped, the on_error event will be triggered } - }); - - thread::sleep(sleep_duration); - let accounts_ = accounts.read().unwrap(); - for account_handle in accounts_.values() { - let mut account = account_handle.write().unwrap(); - let _ = account.save(); - } + let accounts_ = accounts.read().unwrap(); + for account_handle in accounts_.values() { + let mut account = account_handle.write().await; + let _ = account.save(); + } + }); - thread::sleep(sleep_duration); + thread::sleep(interval); } }) } @@ -197,12 +194,12 @@ impl AccountManager { } /// Deletes an account. - pub fn remove_account(&self, account_id: &AccountIdentifier) -> crate::Result<()> { + pub async fn remove_account(&self, account_id: &AccountIdentifier) -> crate::Result<()> { let mut accounts = self.accounts.write().unwrap(); { let account_handle = accounts.get(&account_id).ok_or(crate::WalletError::AccountNotFound)?; - let account = account_handle.read().unwrap(); + let account = account_handle.read().await; if !(account.messages().is_empty() && account.total_balance() == 0) { return Err(crate::WalletError::MessageNotEmpty); @@ -237,12 +234,12 @@ impl AccountManager { let to_address = self .get_account(to_account_id)? .read() - .unwrap() + .await .latest_address() .ok_or_else(|| anyhow::anyhow!("destination account address list empty"))? .clone(); - let from_synchronized = self.get_account(from_account_id)?.sync().execute().await?; + let from_synchronized = self.get_account(from_account_id)?.sync().await.execute().await?; from_synchronized .transfer(Transfer::new(to_address.address().clone(), amount)) .await @@ -327,14 +324,21 @@ impl AccountManager { } /// Gets the account associated with the given alias (case insensitive). - pub fn get_account_by_alias>(&self, alias: S) -> Option { + pub async fn get_account_by_alias>(&self, alias: S) -> Option { let alias = alias.into().to_lowercase(); - self.accounts - .read() - .unwrap() - .values() - .find(|a| a.alias().to_lowercase().chars().zip(alias.chars()).all(|(x, y)| x == y)) - .cloned() + for account_handle in self.accounts.read().unwrap().values() { + let account = account_handle.read().await; + if account + .alias() + .to_lowercase() + .chars() + .zip(alias.chars()) + .all(|(x, y)| x == y) + { + return Some(account_handle.clone()); + } + } + None } /// Gets all accounts from storage. @@ -346,19 +350,19 @@ impl AccountManager { /// Reattaches an unconfirmed transaction. pub async fn reattach(&self, account_id: &AccountIdentifier, message_id: &MessageId) -> crate::Result { let account = self.get_account(account_id)?; - account.sync().execute().await?.reattach(message_id).await + account.sync().await.execute().await?.reattach(message_id).await } /// Promotes an unconfirmed transaction. pub async fn promote(&self, account_id: &AccountIdentifier, message_id: &MessageId) -> crate::Result { let account = self.get_account(account_id)?; - account.sync().execute().await?.promote(message_id).await + account.sync().await.execute().await?.promote(message_id).await } /// Retries an unconfirmed transaction. pub async fn retry(&self, account_id: &AccountIdentifier, message_id: &MessageId) -> crate::Result { let account = self.get_account(account_id)?; - account.sync().execute().await?.retry(message_id).await + account.sync().await.execute().await?.retry(message_id).await } } @@ -366,7 +370,7 @@ async fn poll(accounts: AccountStore, storage_path: PathBuf, syncing: bool) -> c let retried = if syncing { let mut accounts_before_sync = Vec::new(); for account_handle in accounts.read().unwrap().values() { - accounts_before_sync.push(account_handle.read().unwrap().clone()); + accounts_before_sync.push(account_handle.read().await.clone()); } let synced_accounts = sync_accounts(accounts.clone(), &storage_path, Some(0)).await?; let accounts_after_sync = accounts.read().unwrap(); @@ -378,7 +382,7 @@ async fn poll(accounts: AccountStore, storage_path: PathBuf, syncing: bool) -> c .find(|(id, _)| id == &account_before_sync.id()) .unwrap() .1; - let account_after_sync = account_after_sync.read().unwrap(); + let account_after_sync = account_after_sync.read().await; // balance event for address_before_sync in account_before_sync.addresses() { @@ -421,7 +425,7 @@ async fn poll(accounts: AccountStore, storage_path: PathBuf, syncing: bool) -> c let mut retried_messages = vec![]; for account_handle in accounts.read().unwrap().values() { let (account_id, unconfirmed_messages): (AccountIdentifier, Vec<(MessageId, Payload)>) = { - let account = account_handle.read().unwrap(); + let account = account_handle.read().await; let account_id = account.id().clone(); let unconfirmed_messages = account .list_messages(account.messages().len(), 0, Some(MessageType::Unconfirmed)) @@ -473,8 +477,8 @@ async fn discover_accounts( if let Some(signer_type) = &signer_type { account_initialiser = account_initialiser.signer_type(signer_type.clone()); } - let account = account_initialiser.initialise()?; - let synced_account = account.sync().execute().await?; + let account = account_initialiser.initialise().await?; + let synced_account = account.sync().await.execute().await?; let is_empty = *synced_account.is_empty(); if is_empty { break; @@ -496,13 +500,13 @@ async fn sync_accounts<'a>( { let mut accounts = accounts.write().unwrap(); for account_handle in accounts.values_mut() { - let mut sync = account_handle.sync(); + let mut sync = account_handle.sync().await; if let Some(index) = address_index { sync = sync.address_index(index); } let synced_account = sync.execute().await?; - let account = account_handle.read().unwrap(); + let account = account_handle.read().await; last_account = Some(( account.messages().is_empty() || account.addresses().iter().all(|addr| *addr.balance() == 0), account.client_options().clone(), @@ -527,7 +531,7 @@ async fn sync_accounts<'a>( if let Ok(discovered_accounts) = discovered_accounts_res { let mut accounts = accounts.write().unwrap(); for (account_handle, synced_account) in discovered_accounts { - accounts.insert(account_handle.id(), account_handle); + accounts.insert(account_handle.id().await, account_handle); synced_accounts.push(synced_account); } } @@ -544,7 +548,7 @@ struct RetriedData { async fn retry_unconfirmed_transactions(synced_accounts: Vec) -> crate::Result> { let mut retried_messages = vec![]; for synced in synced_accounts { - let account = synced.account_handle().read().unwrap(); + let account = synced.account_handle().read().await; let unconfirmed_messages = account.list_messages(account.messages().len(), 0, Some(MessageType::Unconfirmed)); let mut reattachments = vec![]; @@ -621,18 +625,22 @@ mod tests { .expect("invalid node URL") .build(); - let account_handle = manager - .create_account(client_options) - .alias("alias") - .initialise() - .expect("failed to add account"); - let account = account_handle.read().unwrap(); - - manager.stop_background_sync().unwrap(); - - manager - .remove_account(account.id()) - .expect("failed to remove account"); + crate::block_on(async move { + let account_handle = manager + .create_account(client_options) + .alias("alias") + .initialise() + .await + .expect("failed to add account"); + let account = account_handle.read().await; + + manager.stop_background_sync().unwrap(); + + manager + .remove_account(account.id()) + .await + .expect("failed to remove account"); + }); } } @@ -663,15 +671,18 @@ mod tests { ) .unwrap()]; - let account_handle = manager - .create_account(client_options) - .messages(messages) - .initialise() - .unwrap(); + crate::block_on(async move { + let account_handle = manager + .create_account(client_options) + .messages(messages) + .initialise() + .await + .unwrap(); - let account = account_handle.read().unwrap(); - let remove_response = manager.remove_account(account.id()); - assert!(remove_response.is_err()); + let account = account_handle.read().await; + let remove_response = manager.remove_account(account.id()).await; + assert!(remove_response.is_err()); + }); } } @@ -685,21 +696,24 @@ mod tests { .expect("invalid node URL") .build(); - let account_handle = manager - .create_account(client_options) - .addresses(vec![AddressBuilder::new() - .balance(5) - .key_index(0) - .address(IotaAddress::Ed25519(Ed25519Address::new([0; 32]))) - .outputs(vec![]) - .build() - .unwrap()]) - .initialise() - .unwrap(); - let account = account_handle.read().unwrap(); - - let remove_response = manager.remove_account(account.id()); - assert!(remove_response.is_err()); + crate::block_on(async move { + let account_handle = manager + .create_account(client_options) + .addresses(vec![AddressBuilder::new() + .balance(5) + .key_index(0) + .address(IotaAddress::Ed25519(Ed25519Address::new([0; 32]))) + .outputs(vec![]) + .build() + .unwrap()]) + .initialise() + .await + .unwrap(); + let account = account_handle.read().await; + + let remove_response = manager.remove_account(account.id()).await; + assert!(remove_response.is_err()); + }); } } @@ -713,14 +727,17 @@ mod tests { .expect("invalid node URL") .build(); - let account = manager - .create_account(client_options.clone()) - .alias("alias") - .initialise() - .expect("failed to add account"); + crate::block_on(async move { + let account = manager + .create_account(client_options.clone()) + .alias("alias") + .initialise() + .await + .expect("failed to add account"); - let create_response = manager.create_account(client_options).initialise(); - assert!(create_response.is_err()); + let create_response = manager.create_account(client_options).initialise().await; + assert!(create_response.is_err()); + }); } } } diff --git a/src/actor/mod.rs b/src/actor/mod.rs index 2417e9e6b..2f7324557 100644 --- a/src/actor/mod.rs +++ b/src/actor/mod.rs @@ -87,10 +87,16 @@ impl WalletMessageHandler { /// Handles a message. pub async fn handle(&mut self, message: Message) { let response: Result = match message.message_type() { - MessageType::RemoveAccount(account_id) => convert_panics(|| self.remove_account(account_id)), - MessageType::CreateAccount(account) => convert_panics(|| self.create_account(account)), - MessageType::GetAccount(account_id) => convert_panics(|| self.get_account(account_id)), - MessageType::GetAccounts => convert_panics(|| self.get_accounts()), + MessageType::RemoveAccount(account_id) => { + convert_async_panics(|| async { self.remove_account(account_id).await }).await + } + MessageType::CreateAccount(account) => { + convert_async_panics(|| async { self.create_account(account).await }).await + } + MessageType::GetAccount(account_id) => { + convert_async_panics(|| async { self.get_account(account_id).await }).await + } + MessageType::GetAccounts => convert_async_panics(|| async { self.get_accounts().await }).await, MessageType::CallAccountMethod { account_id, method } => { convert_async_panics(|| async { self.call_account_method(account_id, method).await }).await } @@ -100,7 +106,9 @@ impl WalletMessageHandler { } MessageType::Backup(destination_path) => convert_panics(|| self.backup(destination_path)), MessageType::RestoreBackup(backup_path) => convert_panics(|| self.restore_backup(backup_path)), - MessageType::SetStrongholdPassword(password) => convert_panics(|| self.set_stronghold_password(password)), + MessageType::SetStrongholdPassword(password) => { + convert_async_panics(|| async { self.set_stronghold_password(password).await }).await + } MessageType::SendTransfer { account_id, transfer } => { convert_async_panics(|| async { self.send_transfer(account_id, transfer).await }).await } @@ -157,7 +165,7 @@ impl WalletMessageHandler { match method { AccountMethod::GenerateAddress => { - let address = account_handle.generate_address()?; + let address = account_handle.generate_address().await?; Ok(ResponseType::GeneratedAddress(address)) } AccountMethod::ListMessages { @@ -165,7 +173,7 @@ impl WalletMessageHandler { from, message_type, } => { - let account = account_handle.read().unwrap(); + let account = account_handle.read().await; let messages: Vec = account .list_messages(*count, *from, message_type.clone()) .into_iter() @@ -174,20 +182,20 @@ impl WalletMessageHandler { Ok(ResponseType::Messages(messages)) } AccountMethod::ListAddresses { unspent } => { - let account = account_handle.read().unwrap(); + let account = account_handle.read().await; let addresses = account.list_addresses(*unspent).into_iter().cloned().collect(); Ok(ResponseType::Addresses(addresses)) } AccountMethod::GetAvailableBalance => { - let account = account_handle.read().unwrap(); + let account = account_handle.read().await; Ok(ResponseType::AvailableBalance(account.available_balance())) } AccountMethod::GetTotalBalance => { - let account = account_handle.read().unwrap(); + let account = account_handle.read().await; Ok(ResponseType::TotalBalance(account.total_balance())) } AccountMethod::GetLatestAddress => { - let account = account_handle.read().unwrap(); + let account = account_handle.read().await; Ok(ResponseType::LatestAddress(account.latest_address().cloned())) } AccountMethod::SyncAccount { @@ -195,7 +203,7 @@ impl WalletMessageHandler { gap_limit, skip_persistance, } => { - let mut synchronizer = account_handle.sync(); + let mut synchronizer = account_handle.sync().await; if let Some(address_index) = address_index { synchronizer = synchronizer.address_index(*address_index); } @@ -214,14 +222,15 @@ impl WalletMessageHandler { } /// The remove account message handler. - fn remove_account(&self, account_id: &AccountIdentifier) -> Result { + async fn remove_account(&self, account_id: &AccountIdentifier) -> Result { self.account_manager .remove_account(&account_id) + .await .map(|_| ResponseType::RemovedAccount(account_id.clone())) } /// The create account message handler. - fn create_account(&self, account: &AccountToCreate) -> Result { + async fn create_account(&self, account: &AccountToCreate) -> Result { let mut builder = self.account_manager.create_account(account.client_options.clone()); if let Some(mnemonic) = &account.mnemonic { @@ -238,35 +247,38 @@ impl WalletMessageHandler { ); } - builder.initialise().map(|account_handle| { - let account = account_handle.read().unwrap(); - ResponseType::CreatedAccount(account.clone()) - }) + match builder.initialise().await { + Ok(account_handle) => { + let account = account_handle.read().await; + Ok(ResponseType::CreatedAccount(account.clone())) + } + Err(e) => Err(e), + } } - fn get_account(&self, account_id: &AccountIdentifier) -> Result { + async fn get_account(&self, account_id: &AccountIdentifier) -> Result { let account_handle = self.account_manager.get_account(&account_id)?; - let account = account_handle.read().unwrap(); + let account = account_handle.read().await; Ok(ResponseType::ReadAccount(account.clone())) } - fn get_accounts(&self) -> Result { + async fn get_accounts(&self) -> Result { let accounts = self.account_manager.get_accounts(); let mut accounts_ = Vec::new(); for account_handle in accounts { - accounts_.push(account_handle.read().unwrap().clone()); + accounts_.push(account_handle.read().await.clone()); } Ok(ResponseType::ReadAccounts(accounts_)) } - fn set_stronghold_password(&mut self, password: &str) -> Result { - self.account_manager.set_stronghold_password(password)?; + async fn set_stronghold_password(&mut self, password: &str) -> Result { + self.account_manager.set_stronghold_password(password).await?; Ok(ResponseType::StrongholdPasswordSet) } async fn send_transfer(&self, account_id: &AccountIdentifier, transfer: &Transfer) -> Result { let account = self.account_manager.get_account(account_id)?; - let synced = account.sync().execute().await?; + let synced = account.sync().await.execute().await?; let message = synced.transfer(transfer.clone()).await?; Ok(ResponseType::SentTransfer(message)) } diff --git a/src/lib.rs b/src/lib.rs index 07dcf2ac7..2365a29c5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -169,13 +169,15 @@ mod test_utils { static MANAGER_INSTANCE: OnceCell> = OnceCell::new(); pub fn get_account_manager() -> &'static Mutex { MANAGER_INSTANCE.get_or_init(|| { - let storage_path: String = thread_rng().gen_ascii_chars().take(10).collect(); - let storage_path = PathBuf::from(format!("./example-database/{}", storage_path)); - - let mut manager = AccountManager::with_storage_path(storage_path).unwrap(); - manager.set_polling_interval(Duration::from_secs(4)); - manager.set_stronghold_password("password").unwrap(); - Mutex::new(manager) + crate::block_on(async move { + let storage_path: String = thread_rng().gen_ascii_chars().take(10).collect(); + let storage_path = PathBuf::from(format!("./example-database/{}", storage_path)); + + let mut manager = AccountManager::with_storage_path(storage_path).unwrap(); + manager.set_polling_interval(Duration::from_secs(4)); + manager.set_stronghold_password("password").await.unwrap(); + Mutex::new(manager) + }) }) } diff --git a/src/monitor.rs b/src/monitor.rs index 49955535c..a91c0d532 100644 --- a/src/monitor.rs +++ b/src/monitor.rs @@ -42,8 +42,8 @@ struct AddressOutputPayloadAddress { } /// Unsubscribe from all topics associated with the account. -pub fn unsubscribe(account_handle: AccountHandle) -> crate::Result<()> { - let account = account_handle.read().unwrap(); +pub async fn unsubscribe(account_handle: AccountHandle) -> crate::Result<()> { + let account = account_handle.read().await; let client = crate::client::get_client(account.client_options()); let mut client = client.write().unwrap(); client.subscriber().unsubscribe()?; @@ -62,17 +62,17 @@ fn subscribe_to_topic( } /// Monitor account addresses for balance changes. -pub fn monitor_account_addresses_balance(account_handle: AccountHandle) -> crate::Result<()> { - let account = account_handle.read().unwrap(); +pub async fn monitor_account_addresses_balance(account_handle: AccountHandle) -> crate::Result<()> { + let account = account_handle.read().await; for address in account.addresses() { - monitor_address_balance(account_handle.clone(), address.address())?; + monitor_address_balance(account_handle.clone(), address.address()).await?; } Ok(()) } /// Monitor address for balance changes. -pub fn monitor_address_balance(account_handle: AccountHandle, address: &IotaAddress) -> crate::Result<()> { - let client_options = account_handle.client_options(); +pub async fn monitor_address_balance(account_handle: AccountHandle, address: &IotaAddress) -> crate::Result<()> { + let client_options = account_handle.client_options().await; let client_options_ = client_options.clone(); let address = address.clone(); @@ -85,10 +85,8 @@ pub fn monitor_address_balance(account_handle: AccountHandle, address: &IotaAddr let client_options = client_options.clone(); let account_handle = account_handle.clone(); - std::thread::spawn(move || { - crate::block_on(async { - let _ = process_output(topic_event.payload.clone(), account_handle, address, client_options).await; - }); + crate::block_on(async { + let _ = process_output(topic_event.payload.clone(), account_handle, address, client_options).await; }); }, )?; @@ -123,7 +121,7 @@ async fn process_output( client.get_message().data(&message_id_).await? }; - let mut account = account_handle.write().unwrap(); + let mut account = account_handle.write().await; let account_id = account.id().clone(); let message_id_ = *message_id; @@ -151,18 +149,21 @@ async fn process_output( } /// Monitor the account's unconfirmed messages for confirmation state change. -pub fn monitor_unconfirmed_messages(account_handle: AccountHandle) -> crate::Result<()> { - let account = account_handle.read().unwrap(); +pub async fn monitor_unconfirmed_messages(account_handle: AccountHandle) -> crate::Result<()> { + let account = account_handle.read().await; for message in account.list_messages(0, 0, Some(MessageType::Unconfirmed)) { - monitor_confirmation_state_change(account_handle.clone(), message.id())?; + monitor_confirmation_state_change(account_handle.clone(), message.id()).await?; } Ok(()) } /// Monitor message for confirmation state. -pub fn monitor_confirmation_state_change(account_handle: AccountHandle, message_id: &MessageId) -> crate::Result<()> { +pub async fn monitor_confirmation_state_change( + account_handle: AccountHandle, + message_id: &MessageId, +) -> crate::Result<()> { let (message, client_options) = { - let account = account_handle.read().unwrap(); + let account = account_handle.read().await; let message = account .messages() .iter() @@ -178,13 +179,15 @@ pub fn monitor_confirmation_state_change(account_handle: AccountHandle, message_ format!("messages/{}/metadata", message_id.to_string()), move |topic_event| { let account_handle = account_handle.clone(); - let _ = process_metadata(topic_event.payload.clone(), account_handle, message_id, &message); + crate::block_on(async { + let _ = process_metadata(topic_event.payload.clone(), account_handle, message_id, &message).await; + }); }, )?; Ok(()) } -fn process_metadata( +async fn process_metadata( payload: String, account_handle: AccountHandle, message_id: MessageId, @@ -195,7 +198,7 @@ fn process_metadata( if let Some(inclusion_state) = metadata.ledger_inclusion_state { let confirmed = inclusion_state == "included"; if message.confirmed().is_none() || confirmed != message.confirmed().unwrap() { - let mut account = account_handle.write().unwrap(); + let mut account = account_handle.write().await; let messages = account.messages_mut(); let account_message = messages.iter_mut().find(|m| m.id() == &message_id).unwrap(); From d7482fb9816d51c124480cecf4a052e07834b9ee Mon Sep 17 00:00:00 2001 From: Lucas Nogueira Date: Wed, 16 Dec 2020 17:33:58 -0300 Subject: [PATCH 18/29] refactor(lib): use tokio's RwLock on Client --- src/account/sync/mod.rs | 10 +++++----- src/client.rs | 3 ++- src/monitor.rs | 17 ++++++++--------- 3 files changed, 15 insertions(+), 15 deletions(-) diff --git a/src/account/sync/mod.rs b/src/account/sync/mod.rs index 949c674db..d01afdb06 100644 --- a/src/account/sync/mod.rs +++ b/src/account/sync/mod.rs @@ -71,7 +71,7 @@ async fn sync_addresses( for (iota_address_index, iota_address_internal, iota_address) in &generated_iota_addresses { futures_.push(async move { let client = crate::client::get_client(account.client_options()); - let client = client.read().unwrap(); + let client = client.read().await; let address_outputs = client.get_address().outputs(&iota_address).await?; let balance = client.get_address().balance(&iota_address).await?; @@ -157,7 +157,7 @@ async fn sync_messages( let client_options = client_options.clone(); async move { let client = crate::client::get_client(&client_options); - let client = client.read().unwrap(); + let client = client.read().await; let address_outputs = client.get_address().outputs(address.address()).await?; let balance = client.get_address().balance(address.address()).await?; @@ -215,7 +215,7 @@ async fn update_account_messages<'a>( .filter(|message| message.confirmed().is_none()) .collect(); - let client = client.read().unwrap(); + let client = client.read().await; for message in unconfirmed_messages.iter_mut() { let metadata = client.get_message().metadata(message.id()).await?; @@ -528,7 +528,7 @@ impl SyncedAccount { let account_ = self.account_handle.read().await; let client = crate::client::get_client(account_.client_options()); - let client = client.read().unwrap(); + let client = client.read().await; if let RemainderValueStrategy::AccountAddress(ref remainder_target_address) = transfer_obj.remainder_value_strategy @@ -778,7 +778,7 @@ pub(crate) async fn repost_message( } let client = crate::client::get_client(account.client_options()); - let client = client.read().unwrap(); + let client = client.read().await; let (id, message) = match action { RepostAction::Promote => { diff --git a/src/client.rs b/src/client.rs index ac414a570..1fd650168 100644 --- a/src/client.rs +++ b/src/client.rs @@ -6,11 +6,12 @@ pub use iota::client::builder::Network; use iota::client::{BrokerOptions, Client, ClientBuilder}; use once_cell::sync::Lazy; use serde::{Deserialize, Serialize}; +use tokio::sync::RwLock; use url::Url; use std::{ collections::HashMap, - sync::{Arc, Mutex, RwLock}, + sync::{Arc, Mutex}, }; type ClientInstanceMap = Arc>>>>; diff --git a/src/monitor.rs b/src/monitor.rs index a91c0d532..fca24e99c 100644 --- a/src/monitor.rs +++ b/src/monitor.rs @@ -45,18 +45,18 @@ struct AddressOutputPayloadAddress { pub async fn unsubscribe(account_handle: AccountHandle) -> crate::Result<()> { let account = account_handle.read().await; let client = crate::client::get_client(account.client_options()); - let mut client = client.write().unwrap(); + let mut client = client.write().await; client.subscriber().unsubscribe()?; Ok(()) } -fn subscribe_to_topic( +async fn subscribe_to_topic( client_options: &ClientOptions, topic: String, handler: C, ) -> crate::Result<()> { let client = crate::client::get_client(&client_options); - let mut client = client.write().unwrap(); + let mut client = client.write().await; client.subscriber().topic(Topic::new(topic)?).subscribe(handler)?; Ok(()) } @@ -89,9 +89,8 @@ pub async fn monitor_address_balance(account_handle: AccountHandle, address: &Io let _ = process_output(topic_event.payload.clone(), account_handle, address, client_options).await; }); }, - )?; - - Ok(()) + ) + .await } async fn process_output( @@ -117,7 +116,7 @@ async fn process_output( let message = { let client = crate::client::get_client(&client_options_); - let client = client.read().unwrap(); + let client = client.read().await; client.get_message().data(&message_id_).await? }; @@ -183,8 +182,8 @@ pub async fn monitor_confirmation_state_change( let _ = process_metadata(topic_event.payload.clone(), account_handle, message_id, &message).await; }); }, - )?; - Ok(()) + ) + .await } async fn process_metadata( From 828cc2392bcd9791ed65af6a00d38933fafb473a Mon Sep 17 00:00:00 2001 From: Lucas Nogueira Date: Wed, 16 Dec 2020 17:39:32 -0300 Subject: [PATCH 19/29] refactor(lib): use tokio's RwLock on accountmanager accounts --- .../native/src/classes/account_manager/mod.rs | 6 +-- examples/backup_and_restore.rs | 2 +- src/account/mod.rs | 5 +- src/account_manager.rs | 54 ++++++++++--------- src/actor/mod.rs | 8 +-- 5 files changed, 39 insertions(+), 36 deletions(-) diff --git a/bindings/node/native/src/classes/account_manager/mod.rs b/bindings/node/native/src/classes/account_manager/mod.rs index f73f85774..f98929d9b 100644 --- a/bindings/node/native/src/classes/account_manager/mod.rs +++ b/bindings/node/native/src/classes/account_manager/mod.rs @@ -137,7 +137,7 @@ declare_types! { let guard = cx.lock(); let ref_ = &this.borrow(&guard).0; let mut manager = ref_.write().unwrap(); - manager.load_accounts().expect("failed to load accounts"); + crate::block_on(async move { manager.load_accounts().await }).expect("failed to load accounts"); } Ok(cx.undefined().upcast()) } @@ -187,7 +187,7 @@ declare_types! { let guard = cx.lock(); let ref_ = &this.borrow(&guard).0; let manager = ref_.read().unwrap(); - manager.get_account(&id) + crate::block_on(async move { manager.get_account(&id).await }) }; match account { Ok(acc) => { @@ -224,7 +224,7 @@ declare_types! { let guard = cx.lock(); let ref_ = &this.borrow(&guard).0; let manager = ref_.read().unwrap(); - manager.get_accounts() + crate::block_on(async move { manager.get_accounts().await }) }; let js_array = JsArray::new(&mut cx, accounts.len() as u32); diff --git a/examples/backup_and_restore.rs b/examples/backup_and_restore.rs index 914ea35bc..79881e89a 100644 --- a/examples/backup_and_restore.rs +++ b/examples/backup_and_restore.rs @@ -25,7 +25,7 @@ async fn main() -> iota_wallet::Result<()> { // import the accounts from the backup and assert that it's the same manager.import_accounts(backup_path)?; - let imported_account_handle = manager.get_account(&id)?; + let imported_account_handle = manager.get_account(&id).await?; let account = account_handle.read().await; let imported_account = imported_account_handle.read().await; diff --git a/src/account/mod.rs b/src/account/mod.rs index 6323c95a3..ed98176b2 100644 --- a/src/account/mod.rs +++ b/src/account/mod.rs @@ -150,7 +150,7 @@ impl AccountInitialiser { /// Initialises the account. pub async fn initialise(self) -> crate::Result { - let mut accounts = self.accounts.write().unwrap(); + let mut accounts = self.accounts.write().await; let alias = self.alias.unwrap_or_else(|| format!("Account {}", accounts.len())); let signer_type = self @@ -593,10 +593,11 @@ mod tests { account_handle.id().await }; - manager.stop_background_sync().unwrap(); + manager.stop_background_sync().await.unwrap(); let account_in_storage = manager .get_account(&account_id) + .await .expect("failed to get account from storage"); let account_in_storage = account_in_storage.read().await; assert_eq!( diff --git a/src/account_manager.rs b/src/account_manager.rs index b68ff6f28..f07cbc215 100644 --- a/src/account_manager.rs +++ b/src/account_manager.rs @@ -21,7 +21,7 @@ use std::{ path::{Path, PathBuf}, sync::{ atomic::{AtomicBool, Ordering}, - Arc, RwLock, + Arc, }, thread::{self, JoinHandle}, time::Duration, @@ -31,6 +31,7 @@ use futures::FutureExt; use getset::{Getters, Setters}; use iota::{MessageId, Payload}; use stronghold::Stronghold; +use tokio::sync::RwLock; /// The default storage path. pub const DEFAULT_STORAGE_PATH: &str = "./example-database"; @@ -56,7 +57,7 @@ pub struct AccountManager { impl Drop for AccountManager { fn drop(&mut self) { - let _ = self.stop_background_sync(); + let _ = crate::block_on(async { self.stop_background_sync().await }); } } @@ -90,8 +91,8 @@ impl AccountManager { } /// Loads the account from the storage into the manager instance. - pub fn load_accounts(&mut self) -> crate::Result<()> { - if self.accounts.read().unwrap().is_empty() { + pub async fn load_accounts(&mut self) -> crate::Result<()> { + if self.accounts.read().await.is_empty() { let accounts = crate::storage::with_adapter(&self.storage_path, |storage| storage.get_all())?; let accounts = crate::storage::parse_accounts(&self.storage_path, &accounts)? .into_iter() @@ -104,7 +105,7 @@ impl AccountManager { /// Starts monitoring the accounts with the node's mqtt topics. async fn start_monitoring(&self) -> crate::Result<()> { - for account in self.accounts.read().unwrap().values() { + for account in self.accounts.read().await.values() { crate::monitor::monitor_account_addresses_balance(account.clone()).await?; crate::monitor::monitor_unconfirmed_messages(account.clone()).await?; } @@ -121,8 +122,8 @@ impl AccountManager { } /// Stops the background polling and MQTT monitoring. - pub fn stop_background_sync(&mut self) -> crate::Result<()> { - for account in self.accounts.read().unwrap().values() { + pub async fn stop_background_sync(&mut self) -> crate::Result<()> { + for account in self.accounts.read().await.values() { let _ = crate::monitor::unsubscribe(account.clone()); } @@ -145,7 +146,7 @@ impl AccountManager { )?; crate::init_stronghold(&self.storage_path, stronghold); self.start_background_sync().await; - self.load_accounts()?; + self.load_accounts().await?; Ok(()) } @@ -176,7 +177,7 @@ impl AccountManager { // when the error is dropped, the on_error event will be triggered } - let accounts_ = accounts.read().unwrap(); + let accounts_ = accounts.read().await; for account_handle in accounts_.values() { let mut account = account_handle.write().await; let _ = account.save(); @@ -195,7 +196,7 @@ impl AccountManager { /// Deletes an account. pub async fn remove_account(&self, account_id: &AccountIdentifier) -> crate::Result<()> { - let mut accounts = self.accounts.write().unwrap(); + let mut accounts = self.accounts.write().await; { let account_handle = accounts.get(&account_id).ok_or(crate::WalletError::AccountNotFound)?; @@ -232,14 +233,15 @@ impl AccountManager { amount: u64, ) -> crate::Result { let to_address = self - .get_account(to_account_id)? + .get_account(to_account_id) + .await? .read() .await .latest_address() .ok_or_else(|| anyhow::anyhow!("destination account address list empty"))? .clone(); - let from_synchronized = self.get_account(from_account_id)?.sync().await.execute().await?; + let from_synchronized = self.get_account(from_account_id).await?.sync().await.execute().await?; from_synchronized .transfer(Transfer::new(to_address.address().clone(), amount)) .await @@ -315,8 +317,8 @@ impl AccountManager { } /// Gets the account associated with the given identifier. - pub fn get_account(&self, account_id: &AccountIdentifier) -> crate::Result { - let accounts = self.accounts.read().unwrap(); + pub async fn get_account(&self, account_id: &AccountIdentifier) -> crate::Result { + let accounts = self.accounts.read().await; accounts .get(account_id) .cloned() @@ -326,7 +328,7 @@ impl AccountManager { /// Gets the account associated with the given alias (case insensitive). pub async fn get_account_by_alias>(&self, alias: S) -> Option { let alias = alias.into().to_lowercase(); - for account_handle in self.accounts.read().unwrap().values() { + for account_handle in self.accounts.read().await.values() { let account = account_handle.read().await; if account .alias() @@ -342,26 +344,26 @@ impl AccountManager { } /// Gets all accounts from storage. - pub fn get_accounts(&self) -> Vec { - let accounts = self.accounts.read().unwrap(); + pub async fn get_accounts(&self) -> Vec { + let accounts = self.accounts.read().await; accounts.values().cloned().collect() } /// Reattaches an unconfirmed transaction. pub async fn reattach(&self, account_id: &AccountIdentifier, message_id: &MessageId) -> crate::Result { - let account = self.get_account(account_id)?; + let account = self.get_account(account_id).await?; account.sync().await.execute().await?.reattach(message_id).await } /// Promotes an unconfirmed transaction. pub async fn promote(&self, account_id: &AccountIdentifier, message_id: &MessageId) -> crate::Result { - let account = self.get_account(account_id)?; + let account = self.get_account(account_id).await?; account.sync().await.execute().await?.promote(message_id).await } /// Retries an unconfirmed transaction. pub async fn retry(&self, account_id: &AccountIdentifier, message_id: &MessageId) -> crate::Result { - let account = self.get_account(account_id)?; + let account = self.get_account(account_id).await?; account.sync().await.execute().await?.retry(message_id).await } } @@ -369,11 +371,11 @@ impl AccountManager { async fn poll(accounts: AccountStore, storage_path: PathBuf, syncing: bool) -> crate::Result<()> { let retried = if syncing { let mut accounts_before_sync = Vec::new(); - for account_handle in accounts.read().unwrap().values() { + for account_handle in accounts.read().await.values() { accounts_before_sync.push(account_handle.read().await.clone()); } let synced_accounts = sync_accounts(accounts.clone(), &storage_path, Some(0)).await?; - let accounts_after_sync = accounts.read().unwrap(); + let accounts_after_sync = accounts.read().await; // compare accounts to check for balance changes and new messages for account_before_sync in &accounts_before_sync { @@ -423,7 +425,7 @@ async fn poll(accounts: AccountStore, storage_path: PathBuf, syncing: bool) -> c retry_unconfirmed_transactions(synced_accounts).await? } else { let mut retried_messages = vec![]; - for account_handle in accounts.read().unwrap().values() { + for account_handle in accounts.read().await.values() { let (account_id, unconfirmed_messages): (AccountIdentifier, Vec<(MessageId, Payload)>) = { let account = account_handle.read().await; let account_id = account.id().clone(); @@ -498,7 +500,7 @@ async fn sync_accounts<'a>( let mut last_account = None; { - let mut accounts = accounts.write().unwrap(); + let mut accounts = accounts.write().await; for account_handle in accounts.values_mut() { let mut sync = account_handle.sync().await; if let Some(index) = address_index { @@ -529,7 +531,7 @@ async fn sync_accounts<'a>( }; if let Ok(discovered_accounts) = discovered_accounts_res { - let mut accounts = accounts.write().unwrap(); + let mut accounts = accounts.write().await; for (account_handle, synced_account) in discovered_accounts { accounts.insert(account_handle.id().await, account_handle); synced_accounts.push(synced_account); @@ -634,7 +636,7 @@ mod tests { .expect("failed to add account"); let account = account_handle.read().await; - manager.stop_background_sync().unwrap(); + manager.stop_background_sync().await.unwrap(); manager .remove_account(account.id()) diff --git a/src/actor/mod.rs b/src/actor/mod.rs index 2f7324557..91a8943a4 100644 --- a/src/actor/mod.rs +++ b/src/actor/mod.rs @@ -161,7 +161,7 @@ impl WalletMessageHandler { account_id: &AccountIdentifier, method: &AccountMethod, ) -> Result { - let account_handle = self.account_manager.get_account(account_id)?; + let account_handle = self.account_manager.get_account(account_id).await?; match method { AccountMethod::GenerateAddress => { @@ -257,13 +257,13 @@ impl WalletMessageHandler { } async fn get_account(&self, account_id: &AccountIdentifier) -> Result { - let account_handle = self.account_manager.get_account(&account_id)?; + let account_handle = self.account_manager.get_account(&account_id).await?; let account = account_handle.read().await; Ok(ResponseType::ReadAccount(account.clone())) } async fn get_accounts(&self) -> Result { - let accounts = self.account_manager.get_accounts(); + let accounts = self.account_manager.get_accounts().await; let mut accounts_ = Vec::new(); for account_handle in accounts { accounts_.push(account_handle.read().await.clone()); @@ -277,7 +277,7 @@ impl WalletMessageHandler { } async fn send_transfer(&self, account_id: &AccountIdentifier, transfer: &Transfer) -> Result { - let account = self.account_manager.get_account(account_id)?; + let account = self.account_manager.get_account(account_id).await?; let synced = account.sync().await.execute().await?; let message = synced.transfer(transfer.clone()).await?; Ok(ResponseType::SentTransfer(message)) From 9c0f5614a7c9a7647b6d440bf803c8d8691015c6 Mon Sep 17 00:00:00 2001 From: Lucas Nogueira Date: Wed, 16 Dec 2020 17:50:01 -0300 Subject: [PATCH 20/29] feat(manager): use polling system with tokio instead of thread::spawn --- Cargo.toml | 2 +- src/account/mod.rs | 4 +- src/account_manager.rs | 133 ++++++++++++++++++++++------------------- src/lib.rs | 9 ++- 4 files changed, 79 insertions(+), 69 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 06f3f871c..2596b5063 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,7 +15,7 @@ serde_repr = "0.1" once_cell = "1.4" iota-core = { git = "https://github.com/iotaledger/iota.rs", branch = "dev" } url = { version = "2.1", features = [ "serde" ] } -tokio = "0.2" +tokio = { version = "0.2", features = ["macros", "sync"] } rand = "0.3" rusqlite = { version = "0.23", features = ["bundled"], optional = true } slip10 = "0.4" diff --git a/src/account/mod.rs b/src/account/mod.rs index ed98176b2..bdce2b825 100644 --- a/src/account/mod.rs +++ b/src/account/mod.rs @@ -573,7 +573,7 @@ mod tests { #[test] fn set_alias() { let manager = crate::test_utils::get_account_manager(); - let mut manager = manager.lock().unwrap(); + let manager = manager.lock().unwrap(); let updated_alias = "updated alias"; let client_options = ClientOptionsBuilder::node("https://nodes.devnet.iota.org:443") @@ -593,8 +593,6 @@ mod tests { account_handle.id().await }; - manager.stop_background_sync().await.unwrap(); - let account_in_storage = manager .get_account(&account_id) .await diff --git a/src/account_manager.rs b/src/account_manager.rs index f07cbc215..965428080 100644 --- a/src/account_manager.rs +++ b/src/account_manager.rs @@ -19,11 +19,8 @@ use std::{ fs, panic::AssertUnwindSafe, path::{Path, PathBuf}, - sync::{ - atomic::{AtomicBool, Ordering}, - Arc, - }, - thread::{self, JoinHandle}, + sync::Arc, + thread, time::Duration, }; @@ -31,7 +28,13 @@ use futures::FutureExt; use getset::{Getters, Setters}; use iota::{MessageId, Payload}; use stronghold::Stronghold; -use tokio::sync::RwLock; +use tokio::{ + sync::{ + broadcast::{channel as broadcast_channel, Receiver as BroadcastReceiver, Sender as BroadcastSender}, + RwLock, + }, + time::{delay_for, Duration as AsyncDuration}, +}; /// The default storage path. pub const DEFAULT_STORAGE_PATH: &str = "./example-database"; @@ -49,15 +52,26 @@ pub struct AccountManager { /// the polling interval. #[getset(get = "pub", set = "pub")] polling_interval: Duration, - started_monitoring: bool, + background_sync_running: bool, accounts: AccountStore, - stop_polling: Arc, - polling_handle: Option>, + stop_polling_sender: Option>, } impl Drop for AccountManager { fn drop(&mut self) { - let _ = crate::block_on(async { self.stop_background_sync().await }); + let accounts = self.accounts.clone(); + let stop_polling_sender = self.stop_polling_sender.clone(); + thread::spawn(move || { + let _ = crate::block_on(async { + for account in accounts.read().await.values() { + let _ = crate::monitor::unsubscribe(account.clone()); + } + + if let Some(sender) = stop_polling_sender { + sender.send(()).expect("failed to stop polling process"); + } + }); + }); } } @@ -82,10 +96,9 @@ impl AccountManager { let instance = Self { storage_path: storage_path.as_ref().to_path_buf(), polling_interval: Duration::from_millis(30_000), - started_monitoring: false, + background_sync_running: false, accounts: Default::default(), - stop_polling: Arc::new(AtomicBool::new(false)), - polling_handle: None, + stop_polling_sender: None, }; Ok(instance) } @@ -114,25 +127,13 @@ impl AccountManager { /// Initialises the background polling and MQTT monitoring. pub async fn start_background_sync(&mut self) { - if !self.started_monitoring { + if !self.background_sync_running { let monitoring_disabled = self.start_monitoring().await.is_err(); - self.polling_handle = Some(self.start_polling(monitoring_disabled)); - self.started_monitoring = true; - } - } - - /// Stops the background polling and MQTT monitoring. - pub async fn stop_background_sync(&mut self) -> crate::Result<()> { - for account in self.accounts.read().await.values() { - let _ = crate::monitor::unsubscribe(account.clone()); - } - - if let Some(handle) = self.polling_handle.take() { - self.stop_polling.store(true, Ordering::Relaxed); - handle.join().expect("failed to stop polling thread"); + let (stop_polling_sender, stop_polling_receiver) = broadcast_channel(1); + self.start_polling(monitoring_disabled, stop_polling_receiver); + self.stop_polling_sender = Some(stop_polling_sender); + self.background_sync_running = true; } - - Ok(()) } /// Sets the stronghold password. @@ -152,41 +153,49 @@ impl AccountManager { } /// Starts the polling mechanism. - fn start_polling(&self, is_monitoring_disabled: bool) -> JoinHandle<()> { + fn start_polling(&self, is_monitoring_disabled: bool, mut stop: BroadcastReceiver<()>) { let storage_path = self.storage_path.clone(); - let interval = self.polling_interval; let accounts = self.accounts.clone(); - let stop = self.stop_polling.clone(); - thread::spawn(move || { - while !stop.load(Ordering::Relaxed) { - let storage_path_ = storage_path.clone(); - let accounts = accounts.clone(); - crate::block_on(async move { - if let Err(panic) = AssertUnwindSafe(poll(accounts.clone(), storage_path_, is_monitoring_disabled)) - .catch_unwind() - .await - { - let msg = if let Some(message) = panic.downcast_ref::() { - format!("Internal error: {}", message) - } else if let Some(message) = panic.downcast_ref::<&str>() { - format!("Internal error: {}", message) - } else { - "Internal error".to_string() - }; - let _error = crate::WalletError::UnknownError(msg); - // when the error is dropped, the on_error event will be triggered - } - let accounts_ = accounts.read().await; - for account_handle in accounts_.values() { - let mut account = account_handle.write().await; - let _ = account.save(); + let interval = AsyncDuration::from_millis(self.polling_interval.as_millis().try_into().unwrap()); + + thread::spawn(move || { + crate::enter(|| { + tokio::spawn(async move { + loop { + tokio::select! { + _ = async { + let storage_path_ = storage_path.clone(); + let accounts_ = accounts.clone(); + + if let Err(panic) = AssertUnwindSafe(poll(accounts_.clone(), storage_path_, is_monitoring_disabled)) + .catch_unwind() + .await { + let msg = if let Some(message) = panic.downcast_ref::() { + format!("Internal error: {}", message) + } else if let Some(message) = panic.downcast_ref::<&str>() { + format!("Internal error: {}", message) + } else { + "Internal error".to_string() + }; + let _error = crate::WalletError::UnknownError(msg); + // when the error is dropped, the on_error event will be triggered + } + + let accounts_ = accounts_.read().await; + for account_handle in accounts_.values() { + let mut account = account_handle.write().await; + let _ = account.save(); + } + + delay_for(interval).await; + } => {} + _ = stop.recv() => {} + } } }); - - thread::sleep(interval); - } - }) + }); + }); } /// Adds a new account. @@ -621,7 +630,7 @@ mod tests { #[test] fn store_accounts() { let manager = crate::test_utils::get_account_manager(); - let mut manager = manager.lock().unwrap(); + let manager = manager.lock().unwrap(); let client_options = ClientOptionsBuilder::node("https://nodes.devnet.iota.org:443") .expect("invalid node URL") @@ -636,8 +645,6 @@ mod tests { .expect("failed to add account"); let account = account_handle.read().await; - manager.stop_background_sync().await.unwrap(); - manager .remove_account(account.id()) .await diff --git a/src/lib.rs b/src/lib.rs index 2365a29c5..d23cf0817 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -41,6 +41,7 @@ use stronghold::Stronghold; use tokio::runtime::Runtime; static STRONGHOLD_INSTANCE: OnceCell>>> = OnceCell::new(); +static RUNTIME: OnceCell> = OnceCell::new(); /// The wallet error type. #[derive(Debug, thiserror::Error)] @@ -153,11 +154,15 @@ pub(crate) fn with_stronghold_from_path T>(path: &P } pub(crate) fn block_on(cb: C) -> C::Output { - static INSTANCE: OnceCell> = OnceCell::new(); - let runtime = INSTANCE.get_or_init(|| Mutex::new(Runtime::new().unwrap())); + let runtime = RUNTIME.get_or_init(|| Mutex::new(Runtime::new().unwrap())); runtime.lock().unwrap().block_on(cb) } +pub(crate) fn enter R>(cb: C) -> R { + let runtime = RUNTIME.get_or_init(|| Mutex::new(Runtime::new().unwrap())); + runtime.lock().unwrap().enter(cb) +} + #[cfg(test)] mod test_utils { use super::account_manager::AccountManager; From bbc280c7a5583a5ff58359ed30fd44cc4eef7e17 Mon Sep 17 00:00:00 2001 From: Lucas Nogueira Date: Wed, 16 Dec 2020 18:01:07 -0300 Subject: [PATCH 21/29] chore(bindings): rename variable --- bindings/node/native/src/classes/account_manager/mod.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bindings/node/native/src/classes/account_manager/mod.rs b/bindings/node/native/src/classes/account_manager/mod.rs index f98929d9b..0831c194b 100644 --- a/bindings/node/native/src/classes/account_manager/mod.rs +++ b/bindings/node/native/src/classes/account_manager/mod.rs @@ -190,8 +190,8 @@ declare_types! { crate::block_on(async move { manager.get_account(&id).await }) }; match account { - Ok(acc) => { - let id = crate::store_account(acc); + Ok(account) => { + let id = crate::store_account(account); let id = cx.string(serde_json::to_string(&id).unwrap()); Ok(JsAccount::new(&mut cx, vec![id])?.upcast()) }, From 285a3260717f05bede27d0012dd4f4f251207b3d Mon Sep 17 00:00:00 2001 From: Lucas Nogueira Date: Wed, 16 Dec 2020 18:18:41 -0300 Subject: [PATCH 22/29] refactor(account): perform mutable operations with `do_mut` --- src/account/mod.rs | 19 +++++++++++-------- src/account/sync/mod.rs | 38 +++++++++++++++++++++++++------------- src/monitor.rs | 18 ++++++++++++------ 3 files changed, 48 insertions(+), 27 deletions(-) diff --git a/src/account/mod.rs b/src/account/mod.rs index bdce2b825..6248fd2f9 100644 --- a/src/account/mod.rs +++ b/src/account/mod.rs @@ -305,9 +305,10 @@ impl AccountHandle { pub async fn generate_address(&self) -> crate::Result
{ let mut account = self.0.write().await; let address = crate::address::get_new_address(&account)?; - account.addresses.push(address.clone()); - account.has_pending_changes = true; + account.do_mut(|account| { + account.addresses.push(address.clone()); + }); let _ = crate::monitor::monitor_address_balance(self.clone(), address.address()); @@ -372,6 +373,12 @@ impl AccountHandle { } impl Account { + pub(crate) fn do_mut(&mut self, f: impl FnOnce(&mut Self) -> R) -> R { + let res = f(self); + self.has_pending_changes = true; + res + } + /// Returns the most recent address of the account. pub fn latest_address(&self) -> Option<&Address> { self.addresses @@ -522,15 +529,11 @@ impl Account { self.has_pending_changes = true; } - #[doc(hidden)] - pub fn addresses_mut(&mut self) -> &mut Vec
{ - self.has_pending_changes = true; + pub(crate) fn addresses_mut(&mut self) -> &mut Vec
{ &mut self.addresses } - #[doc(hidden)] - pub fn messages_mut(&mut self) -> &mut Vec { - self.has_pending_changes = true; + pub(crate) fn messages_mut(&mut self) -> &mut Vec { &mut self.messages } diff --git a/src/account/sync/mod.rs b/src/account/sync/mod.rs index d01afdb06..45a969a47 100644 --- a/src/account/sync/mod.rs +++ b/src/account/sync/mod.rs @@ -199,29 +199,41 @@ async fn update_account_messages<'a>( new_messages: &'a [(MessageId, Option, IotaMessage)], ) -> crate::Result<()> { let client = get_client(account.client_options()); - let messages = account.messages_mut(); - // sync `broadcasted` state - messages - .iter_mut() - .filter(|message| !message.broadcasted() && new_messages.iter().any(|(id, _, _)| id == message.id())) - .for_each(|message| { - message.set_broadcasted(true); - }); + account.do_mut(|account| { + let messages = account.messages_mut(); + + // sync `broadcasted` state + messages + .iter_mut() + .filter(|message| !message.broadcasted() && new_messages.iter().any(|(id, _, _)| id == message.id())) + .for_each(|message| { + message.set_broadcasted(true); + }); + }); // sync `confirmed` state - let mut unconfirmed_messages: Vec<&mut Message> = messages - .iter_mut() + let unconfirmed_message_ids: Vec = account + .messages() + .iter() .filter(|message| message.confirmed().is_none()) + .map(|m| *m.id()) .collect(); let client = client.read().await; - for message in unconfirmed_messages.iter_mut() { - let metadata = client.get_message().metadata(message.id()).await?; + for message_id in unconfirmed_message_ids { + let metadata = client.get_message().metadata(&message_id).await?; if let Some(inclusion_state) = metadata.ledger_inclusion_state { let confirmed = inclusion_state == "included"; - message.set_confirmed(Some(confirmed)); + account.do_mut(|account| { + let message = account + .messages_mut() + .iter_mut() + .find(|m| m.id() == &message_id) + .unwrap(); + message.set_confirmed(Some(confirmed)); + }); } } diff --git a/src/monitor.rs b/src/monitor.rs index fca24e99c..6b80c372d 100644 --- a/src/monitor.rs +++ b/src/monitor.rs @@ -131,8 +131,10 @@ async fn process_output( match account.messages_mut().iter().position(|m| m.id() == &message_id_) { Some(message_index) => { - let message = &mut account.messages_mut()[message_index]; - message.set_confirmed(Some(true)); + account.do_mut(|account| { + let message = &mut account.messages_mut()[message_index]; + message.set_confirmed(Some(true)); + }); } None => { let message = Message::from_iota_message(message_id_, account.addresses(), &message, Some(true)).unwrap(); @@ -141,7 +143,9 @@ async fn process_output( account.id(), &message, ); - account.messages_mut().push(message); + account.do_mut(|account| { + account.messages_mut().push(message); + }); } } Ok(()) @@ -199,9 +203,11 @@ async fn process_metadata( if message.confirmed().is_none() || confirmed != message.confirmed().unwrap() { let mut account = account_handle.write().await; - let messages = account.messages_mut(); - let account_message = messages.iter_mut().find(|m| m.id() == &message_id).unwrap(); - account_message.set_confirmed(Some(confirmed)); + account.do_mut(|account| { + let messages = account.messages_mut(); + let account_message = messages.iter_mut().find(|m| m.id() == &message_id).unwrap(); + account_message.set_confirmed(Some(confirmed)); + }); crate::event::emit_confirmation_state_change(account.id(), &message, confirmed); } From 2a00b38983972ce1921cfc2ddf8d2cac4ef54048 Mon Sep 17 00:00:00 2001 From: Lucas Nogueira Date: Wed, 16 Dec 2020 18:45:29 -0300 Subject: [PATCH 23/29] refactor(manager): remove `load_accounts` and `start_background_sync` --- README.md | 2 +- bindings/node/README.md | 4 - bindings/node/lib/index.d.ts | 1 - .../node/native/src/classes/account/mod.rs | 6 -- .../native/src/classes/account_manager/mod.rs | 26 +------ bindings/node/native/src/lib.rs | 7 -- examples/account_operations.rs | 2 +- examples/actor.rs | 12 +-- examples/backup_and_restore.rs | 2 +- examples/custom_storage.rs | 1 + examples/transfer.rs | 2 +- src/account_manager.rs | 76 ++++++++++--------- src/actor/mod.rs | 24 ++---- src/lib.rs | 9 ++- src/signing/stronghold.rs | 13 ++-- src/storage/stronghold.rs | 5 +- 16 files changed, 76 insertions(+), 116 deletions(-) diff --git a/README.md b/README.md index 7bacea5f2..3ba9f75dc 100644 --- a/README.md +++ b/README.md @@ -85,7 +85,7 @@ use std::path::PathBuf; async fn main() -> iota_wallet::Result<()> { let storage_folder: PathBuf = "./my-db".into(); let manager = - AccountManager::with_storage_adapter(&storage_folder, SqliteStorageAdapter::new(&storage_folder, "accounts")?)?; + AccountManager::with_storage_adapter(&storage_folder, SqliteStorageAdapter::new(&storage_folder, "accounts")?).await?; let client_options = ClientOptionsBuilder::node("http://api.lb-0.testnet.chrysalis2.com")?.build(); let account = manager .create_account(client_options) diff --git a/bindings/node/README.md b/bindings/node/README.md index 895bf7c39..62816f2ef 100644 --- a/bindings/node/README.md +++ b/bindings/node/README.md @@ -63,10 +63,6 @@ Creates a new instance of the AccountManager. | [storagePath] | string | undefined | The path where the database file will be saved | | [storageType] | number | undefined | The type of the database. Stronghold = 1, Sqlite = 2 | -#### startBackgroundSync(): void - -Initialises the background polling mechanism and MQTT monitoring. Automatically called on `setStrongholdPassword`. - #### setStrongholdPassword(password): void Sets the stronghold password and initialises it. diff --git a/bindings/node/lib/index.d.ts b/bindings/node/lib/index.d.ts index 9a15d7338..280151448 100644 --- a/bindings/node/lib/index.d.ts +++ b/bindings/node/lib/index.d.ts @@ -128,7 +128,6 @@ export declare interface ManagerOptions { export declare class AccountManager { constructor(storagePath?: string) - startBackgroundSync(): void setStrongholdPassword(password: string): void createAccount(account: AccountToCreate): Account getAccount(accountId: string | number): Account | undefined diff --git a/bindings/node/native/src/classes/account/mod.rs b/bindings/node/native/src/classes/account/mod.rs index 28a9c5c3c..fd28b36e1 100644 --- a/bindings/node/native/src/classes/account/mod.rs +++ b/bindings/node/native/src/classes/account/mod.rs @@ -10,12 +10,6 @@ mod sync; pub struct AccountWrapper(pub AccountIdentifier); -impl Drop for AccountWrapper { - fn drop(&mut self) { - crate::remove_account(&self.0); - } -} - declare_types! { pub class JsAccount for AccountWrapper { init(mut cx) { diff --git a/bindings/node/native/src/classes/account_manager/mod.rs b/bindings/node/native/src/classes/account_manager/mod.rs index 0831c194b..3cf541744 100644 --- a/bindings/node/native/src/classes/account_manager/mod.rs +++ b/bindings/node/native/src/classes/account_manager/mod.rs @@ -101,8 +101,8 @@ declare_types! { None => Default::default(), }; let manager = match options.storage_type { - StorageType::Sqlite => AccountManager::with_storage_adapter(&options.storage_path, SqliteStorageAdapter::new(&options.storage_path, "accounts").unwrap()), - StorageType::Stronghold => AccountManager::with_storage_adapter(&options.storage_path, StrongholdStorageAdapter::new(&options.storage_path).unwrap()), + StorageType::Sqlite => crate::block_on(AccountManager::with_storage_adapter(&options.storage_path, SqliteStorageAdapter::new(&options.storage_path, "accounts").unwrap())), + StorageType::Stronghold => crate::block_on(AccountManager::with_storage_adapter(&options.storage_path, StrongholdStorageAdapter::new(&options.storage_path).unwrap())), }; let manager = manager.expect("error initializing account manager"); Ok(AccountManagerWrapper(Arc::new(RwLock::new(manager)))) @@ -120,28 +120,6 @@ declare_types! { Ok(cx.undefined().upcast()) } - method startBackgroundSync(mut cx) { - { - let this = cx.this(); - let guard = cx.lock(); - let ref_ = &this.borrow(&guard).0; - let mut manager = ref_.write().unwrap(); - crate::block_on(async move { manager.start_background_sync().await }); - } - Ok(cx.undefined().upcast()) - } - - method loadAccounts(mut cx) { - { - let this = cx.this(); - let guard = cx.lock(); - let ref_ = &this.borrow(&guard).0; - let mut manager = ref_.write().unwrap(); - crate::block_on(async move { manager.load_accounts().await }).expect("failed to load accounts"); - } - Ok(cx.undefined().upcast()) - } - method createAccount(mut cx) { let account = { let account_to_create = cx.argument::(0)?; diff --git a/bindings/node/native/src/lib.rs b/bindings/node/native/src/lib.rs index 560ec1dd2..da8bad7dd 100644 --- a/bindings/node/native/src/lib.rs +++ b/bindings/node/native/src/lib.rs @@ -50,13 +50,6 @@ pub(crate) fn store_account(account_handle: AccountHandle) -> AccountIdentifier id } -pub(crate) fn remove_account(id: &AccountIdentifier) { - let mut map = account_instances() - .write() - .expect("failed to lock account instances: remove_account()"); - map.remove(id); -} - /// Gets the synced account instances map. fn synced_account_instances() -> &'static SyncedAccountInstanceMap { static INSTANCES: Lazy = Lazy::new(Default::default); diff --git a/examples/account_operations.rs b/examples/account_operations.rs index c27542b7a..070cd2054 100644 --- a/examples/account_operations.rs +++ b/examples/account_operations.rs @@ -5,7 +5,7 @@ use iota_wallet::{account_manager::AccountManager, client::ClientOptionsBuilder, #[tokio::main] async fn main() -> iota_wallet::Result<()> { - let mut manager = AccountManager::new().unwrap(); + let mut manager = AccountManager::new().await.unwrap(); manager.set_stronghold_password("password").await.unwrap(); // first we'll create an example account and store it diff --git a/examples/actor.rs b/examples/actor.rs index 62da7c181..d59048d0e 100644 --- a/examples/actor.rs +++ b/examples/actor.rs @@ -26,13 +26,15 @@ impl WalletActor { fn spawn_actor() -> UnboundedSender { let (tx, rx) = unbounded_channel(); - let actor = WalletActor { - rx, - message_handler: Default::default(), - }; std::thread::spawn(|| { let mut runtime = tokio::runtime::Runtime::new().unwrap(); - runtime.block_on(actor.run()); + runtime.block_on(async move { + let actor = WalletActor { + rx, + message_handler: WalletMessageHandler::new().await.unwrap(), + }; + actor.run().await + }); }); tx } diff --git a/examples/backup_and_restore.rs b/examples/backup_and_restore.rs index 79881e89a..e58069d93 100644 --- a/examples/backup_and_restore.rs +++ b/examples/backup_and_restore.rs @@ -5,7 +5,7 @@ use iota_wallet::{account_manager::AccountManager, client::ClientOptionsBuilder} #[tokio::main] async fn main() -> iota_wallet::Result<()> { - let mut manager = AccountManager::new().unwrap(); + let mut manager = AccountManager::new().await.unwrap(); manager.set_stronghold_password("password").await.unwrap(); // first we'll create an example account diff --git a/examples/custom_storage.rs b/examples/custom_storage.rs index 1d70cc0bd..45560770b 100644 --- a/examples/custom_storage.rs +++ b/examples/custom_storage.rs @@ -63,6 +63,7 @@ impl StorageAdapter for MyStorage { async fn main() -> iota_wallet::Result<()> { let mut manager = AccountManager::with_storage_adapter("./example-database/sled", MyStorage::new("./example-database/sled")?) + .await .unwrap(); manager.set_stronghold_password("password").await.unwrap(); diff --git a/examples/transfer.rs b/examples/transfer.rs index 6f435c999..1e75ddbdf 100644 --- a/examples/transfer.rs +++ b/examples/transfer.rs @@ -5,7 +5,7 @@ use iota_wallet::{account_manager::AccountManager, client::ClientOptionsBuilder, #[tokio::main] async fn main() -> iota_wallet::Result<()> { - let mut manager = AccountManager::new().unwrap(); + let mut manager = AccountManager::new().await.unwrap(); manager.set_stronghold_password("password").await.unwrap(); // first we'll create an example account and store it diff --git a/src/account_manager.rs b/src/account_manager.rs index 965428080..d0a182c18 100644 --- a/src/account_manager.rs +++ b/src/account_manager.rs @@ -52,7 +52,6 @@ pub struct AccountManager { /// the polling interval. #[getset(get = "pub", set = "pub")] polling_interval: Duration, - background_sync_running: bool, accounts: AccountStore, stop_polling_sender: Option>, } @@ -77,43 +76,44 @@ impl Drop for AccountManager { impl AccountManager { /// Initialises a new instance of the account manager with the default storage adapter. - pub fn new() -> crate::Result { - Self::with_storage_path(DEFAULT_STORAGE_PATH) + pub async fn new() -> crate::Result { + Self::with_storage_path(DEFAULT_STORAGE_PATH).await } /// Initialises a new instance of the account manager with the default storage adapter using the specified storage /// path. - pub fn with_storage_path(storage_path: impl AsRef) -> crate::Result { - Self::with_storage_adapter(&storage_path, crate::storage::get_adapter_from_path(&storage_path)?) + pub async fn with_storage_path(storage_path: impl AsRef) -> crate::Result { + Self::with_storage_adapter(&storage_path, crate::storage::get_adapter_from_path(&storage_path)?).await } /// Initialises a new instance of the account manager with the specified adapter. - pub fn with_storage_adapter( + pub async fn with_storage_adapter( storage_path: impl AsRef, adapter: S, ) -> crate::Result { crate::storage::set_adapter(&storage_path, adapter); - let instance = Self { - storage_path: storage_path.as_ref().to_path_buf(), + let storage_path = storage_path.as_ref().to_path_buf(); + let mut instance = Self { + storage_path: storage_path.clone(), polling_interval: Duration::from_millis(30_000), - background_sync_running: false, - accounts: Default::default(), + accounts: Self::load_accounts(&storage_path) + .await + .unwrap_or_else(|_| Default::default()), stop_polling_sender: None, }; + + instance.start_background_sync().await; + Ok(instance) } - /// Loads the account from the storage into the manager instance. - pub async fn load_accounts(&mut self) -> crate::Result<()> { - if self.accounts.read().await.is_empty() { - let accounts = crate::storage::with_adapter(&self.storage_path, |storage| storage.get_all())?; - let accounts = crate::storage::parse_accounts(&self.storage_path, &accounts)? - .into_iter() - .map(|account| (account.id().clone(), account.into())) - .collect(); - self.accounts = Arc::new(RwLock::new(accounts)); - } - Ok(()) + async fn load_accounts(storage_path: &PathBuf) -> crate::Result { + let accounts = crate::storage::with_adapter(&storage_path, |storage| storage.get_all())?; + let accounts = crate::storage::parse_accounts(&storage_path, &accounts)? + .into_iter() + .map(|account| (account.id().clone(), account.into())) + .collect(); + Ok(Arc::new(RwLock::new(accounts))) } /// Starts monitoring the accounts with the node's mqtt topics. @@ -126,14 +126,11 @@ impl AccountManager { } /// Initialises the background polling and MQTT monitoring. - pub async fn start_background_sync(&mut self) { - if !self.background_sync_running { - let monitoring_disabled = self.start_monitoring().await.is_err(); - let (stop_polling_sender, stop_polling_receiver) = broadcast_channel(1); - self.start_polling(monitoring_disabled, stop_polling_receiver); - self.stop_polling_sender = Some(stop_polling_sender); - self.background_sync_running = true; - } + async fn start_background_sync(&mut self) { + let monitoring_disabled = self.start_monitoring().await.is_err(); + let (stop_polling_sender, stop_polling_receiver) = broadcast_channel(1); + self.start_polling(monitoring_disabled, stop_polling_receiver); + self.stop_polling_sender = Some(stop_polling_sender); } /// Sets the stronghold password. @@ -146,8 +143,11 @@ impl AccountManager { None, )?; crate::init_stronghold(&self.storage_path, stronghold); - self.start_background_sync().await; - self.load_accounts().await?; + + if self.accounts.read().await.is_empty() { + self.accounts = Self::load_accounts(&self.storage_path).await?; + let _ = self.start_monitoring().await; + } Ok(()) } @@ -311,12 +311,14 @@ impl AccountManager { backup_stronghold.account_get_by_id(&account_id_to_stronghold_record_id(account.id())?)?; let created_at_timestamp: u128 = account.created_at().timestamp().try_into().unwrap(); // safe to unwrap since it's > 0 let stronghold_account = crate::with_stronghold_from_path(&self.storage_path, |stronghold| { - stronghold.account_import( - Some(created_at_timestamp), - Some(created_at_timestamp), - stronghold_account.mnemonic().to_string(), - Some("password"), - ) + stronghold + .account_import( + Some(created_at_timestamp), + Some(created_at_timestamp), + stronghold_account.mnemonic().to_string(), + Some("password"), + ) + .map_err(|e| e.into()) }); account.save()?; diff --git a/src/actor/mod.rs b/src/actor/mod.rs index 91a8943a4..335729727 100644 --- a/src/actor/mod.rs +++ b/src/actor/mod.rs @@ -25,14 +25,6 @@ pub struct WalletMessageHandler { account_manager: AccountManager, } -impl Default for WalletMessageHandler { - fn default() -> Self { - Self { - account_manager: AccountManager::new().unwrap(), - } - } -} - fn panic_to_response_message(panic: Box) -> Result { let msg = if let Some(message) = panic.downcast_ref::() { format!("Internal error: {}", message) @@ -64,17 +56,17 @@ where impl WalletMessageHandler { /// Creates a new instance of the message handler with the default account manager. - pub fn new() -> Result { + pub async fn new() -> Result { let instance = Self { - account_manager: AccountManager::new()?, + account_manager: AccountManager::new().await?, }; Ok(instance) } /// Creates a new instance of the message handler with the account manager using the given storage path. - pub fn with_storage_path(storage_path: PathBuf) -> Result { + pub async fn with_storage_path(storage_path: PathBuf) -> Result { let instance = Self { - account_manager: AccountManager::with_storage_path(storage_path)?, + account_manager: AccountManager::with_storage_path(storage_path).await?, }; Ok(instance) } @@ -329,12 +321,10 @@ mod tests { } /// Builds the Wallet actor. - pub fn build(self) -> Wallet { + pub async fn build(self) -> Wallet { Wallet { rx: self.rx.expect("rx is required"), - message_handler: self - .message_handler - .unwrap_or_else(|| WalletMessageHandler::new().expect("failed to initialise account manager")), + message_handler: self.message_handler.expect("message handler is required"), } } } @@ -361,7 +351,7 @@ mod tests { let actor = WalletBuilder::new().rx(rx).build(); std::thread::spawn(|| { let mut runtime = tokio::runtime::Runtime::new().unwrap(); - runtime.block_on(actor.run()); + runtime.block_on(async move { actor.await.run().await }); }); tx } diff --git a/src/lib.rs b/src/lib.rs index d23cf0817..a9dceff13 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -144,12 +144,15 @@ pub(crate) fn remove_stronghold(stronghold_path: PathBuf) { stronghold_map.remove(&stronghold_path); } -pub(crate) fn with_stronghold_from_path T>(path: &PathBuf, cb: F) -> T { +pub(crate) fn with_stronghold_from_path crate::Result>( + path: &PathBuf, + cb: F, +) -> crate::Result { let stronghold_map = STRONGHOLD_INSTANCE.get_or_init(Default::default).lock().unwrap(); if let Some(stronghold) = stronghold_map.get(path) { cb(stronghold) } else { - panic!("should initialize stronghold instance before using it") + Err(anyhow::anyhow!("should initialize stronghold instance before using it").into()) } } @@ -178,7 +181,7 @@ mod test_utils { let storage_path: String = thread_rng().gen_ascii_chars().take(10).collect(); let storage_path = PathBuf::from(format!("./example-database/{}", storage_path)); - let mut manager = AccountManager::with_storage_path(storage_path).unwrap(); + let mut manager = AccountManager::with_storage_path(storage_path).await.unwrap(); manager.set_polling_interval(Duration::from_secs(4)); manager.set_stronghold_password("password").await.unwrap(); Mutex::new(manager) diff --git a/src/signing/stronghold.rs b/src/signing/stronghold.rs index 96c4cd478..2821ac4d1 100644 --- a/src/signing/stronghold.rs +++ b/src/signing/stronghold.rs @@ -61,12 +61,13 @@ impl super::Signer for StrongholdSigner { }) .collect::>(); crate::with_stronghold_from_path(account.storage_path(), |stronghold| { - stronghold.get_transaction_unlock_blocks( - &account_id_to_stronghold_record_id(account.id())?, - &essence, - &mut inputs, - ) + stronghold + .get_transaction_unlock_blocks( + &account_id_to_stronghold_record_id(account.id())?, + &essence, + &mut inputs, + ) + .map_err(|e| e.into()) }) - .map_err(|e| e.into()) } } diff --git a/src/storage/stronghold.rs b/src/storage/stronghold.rs index a9bc92600..475518f55 100644 --- a/src/storage/stronghold.rs +++ b/src/storage/stronghold.rs @@ -85,8 +85,9 @@ impl StorageAdapter for StrongholdStorageAdapter { let mut accounts = vec![]; let (_, index) = crate::with_stronghold_from_path(&self.path, |stronghold| get_account_index(&stronghold))?; for (_, record_id) in index { - let account = - crate::with_stronghold_from_path(&self.path, |stronghold| stronghold.record_read(&record_id))?; + let account = crate::with_stronghold_from_path(&self.path, |stronghold| { + stronghold.record_read(&record_id).map_err(|e| e.into()) + })?; accounts.push(account); } Ok(accounts) From 95fdca34a354903be84bab4296b816a8111e7f4d Mon Sep 17 00:00:00 2001 From: Lucas Nogueira Date: Wed, 16 Dec 2020 20:34:17 -0300 Subject: [PATCH 24/29] refactor(manager): change get_account_by_alias' alias type --- src/account_manager.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/account_manager.rs b/src/account_manager.rs index d0a182c18..64a99d3bd 100644 --- a/src/account_manager.rs +++ b/src/account_manager.rs @@ -337,8 +337,8 @@ impl AccountManager { } /// Gets the account associated with the given alias (case insensitive). - pub async fn get_account_by_alias>(&self, alias: S) -> Option { - let alias = alias.into().to_lowercase(); + pub async fn get_account_by_alias>(&self, alias: S) -> Option { + let alias = alias.as_ref().to_lowercase(); for account_handle in self.accounts.read().await.values() { let account = account_handle.read().await; if account From 09451b8ceb01776fb3aeead7d1becbbb008ccb4e Mon Sep 17 00:00:00 2001 From: Lucas Nogueira Date: Thu, 17 Dec 2020 00:37:27 -0300 Subject: [PATCH 25/29] fix(tests): deadlock --- src/account/sync/mod.rs | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/src/account/sync/mod.rs b/src/account/sync/mod.rs index 45a969a47..4b7aac604 100644 --- a/src/account/sync/mod.rs +++ b/src/account/sync/mod.rs @@ -837,25 +837,23 @@ mod tests { rusty_fork_test! { #[test] fn account_sync() { + let manager = crate::test_utils::get_account_manager(); + let manager = manager.lock().unwrap(); + + let client_options = ClientOptionsBuilder::node("https://nodes.devnet.iota.org:443") + .unwrap() + .build(); crate::block_on(async move { - let manager = crate::test_utils::get_account_manager(); - let manager = manager.lock().unwrap(); - - let client_options = ClientOptionsBuilder::node("https://nodes.devnet.iota.org:443") - .unwrap() - .build(); - crate::block_on(async move { - let account = manager + let account = manager .create_account(client_options) .alias("alias") .initialise() .await .unwrap(); - }); - - // let synced_accounts = account.sync().execute().await.unwrap(); - // TODO improve test when the node API is ready to use }); + + // let synced_accounts = account.sync().execute().await.unwrap(); + // TODO improve test when the node API is ready to use } } } From c94b8ec021b3594268af7b86a8649ac2746a5b23 Mon Sep 17 00:00:00 2001 From: Lucas Nogueira Date: Thu, 17 Dec 2020 00:47:40 -0300 Subject: [PATCH 26/29] fix(tests): actor spawn --- src/actor/mod.rs | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/actor/mod.rs b/src/actor/mod.rs index 335729727..89d706a8b 100644 --- a/src/actor/mod.rs +++ b/src/actor/mod.rs @@ -343,15 +343,22 @@ mod tests { while let Some(message) = self.rx.recv().await { self.message_handler.handle(message).await; } + println!("DONE"); } } fn spawn_actor() -> UnboundedSender { let (tx, rx) = unbounded_channel(); - let actor = WalletBuilder::new().rx(rx).build(); std::thread::spawn(|| { let mut runtime = tokio::runtime::Runtime::new().unwrap(); - runtime.block_on(async move { actor.await.run().await }); + runtime.block_on(async move { + let actor = WalletBuilder::new() + .rx(rx) + .message_handler(WalletMessageHandler::new().await.unwrap()) + .build() + .await; + actor.run().await + }); }); tx } From 8e94f8ee4f55f464f40533b36567d2189253d949 Mon Sep 17 00:00:00 2001 From: Lucas Nogueira Date: Thu, 17 Dec 2020 02:47:29 -0300 Subject: [PATCH 27/29] fix(tests): doc tests --- src/account/mod.rs | 34 +++++++++++++++++++--------------- 1 file changed, 19 insertions(+), 15 deletions(-) diff --git a/src/account/mod.rs b/src/account/mod.rs index 6248fd2f9..c97ec6d08 100644 --- a/src/account/mod.rs +++ b/src/account/mod.rs @@ -445,21 +445,25 @@ impl Account { /// use iota_wallet::{account_manager::AccountManager, client::ClientOptionsBuilder, message::MessageType}; /// # use rand::{thread_rng, Rng}; /// - /// # let storage_path: String = thread_rng().gen_ascii_chars().take(10).collect(); - /// # let storage_path = std::path::PathBuf::from(format!("./example-database/{}", storage_path)); - /// // gets 10 received messages, skipping the first 5 most recent messages. - /// let client_options = ClientOptionsBuilder::node("https://nodes.devnet.iota.org:443") - /// .expect("invalid node URL") - /// .build(); - /// let mut manager = AccountManager::new().unwrap(); - /// # let mut manager = AccountManager::with_storage_path(storage_path).unwrap(); - /// manager.set_stronghold_password("password").unwrap(); - /// let account = manager - /// .create_account(client_options) - /// .initialise() - /// .expect("failed to add account"); - /// let account = account_handle.read().await; - /// account.list_messages(10, 5, Some(MessageType::Received)); + /// #[tokio::main] + /// async fn main() { + /// # let storage_path: String = thread_rng().gen_ascii_chars().take(10).collect(); + /// # let storage_path = std::path::PathBuf::from(format!("./example-database/{}", storage_path)); + /// // gets 10 received messages, skipping the first 5 most recent messages. + /// let client_options = ClientOptionsBuilder::node("https://nodes.devnet.iota.org:443") + /// .expect("invalid node URL") + /// .build(); + /// let mut manager = AccountManager::new().await.unwrap(); + /// # let mut manager = AccountManager::with_storage_path(storage_path).await.unwrap(); + /// manager.set_stronghold_password("password").await.unwrap(); + /// let account_handle = manager + /// .create_account(client_options) + /// .initialise() + /// .await + /// .expect("failed to add account"); + /// let account = account_handle.read().await; + /// account.list_messages(10, 5, Some(MessageType::Received)); + /// } /// ``` pub fn list_messages(&self, count: usize, from: usize, message_type: Option) -> Vec<&Message> { let mut messages: Vec<&Message> = vec![]; From 36c1140b1911674210468d5450abf338ef2dc3e6 Mon Sep 17 00:00:00 2001 From: Lucas Nogueira Date: Fri, 18 Dec 2020 09:53:55 -0300 Subject: [PATCH 28/29] code review changes --- bindings/node/native/src/lib.rs | 37 ++++++++++++++++++--------------- src/account_manager.rs | 19 +++++++---------- src/actor/mod.rs | 5 ++--- src/signing/stronghold.rs | 2 +- src/storage/stronghold.rs | 2 +- 5 files changed, 32 insertions(+), 33 deletions(-) diff --git a/bindings/node/native/src/lib.rs b/bindings/node/native/src/lib.rs index da8bad7dd..747f41e40 100644 --- a/bindings/node/native/src/lib.rs +++ b/bindings/node/native/src/lib.rs @@ -3,6 +3,7 @@ use std::{ any::Any, + borrow::Cow, collections::HashMap, panic::AssertUnwindSafe, sync::{Arc, Mutex, RwLock}, @@ -32,21 +33,23 @@ fn account_instances() -> &'static AccountInstanceMap { } pub(crate) fn get_account(id: &AccountIdentifier) -> AccountHandle { - let map = account_instances() + account_instances() .read() - .expect("failed to lock account instances: get_account()"); - map.get(id).expect("account dropped or not initialised").clone() + .expect("failed to lock account instances: get_account()") + .get(id) + .expect("account dropped or not initialised") + .clone() } pub(crate) fn store_account(account_handle: AccountHandle) -> AccountIdentifier { - let mut map = account_instances() - .write() - .expect("failed to lock account instances: store_account()"); - let handle = account_handle.clone(); let id = block_on(async move { handle.id().await }); - map.insert(id.clone(), account_handle); + account_instances() + .write() + .expect("failed to lock account instances: store_account()") + .insert(id.clone(), account_handle); + id } @@ -57,10 +60,12 @@ fn synced_account_instances() -> &'static SyncedAccountInstanceMap { } pub(crate) fn get_synced_account(id: &str) -> SyncedAccountHandle { - let map = synced_account_instances() + synced_account_instances() .read() - .expect("failed to lock synced account instances: get_synced_account()"); - map.get(id).expect("synced account dropped or not initialised").clone() + .expect("failed to lock synced account instances: get_synced_account()") + .get(id) + .expect("synced account dropped or not initialised") + .clone() } pub(crate) fn store_synced_account(synced_account: SyncedAccount) -> String { @@ -73,16 +78,14 @@ pub(crate) fn store_synced_account(synced_account: SyncedAccount) -> String { } pub(crate) fn remove_synced_account(id: &str) { - let mut map = synced_account_instances() + synced_account_instances() .write() - .expect("failed to lock synced account instances: remove_synced_account()"); - map.remove(id); + .expect("failed to lock synced account instances: remove_synced_account()") + .remove(id); } fn panic_to_response_message(panic: Box) -> Result { - let msg = if let Some(message) = panic.downcast_ref::() { - format!("Internal error: {}", message) - } else if let Some(message) = panic.downcast_ref::<&str>() { + let msg = if let Some(message) = panic.downcast_ref::>() { format!("Internal error: {}", message) } else { "Internal error".to_string() diff --git a/src/account_manager.rs b/src/account_manager.rs index 64a99d3bd..cba4c4940 100644 --- a/src/account_manager.rs +++ b/src/account_manager.rs @@ -14,6 +14,7 @@ use crate::{ }; use std::{ + borrow::Cow, collections::HashMap, convert::TryInto, fs, @@ -70,7 +71,9 @@ impl Drop for AccountManager { sender.send(()).expect("failed to stop polling process"); } }); - }); + }) + .join() + .expect("failed to stop monitoring and polling systems"); } } @@ -171,9 +174,7 @@ impl AccountManager { if let Err(panic) = AssertUnwindSafe(poll(accounts_.clone(), storage_path_, is_monitoring_disabled)) .catch_unwind() .await { - let msg = if let Some(message) = panic.downcast_ref::() { - format!("Internal error: {}", message) - } else if let Some(message) = panic.downcast_ref::<&str>() { + let msg = if let Some(message) = panic.downcast_ref::>() { format!("Internal error: {}", message) } else { "Internal error".to_string() @@ -195,7 +196,7 @@ impl AccountManager { } }); }); - }); + }).join().expect("failed to start polling"); } /// Adds a new account. @@ -318,7 +319,7 @@ impl AccountManager { stronghold_account.mnemonic().to_string(), Some("password"), ) - .map_err(|e| e.into()) + .map_err(Into::into) }); account.save()?; @@ -390,11 +391,7 @@ async fn poll(accounts: AccountStore, storage_path: PathBuf, syncing: bool) -> c // compare accounts to check for balance changes and new messages for account_before_sync in &accounts_before_sync { - let account_after_sync = accounts_after_sync - .iter() - .find(|(id, _)| id == &account_before_sync.id()) - .unwrap() - .1; + let account_after_sync = accounts_after_sync.get(account_before_sync.id()).unwrap(); let account_after_sync = account_after_sync.read().await; // balance event diff --git a/src/actor/mod.rs b/src/actor/mod.rs index 89d706a8b..23ae0c74b 100644 --- a/src/actor/mod.rs +++ b/src/actor/mod.rs @@ -11,6 +11,7 @@ use futures::{Future, FutureExt}; use iota::message::prelude::MessageId; use std::{ any::Any, + borrow::Cow, convert::TryInto, panic::{catch_unwind, AssertUnwindSafe}, path::PathBuf, @@ -26,9 +27,7 @@ pub struct WalletMessageHandler { } fn panic_to_response_message(panic: Box) -> Result { - let msg = if let Some(message) = panic.downcast_ref::() { - format!("Internal error: {}", message) - } else if let Some(message) = panic.downcast_ref::<&str>() { + let msg = if let Some(message) = panic.downcast_ref::>() { format!("Internal error: {}", message) } else { "Internal error".to_string() diff --git a/src/signing/stronghold.rs b/src/signing/stronghold.rs index 2821ac4d1..dac342ee0 100644 --- a/src/signing/stronghold.rs +++ b/src/signing/stronghold.rs @@ -67,7 +67,7 @@ impl super::Signer for StrongholdSigner { &essence, &mut inputs, ) - .map_err(|e| e.into()) + .map_err(Into::into) }) } } diff --git a/src/storage/stronghold.rs b/src/storage/stronghold.rs index 475518f55..e929a576c 100644 --- a/src/storage/stronghold.rs +++ b/src/storage/stronghold.rs @@ -86,7 +86,7 @@ impl StorageAdapter for StrongholdStorageAdapter { let (_, index) = crate::with_stronghold_from_path(&self.path, |stronghold| get_account_index(&stronghold))?; for (_, record_id) in index { let account = crate::with_stronghold_from_path(&self.path, |stronghold| { - stronghold.record_read(&record_id).map_err(|e| e.into()) + stronghold.record_read(&record_id).map_err(Into::into) })?; accounts.push(account); } From 63eb171b41b1e324ce9e9d1f3fd817d0a57acd6b Mon Sep 17 00:00:00 2001 From: Lucas Nogueira Date: Fri, 18 Dec 2020 12:13:28 -0300 Subject: [PATCH 29/29] fix(tests): deadlock --- src/lib.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index a9dceff13..f98768506 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -177,7 +177,8 @@ mod test_utils { static MANAGER_INSTANCE: OnceCell> = OnceCell::new(); pub fn get_account_manager() -> &'static Mutex { MANAGER_INSTANCE.get_or_init(|| { - crate::block_on(async move { + let mut runtime = tokio::runtime::Runtime::new().unwrap(); + runtime.block_on(async move { let storage_path: String = thread_rng().gen_ascii_chars().take(10).collect(); let storage_path = PathBuf::from(format!("./example-database/{}", storage_path));