From fa3b2b8277675f5e89281b14b07a5029f917dc34 Mon Sep 17 00:00:00 2001 From: skoupidi Date: Sat, 7 Jun 2025 18:33:42 +0300 Subject: [PATCH] drk: use block references to reset stuff --- bin/drk/src/cache.rs | 17 ++++++- bin/drk/src/dao.rs | 82 ++++++++++++++++++++++++++++++++-- bin/drk/src/deploy.rs | 30 +++++++++++++ bin/drk/src/lib.rs | 9 ++-- bin/drk/src/main.rs | 2 +- bin/drk/src/money.rs | 41 ++++++++++++++--- bin/drk/src/rpc.rs | 6 +-- bin/drk/src/scanned_blocks.rs | 84 ++++++++++++++++++++++++++++++----- bin/drk/src/token.rs | 24 ++++++++-- bin/drk/src/txs_history.rs | 17 +++++++ 10 files changed, 276 insertions(+), 36 deletions(-) diff --git a/bin/drk/src/cache.rs b/bin/drk/src/cache.rs index 0adcb9b48..9f019c589 100644 --- a/bin/drk/src/cache.rs +++ b/bin/drk/src/cache.rs @@ -28,7 +28,7 @@ use darkfi_sdk::{ error::{ContractError, ContractResult}, pasta::pallas, }; -use darkfi_serial::serialize; +use darkfi_serial::{deserialize, serialize}; use log::error; use num_bigint::BigUint; use sled_overlay::{sled, SledDbOverlay, SledDbOverlayStateDiff}; @@ -79,6 +79,21 @@ impl Cache { money_smt, }) } + + /// Fetch given block height numbers from the store's state inverse + /// diffs tree. The function will fail if a block height number was + /// not found. + pub fn get_state_inverse_diff(&self, heights: &[u32]) -> Result> { + let mut ret = Vec::with_capacity(heights.len()); + for height in heights { + match self.state_inverse_diff.get(height.to_be_bytes())? { + Some(found) => ret.push(deserialize(&found)?), + None => return Err(Error::BlockStateInverseDiffNotFound(*height)), + }; + } + + Ok(ret) + } } /// Overlay structure over a [`Cache`] instance. diff --git a/bin/drk/src/dao.rs b/bin/drk/src/dao.rs index d12cbb8b5..de9e2f820 100644 --- a/bin/drk/src/dao.rs +++ b/bin/drk/src/dao.rs @@ -1628,7 +1628,7 @@ impl Drk { } /// Reset the DAO Merkle trees in the cache. - pub async fn reset_dao_trees(&self) -> WalletDbResult<()> { + pub fn reset_dao_trees(&self) -> WalletDbResult<()> { println!("Resetting DAO Merkle trees"); if let Err(e) = self.cache.merkle_trees.remove(SLED_MERKLE_TREES_DAO_DAOS) { println!("[reset_dao_trees] Resetting DAO DAOs Merkle tree failed: {e:?}"); @@ -1644,7 +1644,7 @@ impl Drk { } /// Reset confirmed DAOs in the wallet. - pub async fn reset_daos(&self) -> WalletDbResult<()> { + pub fn reset_daos(&self) -> WalletDbResult<()> { println!("Resetting DAO confirmations"); let query = format!( "UPDATE {} SET {} = NULL, {} = NULL, {} = NULL, {} = NULL;", @@ -1660,8 +1660,27 @@ impl Drk { Ok(()) } + /// Reset confirmed DAOs in the wallet that were minted after + /// provided height. + pub fn unconfirm_daos_after(&self, height: &u32) -> WalletDbResult<()> { + println!("Resetting DAO confirmations after: {height}"); + let query = format!( + "UPDATE {} SET {} = NULL, {} = NULL, {} = NULL, {} = NULL WHERE {} > ?1;", + *DAO_DAOS_TABLE, + DAO_DAOS_COL_LEAF_POSITION, + DAO_DAOS_COL_MINT_HEIGHT, + DAO_DAOS_COL_TX_HASH, + DAO_DAOS_COL_CALL_INDEX, + DAO_DAOS_COL_MINT_HEIGHT, + ); + self.wallet.exec_sql(&query, rusqlite::params![Some(*height)])?; + println!("Successfully unconfirmed DAOs"); + + Ok(()) + } + /// Reset all DAO proposals in the wallet. - pub async fn reset_dao_proposals(&self) -> WalletDbResult<()> { + pub fn reset_dao_proposals(&self) -> WalletDbResult<()> { println!("Resetting DAO proposals confirmations"); let query = format!( "UPDATE {} SET {} = NULL, {} = NULL, {} = NULL, {} = NULL, {} = NULL, {} = NULL, {} = NULL, {} = NULL;", @@ -1681,11 +1700,66 @@ impl Drk { Ok(()) } + /// Reset DAO proposals in the wallet that were minted after + /// provided height. + pub fn unconfirm_dao_proposals_after(&self, height: &u32) -> WalletDbResult<()> { + println!("Resetting DAO proposals confirmations after: {height}"); + let query = format!( + "UPDATE {} SET {} = NULL, {} = NULL, {} = NULL, {} = NULL, {} = NULL, {} = NULL, {} = NULL, {} = NULL WHERE {} > ?1;", + *DAO_PROPOSALS_TABLE, + DAO_PROPOSALS_COL_LEAF_POSITION, + DAO_PROPOSALS_COL_MONEY_SNAPSHOT_TREE, + DAO_PROPOSALS_COL_NULLIFIERS_SMT_SNAPSHOT, + DAO_PROPOSALS_COL_MINT_HEIGHT, + DAO_PROPOSALS_COL_TX_HASH, + DAO_PROPOSALS_COL_CALL_INDEX, + DAO_PROPOSALS_COL_EXEC_HEIGHT, + DAO_PROPOSALS_COL_EXEC_TX_HASH, + DAO_PROPOSALS_COL_MINT_HEIGHT, + ); + self.wallet.exec_sql(&query, rusqlite::params![Some(*height)])?; + println!("Successfully unconfirmed DAO proposals"); + + Ok(()) + } + + /// Reset execution information in the wallet for DAO proposals + /// that were executed after provided height. + pub fn unexec_dao_proposals_after(&self, height: &u32) -> WalletDbResult<()> { + println!("Resetting DAO proposals execution information after: {height}"); + let query = format!( + "UPDATE {} SET {} = NULL, {} = NULL WHERE {} > ?1;", + *DAO_PROPOSALS_TABLE, + DAO_PROPOSALS_COL_EXEC_HEIGHT, + DAO_PROPOSALS_COL_EXEC_TX_HASH, + DAO_PROPOSALS_COL_EXEC_HEIGHT, + ); + self.wallet.exec_sql(&query, rusqlite::params![Some(*height)])?; + println!("Successfully reset DAO proposals execution information"); + + Ok(()) + } + /// Reset all DAO votes in the wallet. pub fn reset_dao_votes(&self) -> WalletDbResult<()> { println!("Resetting DAO votes"); let query = format!("DELETE FROM {};", *DAO_VOTES_TABLE); - self.wallet.exec_sql(&query, &[]) + self.wallet.exec_sql(&query, &[])?; + println!("Successfully reset DAO votes"); + + Ok(()) + } + + /// Remove the DAO votes in the wallet that were created after + /// provided height. + pub fn remove_dao_votes_after(&self, height: &u32) -> WalletDbResult<()> { + println!("Removing DAO votes after: {height}"); + let query = + format!("DELETE FROM {} WHERE {} > ?1;", *DAO_VOTES_TABLE, DAO_VOTES_COL_BLOCK_HEIGHT); + self.wallet.exec_sql(&query, rusqlite::params![height])?; + println!("Successfully removed DAO votes"); + + Ok(()) } /// Import given DAO params into the wallet with a given name. diff --git a/bin/drk/src/deploy.rs b/bin/drk/src/deploy.rs index d1e64fa5f..0f6646201 100644 --- a/bin/drk/src/deploy.rs +++ b/bin/drk/src/deploy.rs @@ -84,6 +84,36 @@ impl Drk { Ok(()) } + /// Reset all token deploy authorities frozen status in the wallet. + pub fn reset_deploy_authorities(&self) -> WalletDbResult<()> { + println!("Resetting deploy authorities frozen status"); + let query = format!( + "UPDATE {} SET {} = 0, {} = NULL;", + *DEPLOY_AUTH_TABLE, DEPLOY_AUTH_COL_IS_FROZEN, DEPLOY_AUTH_COL_FREEZE_HEIGHT + ); + self.wallet.exec_sql(&query, &[])?; + println!("Successfully reset deploy authorities frozen status"); + + Ok(()) + } + + /// Remove deploy authorities frozen status in the wallet that + /// where frozen after provided height. + pub fn unfreeze_deploy_authorities_after(&self, height: &u32) -> WalletDbResult<()> { + println!("Resetting deploy authorities frozen status after: {height}"); + let query = format!( + "UPDATE {} SET {} = 0, {} = NULL WHERE {} > ?1;", + *DEPLOY_AUTH_TABLE, + DEPLOY_AUTH_COL_IS_FROZEN, + DEPLOY_AUTH_COL_FREEZE_HEIGHT, + DEPLOY_AUTH_COL_FREEZE_HEIGHT + ); + self.wallet.exec_sql(&query, rusqlite::params![Some(*height)])?; + println!("Successfully reset deploy authorities frozen status"); + + Ok(()) + } + /// List contract deploy authorities from the wallet pub async fn list_deploy_auth(&self) -> Result)>> { let rows = match self.wallet.query_multiple(&DEPLOY_AUTH_TABLE, &[], &[]) { diff --git a/bin/drk/src/lib.rs b/bin/drk/src/lib.rs index fd35be65e..cb4b51ad1 100644 --- a/bin/drk/src/lib.rs +++ b/bin/drk/src/lib.rs @@ -122,17 +122,18 @@ impl Drk { } /// Auxiliary function to completely reset wallet state. - pub async fn reset(&self) -> WalletDbResult<()> { + pub fn reset(&self) -> WalletDbResult<()> { println!("Resetting full wallet state"); self.reset_scanned_blocks()?; self.reset_money_tree()?; self.reset_money_smt()?; self.reset_money_coins()?; self.reset_mint_authorities()?; - self.reset_dao_trees().await?; - self.reset_daos().await?; - self.reset_dao_proposals().await?; + self.reset_dao_trees()?; + self.reset_daos()?; + self.reset_dao_proposals()?; self.reset_dao_votes()?; + self.reset_deploy_authorities()?; self.reset_tx_history()?; println!("Successfully reset full wallet state"); Ok(()) diff --git a/bin/drk/src/main.rs b/bin/drk/src/main.rs index 9d016a47d..93c076b06 100644 --- a/bin/drk/src/main.rs +++ b/bin/drk/src/main.rs @@ -2032,7 +2032,7 @@ async fn realmain(args: Args, ex: Arc>) -> Result<()> { .await; if let Some(height) = reset { - if let Err(e) = drk.reset_to_height(height).await { + if let Err(e) = drk.reset_to_height(height) { eprintln!("Failed during wallet reset: {e:?}"); exit(2); } diff --git a/bin/drk/src/money.rs b/bin/drk/src/money.rs index 2db0a702c..14b59fd90 100644 --- a/bin/drk/src/money.rs +++ b/bin/drk/src/money.rs @@ -651,20 +651,15 @@ impl Drk { /// Mark a given coin in the wallet as unspent. pub async fn unspend_coin(&self, coin: &Coin) -> WalletDbResult<()> { - let is_spend = 0; - let spent_height: Option = None; let query = format!( - "UPDATE {} SET {} = ?1, {} = ?2, {} = ?3 WHERE {} = ?4;", + "UPDATE {} SET {} = 0, {} = NULL, {} = '-' WHERE {} = ?1;", *MONEY_COINS_TABLE, MONEY_COINS_COL_IS_SPENT, MONEY_COINS_COL_SPENT_HEIGHT, MONEY_COINS_COL_SPENT_TX_HASH, MONEY_COINS_COL_COIN ); - self.wallet.exec_sql( - &query, - rusqlite::params![is_spend, spent_height, "-", serialize_async(&coin.inner()).await], - ) + self.wallet.exec_sql(&query, rusqlite::params![serialize_async(&coin.inner()).await]) } /// Fetch the Money Merkle tree from the cache. @@ -1120,6 +1115,38 @@ impl Drk { Ok(()) } + /// Remove the Money coins in the wallet that were created after + /// provided height. + pub fn remove_money_coins_after(&self, height: &u32) -> WalletDbResult<()> { + println!("Removing coins after: {height}"); + let query = format!( + "DELETE FROM {} WHERE {} > ?1;", + *MONEY_COINS_TABLE, MONEY_COINS_COL_CREATION_HEIGHT + ); + self.wallet.exec_sql(&query, rusqlite::params![height])?; + println!("Successfully removed coins"); + + Ok(()) + } + + /// Mark the Money coins in the wallet that were spent after + /// provided height as unspent. + pub fn unspent_money_coins_after(&self, height: &u32) -> WalletDbResult<()> { + println!("Unspenting coins after: {height}"); + let query = format!( + "UPDATE {} SET {} = 0, {} = NULL, {} = '=' WHERE {} > ?1;", + *MONEY_COINS_TABLE, + MONEY_COINS_COL_IS_SPENT, + MONEY_COINS_COL_SPENT_HEIGHT, + MONEY_COINS_COL_SPENT_TX_HASH, + MONEY_COINS_COL_SPENT_HEIGHT + ); + self.wallet.exec_sql(&query, rusqlite::params![Some(*height)])?; + println!("Successfully unspent coins"); + + Ok(()) + } + /// Retrieve token by provided string. /// Input string represents either an alias or a token id. pub async fn get_token(&self, input: String) -> Result { diff --git a/bin/drk/src/rpc.rs b/bin/drk/src/rpc.rs index 494db510d..7c22fef26 100644 --- a/bin/drk/src/rpc.rs +++ b/bin/drk/src/rpc.rs @@ -239,7 +239,7 @@ impl Drk { // Check if a reorg block was received, to reset to its previous if block.header.height <= last_scanned_height { let reset_height = block.header.height.saturating_sub(1); - if let Err(e) = self.reset_to_height(reset_height).await { + if let Err(e) = self.reset_to_height(reset_height) { return Err(Error::DatabaseError(format!( "[subscribe_blocks] Wallet state reset failed: {e:?}" ))) @@ -483,7 +483,7 @@ impl Drk { // Reset to its height println!("Last common block found: {height} - {scanned_block_hash}"); - self.reset_to_height(height).await?; + self.reset_to_height(height)?; break } } @@ -491,7 +491,7 @@ impl Drk { // If last scanned block is genesis(0) we reset, // otherwise continue with the next block height. if height == 0 { - self.reset().await?; + self.reset()?; } else { height += 1; } diff --git a/bin/drk/src/scanned_blocks.rs b/bin/drk/src/scanned_blocks.rs index 1ca542b3c..3bd5d752e 100644 --- a/bin/drk/src/scanned_blocks.rs +++ b/bin/drk/src/scanned_blocks.rs @@ -19,6 +19,7 @@ use darkfi_serial::deserialize; use crate::{ + cache::CacheOverlay, error::{WalletDbError, WalletDbResult}, Drk, }; @@ -88,16 +89,15 @@ impl Drk { /// Reset state to provided block height. /// If genesis block height(0) was provided, perform a full reset. - pub async fn reset_to_height(&self, height: u32) -> WalletDbResult<()> { + pub fn reset_to_height(&self, height: u32) -> WalletDbResult<()> { println!("Resetting wallet state to block: {height}"); // If genesis block height(0) was provided, // perform a full reset. if height == 0 { - return self.reset().await + return self.reset() } - // TODO - /* + // Grab last scanned block height let (last, _) = self.get_last_scanned_block()?; @@ -107,15 +107,75 @@ impl Drk { return Ok(()) } - // Iterate the range (height, last] in reverse to grab the corresponding blocks - for height in (height + 1..=last).rev() { - let (height, hash, query) = self.get_scanned_block_record(height)?; - println!("Reverting block: {height} - {hash}"); - self.wallet.exec_batch_sql(&query)?; - let query = format!("DELETE FROM {WALLET_SCANNED_BLOCKS_TABLE} WHERE {WALLET_SCANNED_BLOCKS_COL_HEIGH} = {height};"); - self.wallet.exec_batch_sql(&query)?; + // Grab all state inverse diffs until requested height, + // going backwards. + let heights: Vec = (height + 1..=last).rev().collect(); + let inverse_diffs = match self.cache.get_state_inverse_diff(&heights) { + Ok(d) => d, + Err(e) => { + println!( + "[reset_to_height] Retrieving state inverse diffs from cache failed: {e:?}" + ); + return Err(WalletDbError::GenericError) + } + }; + + // Create an overlay to apply the reverse diffs + let mut overlay = match CacheOverlay::new(&self.cache) { + Ok(o) => o, + Err(e) => { + println!("[reset_to_height] Creating cache overlay failed: {e:?}"); + return Err(WalletDbError::GenericError) + } + }; + + // Apply the inverse diffs sequence + for inverse_diff in inverse_diffs { + if let Err(e) = overlay.0.add_diff(&inverse_diff) { + println!("[reset_to_height] Adding state inverse diff to the cache overlay failed: {e:?}"); + return Err(WalletDbError::GenericError) + } + if let Err(e) = overlay.0.apply_diff(&inverse_diff) { + println!("[reset_to_height] Applying state inverse diff to the cache overlay failed: {e:?}"); + return Err(WalletDbError::GenericError) + } + if let Err(e) = self.cache.sled_db.flush() { + println!("[reset_to_height] Flushing cache sled database failed: {e:?}"); + return Err(WalletDbError::GenericError) + } } - */ + + // Remove all wallet coins created after the reset height + self.remove_money_coins_after(&height)?; + + // Unspent all wallet coins spent after the reset height + self.unspent_money_coins_after(&height)?; + + // Unfreeze tokens mint authorities frozen after the reset + // height. + self.unfreeze_mint_authorities_after(&height)?; + + // Unconfirm DAOs minted after the reset height + self.unconfirm_daos_after(&height)?; + + // Unconfirm DAOs proposals minted after the reset height + self.unconfirm_dao_proposals_after(&height)?; + + // Reset execution information for DAOs proposals executed + // after the reset height. + self.unexec_dao_proposals_after(&height)?; + + // Remove all DAOs proposals votes created after the reset + // height. + self.remove_dao_votes_after(&height)?; + + // Unfreeze all contracts frozen after the reset height + self.unfreeze_deploy_authorities_after(&height)?; + + // Set reverted status to all transactions executed after reset + // height. + self.revert_transactions_after(&height)?; + println!("Successfully reset wallet state"); Ok(()) } diff --git a/bin/drk/src/token.rs b/bin/drk/src/token.rs index e74fc709b..6c4c6510a 100644 --- a/bin/drk/src/token.rs +++ b/bin/drk/src/token.rs @@ -177,13 +177,29 @@ impl Drk { /// Reset all token mint authorities frozen status in the wallet. pub fn reset_mint_authorities(&self) -> WalletDbResult<()> { println!("Resetting mint authorities frozen status"); - let freeze_height: Option = None; let query = format!( - "UPDATE {} SET {} = 0, {} = ?1", + "UPDATE {} SET {} = 0, {} = NULL;", *MONEY_TOKENS_TABLE, MONEY_TOKENS_COL_IS_FROZEN, MONEY_TOKENS_COL_FREEZE_HEIGHT ); - self.wallet.exec_sql(&query, rusqlite::params![freeze_height])?; - println!("Successfully mint authorities frozen status"); + self.wallet.exec_sql(&query, &[])?; + println!("Successfully reset mint authorities frozen status"); + + Ok(()) + } + + /// Remove token mint authorities frozen status in the wallet that + /// where frozen after provided height. + pub fn unfreeze_mint_authorities_after(&self, height: &u32) -> WalletDbResult<()> { + println!("Resetting mint authorities frozen status after: {height}"); + let query = format!( + "UPDATE {} SET {} = 0, {} = NULL WHERE {} > ?1;", + *MONEY_TOKENS_TABLE, + MONEY_TOKENS_COL_IS_FROZEN, + MONEY_TOKENS_COL_FREEZE_HEIGHT, + MONEY_TOKENS_COL_FREEZE_HEIGHT + ); + self.wallet.exec_sql(&query, rusqlite::params![Some(*height)])?; + println!("Successfully reset mint authorities frozen status"); Ok(()) } diff --git a/bin/drk/src/txs_history.rs b/bin/drk/src/txs_history.rs index 00c14ace7..b5c9a1023 100644 --- a/bin/drk/src/txs_history.rs +++ b/bin/drk/src/txs_history.rs @@ -183,6 +183,23 @@ impl Drk { Ok(()) } + /// Set reverted status to the transaction history records in the + /// wallet that where executed after provided height. + pub fn revert_transactions_after(&self, height: &u32) -> WalletDbResult<()> { + println!("Reverting transactions history after: {height}"); + let query = format!( + "UPDATE {} SET {} = 'Reverted', {} = NULL WHERE {} > ?1;", + WALLET_TXS_HISTORY_TABLE, + WALLET_TXS_HISTORY_COL_STATUS, + WALLET_TXS_HISTORY_BLOCK_HEIGHT, + WALLET_TXS_HISTORY_BLOCK_HEIGHT + ); + self.wallet.exec_sql(&query, rusqlite::params![Some(*height)])?; + println!("Successfully reverted transactions history"); + + Ok(()) + } + /// Remove the transaction history records in the wallet /// that have been reverted. pub fn remove_reverted_txs(&self) -> WalletDbResult<()> {