/ bin / drk / src / scanned_blocks.rs
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  }