/* This file is part of DarkFi (https://dark.fi) * * Copyright (C) 2020-2024 Dyne.org foundation * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ use rusqlite::types::Value; use darkfi::{tx::Transaction, Error, Result}; use darkfi_serial::{deserialize_async, serialize_async}; use crate::{ convert_named_params, error::{WalletDbError, WalletDbResult}, Drk, }; // Wallet SQL table constant names. These have to represent the `wallet.sql` // SQL schema. const WALLET_TXS_HISTORY_TABLE: &str = "transactions_history"; const WALLET_TXS_HISTORY_COL_TX_HASH: &str = "transaction_hash"; const WALLET_TXS_HISTORY_COL_STATUS: &str = "status"; const WALLET_TXS_HISTORY_COL_TX: &str = "tx"; impl Drk { /// Insert or update a `Transaction` history record into the wallet, /// with the provided status, and store its inverse query into the cache. pub async fn put_tx_history_record( &self, tx: &Transaction, status: &str, ) -> WalletDbResult { // Create an SQL `INSERT OR REPLACE` query let query = format!( "INSERT OR REPLACE INTO {} ({}, {}, {}) VALUES (?1, ?2, ?3);", WALLET_TXS_HISTORY_TABLE, WALLET_TXS_HISTORY_COL_TX_HASH, WALLET_TXS_HISTORY_COL_STATUS, WALLET_TXS_HISTORY_COL_TX, ); // Create its inverse query let tx_hash = tx.hash().to_string(); // We only need to set the transaction status to "Reverted" let inverse = self.wallet.create_prepared_statement( &format!( "UPDATE {} SET {} = ?1 WHERE {} = ?2;", WALLET_TXS_HISTORY_TABLE, WALLET_TXS_HISTORY_COL_STATUS, WALLET_TXS_HISTORY_COL_TX_HASH ), rusqlite::params!["Reverted", tx_hash], )?; // Execute the query self.wallet .exec_sql(&query, rusqlite::params![tx_hash, status, &serialize_async(tx).await,])?; // Store its inverse self.wallet.cache_inverse(inverse)?; Ok(tx_hash) } /// Insert or update a slice of [`Transaction`] history records into the wallet, /// with the provided status. pub async fn put_tx_history_records( &self, txs: &[&Transaction], status: &str, ) -> WalletDbResult> { let mut ret = Vec::with_capacity(txs.len()); for tx in txs { ret.push(self.put_tx_history_record(tx, status).await?); } Ok(ret) } /// Get a transaction history record. pub async fn get_tx_history_record( &self, tx_hash: &str, ) -> Result<(String, String, Transaction)> { let row = match self.wallet.query_single( WALLET_TXS_HISTORY_TABLE, &[], convert_named_params! {(WALLET_TXS_HISTORY_COL_TX_HASH, tx_hash)}, ) { Ok(r) => r, Err(e) => { return Err(Error::DatabaseError(format!( "[get_tx_history_record] Transaction history record retrieval failed: {e:?}" ))) } }; let Value::Text(ref tx_hash) = row[0] else { return Err(Error::ParseFailed( "[get_tx_history_record] Transaction hash parsing failed", )) }; let Value::Text(ref status) = row[1] else { return Err(Error::ParseFailed("[get_tx_history_record] Status parsing failed")) }; let Value::Blob(ref bytes) = row[2] else { return Err(Error::ParseFailed( "[get_tx_history_record] Transaction bytes parsing failed", )) }; let tx: Transaction = deserialize_async(bytes).await?; Ok((tx_hash.clone(), status.clone(), tx)) } /// Fetch all transactions history records, excluding bytes column. pub fn get_txs_history(&self) -> WalletDbResult> { let rows = self.wallet.query_multiple( WALLET_TXS_HISTORY_TABLE, &[WALLET_TXS_HISTORY_COL_TX_HASH, WALLET_TXS_HISTORY_COL_STATUS], &[], )?; let mut ret = Vec::with_capacity(rows.len()); for row in rows { let Value::Text(ref tx_hash) = row[0] else { return Err(WalletDbError::ParseColumnValueError) }; let Value::Text(ref status) = row[1] else { return Err(WalletDbError::ParseColumnValueError) }; ret.push((tx_hash.clone(), status.clone())); } Ok(ret) } /// Reset the transaction history records in the wallet. pub fn reset_tx_history(&self) -> WalletDbResult<()> { println!("Resetting transactions history"); let query = format!("DELETE FROM {};", WALLET_TXS_HISTORY_TABLE); self.wallet.exec_sql(&query, &[])?; println!("Successfully reset transactions history"); Ok(()) } /// Remove the transaction history records in the wallet /// that have been reverted. pub fn remove_reverted_txs(&self) -> WalletDbResult<()> { println!("Removing reverted transactions history records"); let query = format!( "DELETE FROM {} WHERE {} = 'Reverted';", WALLET_TXS_HISTORY_TABLE, WALLET_TXS_HISTORY_COL_STATUS ); self.wallet.exec_sql(&query, &[])?; println!("Successfully removed reverted transactions history records"); Ok(()) } }