1use log::{debug, warn};
20use sled_overlay::sled::{transaction::ConflictableTransactionError, Transactional};
21use tinyjson::JsonValue;
22
23use darkfi::{
24 blockchain::{
25 block_store::append_tx_to_merkle_tree, BlockInfo, BlockchainOverlay, HeaderHash,
26 SLED_BLOCK_DIFFICULTY_TREE, SLED_BLOCK_ORDER_TREE, SLED_BLOCK_TREE,
27 },
28 runtime::vm_runtime::Runtime,
29 util::time::Timestamp,
30 Error, Result,
31};
32use darkfi_sdk::{
33 crypto::{schnorr::Signature, MerkleTree},
34 tx::TransactionHash,
35 ContractError,
36};
37use darkfi_serial::AsyncEncodable;
38
39use crate::{error::ExplorerdError, ExplorerService};
40
41#[derive(Debug, Clone)]
42pub struct BlockRecord {
44 pub header_hash: String,
46 pub version: u8,
48 pub previous: String,
50 pub height: u32,
52 pub timestamp: Timestamp,
54 pub nonce: u64,
56 pub transactions_root: String,
58 pub state_root: String,
60 pub pow_data: String,
62 pub signature: Signature,
64}
65
66impl BlockRecord {
67 pub fn to_json_array(&self) -> JsonValue {
69 JsonValue::Array(vec![
70 JsonValue::String(self.header_hash.clone()),
71 JsonValue::Number(self.version as f64),
72 JsonValue::String(self.previous.clone()),
73 JsonValue::Number(self.height as f64),
74 JsonValue::String(self.timestamp.to_string()),
75 JsonValue::Number(self.nonce as f64),
76 JsonValue::String(self.transactions_root.clone()),
77 JsonValue::String(self.state_root.clone()),
78 JsonValue::String(self.pow_data.clone()),
79 JsonValue::String(format!("{:?}", self.signature)),
80 ])
81 }
82}
83
84impl From<&BlockInfo> for BlockRecord {
85 fn from(block: &BlockInfo) -> Self {
86 Self {
87 header_hash: block.hash().to_string(),
88 version: block.header.version,
89 previous: block.header.previous.to_string(),
90 height: block.header.height,
91 timestamp: block.header.timestamp,
92 nonce: block.header.nonce,
93 transactions_root: block.header.transactions_root.to_string(),
94 state_root: blake3::Hash::from_bytes(block.header.state_root).to_string(),
95 pow_data: format!("{:?}", block.header.pow_data),
96 signature: block.signature,
97 }
98 }
99}
100
101impl ExplorerService {
102 pub fn reset_blocks(&self) -> Result<()> {
104 let db = &self.db.blockchain.sled_db;
105 let trees_to_reset = [SLED_BLOCK_TREE, SLED_BLOCK_ORDER_TREE, SLED_BLOCK_DIFFICULTY_TREE];
107
108 for tree_name in &trees_to_reset {
110 let tree = db.open_tree(tree_name)?;
111 tree.clear()?;
112 let tree_name_str = std::str::from_utf8(tree_name)?;
113 debug!(target: "explorerd::blocks", "Successfully reset block tree: {tree_name_str}");
114 }
115
116 Ok(())
117 }
118
119 pub async fn put_block(&self, block: &BlockInfo) -> Result<()> {
127 let blockchain_overlay = BlockchainOverlay::new(&self.db.blockchain)?;
128 let mut tree = MerkleTree::new(1);
129
130 let mut tx_gas_data = Vec::with_capacity(block.txs.len());
132 let mut txs_hashes_with_gas_data = Vec::with_capacity(block.txs.len());
133
134 for (idx, tx) in block.txs.iter().enumerate() {
136 if tx.is_pow_reward() {
138 let mut payload = vec![];
139 tx.calls.encode_async(&mut payload).await?;
140
141 let call = &tx.calls[0];
142 let wasm =
143 blockchain_overlay.lock().unwrap().contracts.get(call.data.contract_id)?;
144 let block_target = self.db.blockchain.blocks.get_last()?.0 + 1;
145
146 let mut runtime = Runtime::new(
147 &wasm,
148 blockchain_overlay.clone(),
149 call.data.contract_id,
150 block.header.height,
151 block_target,
152 tx.hash(),
153 0,
154 )?;
155
156 let mut state_update = vec![call.data.data[0]];
158 let exec_result = runtime.exec(&payload);
159
160 if let Err(Error::ContractError(ContractError::Custom(7))) = exec_result {
163 warn!(target: "explorerd::blocks::put_block",
164 "PoW reward coin already applied to the contract state for contract ID {} at height {} for tx: {}. Skipping re-application.",
165 call.data.contract_id,
166 block.header.height,
167 tx.hash()
168 );
169 continue;
170 }
171
172 state_update.append(&mut exec_result?);
173 runtime.apply(&state_update)?;
174
175 append_tx_to_merkle_tree(&mut tree, tx);
177
178 continue;
180 }
181
182 if block.header.height != 0 {
184 tx_gas_data.insert(idx, self.calculate_tx_gas_data(tx, true).await?);
185 txs_hashes_with_gas_data.insert(idx, tx.hash());
186 }
187 }
188
189 if !tx_gas_data.is_empty() {
191 self.db.metrics_store.insert_gas_metrics(
192 block.header.height,
193 &block.header.timestamp,
194 &txs_hashes_with_gas_data,
195 &tx_gas_data,
196 )?;
197 }
198
199 let _ = blockchain_overlay.lock().unwrap().add_block(block)?;
201 blockchain_overlay.lock().unwrap().overlay.lock().unwrap().apply()?;
202 debug!(target: "explorerd::blocks::put_block", "Added block {block:?}");
203
204 Ok(())
205 }
206
207 pub fn get_block_count(&self) -> usize {
209 self.db.blockchain.len()
210 }
211
212 pub fn get_blocks(&self) -> Result<Vec<BlockRecord>> {
214 let blocks = &self.db.blockchain.get_all().map_err(|e| {
216 Error::DatabaseError(format!("[get_blocks] Block retrieval failed: {e:?}"))
217 })?;
218
219 let block_records: Vec<BlockRecord> = blocks.iter().map(BlockRecord::from).collect();
221
222 Ok(block_records)
223 }
224
225 pub fn get_block_by_hash(&self, header_hash: &str) -> Result<Option<BlockRecord>> {
227 let header_hash = header_hash
229 .parse::<HeaderHash>()
230 .map_err(|_| ExplorerdError::InvalidHeaderHash(header_hash.to_string()))?;
231
232 match self.db.blockchain.get_blocks_by_hash(&[header_hash]) {
234 Ok(blocks) => Ok(blocks.first().map(BlockRecord::from)),
235 Err(Error::BlockNotFound(_)) => Ok(None),
236 Err(e) => Err(Error::DatabaseError(format!(
237 "[get_block_by_hash] Block retrieval failed: {e:?}"
238 ))),
239 }
240 }
241
242 pub fn get_block_by_height(&self, height: u32) -> Result<Option<BlockRecord>> {
244 match self.db.blockchain.get_blocks_by_heights(&[height]) {
246 Ok(blocks) => Ok(blocks.first().map(BlockRecord::from)),
247 Err(Error::BlockNotFound(_)) => Ok(None),
248 Err(e) => Err(Error::DatabaseError(format!(
249 "[get_block_by_height] Block retrieval failed: {e:?}"
250 ))),
251 }
252 }
253
254 pub fn last_block(&self) -> Result<Option<(u32, String)>> {
256 let block_store = &self.db.blockchain.blocks;
257
258 if block_store.is_empty() {
260 return Ok(None);
261 }
262
263 let (height, header_hash) = block_store.get_last().map_err(|e| {
265 Error::DatabaseError(format!("[last_block] Block retrieval failed: {e:?}"))
266 })?;
267
268 Ok(Some((height, header_hash.to_string())))
270 }
271
272 pub fn get_last_n(&self, n: usize) -> Result<Vec<BlockRecord>> {
274 let blocks_result = &self.db.blockchain.get_last_n(n).map_err(|e| {
276 Error::DatabaseError(format!("[get_last_n] Block retrieval failed: {e:?}"))
277 })?;
278
279 let block_records: Vec<BlockRecord> = blocks_result.iter().map(BlockRecord::from).collect();
281
282 Ok(block_records)
283 }
284
285 pub fn get_by_range(&self, start: u32, end: u32) -> Result<Vec<BlockRecord>> {
287 let blocks_result = &self.db.blockchain.get_by_range(start, end).map_err(|e| {
289 Error::DatabaseError(format!("[get_by_range]: Block retrieval failed: {e:?}"))
290 })?;
291
292 let block_records: Vec<BlockRecord> = blocks_result.iter().map(BlockRecord::from).collect();
294
295 Ok(block_records)
296 }
297
298 pub fn reset_to_height(&self, reset_height: u32) -> Result<()> {
310 let block_store = &self.db.blockchain.blocks;
311 let tx_store = &self.db.blockchain.transactions;
312
313 let (last_block_height, _) = block_store.get_last().map_err(|e| {
315 Error::DatabaseError(format!(
316 "[reset_to_height]: Failed to get the last block height: {e:?}"
317 ))
318 })?;
319
320 debug!(target: "explorerd::blocks::reset_to_height",
321 "Resetting to height {reset_height} from last block height {last_block_height}: block_count={}, txs_count={}",
322 block_store.len(), tx_store.len());
323
324 if reset_height >= last_block_height {
326 warn!(target: "explorerd::blocks::reset_to_height",
327 "Nothing to reset because reset_height is greater than or equal to last_block_height: {reset_height} >= {last_block_height}");
328 return Ok(());
329 }
330
331 let block_infos_to_reset =
333 &self.db.blockchain.get_by_range(reset_height + 1, last_block_height).map_err(|e| {
334 Error::DatabaseError(format!(
335 "[reset_to_height]: Failed to get the transaction hashes to reset: {e:?}"
336 ))
337 })?;
338
339 let txs_hashes_to_reset: Vec<(u32, TransactionHash)> = block_infos_to_reset
341 .iter()
342 .flat_map(|block_info| {
343 let block_height = block_info.header.height;
344 block_info.txs.iter().map(move |tx| {
345 debug!(target: "explorerd::blocks::reset_to_height", "Adding trx to delete for height {block_height}: {}", tx.hash());
346 (block_height, tx.hash())
347 })
348 })
349 .collect();
350
351 let tx_result = (&block_store.main, &block_store.order, &block_store.difficulty, &tx_store.main, &tx_store.location)
353 .transaction(|(block_main, block_order, block_difficulty, tx_main, tx_location)| {
354 for height in (reset_height + 1..=last_block_height).rev() {
356 let height_key = height.to_be_bytes();
357
358 let order_header_hash = block_order.get(height_key).map_err(ConflictableTransactionError::Abort)?;
360
361 if let Some(header_hash) = order_header_hash {
362
363 block_main.remove(&header_hash).map_err(ConflictableTransactionError::Abort)?;
365
366 block_difficulty.remove(&height_key).map_err(ConflictableTransactionError::Abort)?;
368
369 block_order.remove(&height_key).map_err(ConflictableTransactionError::Abort)?;
371 }
372
373 debug!(target: "explorerd::blocks::reset_to_height", "Removed block at height: {height}");
374 }
375
376 for (height, tx_hash) in txs_hashes_to_reset.iter() {
378 tx_main.remove(tx_hash.inner()).map_err(ConflictableTransactionError::Abort)?;
380 tx_location.remove(tx_hash.inner()).map_err(ConflictableTransactionError::Abort)?;
382 debug!(target: "explorerd::blocks::reset_to_height", "Removed transaction at height {height}: {tx_hash}");
383 }
384
385 Ok(())
386 })
387 .map_err(|e| {
388 Error::DatabaseError(format!("[reset_to_height]: Resetting height failed: {e:?}"))
389 });
390
391 debug!(target: "explorerd::blocks::reset_to_height", "Successfully reset to height {reset_height}: block_count={}, txs_count={}", block_store.len(), tx_store.len());
392
393 tx_result
394 }
395}