1use std::collections::HashMap;
20
21use log::{debug, error};
22use smol::io::Cursor;
23use tinyjson::JsonValue;
24
25use darkfi::{
26 blockchain::{
27 BlockInfo, BlockchainOverlay, HeaderHash, SLED_PENDING_TX_ORDER_TREE, SLED_PENDING_TX_TREE,
28 SLED_TX_LOCATION_TREE, SLED_TX_TREE,
29 },
30 error::TxVerifyFailed,
31 runtime::vm_runtime::Runtime,
32 tx::Transaction,
33 util::time::Timestamp,
34 validator::fees::{circuit_gas_use, GasData, PALLAS_SCHNORR_SIGNATURE_FEE},
35 zk::VerifyingKey,
36 Error, Result,
37};
38use darkfi_sdk::{
39 crypto::{ContractId, PublicKey},
40 deploy::DeployParamsV1,
41 pasta::pallas,
42 tx::TransactionHash,
43};
44use darkfi_serial::{deserialize_async, serialize_async, AsyncDecodable, AsyncEncodable};
45
46use crate::{error::ExplorerdError, ExplorerService};
47
48#[derive(Debug, Clone)]
49pub struct TransactionRecord {
51 pub transaction_hash: String,
53 pub header_hash: String,
55 pub payload: Transaction,
58 pub timestamp: Timestamp,
60 pub total_gas_used: u64,
62 pub wasm_gas_used: u64,
64 pub zk_circuit_gas_used: u64,
66 pub signature_gas_used: u64,
68 pub deployment_gas_used: u64,
70}
71
72impl TransactionRecord {
73 pub fn to_json_array(&self) -> JsonValue {
75 JsonValue::Array(vec![
76 JsonValue::String(self.transaction_hash.clone()),
77 JsonValue::String(self.header_hash.clone()),
78 JsonValue::String(format!("{:?}", self.payload)),
79 JsonValue::String(self.timestamp.to_string()),
80 JsonValue::Number(self.total_gas_used as f64),
81 JsonValue::Number(self.wasm_gas_used as f64),
82 JsonValue::Number(self.zk_circuit_gas_used as f64),
83 JsonValue::Number(self.signature_gas_used as f64),
84 JsonValue::Number(self.deployment_gas_used as f64),
85 ])
86 }
87}
88
89impl ExplorerService {
90 pub fn reset_transactions(&self) -> Result<()> {
92 let trees_to_reset =
94 [SLED_TX_TREE, SLED_TX_LOCATION_TREE, SLED_PENDING_TX_TREE, SLED_PENDING_TX_ORDER_TREE];
95
96 for tree_name in &trees_to_reset {
98 let tree = &self.db.blockchain.sled_db.open_tree(tree_name)?;
99 tree.clear()?;
100 let tree_name_str = std::str::from_utf8(tree_name)?;
101 debug!(target: "explorerd::blocks", "Successfully reset transaction tree: {tree_name_str}");
102 }
103
104 Ok(())
105 }
106
107 pub fn get_transaction_count(&self) -> usize {
109 self.db.blockchain.txs_len()
110 }
111
112 pub fn get_transactions(&self) -> Result<Vec<TransactionRecord>> {
118 let txs = self.db.blockchain.transactions.get_all().map_err(|e| {
120 Error::DatabaseError(format!("[get_transactions] Trxs retrieval: {e:?}"))
121 })?;
122
123 let txs_records = txs
125 .iter()
126 .map(|(_, tx)| self.to_tx_record(None, tx))
127 .collect::<Result<Vec<TransactionRecord>>>()?;
128
129 Ok(txs_records)
130 }
131
132 pub fn get_transactions_by_header_hash(
140 &self,
141 header_hash: &str,
142 ) -> Result<Vec<TransactionRecord>> {
143 let header_hash = header_hash
145 .parse::<HeaderHash>()
146 .map_err(|_| ExplorerdError::InvalidHeaderHash(header_hash.to_string()))?;
147
148 let block = match self.db.blockchain.get_blocks_by_hash(&[header_hash]) {
150 Ok(blocks) => blocks.first().cloned().unwrap(),
151 Err(Error::BlockNotFound(_)) => return Ok(vec![]),
152 Err(e) => {
153 return Err(Error::DatabaseError(format!(
154 "[get_transactions_by_header_hash] Block retrieval failed: {e:?}"
155 )))
156 }
157 };
158
159 block
161 .txs
162 .iter()
163 .map(|tx| self.to_tx_record(self.get_block_info(block.header.hash())?, tx))
164 .collect::<Result<Vec<TransactionRecord>>>()
165 }
166
167 pub fn get_transaction_by_hash(
173 &self,
174 tx_hash: &TransactionHash,
175 ) -> Result<Option<TransactionRecord>> {
176 let tx_store = &self.db.blockchain.transactions;
177
178 let tx_opt = &tx_store.get(&[*tx_hash], false).map_err(|e| {
180 Error::DatabaseError(format!(
181 "[get_transaction_by_hash] Transaction retrieval failed: {e:?}"
182 ))
183 })?[0];
184
185 tx_opt.as_ref().map(|tx| self.to_tx_record(None, tx)).transpose()
187 }
188
189 fn get_tx_block_info(&self, tx_hash: &TransactionHash) -> Result<Option<BlockInfo>> {
196 let location =
198 self.db.blockchain.transactions.get_location(&[*tx_hash], false).map_err(|e| {
199 Error::DatabaseError(format!(
200 "[get_tx_block_info] Location retrieval failed: {e:?}"
201 ))
202 })?[0];
203
204 let header_hash = match location {
206 None => return Ok(None),
207 Some((block_height, _)) => {
208 self.db.blockchain.blocks.get_order(&[block_height], false).map_err(|e| {
209 Error::DatabaseError(format!(
210 "[get_tx_block_info] Block retrieval failed: {e:?}"
211 ))
212 })?[0]
213 }
214 };
215
216 match header_hash {
218 None => Ok(None),
219 Some(header_hash) => self.get_block_info(header_hash).map_err(|e| {
220 Error::DatabaseError(format!(
221 "[get_tx_block_info] BlockInfo retrieval failed: {e:?}"
222 ))
223 }),
224 }
225 }
226
227 fn get_block_info(&self, header_hash: HeaderHash) -> Result<Option<BlockInfo>> {
233 match self.db.blockchain.get_blocks_by_hash(&[header_hash]) {
234 Err(Error::BlockNotFound(_)) => Ok(None),
235 Ok(block_info) => Ok(block_info.into_iter().next()),
236 Err(e) => Err(Error::DatabaseError(format!(
237 "[get_transactions_by_header_hash] Block retrieval failed: {e:?}"
238 ))),
239 }
240 }
241
242 pub async fn calculate_tx_gas_data(
245 &self,
246 tx: &Transaction,
247 verify_fee: bool,
248 ) -> Result<GasData> {
249 let tx_hash = tx.hash();
250
251 let overlay = BlockchainOverlay::new(&self.db.blockchain)?;
252
253 let mut total_gas_used = 0;
255 let mut zk_circuit_gas_used = 0;
256 let mut wasm_gas_used = 0;
257 let mut deploy_gas_used = 0;
258 let mut gas_paid = 0;
259
260 let mut zkp_table = vec![];
262 let mut sig_table = vec![];
264
265 let fee_call_idx = 0;
267
268 let mut payload = vec![];
270 tx.calls.encode_async(&mut payload).await?;
271
272 let mut verifying_keys: HashMap<[u8; 32], HashMap<String, VerifyingKey>> = HashMap::new();
274 for call in &tx.calls {
275 verifying_keys.insert(call.data.contract_id.to_bytes(), HashMap::new());
276 }
277
278 let block_target = self.db.blockchain.blocks.get_last()?.0 + 1;
279
280 let mut circuits_to_verify = vec![];
282
283 for (idx, call) in tx.calls.iter().enumerate() {
285 if call.data.is_money_pow_reward() {
287 error!(target: "explorerd::calculate_tx_gas_data", "Reward transaction detected");
288 return Err(TxVerifyFailed::ErroneousTxs(vec![tx.clone()]).into())
289 }
290
291 let wasm = overlay.lock().unwrap().contracts.get(call.data.contract_id)?;
292 let mut runtime = Runtime::new(
293 &wasm,
294 overlay.clone(),
295 call.data.contract_id,
296 block_target,
297 block_target,
298 tx_hash,
299 idx as u8,
300 )?;
301
302 let metadata = runtime.metadata(&payload)?;
304
305 let mut decoder = Cursor::new(&metadata);
307
308 let zkp_pub: Vec<(String, Vec<pallas::Base>)> =
310 AsyncDecodable::decode_async(&mut decoder).await?;
311 let sig_pub: Vec<PublicKey> = AsyncDecodable::decode_async(&mut decoder).await?;
312
313 if decoder.position() != metadata.len() as u64 {
314 error!(
315 target: "block_explorer::calculate_tx_gas_data",
316 "[BLOCK_EXPLORER] Failed decoding entire metadata buffer for {tx_hash}:{idx}"
317 );
318 return Err(TxVerifyFailed::ErroneousTxs(vec![tx.clone()]).into())
319 }
320
321 for (zkas_ns, _) in &zkp_pub {
323 let inner_vk_map =
324 verifying_keys.get_mut(&call.data.contract_id.to_bytes()).unwrap();
325
326 if inner_vk_map.contains_key(zkas_ns.as_str()) {
330 continue
331 }
332
333 let (zkbin, vk) =
334 overlay.lock().unwrap().contracts.get_zkas(&call.data.contract_id, zkas_ns)?;
335
336 inner_vk_map.insert(zkas_ns.to_string(), vk);
337 circuits_to_verify.push(zkbin);
338 }
339
340 zkp_table.push(zkp_pub);
341 sig_table.push(sig_pub);
342
343 let mut state_update = vec![call.data.data[0]];
345 state_update.append(&mut runtime.exec(&payload)?);
346
347 runtime.apply(&state_update)?;
349
350 if call.data.is_deployment()
352 {
354 let deploy_params: DeployParamsV1 = deserialize_async(&call.data.data[1..]).await?;
356 let deploy_cid = ContractId::derive_public(deploy_params.public_key);
357
358 let mut deploy_runtime = Runtime::new(
360 &deploy_params.wasm_bincode,
361 overlay.clone(),
362 deploy_cid,
363 block_target,
364 block_target,
365 tx_hash,
366 idx as u8,
367 )?;
368
369 deploy_runtime.deploy(&deploy_params.ix)?;
370
371 deploy_gas_used = deploy_runtime.gas_used();
372
373 total_gas_used += deploy_gas_used;
375 }
376
377 wasm_gas_used = runtime.gas_used();
380
381 total_gas_used += wasm_gas_used;
383 }
384
385 let signature_gas_used = (PALLAS_SCHNORR_SIGNATURE_FEE * tx.signatures.len() as u64) +
387 serialize_async(tx).await.len() as u64;
388
389 total_gas_used += signature_gas_used;
391
392 for zkbin in circuits_to_verify.iter() {
394 zk_circuit_gas_used = circuit_gas_use(zkbin);
395
396 total_gas_used += zk_circuit_gas_used;
398 }
399
400 if verify_fee {
401 let fee: u64 = match deserialize_async(&tx.calls[fee_call_idx].data.data[1..9]).await {
403 Ok(v) => v,
404 Err(e) => {
405 error!(
406 target: "block_explorer::calculate_tx_gas_data",
407 "[VALIDATOR] Failed deserializing tx {tx_hash} fee call: {e}"
408 );
409 return Err(TxVerifyFailed::InvalidFee.into())
410 }
411 };
412
413 if total_gas_used > fee {
416 error!(
417 target: "block_explorer::calculate_tx_gas_data",
418 "[VALIDATOR] Transaction {tx_hash} has insufficient fee. Required: {total_gas_used}, Paid: {fee}");
419 return Err(TxVerifyFailed::InsufficientFee.into())
420 }
421 debug!(target: "block_explorer::calculate_tx_gas_data", "The gas paid for transaction {tx_hash}: {gas_paid}");
422
423 gas_paid = fee;
425 }
426
427 overlay.lock().unwrap().overlay.lock().unwrap().apply()?;
429
430 let fee_data = GasData {
431 paid: gas_paid,
432 wasm: wasm_gas_used,
433 zk_circuits: zk_circuit_gas_used,
434 signatures: signature_gas_used,
435 deployments: deploy_gas_used,
436 };
437
438 debug!(target: "block_explorer::calculate_tx_gas_data", "The total gas usage for transaction {tx_hash}: {fee_data:?}");
439
440 Ok(fee_data)
441 }
442
443 fn to_tx_record(
450 &self,
451 block_info_opt: Option<BlockInfo>,
452 tx: &Transaction,
453 ) -> Result<TransactionRecord> {
454 let gas_data_option = self.db.metrics_store.get_tx_gas_data(&tx.hash()).map_err(|e| {
456 Error::DatabaseError(format!(
457 "[to_tx_record] Failed to fetch the gas data associated with transaction {}: {e:?}",
458 tx.hash()
459 ))
460 })?;
461
462 let gas_data = gas_data_option.unwrap_or_else(GasData::default);
464
465 let block_info = match block_info_opt {
467 Some(block_info) => block_info,
469 None => {
471 match self.get_tx_block_info(&tx.hash())? {
472 Some(block_info) => block_info,
473 None => {
475 return Err(Error::BlockNotFound(format!(
476 "[to_tx_record] Required `BlockInfo` was not found for transaction: {}",
477 tx.hash()
478 )))
479 }
480 }
481 }
482 };
483
484 Ok(TransactionRecord {
486 transaction_hash: tx.hash().to_string(),
487 header_hash: block_info.hash().to_string(),
488 timestamp: block_info.header.timestamp,
489 payload: tx.clone(),
490 total_gas_used: gas_data.total_gas_used(),
491 wasm_gas_used: gas_data.wasm,
492 zk_circuit_gas_used: gas_data.zk_circuits,
493 signature_gas_used: gas_data.signatures,
494 deployment_gas_used: gas_data.deployments,
495 })
496 }
497}