scanned_blocks.rs
1 /* This file is part of DarkFi (https://dark.fi) 2 * 3 * Copyright (C) 2020-2025 Dyne.org foundation 4 * 5 * This program is free software: you can redistribute it and/or modify 6 * it under the terms of the GNU Affero General Public License as 7 * published by the Free Software Foundation, either version 3 of the 8 * License, or (at your option) any later version. 9 * 10 * This program is distributed in the hope that it will be useful, 11 * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 * GNU Affero General Public License for more details. 14 * 15 * You should have received a copy of the GNU Affero General Public License 16 * along with this program. If not, see <https://www.gnu.org/licenses/>. 17 */ 18 19 use darkfi_serial::deserialize; 20 21 use crate::{ 22 cache::CacheOverlay, 23 dao::{SLED_MERKLE_TREES_DAO_DAOS, SLED_MERKLE_TREES_DAO_PROPOSALS}, 24 error::{WalletDbError, WalletDbResult}, 25 money::SLED_MERKLE_TREES_MONEY, 26 Drk, 27 }; 28 29 impl Drk { 30 /// Get a scanned block information record. 31 pub fn get_scanned_block_hash(&self, height: &u32) -> WalletDbResult<String> { 32 let Ok(query_result) = self.cache.scanned_blocks.get(height.to_be_bytes()) else { 33 return Err(WalletDbError::QueryExecutionFailed); 34 }; 35 let Some(hash_bytes) = query_result else { 36 return Err(WalletDbError::RowNotFound); 37 }; 38 let Ok(hash) = deserialize(&hash_bytes) else { 39 return Err(WalletDbError::ParseColumnValueError); 40 }; 41 Ok(hash) 42 } 43 44 /// Fetch all scanned block information records. 45 pub fn get_scanned_block_records(&self) -> WalletDbResult<Vec<(u32, String)>> { 46 let mut scanned_blocks = vec![]; 47 48 for record in self.cache.scanned_blocks.iter() { 49 let Ok((key, value)) = record else { 50 return Err(WalletDbError::QueryExecutionFailed); 51 }; 52 let key: [u8; 4] = match key.as_ref().try_into() { 53 Ok(k) => k, 54 Err(_) => return Err(WalletDbError::ParseColumnValueError), 55 }; 56 let key = u32::from_be_bytes(key); 57 let Ok(value) = deserialize(&value) else { 58 return Err(WalletDbError::ParseColumnValueError); 59 }; 60 scanned_blocks.push((key, value)); 61 } 62 63 Ok(scanned_blocks) 64 } 65 66 /// Get the last scanned block height and hash from the wallet. 67 /// If database is empty default (0, '-') is returned. 68 pub fn get_last_scanned_block(&self) -> WalletDbResult<(u32, String)> { 69 let Ok(query_result) = self.cache.scanned_blocks.last() else { 70 return Err(WalletDbError::QueryExecutionFailed); 71 }; 72 let Some((key, value)) = query_result else { return Ok((0, String::from("-"))) }; 73 let key: [u8; 4] = match key.as_ref().try_into() { 74 Ok(k) => k, 75 Err(_) => return Err(WalletDbError::ParseColumnValueError), 76 }; 77 let key = u32::from_be_bytes(key); 78 let Ok(value) = deserialize(&value) else { 79 return Err(WalletDbError::ParseColumnValueError); 80 }; 81 Ok((key, value)) 82 } 83 84 /// Reset the scanned blocks information records in the cache. 85 pub fn reset_scanned_blocks(&self, output: &mut Vec<String>) -> WalletDbResult<()> { 86 output.push(String::from("Resetting scanned blocks")); 87 if let Err(e) = self.cache.scanned_blocks.clear() { 88 output 89 .push(format!("[reset_scanned_blocks] Resetting scanned blocks tree failed: {e}")); 90 return Err(WalletDbError::GenericError) 91 } 92 if let Err(e) = self.cache.state_inverse_diff.clear() { 93 output.push(format!( 94 "[reset_scanned_blocks] Resetting state inverse diffs tree failed: {e}" 95 )); 96 return Err(WalletDbError::GenericError) 97 } 98 output.push(String::from("Successfully reset scanned blocks")); 99 100 Ok(()) 101 } 102 103 /// Reset state to provided block height. 104 /// If genesis block height(0) was provided, perform a full reset. 105 pub async fn reset_to_height( 106 &self, 107 height: u32, 108 output: &mut Vec<String>, 109 ) -> WalletDbResult<()> { 110 output.push(format!("Resetting wallet state to block: {height}")); 111 112 // If genesis block height(0) was provided, 113 // perform a full reset. 114 if height == 0 { 115 return self.reset(output) 116 } 117 118 // Grab last scanned block height 119 let (last, _) = self.get_last_scanned_block()?; 120 121 // Check if requested height is after it 122 if last <= height { 123 output.push(String::from( 124 "Requested block height is greater or equal to last scanned block", 125 )); 126 return Ok(()) 127 } 128 129 // Grab our current merkle trees 130 let mut money_tree = match self.get_money_tree().await { 131 Ok(t) => t, 132 Err(e) => { 133 output.push(format!("[reset_to_height] Money merkle tree retrieval failed: {e}")); 134 return Err(WalletDbError::GenericError) 135 } 136 }; 137 let (mut dao_daos_tree, mut dao_proposals_tree) = match self.get_dao_trees().await { 138 Ok(p) => p, 139 Err(e) => { 140 output.push(format!("[reset_to_height] DAO merkle trees retrieval failed: {e}")); 141 return Err(WalletDbError::GenericError) 142 } 143 }; 144 145 // Create an overlay to apply the reverse diffs 146 let mut overlay = match CacheOverlay::new(&self.cache) { 147 Ok(o) => o, 148 Err(e) => { 149 output.push(format!("[reset_to_height] Creating cache overlay failed: {e}")); 150 return Err(WalletDbError::GenericError) 151 } 152 }; 153 154 // Grab all state inverse diffs until requested height, 155 // going backwards. 156 for height in (height + 1..=last).rev() { 157 let inverse_diff = match self.cache.get_state_inverse_diff(&height) { 158 Ok(d) => d, 159 Err(e) => { 160 output.push(format!( 161 "[reset_to_height] Retrieving state inverse diff from cache failed: {e}" 162 )); 163 return Err(WalletDbError::GenericError) 164 } 165 }; 166 167 // Apply it 168 if let Err(e) = overlay.0.add_diff(&inverse_diff) { 169 output.push(format!( 170 "[reset_to_height] Adding state inverse diff to the cache overlay failed: {e}" 171 )); 172 return Err(WalletDbError::GenericError) 173 } 174 if let Err(e) = overlay.0.apply_diff(&inverse_diff) { 175 output.push(format!("[reset_to_height] Applying state inverse diff to the cache overlay failed: {e}")); 176 return Err(WalletDbError::GenericError) 177 } 178 179 // Remove it 180 if let Err(e) = self.cache.state_inverse_diff.remove(height.to_be_bytes()) { 181 output.push(format!( 182 "[reset_to_height] Removing state inverse diff from the cache failed: {e}" 183 )); 184 return Err(WalletDbError::GenericError) 185 } 186 187 // Rewind and update the merkle trees 188 money_tree.rewind(); 189 dao_daos_tree.rewind(); 190 dao_proposals_tree.rewind(); 191 if let Err(e) = self.cache.insert_merkle_trees(&[ 192 (SLED_MERKLE_TREES_MONEY, &money_tree), 193 (SLED_MERKLE_TREES_DAO_DAOS, &dao_daos_tree), 194 (SLED_MERKLE_TREES_DAO_PROPOSALS, &dao_proposals_tree), 195 ]) { 196 output.push(format!("[reset_to_height] Updating merkle trees failed: {e}")); 197 return Err(WalletDbError::GenericError) 198 }; 199 200 // Flush sled 201 if let Err(e) = self.cache.sled_db.flush() { 202 output.push(format!("[reset_to_height] Flushing cache sled database failed: {e}")); 203 return Err(WalletDbError::GenericError) 204 } 205 } 206 207 // Remove all wallet coins created after the reset height 208 self.remove_money_coins_after(&height, output)?; 209 210 // Unspent all wallet coins spent after the reset height 211 self.unspent_money_coins_after(&height, output)?; 212 213 // Unfreeze tokens mint authorities frozen after the reset 214 // height. 215 self.unfreeze_mint_authorities_after(&height, output)?; 216 217 // Unconfirm DAOs minted after the reset height 218 self.unconfirm_daos_after(&height, output)?; 219 220 // Unconfirm DAOs proposals minted after the reset height 221 self.unconfirm_dao_proposals_after(&height, output)?; 222 223 // Reset execution information for DAOs proposals executed 224 // after the reset height. 225 self.unexec_dao_proposals_after(&height, output)?; 226 227 // Remove all DAOs proposals votes created after the reset 228 // height. 229 self.remove_dao_votes_after(&height, output)?; 230 231 // Unlock all contracts frozen after the reset height 232 self.unlock_deploy_authorities_after(&height, output)?; 233 234 // Remove all contracts history records created after the reset 235 // height. 236 self.remove_deploy_history_after(&height, output)?; 237 238 // Set reverted status to all transactions executed after reset 239 // height. 240 self.revert_transactions_after(&height, output)?; 241 242 output.push(String::from("Successfully reset wallet state")); 243 Ok(()) 244 } 245 }