1use rusqlite::types::Value;
20
21use darkfi::{tx::Transaction, Error, Result};
22use darkfi_serial::{deserialize_async, serialize};
23
24use crate::{
25 convert_named_params,
26 error::{WalletDbError, WalletDbResult},
27 Drk,
28};
29
30const 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_BLOCK_HEIGHT: &str = "block_height";
36const WALLET_TXS_HISTORY_COL_TX: &str = "tx";
37
38impl Drk {
39 pub async fn put_tx_history_record(
42 &self,
43 tx: &Transaction,
44 status: &str,
45 block_height: Option<u32>,
46 ) -> WalletDbResult<String> {
47 let query = format!(
49 "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);"
50 );
51
52 let tx_hash = tx.hash().to_string();
54 self.wallet
55 .exec_sql(&query, rusqlite::params![tx_hash, status, block_height, &serialize(tx)])?;
56
57 Ok(tx_hash)
58 }
59
60 pub async fn put_tx_history_records(
63 &self,
64 txs: &[&Transaction],
65 status: &str,
66 block_height: Option<u32>,
67 ) -> WalletDbResult<Vec<String>> {
68 let mut ret = Vec::with_capacity(txs.len());
69 for tx in txs {
70 ret.push(self.put_tx_history_record(tx, status, block_height).await?);
71 }
72 Ok(ret)
73 }
74
75 pub async fn get_tx_history_record(
77 &self,
78 tx_hash: &str,
79 ) -> Result<(String, String, Option<u32>, Transaction)> {
80 let row = match self.wallet.query_single(
81 WALLET_TXS_HISTORY_TABLE,
82 &[],
83 convert_named_params! {(WALLET_TXS_HISTORY_COL_TX_HASH, tx_hash)},
84 ) {
85 Ok(r) => r,
86 Err(e) => {
87 return Err(Error::DatabaseError(format!(
88 "[get_tx_history_record] Transaction history record retrieval failed: {e}"
89 )))
90 }
91 };
92
93 let Value::Text(ref tx_hash) = row[0] else {
94 return Err(Error::ParseFailed(
95 "[get_tx_history_record] Transaction hash parsing failed",
96 ))
97 };
98
99 let Value::Text(ref status) = row[1] else {
100 return Err(Error::ParseFailed("[get_tx_history_record] Status parsing failed"))
101 };
102
103 let block_height = match row[2] {
104 Value::Integer(block_height) => {
105 let Ok(block_height) = u32::try_from(block_height) else {
106 return Err(Error::ParseFailed(
107 "[get_tx_history_record] Block height parsing failed",
108 ))
109 };
110 Some(block_height)
111 }
112 Value::Null => None,
113 _ => {
114 return Err(Error::ParseFailed(
115 "[get_tx_history_record] Block height parsing failed",
116 ))
117 }
118 };
119
120 let Value::Blob(ref bytes) = row[3] else {
121 return Err(Error::ParseFailed(
122 "[get_tx_history_record] Transaction bytes parsing failed",
123 ))
124 };
125 let tx: Transaction = deserialize_async(bytes).await?;
126
127 Ok((tx_hash.clone(), status.clone(), block_height, tx))
128 }
129
130 pub fn get_txs_history(&self) -> WalletDbResult<Vec<(String, String, Option<u32>)>> {
132 let rows = self.wallet.query_multiple(
133 WALLET_TXS_HISTORY_TABLE,
134 &[
135 WALLET_TXS_HISTORY_COL_TX_HASH,
136 WALLET_TXS_HISTORY_COL_STATUS,
137 WALLET_TXS_HISTORY_BLOCK_HEIGHT,
138 ],
139 &[],
140 )?;
141
142 let mut ret = Vec::with_capacity(rows.len());
143 for row in rows {
144 let Value::Text(ref tx_hash) = row[0] else {
145 return Err(WalletDbError::ParseColumnValueError)
146 };
147
148 let Value::Text(ref status) = row[1] else {
149 return Err(WalletDbError::ParseColumnValueError)
150 };
151
152 let block_height = match row[2] {
153 Value::Integer(block_height) => {
154 let Ok(block_height) = u32::try_from(block_height) else {
155 return Err(WalletDbError::ParseColumnValueError)
156 };
157 Some(block_height)
158 }
159 Value::Null => None,
160 _ => return Err(WalletDbError::ParseColumnValueError),
161 };
162
163 ret.push((tx_hash.clone(), status.clone(), block_height));
164 }
165
166 Ok(ret)
167 }
168
169 pub fn reset_tx_history(&self, output: &mut Vec<String>) -> WalletDbResult<()> {
171 output.push(String::from("Resetting transactions history"));
172 let query = format!("DELETE FROM {WALLET_TXS_HISTORY_TABLE};");
173 self.wallet.exec_sql(&query, &[])?;
174 output.push(String::from("Successfully reset transactions history"));
175
176 Ok(())
177 }
178
179 pub fn revert_transactions_after(
182 &self,
183 height: &u32,
184 output: &mut Vec<String>,
185 ) -> WalletDbResult<()> {
186 output.push(format!("Reverting transactions history after: {height}"));
187 let query = format!(
188 "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;"
189 );
190 self.wallet.exec_sql(&query, rusqlite::params![Some(*height)])?;
191 output.push(String::from("Successfully reverted transactions history"));
192
193 Ok(())
194 }
195
196 pub fn remove_reverted_txs(&self, output: &mut Vec<String>) -> WalletDbResult<()> {
199 output.push(String::from("Removing reverted transactions history records"));
200 let query = format!(
201 "DELETE FROM {WALLET_TXS_HISTORY_TABLE} WHERE {WALLET_TXS_HISTORY_COL_STATUS} = 'Reverted';"
202 );
203 self.wallet.exec_sql(&query, &[])?;
204 output.push(String::from("Successfully removed reverted transactions history records"));
205
206 Ok(())
207 }
208}