transactions.rs 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200
  1. /* This file is part of DarkFi (https://dark.fi)
  2. *
  3. * Copyright (C) 2020-2024 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 log::info;
  19. use rusqlite::types::Value;
  20. use tinyjson::JsonValue;
  21. use darkfi::{tx::Transaction, Error, Result};
  22. use darkfi_serial::{deserialize, serialize};
  23. use drk::{convert_named_params, error::WalletDbResult};
  24. use crate::BlockchainExplorer;
  25. // Database SQL table constant names. These have to represent the `transactions.sql`
  26. // SQL schema.
  27. pub const TRANSACTIONS_TABLE: &str = "transactions";
  28. // TRANSACTIONS_TABLE
  29. pub const TRANSACTIONS_COL_TRANSACTION_HASH: &str = "transaction_hash";
  30. pub const TRANSACTIONS_COL_HEADER_HASH: &str = "header_hash";
  31. pub const TRANSACTIONS_COL_PAYLOAD: &str = "payload";
  32. #[derive(Debug, Clone)]
  33. /// Structure representing a `TRANSACTIONS_TABLE` record.
  34. pub struct TransactionRecord {
  35. /// Transaction hash identifier
  36. pub transaction_hash: String,
  37. /// Header hash identifier of the block this transaction was included in
  38. pub header_hash: String,
  39. // TODO: Split the payload into a more easily readable fields
  40. /// Transaction payload
  41. pub payload: Transaction,
  42. }
  43. impl TransactionRecord {
  44. /// Auxiliary function to convert a `TransactionRecord` into a `JsonValue` array.
  45. pub fn to_json_array(&self) -> JsonValue {
  46. JsonValue::Array(vec![
  47. JsonValue::String(self.transaction_hash.clone()),
  48. JsonValue::String(self.header_hash.clone()),
  49. JsonValue::String(format!("{:?}", self.payload)),
  50. ])
  51. }
  52. }
  53. impl From<(&String, &Transaction)> for TransactionRecord {
  54. fn from((header_hash, transaction): (&String, &Transaction)) -> Self {
  55. Self {
  56. transaction_hash: transaction.hash().to_string(),
  57. header_hash: header_hash.clone(),
  58. payload: transaction.clone(),
  59. }
  60. }
  61. }
  62. impl BlockchainExplorer {
  63. /// Initialize database with transactions tables.
  64. pub async fn initialize_transactions(&self) -> WalletDbResult<()> {
  65. // Initialize transactions database schema
  66. let database_schema = include_str!("../transactions.sql");
  67. self.database.exec_batch_sql(database_schema)?;
  68. Ok(())
  69. }
  70. /// Reset transactions table in the database.
  71. pub fn reset_transactions(&self) -> WalletDbResult<()> {
  72. info!(target: "blockchain-explorer::transactions::reset_transactions", "Resetting transactions...");
  73. let query = format!("DELETE FROM {};", TRANSACTIONS_TABLE);
  74. self.database.exec_sql(&query, &[])
  75. }
  76. /// Import given transaction into the database.
  77. pub async fn put_transaction(&self, transaction: &TransactionRecord) -> Result<()> {
  78. let query = format!(
  79. "INSERT OR REPLACE INTO {} ({}, {}, {}) VALUES (?1, ?2, ?3);",
  80. TRANSACTIONS_TABLE,
  81. TRANSACTIONS_COL_TRANSACTION_HASH,
  82. TRANSACTIONS_COL_HEADER_HASH,
  83. TRANSACTIONS_COL_PAYLOAD
  84. );
  85. if let Err(e) = self.database.exec_sql(
  86. &query,
  87. rusqlite::params![
  88. transaction.transaction_hash,
  89. transaction.header_hash,
  90. serialize(&transaction.payload),
  91. ],
  92. ) {
  93. return Err(Error::RusqliteError(format!(
  94. "[put_transaction] Transaction insert failed: {e:?}"
  95. )))
  96. };
  97. Ok(())
  98. }
  99. /// Auxiliary function to parse a `TRANSACTIONS_TABLE` record.
  100. fn parse_transaction_record(&self, row: &[Value]) -> Result<TransactionRecord> {
  101. let Value::Text(ref transaction_hash) = row[0] else {
  102. return Err(Error::ParseFailed(
  103. "[parse_transaction_record] Transaction hash parsing failed",
  104. ))
  105. };
  106. let transaction_hash = transaction_hash.clone();
  107. let Value::Text(ref header_hash) = row[1] else {
  108. return Err(Error::ParseFailed("[parse_transaction_record] Header hash parsing failed"))
  109. };
  110. let header_hash = header_hash.clone();
  111. let Value::Blob(ref payload_bytes) = row[2] else {
  112. return Err(Error::ParseFailed(
  113. "[parse_transaction_record] Payload bytes bytes parsing failed",
  114. ))
  115. };
  116. let payload = deserialize(payload_bytes)?;
  117. Ok(TransactionRecord { transaction_hash, header_hash, payload })
  118. }
  119. /// Fetch all known transactions from the database.
  120. pub fn get_transactions(&self) -> Result<Vec<TransactionRecord>> {
  121. let rows = match self.database.query_multiple(TRANSACTIONS_TABLE, &[], &[]) {
  122. Ok(r) => r,
  123. Err(e) => {
  124. return Err(Error::RusqliteError(format!(
  125. "[get_transactions] Transactions retrieval failed: {e:?}"
  126. )))
  127. }
  128. };
  129. let mut transactions = Vec::with_capacity(rows.len());
  130. for row in rows {
  131. transactions.push(self.parse_transaction_record(&row)?);
  132. }
  133. Ok(transactions)
  134. }
  135. /// Fetch all transactions from the database for the given block header hash.
  136. pub fn get_transactions_by_header_hash(
  137. &self,
  138. header_hash: &str,
  139. ) -> Result<Vec<TransactionRecord>> {
  140. let rows = match self.database.query_multiple(
  141. TRANSACTIONS_TABLE,
  142. &[],
  143. convert_named_params! {(TRANSACTIONS_COL_HEADER_HASH, header_hash)},
  144. ) {
  145. Ok(r) => r,
  146. Err(e) => {
  147. return Err(Error::RusqliteError(format!(
  148. "[get_transactions_by_header_hash] Transactions retrieval failed: {e:?}"
  149. )))
  150. }
  151. };
  152. let mut transactions = Vec::with_capacity(rows.len());
  153. for row in rows {
  154. transactions.push(self.parse_transaction_record(&row)?);
  155. }
  156. Ok(transactions)
  157. }
  158. /// Fetch a transaction given its header hash.
  159. pub fn get_transaction_by_hash(&self, transaction_hash: &str) -> Result<TransactionRecord> {
  160. let row = match self.database.query_single(
  161. TRANSACTIONS_TABLE,
  162. &[],
  163. convert_named_params! {(TRANSACTIONS_COL_TRANSACTION_HASH, transaction_hash)},
  164. ) {
  165. Ok(r) => r,
  166. Err(e) => {
  167. return Err(Error::RusqliteError(format!(
  168. "[get_transaction_by_hash] Transaction retrieval failed: {e:?}"
  169. )))
  170. }
  171. };
  172. self.parse_transaction_record(&row)
  173. }
  174. }