drk/
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
19use darkfi_serial::deserialize;
20
21use 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
29impl 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}