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 */
1819use rusqlite::types::Value;
2021use darkfi::{tx::Transaction, Error, Result};
22use darkfi_serial::{deserialize_async, serialize_async};
2324use crate::{
25 convert_named_params,
26 error::{WalletDbError, WalletDbResult},
27 Drk,
28};
2930// 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";
3637impl 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.
40pub 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
46let query = format!(
47"INSERT OR REPLACE INTO {} ({}, {}, {}) VALUES (?1, ?2, ?3);",
48 WALLET_TXS_HISTORY_TABLE,
49 WALLET_TXS_HISTORY_COL_TX_HASH,
50 WALLET_TXS_HISTORY_COL_STATUS,
51 WALLET_TXS_HISTORY_COL_TX,
52 );
5354// Create its inverse query
55let tx_hash = tx.hash().to_string();
56// We only need to set the transaction status to "Reverted"
57let inverse = self.wallet.create_prepared_statement(
58&format!(
59"UPDATE {} SET {} = ?1 WHERE {} = ?2;",
60 WALLET_TXS_HISTORY_TABLE,
61 WALLET_TXS_HISTORY_COL_STATUS,
62 WALLET_TXS_HISTORY_COL_TX_HASH
63 ),
64rusqlite::params!["Reverted", tx_hash],
65 )?;
6667// Execute the query
68self.wallet
69 .exec_sql(&query, rusqlite::params![tx_hash, status, &serialize_async(tx).await,])?;
7071// Store its inverse
72self.wallet.cache_inverse(inverse)?;
7374Ok(tx_hash)
75 }
7677/// Insert or update a slice of [`Transaction`] history records into the wallet,
78 /// with the provided status.
79pub async fn put_tx_history_records(
80&self,
81 txs: &[&Transaction],
82 status: &str,
83 ) -> WalletDbResult<Vec<String>> {
84let mut ret = Vec::with_capacity(txs.len());
85for tx in txs {
86 ret.push(self.put_tx_history_record(tx, status).await?);
87 }
88Ok(ret)
89 }
9091/// Get a transaction history record.
92pub async fn get_tx_history_record(
93&self,
94 tx_hash: &str,
95 ) -> Result<(String, String, Transaction)> {
96let row = match self.wallet.query_single(
97 WALLET_TXS_HISTORY_TABLE,
98&[],
99convert_named_params! {(WALLET_TXS_HISTORY_COL_TX_HASH, tx_hash)},
100 ) {
101Ok(r) => r,
102Err(e) => {
103return Err(Error::DatabaseError(format!(
104"[get_tx_history_record] Transaction history record retrieval failed: {e:?}"
105)))
106 }
107 };
108109let Value::Text(ref tx_hash) = row[0] else {
110return Err(Error::ParseFailed(
111"[get_tx_history_record] Transaction hash parsing failed",
112 ))
113 };
114115let Value::Text(ref status) = row[1] else {
116return Err(Error::ParseFailed("[get_tx_history_record] Status parsing failed"))
117 };
118119let Value::Blob(ref bytes) = row[2] else {
120return Err(Error::ParseFailed(
121"[get_tx_history_record] Transaction bytes parsing failed",
122 ))
123 };
124let tx: Transaction = deserialize_async(bytes).await?;
125126Ok((tx_hash.clone(), status.clone(), tx))
127 }
128129/// Fetch all transactions history records, excluding bytes column.
130pub fn get_txs_history(&self) -> WalletDbResult<Vec<(String, String)>> {
131let rows = self.wallet.query_multiple(
132 WALLET_TXS_HISTORY_TABLE,
133&[WALLET_TXS_HISTORY_COL_TX_HASH, WALLET_TXS_HISTORY_COL_STATUS],
134&[],
135 )?;
136137let mut ret = Vec::with_capacity(rows.len());
138for row in rows {
139let Value::Text(ref tx_hash) = row[0] else {
140return Err(WalletDbError::ParseColumnValueError)
141 };
142143let Value::Text(ref status) = row[1] else {
144return Err(WalletDbError::ParseColumnValueError)
145 };
146147 ret.push((tx_hash.clone(), status.clone()));
148 }
149150Ok(ret)
151 }
152153/// Reset the transaction history records in the wallet.
154pub fn reset_tx_history(&self) -> WalletDbResult<()> {
155println!("Resetting transactions history");
156let query = format!("DELETE FROM {};", WALLET_TXS_HISTORY_TABLE);
157self.wallet.exec_sql(&query, &[])?;
158println!("Successfully reset transactions history");
159160Ok(())
161 }
162163/// Remove the transaction history records in the wallet
164 /// that have been reverted.
165pub fn remove_reverted_txs(&self) -> WalletDbResult<()> {
166println!("Removing reverted transactions history records");
167let query = format!(
168"DELETE FROM {} WHERE {} = 'Reverted';",
169 WALLET_TXS_HISTORY_TABLE, WALLET_TXS_HISTORY_COL_STATUS
170 );
171self.wallet.exec_sql(&query, &[])?;
172println!("Successfully removed reverted transactions history records");
173174Ok(())
175 }
176}