transactions.rs 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492
  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 std::collections::HashMap;
  19. use log::{debug, error, info};
  20. use smol::io::Cursor;
  21. use tinyjson::JsonValue;
  22. use darkfi::{
  23. blockchain::{
  24. BlockInfo, BlockchainOverlay, HeaderHash, SLED_PENDING_TX_ORDER_TREE, SLED_PENDING_TX_TREE,
  25. SLED_TX_LOCATION_TREE, SLED_TX_TREE,
  26. },
  27. error::TxVerifyFailed,
  28. runtime::vm_runtime::Runtime,
  29. tx::Transaction,
  30. util::time::Timestamp,
  31. validator::fees::{circuit_gas_use, GasData, PALLAS_SCHNORR_SIGNATURE_FEE},
  32. zk::VerifyingKey,
  33. Error, Result,
  34. };
  35. use darkfi_sdk::{
  36. crypto::{ContractId, PublicKey},
  37. deploy::DeployParamsV1,
  38. pasta::pallas,
  39. tx::TransactionHash,
  40. };
  41. use darkfi_serial::{deserialize_async, serialize_async, AsyncDecodable, AsyncEncodable};
  42. use crate::ExplorerService;
  43. #[derive(Debug, Clone)]
  44. /// Structure representing a `TRANSACTIONS_TABLE` record.
  45. pub struct TransactionRecord {
  46. /// Transaction hash identifier
  47. pub transaction_hash: String,
  48. /// Header hash identifier of the block this transaction was included in
  49. pub header_hash: String,
  50. // TODO: Split the payload into a more easily readable fields
  51. /// Transaction payload
  52. pub payload: Transaction,
  53. /// Time transaction was added to the block
  54. pub timestamp: Timestamp,
  55. /// Total gas used for processing transaction
  56. pub total_gas_used: u64,
  57. /// Gas used by WASM
  58. pub wasm_gas_used: u64,
  59. /// Gas used by ZK circuit operations
  60. pub zk_circuit_gas_used: u64,
  61. /// Gas used for creating the transaction signature
  62. pub signature_gas_used: u64,
  63. /// Gas used for deployments
  64. pub deployment_gas_used: u64,
  65. }
  66. impl TransactionRecord {
  67. /// Auxiliary function to convert a `TransactionRecord` into a `JsonValue` array.
  68. pub fn to_json_array(&self) -> JsonValue {
  69. JsonValue::Array(vec![
  70. JsonValue::String(self.transaction_hash.clone()),
  71. JsonValue::String(self.header_hash.clone()),
  72. JsonValue::String(format!("{:?}", self.payload)),
  73. JsonValue::String(self.timestamp.to_string()),
  74. JsonValue::Number(self.total_gas_used as f64),
  75. JsonValue::Number(self.wasm_gas_used as f64),
  76. JsonValue::Number(self.zk_circuit_gas_used as f64),
  77. JsonValue::Number(self.signature_gas_used as f64),
  78. JsonValue::Number(self.deployment_gas_used as f64),
  79. ])
  80. }
  81. }
  82. impl ExplorerService {
  83. /// Resets transactions in the database by clearing transaction-related trees, returning an Ok result on success.
  84. pub fn reset_transactions(&self) -> Result<()> {
  85. // Initialize transaction trees to reset
  86. let trees_to_reset =
  87. [SLED_TX_TREE, SLED_TX_LOCATION_TREE, SLED_PENDING_TX_TREE, SLED_PENDING_TX_ORDER_TREE];
  88. // Iterate over each associated transaction tree and delete its contents
  89. for tree_name in &trees_to_reset {
  90. let tree = &self.db.blockchain.sled_db.open_tree(tree_name)?;
  91. tree.clear()?;
  92. let tree_name_str = std::str::from_utf8(tree_name)?;
  93. info!(target: "blockchain-explorer::blocks", "Successfully reset transaction tree: {tree_name_str}");
  94. }
  95. Ok(())
  96. }
  97. /// Provides the transaction count of all the transactions in the explorer database.
  98. pub fn get_transaction_count(&self) -> usize {
  99. self.db.blockchain.txs_len()
  100. }
  101. /// Fetches all known transactions from the database.
  102. ///
  103. /// This function retrieves all transactions stored in the database and transforms
  104. /// them into a vector of [`TransactionRecord`]s. If no transactions are found,
  105. /// it returns an empty vector.
  106. pub fn get_transactions(&self) -> Result<Vec<TransactionRecord>> {
  107. // Retrieve all transactions and handle any errors encountered
  108. let txs = self.db.blockchain.transactions.get_all().map_err(|e| {
  109. Error::DatabaseError(format!("[get_transactions] Trxs retrieval: {e:?}"))
  110. })?;
  111. // Transform the found `Transactions` into a vector of `TransactionRecords`
  112. let txs_records = txs
  113. .iter()
  114. .map(|(_, tx)| self.to_tx_record(None, tx))
  115. .collect::<Result<Vec<TransactionRecord>>>()?;
  116. Ok(txs_records)
  117. }
  118. /// Fetches all transactions from the database for the given block `header_hash`.
  119. ///
  120. /// This function retrieves all transactions associated with the specified
  121. /// block header hash. It first parses the header hash and then fetches
  122. /// the corresponding [`BlockInfo`]. If the block is found, it transforms its
  123. /// transactions into a vector of [`TransactionRecord`]s. If no transactions
  124. /// are found, it returns an empty vector.
  125. pub fn get_transactions_by_header_hash(
  126. &self,
  127. header_hash: &str,
  128. ) -> Result<Vec<TransactionRecord>> {
  129. // Parse header hash, returning an error if parsing fails
  130. let header_hash = header_hash
  131. .parse::<HeaderHash>()
  132. .map_err(|_| Error::ParseFailed("[get_transactions_by_header_hash] Invalid hash"))?;
  133. // Fetch block by hash and handle encountered errors
  134. let block = match self.db.blockchain.get_blocks_by_hash(&[header_hash]) {
  135. Ok(blocks) => blocks.first().cloned().unwrap(),
  136. Err(Error::BlockNotFound(_)) => return Ok(vec![]),
  137. Err(e) => {
  138. return Err(Error::DatabaseError(format!(
  139. "[get_transactions_by_header_hash] Block retrieval failed: {e:?}"
  140. )))
  141. }
  142. };
  143. // Transform block transactions into transaction records
  144. block
  145. .txs
  146. .iter()
  147. .map(|tx| self.to_tx_record(self.get_block_info(block.header.hash())?, tx))
  148. .collect::<Result<Vec<TransactionRecord>>>()
  149. }
  150. /// Fetches a transaction given its header hash.
  151. ///
  152. /// This function retrieves the transaction associated with the provided
  153. /// [`TransactionHash`] and transforms it into a [`TransactionRecord`] if found.
  154. /// If no transaction is found, it returns `None`.
  155. pub fn get_transaction_by_hash(
  156. &self,
  157. tx_hash: &TransactionHash,
  158. ) -> Result<Option<TransactionRecord>> {
  159. let tx_store = &self.db.blockchain.transactions;
  160. // Attempt to retrieve the transaction using the provided hash handling any potential errors
  161. let tx_opt = &tx_store.get(&[*tx_hash], false).map_err(|e| {
  162. Error::DatabaseError(format!(
  163. "[get_transaction_by_hash] Transaction retrieval failed: {e:?}"
  164. ))
  165. })?[0];
  166. // Transform `Transaction` to a `TransactionRecord`, returning None if no transaction was found
  167. tx_opt.as_ref().map(|tx| self.to_tx_record(None, tx)).transpose()
  168. }
  169. /// Fetches the [`BlockInfo`] associated with a given transaction hash.
  170. ///
  171. /// This auxiliary function first fetches the location of the transaction in the blockchain.
  172. /// If the location is found, it retrieves the associated [`HeaderHash`] and then fetches
  173. /// the block information corresponding to that header hash. The function returns the
  174. /// [`BlockInfo`] if successful, or `None` if no location or header hash is found.
  175. fn get_tx_block_info(&self, tx_hash: &TransactionHash) -> Result<Option<BlockInfo>> {
  176. // Retrieve the location of the transaction
  177. let location =
  178. self.db.blockchain.transactions.get_location(&[*tx_hash], false).map_err(|e| {
  179. Error::DatabaseError(format!(
  180. "[get_tx_block_info] Location retrieval failed: {e:?}"
  181. ))
  182. })?[0];
  183. // Fetch the `HeaderHash` associated with the location
  184. let header_hash = match location {
  185. None => return Ok(None),
  186. Some((block_height, _)) => {
  187. self.db.blockchain.blocks.get_order(&[block_height], false).map_err(|e| {
  188. Error::DatabaseError(format!(
  189. "[get_tx_block_info] Block retrieval failed: {e:?}"
  190. ))
  191. })?[0]
  192. }
  193. };
  194. // Return the associated `BlockInfo` if the header hash is found; otherwise, return `None`.
  195. match header_hash {
  196. None => Ok(None),
  197. Some(header_hash) => self.get_block_info(header_hash).map_err(|e| {
  198. Error::DatabaseError(format!(
  199. "[get_tx_block_info] BlockInfo retrieval failed: {e:?}"
  200. ))
  201. }),
  202. }
  203. }
  204. /// Fetches the [`BlockInfo`] associated with a given [`HeaderHash`].
  205. ///
  206. /// This auxiliary function attempts to retrieve the block information using
  207. /// the specified [`HeaderHash`]. It returns the associated [`BlockInfo`] if found,
  208. /// or `None` when not found.
  209. fn get_block_info(&self, header_hash: HeaderHash) -> Result<Option<BlockInfo>> {
  210. match self.db.blockchain.get_blocks_by_hash(&[header_hash]) {
  211. Err(Error::BlockNotFound(_)) => Ok(None),
  212. Ok(block_info) => Ok(block_info.into_iter().next()),
  213. Err(e) => Err(Error::DatabaseError(format!(
  214. "[get_transactions_by_header_hash] Block retrieval failed: {e:?}"
  215. ))),
  216. }
  217. }
  218. /// Calculates the gas data for a given transaction, returning a [`GasData`] instance detailing
  219. /// various aspects of the gas usage.
  220. pub async fn calculate_tx_gas_data(
  221. &self,
  222. tx: &Transaction,
  223. verify_fee: bool,
  224. ) -> Result<GasData> {
  225. let tx_hash = tx.hash();
  226. let overlay = BlockchainOverlay::new(&self.db.blockchain)?;
  227. // Gas accumulators
  228. let mut total_gas_used = 0;
  229. let mut zk_circuit_gas_used = 0;
  230. let mut wasm_gas_used = 0;
  231. let mut deploy_gas_used = 0;
  232. let mut gas_paid = 0;
  233. // Table of public inputs used for ZK proof verification
  234. let mut zkp_table = vec![];
  235. // Table of public keys used for signature verification
  236. let mut sig_table = vec![];
  237. // Index of the Fee-paying call
  238. let fee_call_idx = 0;
  239. // Map of ZK proof verifying keys for the transaction
  240. let mut verifying_keys: HashMap<[u8; 32], HashMap<String, VerifyingKey>> = HashMap::new();
  241. for call in &tx.calls {
  242. verifying_keys.insert(call.data.contract_id.to_bytes(), HashMap::new());
  243. }
  244. let block_target = self.db.blockchain.blocks.get_last()?.0 + 1;
  245. // We'll also take note of all the circuits in a Vec so we can calculate their verification cost.
  246. let mut circuits_to_verify = vec![];
  247. // Iterate over all calls to get the metadata
  248. for (idx, call) in tx.calls.iter().enumerate() {
  249. // Transaction must not contain a Money::PoWReward(0x02) call
  250. if call.data.is_money_pow_reward() {
  251. error!(target: "block_explorer::calculate_tx_gas_data", "Reward transaction detected");
  252. return Err(TxVerifyFailed::ErroneousTxs(vec![tx.clone()]).into())
  253. }
  254. // Write the actual payload data
  255. let mut payload = vec![];
  256. tx.calls.encode_async(&mut payload).await?;
  257. let wasm = overlay.lock().unwrap().contracts.get(call.data.contract_id)?;
  258. let mut runtime = Runtime::new(
  259. &wasm,
  260. overlay.clone(),
  261. call.data.contract_id,
  262. block_target,
  263. block_target,
  264. tx_hash,
  265. idx as u8,
  266. )?;
  267. let metadata = runtime.metadata(&payload)?;
  268. // Decode the metadata retrieved from the execution
  269. let mut decoder = Cursor::new(&metadata);
  270. // The tuple is (zkas_ns, public_inputs)
  271. let zkp_pub: Vec<(String, Vec<pallas::Base>)> =
  272. AsyncDecodable::decode_async(&mut decoder).await?;
  273. let sig_pub: Vec<PublicKey> = AsyncDecodable::decode_async(&mut decoder).await?;
  274. if decoder.position() != metadata.len() as u64 {
  275. error!(
  276. target: "block_explorer::calculate_tx_gas_data",
  277. "[BLOCK_EXPLORER] Failed decoding entire metadata buffer for {}:{}", tx_hash, idx,
  278. );
  279. return Err(TxVerifyFailed::ErroneousTxs(vec![tx.clone()]).into())
  280. }
  281. // Here we'll look up verifying keys and insert them into the per-contract map.
  282. for (zkas_ns, _) in &zkp_pub {
  283. let inner_vk_map =
  284. verifying_keys.get_mut(&call.data.contract_id.to_bytes()).unwrap();
  285. // TODO: This will be a problem in case of ::deploy, unless we force a different
  286. // namespace and disable updating existing circuit. Might be a smart idea to do
  287. // so in order to have to care less about being able to verify historical txs.
  288. if inner_vk_map.contains_key(zkas_ns.as_str()) {
  289. continue
  290. }
  291. let (zkbin, vk) =
  292. overlay.lock().unwrap().contracts.get_zkas(&call.data.contract_id, zkas_ns)?;
  293. inner_vk_map.insert(zkas_ns.to_string(), vk);
  294. circuits_to_verify.push(zkbin);
  295. }
  296. zkp_table.push(zkp_pub);
  297. sig_table.push(sig_pub);
  298. // Contracts are not included within blocks. They need to be deployed off-chain so that they can be accessed and utilized for fee data computation
  299. if call.data.is_deployment()
  300. /* DeployV1 */
  301. {
  302. // Deserialize the deployment parameters
  303. let deploy_params: DeployParamsV1 = deserialize_async(&call.data.data[1..]).await?;
  304. let deploy_cid = ContractId::derive_public(deploy_params.public_key);
  305. // Instantiate the new deployment runtime
  306. let mut deploy_runtime = Runtime::new(
  307. &deploy_params.wasm_bincode,
  308. overlay.clone(),
  309. deploy_cid,
  310. block_target,
  311. block_target,
  312. tx_hash,
  313. idx as u8,
  314. )?;
  315. deploy_runtime.deploy(&deploy_params.ix)?;
  316. deploy_gas_used = deploy_runtime.gas_used();
  317. // Append the used deployment gas
  318. total_gas_used += deploy_gas_used;
  319. }
  320. // At this point we're done with the call and move on to the next one.
  321. // Accumulate the WASM gas used.
  322. wasm_gas_used = runtime.gas_used();
  323. // Append the used wasm gas
  324. total_gas_used += wasm_gas_used;
  325. }
  326. // The signature fee is tx_size + fixed_sig_fee * n_signatures
  327. let signature_gas_used = (PALLAS_SCHNORR_SIGNATURE_FEE * tx.signatures.len() as u64) +
  328. serialize_async(tx).await.len() as u64;
  329. // Append the used signature gas
  330. total_gas_used += signature_gas_used;
  331. // The ZK circuit fee is calculated using a function in validator/fees.rs
  332. for zkbin in circuits_to_verify.iter() {
  333. zk_circuit_gas_used = circuit_gas_use(zkbin);
  334. // Append the used zk circuit gas
  335. total_gas_used += zk_circuit_gas_used;
  336. }
  337. if verify_fee {
  338. // Deserialize the fee call to find the paid fee
  339. let fee: u64 = match deserialize_async(&tx.calls[fee_call_idx].data.data[1..9]).await {
  340. Ok(v) => v,
  341. Err(e) => {
  342. error!(
  343. target: "block_explorer::calculate_tx_gas_data",
  344. "[VALIDATOR] Failed deserializing tx {} fee call: {}", tx_hash, e,
  345. );
  346. return Err(TxVerifyFailed::InvalidFee.into())
  347. }
  348. };
  349. // TODO: This counts 1 gas as 1 token unit. Pricing should be better specified.
  350. // Check that enough fee has been paid for the used gas in this transaction.
  351. if total_gas_used > fee {
  352. error!(
  353. target: "block_explorer::calculate_tx_gas_data",
  354. "[VALIDATOR] Transaction {} has insufficient fee. Required: {}, Paid: {}",
  355. tx_hash, total_gas_used, fee,
  356. );
  357. return Err(TxVerifyFailed::InsufficientFee.into())
  358. }
  359. debug!(target: "block_explorer::calculate_tx_gas_data", "The gas paid for transaction {}: {}", tx_hash, gas_paid);
  360. // Store paid fee
  361. gas_paid = fee;
  362. }
  363. // Commit changes made to the overlay
  364. overlay.lock().unwrap().overlay.lock().unwrap().apply()?;
  365. let fee_data = GasData {
  366. paid: gas_paid,
  367. wasm: wasm_gas_used,
  368. zk_circuits: zk_circuit_gas_used,
  369. signatures: signature_gas_used,
  370. deployments: deploy_gas_used,
  371. };
  372. debug!(target: "block_explorer::calculate_tx_gas_data", "The total gas usage for transaction {}: {:?}", tx_hash, fee_data);
  373. Ok(fee_data)
  374. }
  375. /// Converts a [`Transaction`] and its associated block information into a [`TransactionRecord`].
  376. ///
  377. /// This auxiliary function first retrieves the gas data associated with the provided transaction.
  378. /// If [`BlockInfo`] is not provided, it attempts to fetch it using the transaction's hash,
  379. /// returning an error if the block information cannot be found. Upon success, the function
  380. /// returns a [`TransactionRecord`] containing relevant details about the transaction.
  381. fn to_tx_record(
  382. &self,
  383. block_info_opt: Option<BlockInfo>,
  384. tx: &Transaction,
  385. ) -> Result<TransactionRecord> {
  386. // Fetch the gas data associated with the transaction
  387. let gas_data_option = self.db.metrics_store.get_tx_gas_data(&tx.hash()).map_err(|e| {
  388. Error::DatabaseError(format!(
  389. "[to_tx_record] Failed to fetch the gas data associated with transaction {}: {e:?}",
  390. tx.hash()
  391. ))
  392. })?;
  393. // Unwrap the option, providing a default value when `None`
  394. let gas_data = gas_data_option.unwrap_or_else(GasData::default);
  395. // Process provided block_info option
  396. let block_info = match block_info_opt {
  397. // Use provided block_info when present
  398. Some(block_info) => block_info,
  399. // Fetch the block info associated with the transaction when block info not provided
  400. None => {
  401. match self.get_tx_block_info(&tx.hash())? {
  402. Some(block_info) => block_info,
  403. // If no associated block info found, throw an error as this should not happen
  404. None => {
  405. return Err(Error::BlockNotFound(format!(
  406. "[to_tx_record] Required `BlockInfo` was not found for transaction: {}",
  407. tx.hash()
  408. )))
  409. }
  410. }
  411. }
  412. };
  413. // Return transformed transaction record
  414. Ok(TransactionRecord {
  415. transaction_hash: tx.hash().to_string(),
  416. header_hash: block_info.hash().to_string(),
  417. timestamp: block_info.header.timestamp,
  418. payload: tx.clone(),
  419. total_gas_used: gas_data.total_gas_used(),
  420. wasm_gas_used: gas_data.wasm,
  421. zk_circuit_gas_used: gas_data.zk_circuits,
  422. signature_gas_used: gas_data.signatures,
  423. deployment_gas_used: gas_data.deployments,
  424. })
  425. }
  426. }