txs_history.rs 7.8 KB

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