drk/
lib.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 std::{fs, sync::Arc};
20
21use url::Url;
22
23use darkfi::{rpc::client::RpcClient, util::path::expand_path, Error, Result};
24
25/// Error codes
26pub mod error;
27use error::{WalletDbError, WalletDbResult};
28
29/// darkfid JSON-RPC related methods
30pub mod rpc;
31
32/// Payment methods
33pub mod transfer;
34
35/// Swap methods
36pub mod swap;
37
38/// Token methods
39pub mod token;
40
41/// CLI utility functions
42pub mod cli_util;
43
44/// Wallet functionality related to Money
45pub mod money;
46
47/// Wallet functionality related to Dao
48pub mod dao;
49
50/// Wallet functionality related to Deployooor
51pub mod deploy;
52
53/// Wallet functionality related to transactions history
54pub mod txs_history;
55
56/// Wallet functionality related to scanned blocks
57pub mod scanned_blocks;
58
59/// Wallet database operations handler
60pub mod walletdb;
61use walletdb::{WalletDb, WalletPtr};
62
63/// CLI-util structure
64pub struct Drk {
65    /// Wallet database operations handler
66    pub wallet: WalletPtr,
67    /// JSON-RPC client to execute requests to darkfid daemon
68    pub rpc_client: Option<RpcClient>,
69    /// Flag indicating if fun stuff are enabled
70    pub fun: bool,
71}
72
73impl Drk {
74    pub async fn new(
75        wallet_path: String,
76        wallet_pass: String,
77        endpoint: Option<Url>,
78        ex: Arc<smol::Executor<'static>>,
79        fun: bool,
80    ) -> Result<Self> {
81        // Initialize wallet
82        let wallet_path = expand_path(&wallet_path)?;
83        if !wallet_path.exists() {
84            if let Some(parent) = wallet_path.parent() {
85                fs::create_dir_all(parent)?;
86            }
87        }
88        let Ok(wallet) = WalletDb::new(Some(wallet_path), Some(&wallet_pass)) else {
89            return Err(Error::DatabaseError(format!("{}", WalletDbError::InitializationFailed)));
90        };
91
92        // Initialize rpc client
93        let rpc_client = if let Some(endpoint) = endpoint {
94            Some(RpcClient::new(endpoint, ex).await?)
95        } else {
96            None
97        };
98
99        Ok(Self { wallet, rpc_client, fun })
100    }
101
102    /// Initialize wallet with tables for `Drk`.
103    pub async fn initialize_wallet(&self) -> WalletDbResult<()> {
104        // Initialize wallet schema
105        self.wallet.exec_batch_sql(include_str!("../wallet.sql"))?;
106
107        Ok(())
108    }
109
110    /// Auxiliary function to completely reset wallet state.
111    pub async fn reset(&self) -> WalletDbResult<()> {
112        println!("Resetting full wallet state");
113        self.reset_scanned_blocks()?;
114        self.reset_money_tree().await?;
115        self.reset_money_smt()?;
116        self.reset_money_coins()?;
117        self.reset_mint_authorities()?;
118        self.reset_dao_trees().await?;
119        self.reset_daos().await?;
120        self.reset_dao_proposals().await?;
121        self.reset_dao_votes()?;
122        self.reset_tx_history()?;
123        println!("Successfully reset full wallet state");
124        Ok(())
125    }
126
127    /// Auxiliary function to reset `walletdb` inverse cache state.
128    /// Additionally, set current trees state inverse queries.
129    /// We keep the entire trees state as two distinct inverse queries,
130    /// since we execute per transaction call, so we don't have to update
131    /// them on each iteration.
132    pub async fn reset_inverse_cache(&self) -> Result<()> {
133        // Reset `walletdb` inverse cache
134        if let Err(e) = self.wallet.clear_inverse_cache() {
135            return Err(Error::DatabaseError(format!(
136                "[reset_inverse_cache] Clearing wallet inverse cache failed: {e:?}"
137            )))
138        }
139
140        // Grab current money tree state query and insert it into inverse cache
141        let query = self.get_money_tree_state_query().await?;
142        if let Err(e) = self.wallet.cache_inverse(query) {
143            return Err(Error::DatabaseError(format!(
144                "[reset_inverse_cache] Inserting money query into inverse cache failed: {e:?}"
145            )))
146        }
147
148        // Grab current DAO trees state query and insert it into inverse cache
149        let query = self.get_dao_trees_state_query().await?;
150        if let Err(e) = self.wallet.cache_inverse(query) {
151            return Err(Error::DatabaseError(format!(
152                "[reset_inverse_cache] Inserting DAO query into inverse cache failed: {e:?}"
153            )))
154        }
155
156        Ok(())
157    }
158
159    /// Auxiliary function to store current `walletdb` inverse cache
160    /// in scanned blocks information for provided block height and hash.
161    /// Additionally, clear `walletdb` inverse cache state.
162    pub fn store_inverse_cache(&self, height: u32, hash: &str) -> Result<()> {
163        // Grab current inverse state rollback query
164        let rollback_query = match self.wallet.grab_inverse_cache_block() {
165            Ok(q) => q,
166            Err(e) => {
167                return Err(Error::DatabaseError(format!(
168                    "[store_inverse_cache] Creating rollback query failed: {e:?}"
169                )))
170            }
171        };
172
173        // Store it as a scanned blocks information record
174        if let Err(e) = self.put_scanned_block_record(height, hash, &rollback_query) {
175            return Err(Error::DatabaseError(format!(
176                "[store_inverse_cache] Inserting scanned blocks information record failed: {e:?}"
177            )))
178        };
179
180        // Reset `walletdb` inverse cache
181        if let Err(e) = self.wallet.clear_inverse_cache() {
182            return Err(Error::DatabaseError(format!(
183                "[store_inverse_cache] Clearing wallet inverse cache failed: {e:?}"
184            )))
185        };
186
187        Ok(())
188    }
189}