From 38b2b37f385e04a8f7d436bbd2f4dd60b89e16b2 Mon Sep 17 00:00:00 2001 From: Brian Corbin Date: Tue, 14 Nov 2023 13:39:15 -0800 Subject: [PATCH] Account resyncing (#931) --- .../down.sql | 1 + .../up.sql | 4 +++ full-service/src/db/account.rs | 32 ++++++++++++++++++- full-service/src/db/models.rs | 1 + full-service/src/db/schema.rs | 1 + full-service/src/json_rpc/v1/api/wallet.rs | 8 +++++ full-service/src/json_rpc/v2/api/wallet.rs | 8 +++++ full-service/src/service/account.rs | 13 ++++++++ full-service/src/service/balance.rs | 6 ++++ full-service/src/service/sync.rs | 7 ++++ 10 files changed, 80 insertions(+), 1 deletion(-) create mode 100644 full-service/migrations/2023-11-11-154436_force_account_resync/down.sql create mode 100644 full-service/migrations/2023-11-11-154436_force_account_resync/up.sql diff --git a/full-service/migrations/2023-11-11-154436_force_account_resync/down.sql b/full-service/migrations/2023-11-11-154436_force_account_resync/down.sql new file mode 100644 index 000000000..e6e804a14 --- /dev/null +++ b/full-service/migrations/2023-11-11-154436_force_account_resync/down.sql @@ -0,0 +1 @@ +ALTER TABLE accounts DROP COLUMN resyncing; \ No newline at end of file diff --git a/full-service/migrations/2023-11-11-154436_force_account_resync/up.sql b/full-service/migrations/2023-11-11-154436_force_account_resync/up.sql new file mode 100644 index 000000000..cb15ce9a6 --- /dev/null +++ b/full-service/migrations/2023-11-11-154436_force_account_resync/up.sql @@ -0,0 +1,4 @@ +ALTER TABLE accounts ADD COLUMN resyncing BOOLEAN NOT NULL DEFAULT FALSE; + +UPDATE accounts SET next_block_index = 0; +UPDATE accounts SET resyncing = TRUE; \ No newline at end of file diff --git a/full-service/src/db/account.rs b/full-service/src/db/account.rs index b0606e1e1..54db84161 100644 --- a/full-service/src/db/account.rs +++ b/full-service/src/db/account.rs @@ -17,7 +17,10 @@ use crate::{ }; use base64::engine::{general_purpose::STANDARD as BASE64_ENGINE, Engine}; use bip39::Mnemonic; -use diesel::prelude::*; +use diesel::{ + dsl::{exists, select}, + prelude::*, +}; use mc_account_keys::{ AccountKey, PublicAddress, RootEntropy, RootIdentity, ViewAccountKey, CHANGE_SUBADDRESS_INDEX, DEFAULT_SUBADDRESS_INDEX, @@ -435,6 +438,10 @@ pub trait AccountModel { fn get_shared_secret(&self, tx_public_key: &RistrettoPublic) -> Result; fn public_address(&self, index: u64) -> Result; + + fn update_resyncing(&self, resyncing: bool, conn: Conn) -> Result<(), WalletDbError>; + + fn resync_in_progress(conn: Conn) -> Result; } impl AccountModel for Account { @@ -906,6 +913,24 @@ impl AccountModel for Account { let account_key = self.account_key()?; Ok(account_key.subaddress(index)) } + + fn update_resyncing(&self, resyncing: bool, conn: Conn) -> Result<(), WalletDbError> { + use crate::db::schema::accounts; + + diesel::update(accounts::table.filter(accounts::id.eq(&self.id))) + .set(accounts::resyncing.eq(resyncing)) + .execute(conn)?; + Ok(()) + } + + fn resync_in_progress(conn: Conn) -> Result { + use crate::db::schema::accounts; + + Ok( + select(exists(accounts::table.filter(accounts::resyncing.eq(true)))) + .get_result(conn)?, + ) + } } #[cfg(test)] @@ -969,6 +994,7 @@ mod tests { fog_enabled: false, view_only: false, managed_by_hardware_wallet: false, + resyncing: false, }; assert_eq!(expected_account, acc); @@ -1035,6 +1061,7 @@ mod tests { fog_enabled: false, view_only: false, managed_by_hardware_wallet: false, + resyncing: false, }; assert_eq!(expected_account_secondary, acc_secondary); @@ -1201,6 +1228,7 @@ mod tests { fog_enabled: true, view_only: false, managed_by_hardware_wallet: false, + resyncing: false, }; assert_eq!(expected_account, acc); } @@ -1258,6 +1286,7 @@ mod tests { fog_enabled: false, view_only: true, managed_by_hardware_wallet: false, + resyncing: false, }; assert_eq!(expected_account, account); } @@ -1317,6 +1346,7 @@ mod tests { fog_enabled: true, view_only: true, managed_by_hardware_wallet: true, + resyncing: false, }; // Check to make sure the account in the database is correct diff --git a/full-service/src/db/models.rs b/full-service/src/db/models.rs index 6be3d3233..01d6a457f 100644 --- a/full-service/src/db/models.rs +++ b/full-service/src/db/models.rs @@ -38,6 +38,7 @@ pub struct Account { /// and is required in order to spend funds and generate key images for this /// account. pub managed_by_hardware_wallet: bool, + pub resyncing: bool, } /// A structure that can be inserted to create a new entity in the `accounts` diff --git a/full-service/src/db/schema.rs b/full-service/src/db/schema.rs index ecaadf2f6..5812f551f 100644 --- a/full-service/src/db/schema.rs +++ b/full-service/src/db/schema.rs @@ -13,6 +13,7 @@ diesel::table! { fog_enabled -> Bool, view_only -> Bool, managed_by_hardware_wallet -> Bool, + resyncing -> Bool, } } diff --git a/full-service/src/json_rpc/v1/api/wallet.rs b/full-service/src/json_rpc/v1/api/wallet.rs index 252205266..2e5a9b73b 100644 --- a/full-service/src/json_rpc/v1/api/wallet.rs +++ b/full-service/src/json_rpc/v1/api/wallet.rs @@ -117,6 +117,14 @@ where { global_log::info!("Running command {:?}", command); + if service.resync_in_progress().map_err(format_error)? { + let wallet_status = service.get_wallet_status().map_err(format_error)?; + + return Err(format_error(format!( + "Resync in progress, please wait until it is completed to perform API calls... ({}% complete)", wallet_status.percent_synced() + ))); + } + let response = match command { JsonCommandRequest::assign_address_for_account { account_id, diff --git a/full-service/src/json_rpc/v2/api/wallet.rs b/full-service/src/json_rpc/v2/api/wallet.rs index 76209006e..62842c3c3 100644 --- a/full-service/src/json_rpc/v2/api/wallet.rs +++ b/full-service/src/json_rpc/v2/api/wallet.rs @@ -139,6 +139,14 @@ where { global_log::info!("Running command {:?}", command); + if service.resync_in_progress().map_err(format_error)? { + let wallet_status = service.get_wallet_status().map_err(format_error)?; + + return Err(format_error(format!( + "Resync in progress, please wait until it is completed to perform API calls... ({}% complete)", wallet_status.percent_synced() + ))); + } + let response = match command { JsonCommandRequest::assign_address_for_account { account_id, diff --git a/full-service/src/service/account.rs b/full-service/src/service/account.rs index 19ca10309..8f6c0ec4c 100644 --- a/full-service/src/service/account.rs +++ b/full-service/src/service/account.rs @@ -356,6 +356,8 @@ pub trait AccountService { &self, account_id: &AccountID ) -> Result; + + fn resync_in_progress(&self) -> Result; } #[async_trait] @@ -714,6 +716,17 @@ where Ok(true) }) } + + fn resync_in_progress(&self) -> Result { + let mut pooled_conn = match self.get_pooled_conn() { + Ok(pooled_conn) => Ok(pooled_conn), + Err(WalletDbError::WalletFunctionsDisabled) => return Ok(false), + Err(err) => Err(err), + }?; + + let conn = pooled_conn.deref_mut(); + Ok(Account::resync_in_progress(conn)?) + } } fn get_public_fog_address( diff --git a/full-service/src/service/balance.rs b/full-service/src/service/balance.rs index b057bb8fa..ec51a4960 100644 --- a/full-service/src/service/balance.rs +++ b/full-service/src/service/balance.rs @@ -144,6 +144,12 @@ pub struct WalletStatus { pub account_map: HashMap, } +impl WalletStatus { + pub fn percent_synced(&self) -> u64 { + self.min_synced_block_index * 100 / self.local_block_height + } +} + /// Trait defining the ways in which the wallet can interact with and manage /// balances. #[rustfmt::skip] diff --git a/full-service/src/service/sync.rs b/full-service/src/service/sync.rs index 3bd55b73f..469f7c08f 100644 --- a/full-service/src/service/sync.rs +++ b/full-service/src/service/sync.rs @@ -126,7 +126,14 @@ pub fn sync_all_accounts( for account in accounts { // If there are no new blocks for this account, don't do anything. + // + // If the account is currently resyncing, we need to set it to false + // here. if account.next_block_index as u64 > num_blocks - 1 { + if account.resyncing { + account.update_resyncing(false, conn)?; + } + continue; } sync_account_next_chunk(ledger_db, conn, &account.id, logger)?;