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 Ok(key) = deserialize(&key) else {
53                return Err(WalletDbError::ParseColumnValueError);
54            };
55            let Ok(value) = deserialize(&value) else {
56                return Err(WalletDbError::ParseColumnValueError);
57            };
58            scanned_blocks.push((key, value));
59        }
60
61        Ok(scanned_blocks)
62    }
63
64    /// Get the last scanned block height and hash from the wallet.
65    /// If database is empty default (0, '-') is returned.
66    pub fn get_last_scanned_block(&self) -> WalletDbResult<(u32, String)> {
67        let Ok(query_result) = self.cache.scanned_blocks.last() else {
68            return Err(WalletDbError::QueryExecutionFailed);
69        };
70        let Some((key, value)) = query_result else { return Ok((0, String::from("-"))) };
71        let key: [u8; 4] = match key.as_ref().try_into() {
72            Ok(k) => k,
73            Err(_) => return Err(WalletDbError::ParseColumnValueError),
74        };
75        let key = u32::from_be_bytes(key);
76        let Ok(value) = deserialize(&value) else {
77            return Err(WalletDbError::ParseColumnValueError);
78        };
79        Ok((key, value))
80    }
81
82    /// Reset the scanned blocks information records in the cache.
83    pub fn reset_scanned_blocks(&self, output: &mut Vec<String>) -> WalletDbResult<()> {
84        output.push(String::from("Resetting scanned blocks"));
85        if let Err(e) = self.cache.scanned_blocks.clear() {
86            output
87                .push(format!("[reset_scanned_blocks] Resetting scanned blocks tree failed: {e}"));
88            return Err(WalletDbError::GenericError)
89        }
90        if let Err(e) = self.cache.state_inverse_diff.clear() {
91            output.push(format!(
92                "[reset_scanned_blocks] Resetting state inverse diffs tree failed: {e}"
93            ));
94            return Err(WalletDbError::GenericError)
95        }
96        output.push(String::from("Successfully reset scanned blocks"));
97
98        Ok(())
99    }
100
101    /// Reset state to provided block height.
102    /// If genesis block height(0) was provided, perform a full reset.
103    pub async fn reset_to_height(
104        &self,
105        height: u32,
106        output: &mut Vec<String>,
107    ) -> WalletDbResult<()> {
108        output.push(format!("Resetting wallet state to block: {height}"));
109
110        // If genesis block height(0) was provided,
111        // perform a full reset.
112        if height == 0 {
113            return self.reset(output)
114        }
115
116        // Grab last scanned block height
117        let (last, _) = self.get_last_scanned_block()?;
118
119        // Check if requested height is after it
120        if last <= height {
121            output.push(String::from(
122                "Requested block height is greater or equal to last scanned block",
123            ));
124            return Ok(())
125        }
126
127        // Grab our current merkle trees
128        let mut money_tree = match self.get_money_tree().await {
129            Ok(t) => t,
130            Err(e) => {
131                output.push(format!("[reset_to_height] Money merkle tree retrieval failed: {e}"));
132                return Err(WalletDbError::GenericError)
133            }
134        };
135        let (mut dao_daos_tree, mut dao_proposals_tree) = match self.get_dao_trees().await {
136            Ok(p) => p,
137            Err(e) => {
138                output.push(format!("[reset_to_height] DAO merkle trees retrieval failed: {e}"));
139                return Err(WalletDbError::GenericError)
140            }
141        };
142
143        // Create an overlay to apply the reverse diffs
144        let mut overlay = match CacheOverlay::new(&self.cache) {
145            Ok(o) => o,
146            Err(e) => {
147                output.push(format!("[reset_to_height] Creating cache overlay failed: {e}"));
148                return Err(WalletDbError::GenericError)
149            }
150        };
151
152        // Grab all state inverse diffs until requested height,
153        // going backwards.
154        for height in (height + 1..=last).rev() {
155            let inverse_diff = match self.cache.get_state_inverse_diff(&height) {
156                Ok(d) => d,
157                Err(e) => {
158                    output.push(format!(
159                        "[reset_to_height] Retrieving state inverse diff from cache failed: {e}"
160                    ));
161                    return Err(WalletDbError::GenericError)
162                }
163            };
164
165            // Apply it
166            if let Err(e) = overlay.0.add_diff(&inverse_diff) {
167                output.push(format!(
168                    "[reset_to_height] Adding state inverse diff to the cache overlay failed: {e}"
169                ));
170                return Err(WalletDbError::GenericError)
171            }
172            if let Err(e) = overlay.0.apply_diff(&inverse_diff) {
173                output.push(format!("[reset_to_height] Applying state inverse diff to the cache overlay failed: {e}"));
174                return Err(WalletDbError::GenericError)
175            }
176
177            // Remove it
178            if let Err(e) = self.cache.state_inverse_diff.remove(height.to_be_bytes()) {
179                output.push(format!(
180                    "[reset_to_height] Removing state inverse diff from the cache failed: {e}"
181                ));
182                return Err(WalletDbError::GenericError)
183            }
184
185            // Rewind and update the merkle trees
186            money_tree.rewind();
187            dao_daos_tree.rewind();
188            dao_proposals_tree.rewind();
189            if let Err(e) = self.cache.insert_merkle_trees(&[
190                (SLED_MERKLE_TREES_MONEY, &money_tree),
191                (SLED_MERKLE_TREES_DAO_DAOS, &dao_daos_tree),
192                (SLED_MERKLE_TREES_DAO_PROPOSALS, &dao_proposals_tree),
193            ]) {
194                output.push(format!("[reset_to_height] Updating merkle trees failed: {e}"));
195                return Err(WalletDbError::GenericError)
196            };
197
198            // Flush sled
199            if let Err(e) = self.cache.sled_db.flush() {
200                output.push(format!("[reset_to_height] Flushing cache sled database failed: {e}"));
201                return Err(WalletDbError::GenericError)
202            }
203        }
204
205        // Remove all wallet coins created after the reset height
206        self.remove_money_coins_after(&height, output)?;
207
208        // Unspent all wallet coins spent after the reset height
209        self.unspent_money_coins_after(&height, output)?;
210
211        // Unfreeze tokens mint authorities frozen after the reset
212        // height.
213        self.unfreeze_mint_authorities_after(&height, output)?;
214
215        // Unconfirm DAOs minted after the reset height
216        self.unconfirm_daos_after(&height, output)?;
217
218        // Unconfirm DAOs proposals minted after the reset height
219        self.unconfirm_dao_proposals_after(&height, output)?;
220
221        // Reset execution information for DAOs proposals executed
222        // after the reset height.
223        self.unexec_dao_proposals_after(&height, output)?;
224
225        // Remove all DAOs proposals votes created after the reset
226        // height.
227        self.remove_dao_votes_after(&height, output)?;
228
229        // Unlock all contracts frozen after the reset height
230        self.unlock_deploy_authorities_after(&height, output)?;
231
232        // Remove all contracts history records created after the reset
233        // height.
234        self.remove_deploy_history_after(&height, output)?;
235
236        // Set reverted status to all transactions executed after reset
237        // height.
238        self.revert_transactions_after(&height, output)?;
239
240        output.push(String::from("Successfully reset wallet state"));
241        Ok(())
242    }
243}