drk/
txs_history.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 rusqlite::types::Value;
20
21use darkfi::{tx::Transaction, Error, Result};
22use darkfi_serial::{deserialize_async, serialize_async};
23
24use crate::{
25    convert_named_params,
26    error::{WalletDbError, WalletDbResult},
27    Drk,
28};
29
30// Wallet SQL table constant names. These have to represent the `wallet.sql`
31// SQL schema.
32const WALLET_TXS_HISTORY_TABLE: &str = "transactions_history";
33const WALLET_TXS_HISTORY_COL_TX_HASH: &str = "transaction_hash";
34const WALLET_TXS_HISTORY_COL_STATUS: &str = "status";
35const WALLET_TXS_HISTORY_COL_TX: &str = "tx";
36
37impl Drk {
38    /// Insert or update a `Transaction` history record into the wallet,
39    /// with the provided status, and store its inverse query into the cache.
40    pub async fn put_tx_history_record(
41        &self,
42        tx: &Transaction,
43        status: &str,
44    ) -> WalletDbResult<String> {
45        // Create an SQL `INSERT OR REPLACE` query
46        let query = format!(
47            "INSERT OR REPLACE INTO {WALLET_TXS_HISTORY_TABLE} ({WALLET_TXS_HISTORY_COL_TX_HASH}, {WALLET_TXS_HISTORY_COL_STATUS}, {WALLET_TXS_HISTORY_COL_TX}) VALUES (?1, ?2, ?3);"
48        );
49
50        // Create its inverse query
51        let tx_hash = tx.hash().to_string();
52        // We only need to set the transaction status to "Reverted"
53        let inverse = self.wallet.create_prepared_statement(
54            &format!(
55                "UPDATE {WALLET_TXS_HISTORY_TABLE} SET {WALLET_TXS_HISTORY_COL_STATUS} = ?1 WHERE {WALLET_TXS_HISTORY_COL_TX_HASH} = ?2;"
56            ),
57            rusqlite::params!["Reverted", tx_hash],
58        )?;
59
60        // Execute the query
61        self.wallet
62            .exec_sql(&query, rusqlite::params![tx_hash, status, &serialize_async(tx).await,])?;
63
64        // Store its inverse
65        self.wallet.cache_inverse(inverse)?;
66
67        Ok(tx_hash)
68    }
69
70    /// Insert or update a slice of [`Transaction`] history records into the wallet,
71    /// with the provided status.
72    pub async fn put_tx_history_records(
73        &self,
74        txs: &[&Transaction],
75        status: &str,
76    ) -> WalletDbResult<Vec<String>> {
77        let mut ret = Vec::with_capacity(txs.len());
78        for tx in txs {
79            ret.push(self.put_tx_history_record(tx, status).await?);
80        }
81        Ok(ret)
82    }
83
84    /// Get a transaction history record.
85    pub async fn get_tx_history_record(
86        &self,
87        tx_hash: &str,
88    ) -> Result<(String, String, Transaction)> {
89        let row = match self.wallet.query_single(
90            WALLET_TXS_HISTORY_TABLE,
91            &[],
92            convert_named_params! {(WALLET_TXS_HISTORY_COL_TX_HASH, tx_hash)},
93        ) {
94            Ok(r) => r,
95            Err(e) => {
96                return Err(Error::DatabaseError(format!(
97                    "[get_tx_history_record] Transaction history record retrieval failed: {e:?}"
98                )))
99            }
100        };
101
102        let Value::Text(ref tx_hash) = row[0] else {
103            return Err(Error::ParseFailed(
104                "[get_tx_history_record] Transaction hash parsing failed",
105            ))
106        };
107
108        let Value::Text(ref status) = row[1] else {
109            return Err(Error::ParseFailed("[get_tx_history_record] Status parsing failed"))
110        };
111
112        let Value::Blob(ref bytes) = row[2] else {
113            return Err(Error::ParseFailed(
114                "[get_tx_history_record] Transaction bytes parsing failed",
115            ))
116        };
117        let tx: Transaction = deserialize_async(bytes).await?;
118
119        Ok((tx_hash.clone(), status.clone(), tx))
120    }
121
122    /// Fetch all transactions history records, excluding bytes column.
123    pub fn get_txs_history(&self) -> WalletDbResult<Vec<(String, String)>> {
124        let rows = self.wallet.query_multiple(
125            WALLET_TXS_HISTORY_TABLE,
126            &[WALLET_TXS_HISTORY_COL_TX_HASH, WALLET_TXS_HISTORY_COL_STATUS],
127            &[],
128        )?;
129
130        let mut ret = Vec::with_capacity(rows.len());
131        for row in rows {
132            let Value::Text(ref tx_hash) = row[0] else {
133                return Err(WalletDbError::ParseColumnValueError)
134            };
135
136            let Value::Text(ref status) = row[1] else {
137                return Err(WalletDbError::ParseColumnValueError)
138            };
139
140            ret.push((tx_hash.clone(), status.clone()));
141        }
142
143        Ok(ret)
144    }
145
146    /// Reset the transaction history records in the wallet.
147    pub fn reset_tx_history(&self) -> WalletDbResult<()> {
148        println!("Resetting transactions history");
149        let query = format!("DELETE FROM {WALLET_TXS_HISTORY_TABLE};");
150        self.wallet.exec_sql(&query, &[])?;
151        println!("Successfully reset transactions history");
152
153        Ok(())
154    }
155
156    /// Remove the transaction history records in the wallet
157    /// that have been reverted.
158    pub fn remove_reverted_txs(&self) -> WalletDbResult<()> {
159        println!("Removing reverted transactions history records");
160        let query = format!(
161            "DELETE FROM {WALLET_TXS_HISTORY_TABLE} WHERE {WALLET_TXS_HISTORY_COL_STATUS} = 'Reverted';"
162        );
163        self.wallet.exec_sql(&query, &[])?;
164        println!("Successfully removed reverted transactions history records");
165
166        Ok(())
167    }
168}