txs_history.rs 7.6 KB

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