diff --git a/script/research/blockchain-explorer/src/blocks.rs b/script/research/blockchain-explorer/src/blocks.rs index 697443c26..f8ae654c8 100644 --- a/script/research/blockchain-explorer/src/blocks.rs +++ b/script/research/blockchain-explorer/src/blocks.rs @@ -16,7 +16,8 @@ * along with this program. If not, see . */ -use log::{debug, info}; +use log::{debug, warn}; +use sled_overlay::sled::{transaction::ConflictableTransactionError, Transactional}; use tinyjson::JsonValue; use darkfi::{ @@ -27,7 +28,7 @@ use darkfi::{ util::time::Timestamp, Error, Result, }; -use darkfi_sdk::crypto::schnorr::Signature; +use darkfi_sdk::{crypto::schnorr::Signature, tx::TransactionHash}; use crate::ExplorerService; @@ -95,7 +96,7 @@ impl ExplorerService { let tree = db.open_tree(tree_name)?; tree.clear()?; let tree_name_str = std::str::from_utf8(tree_name)?; - info!(target: "blockchain-explorer::blocks", "Successfully reset block tree: {tree_name_str}"); + debug!(target: "blockchain-explorer::blocks", "Successfully reset block tree: {tree_name_str}"); } Ok(()) @@ -167,7 +168,7 @@ impl ExplorerService { // Fetch block by hash and handle encountered errors match self.db.blockchain.get_blocks_by_hash(&[header_hash]) { - Ok(blocks) => Ok(Some(BlockRecord::from(&blocks[0]))), + Ok(blocks) => Ok(blocks.first().map(BlockRecord::from)), Err(Error::BlockNotFound(_)) => Ok(None), Err(e) => Err(Error::DatabaseError(format!( "[get_block_by_hash] Block retrieval failed: {e:?}" @@ -175,6 +176,18 @@ impl ExplorerService { } } + /// Fetch a block given its height from the database. + pub fn get_block_by_height(&self, height: u32) -> Result> { + // Fetch block by height and handle encountered errors + match self.db.blockchain.get_blocks_by_heights(&[height]) { + Ok(blocks) => Ok(blocks.first().map(BlockRecord::from)), + Err(Error::BlockNotFound(_)) => Ok(None), + Err(e) => Err(Error::DatabaseError(format!( + "[get_block_by_height] Block retrieval failed: {e:?}" + ))), + } + } + /// Fetch the last block from the database. pub fn last_block(&self) -> Result> { let block_store = &self.db.blockchain.blocks; @@ -218,4 +231,94 @@ impl ExplorerService { Ok(block_records) } + + /// Resets the [`ExplorerDb::blockchain::blocks`] and [`ExplorerDb::blockchain::transactions`] + /// trees to a specified height by removing entries above the `reset_height`, returning a result + /// that indicates success or failure. + /// + /// The function retrieves the last explorer block and iteratively rolls back entries + /// in the [`BlockStore::main`], [`BlockStore::order`], and [`BlockStore::difficulty`] trees + /// to the specified `reset_height`. It also resets the [`TxStore::main`] and + /// [`TxStore::location`] trees to reflect the transaction state at the given height. + /// + /// This operation is performed atomically using a sled transaction applied across the affected sled + /// trees, ensuring consistency and avoiding partial updates. + pub fn reset_to_height(&self, reset_height: u32) -> Result<()> { + let block_store = &self.db.blockchain.blocks; + let tx_store = &self.db.blockchain.transactions; + + debug!(target: "blockchain_explorer::blocks::reset_to_height", "Resetting to height {reset_height}: block_count={}, txs_count={}", block_store.len(), tx_store.len()); + + // Get the last block height + let (last_block_height, _) = block_store.get_last().map_err(|e| { + Error::DatabaseError(format!( + "[reset_to_height]: Failed to get the last block height: {e:?}" + )) + })?; + + // Skip resetting blocks if `reset_height` is greater than or equal to `last_block_height` + if reset_height >= last_block_height { + warn!(target: "blockchain_explorer::blocks::reset_to_height", + "Nothing to reset because reset_height is greater than or equal to last_block_height: {reset_height} >= {last_block_height}"); + return Ok(()); + } + + // Get the associated block infos in order to obtain transactions to reset + let block_infos_to_reset = + &self.db.blockchain.get_by_range(reset_height, last_block_height).map_err(|e| { + Error::DatabaseError(format!( + "[reset_to_height]: Failed to get the transaction hashes to reset: {e:?}" + )) + })?; + + // Collect the transaction hashes from the blocks that need resetting + let txs_hashes_to_reset: Vec = block_infos_to_reset + .iter() + .flat_map(|block_info| block_info.txs.iter().map(|tx| tx.hash())) + .collect(); + + // Perform the reset operation atomically using a sled transaction + let tx_result = (&block_store.main, &block_store.order, &block_store.difficulty, &tx_store.main, &tx_store.location) + .transaction(|(block_main, block_order, block_difficulty, tx_main, tx_location)| { + // Traverse the block heights in reverse, removing each block up to (but not including) reset_height + for height in (reset_height + 1..=last_block_height).rev() { + let height_key = height.to_be_bytes(); + + // Fetch block from `order` tree to obtain the block hash needed to remove blocks from `main` tree + let order_header_hash = block_order.get(height_key).map_err(ConflictableTransactionError::Abort)?; + + if let Some(header_hash) = order_header_hash { + + // Remove block from the `main` tree + block_main.remove(&header_hash).map_err(ConflictableTransactionError::Abort)?; + + // Remove block from the `difficulty` tree + block_difficulty.remove(&height_key).map_err(ConflictableTransactionError::Abort)?; + + // Remove block from the `order` tree + block_order.remove(&height_key).map_err(ConflictableTransactionError::Abort)?; + } + + debug!(target: "blockchain_explorer::blocks::reset_to_height", "Removed block at height: {height}"); + } + + // Iterate through the transaction hashes, removing the related transactions + for (tx_count, tx_hash) in txs_hashes_to_reset.iter().enumerate() { + // Remove transaction from the `main` tree + tx_main.remove(tx_hash.inner()).map_err(ConflictableTransactionError::Abort)?; + // Remove transaction from the `location` tree + tx_location.remove(tx_hash.inner()).map_err(ConflictableTransactionError::Abort)?; + debug!(target: "blockchain_explorer::blocks::reset_to_height", "Removed transaction ({tx_count}): {tx_hash}"); + } + + Ok(()) + }) + .map_err(|e| { + Error::DatabaseError(format!("[reset_to_height]: Resetting height failed: {e:?}")) + }); + + debug!(target: "blockchain_explorer::blocks::reset_to_height", "Successfully reset to height {reset_height}: block_count={}, txs_count={}", block_store.len(), tx_store.len()); + + tx_result + } } diff --git a/script/research/blockchain-explorer/src/error.rs b/script/research/blockchain-explorer/src/error.rs index 5a6658e12..da43de208 100644 --- a/script/research/blockchain-explorer/src/error.rs +++ b/script/research/blockchain-explorer/src/error.rs @@ -51,7 +51,6 @@ pub fn server_error(e: RpcError, id: u16, msg: Option<&str>) -> JsonResult { /// Handles a database error by formatting the output, logging it with target-specific context, /// and returning a [`DatabaseError`]. -#[allow(dead_code)] // TODO: Remove once code that uses this function is rebased into master pub fn handle_database_error(target: &str, message: &str, error: impl std::fmt::Debug) -> Error { let error_message = format!("{}: {:?}", message, error); let formatted_target = format!("blockchain-explorer:: {target}"); diff --git a/script/research/blockchain-explorer/src/main.rs b/script/research/blockchain-explorer/src/main.rs index 74c43eb69..4327c64b5 100644 --- a/script/research/blockchain-explorer/src/main.rs +++ b/script/research/blockchain-explorer/src/main.rs @@ -23,7 +23,7 @@ use std::{ }; use lazy_static::lazy_static; -use log::{error, info}; +use log::{debug, error, info}; use rpc_blocks::subscribe_blocks; use sled_overlay::sled; use smol::{lock::Mutex, stream::StreamExt}; @@ -225,6 +225,36 @@ impl ExplorerService { Ok(()) } + + /// Resets the explorer state to the specified height. If a genesis block height is provided, + /// all blocks and transactions are purged from the database. Otherwise, the state is reverted + /// to the given height. The explorer metrics are updated to reflect the updated blocks and + /// transactions up to the reset height, ensuring consistency. Returns a result indicating + /// success or an error if the operation fails. + pub fn reset_explorer_state(&self, height: u32) -> Result<()> { + debug!(target: "blockchain-explorer::reset_explorer_state", "Resetting explorer state to height: {height}"); + + // Check if a genesis block reset or to a specific height + match height { + // Reset for genesis height 0, purge blocks and transactions + 0 => { + self.reset_blocks()?; + self.reset_transactions()?; + debug!(target: "blockchain-explorer::reset_explorer_state", "Successfully reset explorer state to accept a new genesis block"); + } + // Reset for all other heights + _ => { + self.reset_to_height(height)?; + debug!(target: "blockchain-explorer::reset_explorer_state", "Successfully reset blocks to height: {height}"); + } + } + + // Reset gas metrics to the specified height to reflect the updated blockchain state + self.db.metrics_store.reset_gas_metrics(height)?; + debug!(target: "blockchain-explorer::reset_explorer_state", "Successfully reset metrics store to height: {height}"); + + Ok(()) + } } /// Represents the explorer database backed by a `sled` database connection, responsible for maintaining @@ -249,8 +279,7 @@ impl ExplorerDb { let blockchain = Blockchain::new(&sled_db)?; let metrics_store = MetricsStore::new(&sled_db)?; let contract_meta_store = ContractMetaStore::new(&sled_db)?; - - info!(target: "blockchain-explorer", "Initialized explorer database {}, block count: {}", db_path.display(), blockchain.len()); + info!(target: "blockchain-explorer", "Initialized explorer database {}: block count: {}, tx count: {}", db_path.display(), blockchain.len(), blockchain.txs_len()); Ok(Self { sled_db, blockchain, metrics_store, contract_meta_store }) } } diff --git a/script/research/blockchain-explorer/src/rpc_blocks.rs b/script/research/blockchain-explorer/src/rpc_blocks.rs index 0bebf2ac7..be24782c8 100644 --- a/script/research/blockchain-explorer/src/rpc_blocks.rs +++ b/script/research/blockchain-explorer/src/rpc_blocks.rs @@ -18,7 +18,7 @@ use std::sync::Arc; -use log::{error, info, warn}; +use log::{debug, error, info, warn}; use tinyjson::JsonValue; use url::Url; @@ -37,11 +37,11 @@ use darkfi::{ }; use darkfi_serial::deserialize_async; -use crate::Explorerd; +use crate::{error::handle_database_error, Explorerd}; impl Explorerd { // Queries darkfid for a block with given height. - async fn get_block_by_height(&self, height: u32) -> Result { + async fn get_darkfid_block_by_height(&self, height: u32) -> Result { let params = self .darkfid_daemon_request( "blockchain.get_block", @@ -54,64 +54,176 @@ impl Explorerd { Ok(block) } - /// Syncs the blockchain starting from the last synced block. - /// If reset flag is provided, all tables are reset, and start syncing from beginning. + /// Synchronizes blocks between the explorer and a Darkfi blockchain node, ensuring + /// the database remains consistent by syncing any missing or outdated blocks. + /// + /// If provided `reset` is true, the explorer's blockchain-related and metric sled trees are purged + /// and syncing starts from the genesis block. The function also handles reorgs by re-aligning the + /// explorer state to the correct height when blocks are outdated. Returns a result indicating + /// success or failure. + /// + /// Reorg handling is delegated to the [`Self::process_sync_blocks_reorg`] function, whose + /// documentation provides more details on the reorg process during block syncing. pub async fn sync_blocks(&self, reset: bool) -> Result<()> { - // Grab last synced block height - let mut height = match self.service.last_block() { - Ok(Some((height, _))) => height, - Ok(None) => 0, - Err(e) => { - return Err(Error::DatabaseError(format!( - "[sync_blocks] Retrieving last synced block failed: {:?}", - e - ))); - } - }; - // If last synced block is genesis (0) or reset flag - // has been provided we reset, otherwise continue with - // the next block height - if height == 0 || reset { - self.service.reset_blocks()?; - height = 0; - } else { - height += 1; - }; - - loop { - // Grab last confirmed block - let (last_height, last_hash) = self.get_last_confirmed_block().await?; - - info!(target: "blockchain-explorer::rpc_blocks::sync_blocks", "Requested to sync from block number: {height}"); - info!(target: "blockchain-explorer::rpc_blocks::sync_blocks", "Last confirmed block number reported by darkfid: {last_height} - {last_hash}"); - - // Already synced last confirmed block - if height > last_height { - return Ok(()) - } - - while height <= last_height { - let block = match self.get_block_by_height(height).await { - Ok(r) => r, - Err(e) => { - let error_message = - format!("[sync_blocks] RPC client request failed: {:?}", e); - error!(target: "blockchain-explorer::rpc_blocks::sync_blocks", "{}", error_message); - return Err(Error::DatabaseError(error_message)); - } - }; - - if let Err(e) = self.service.put_block(&block).await { - let error_message = format!("[sync_blocks] Put block failed: {:?}", e); - error!(target: "blockchain-explorer::rpc_blocks::sync_blocks", "{}", error_message); - return Err(Error::DatabaseError(error_message)); - }; - - info!(target: "blockchain-explorer::rpc_blocks::sync_blocks", "Synced block {height}"); - - height += 1; + // Grab last synced block height from the explorer's database. + let last_synced_block = self.service.last_block().map_err(|e| { + handle_database_error( + "rpc_blocks::sync_blocks", + "[sync_blocks] Retrieving last synced block failed", + e, + ) + })?; + + // Grab the last confirmed block height and hash from the darkfi node + let (last_darkfid_height, last_darkfid_hash) = self.get_last_confirmed_block().await?; + + // Initialize the current height to sync from, starting from genesis block if last sync block does not exist + let (last_synced_height, last_synced_hash) = last_synced_block + .map_or((0, "".to_string()), |(height, header_hash)| (height, header_hash)); + + // Declare a mutable variable to track the current sync height while processing blocks + let mut current_height = last_synced_height; + + info!(target: "blockchain-explorer::rpc_blocks::sync_blocks", "Requested to sync from block number: {current_height}"); + info!(target: "blockchain-explorer::rpc_blocks::sync_blocks", "Last confirmed block number reported by darkfid: {last_darkfid_height} - {last_darkfid_hash}"); + + // A reorg is detected if the hash of the last synced block differs from the hash of the last confirmed block, + // unless the reset flag is set or the current height is 0 + let reorg_detected = last_synced_hash != last_darkfid_hash && !reset && current_height != 0; + + // If the reset flag is set, reset the explorer state and start syncing from the genesis block height. + // Otherwise, handle reorgs if detected, or proceed to the next block if not at the genesis height. + if reset { + self.service.reset_explorer_state(0)?; + current_height = 0; + info!(target: "blockchain-explorer::rpc_blocks::sync_blocks", "Successfully reset explorer database based on set reset parameter"); + } else if reorg_detected { + current_height = + self.process_sync_blocks_reorg(last_synced_height, last_darkfid_height).await?; + // Log only if a reorg occurred + if current_height != last_synced_height { + info!(target: "blockchain-explorer::rpc_blocks::sync_blocks", "Successfully completed reorg to height: {current_height}"); } + // Prepare to sync the next block after reorg + current_height += 1; + } else if current_height != 0 { + // Resume syncing from the block after the last synced height + current_height += 1; } + + // Sync blocks until the explorer is up to date with the last confirmed block + while current_height <= last_darkfid_height { + // Retrieve the block from darkfi node by height + let block = match self.get_darkfid_block_by_height(current_height).await { + Ok(r) => r, + Err(e) => { + return Err(handle_database_error( + "rpc_blocks::sync_blocks", + "[sync_blocks] RPC client request failed", + e, + )) + } + }; + + // Store the retrieved block in the explorer's database + if let Err(e) = self.service.put_block(&block).await { + return Err(handle_database_error( + "rpc_blocks::sync_blocks", + "[sync_blocks] Put block failed", + e, + )) + }; + + info!(target: "blockchain-explorer::rpc_blocks::sync_blocks", "Synced block {current_height}"); + + // Increment the current height to sync the next block + current_height += 1; + } + + info!(target: "blockchain-explorer::rpc_blocks::sync_blocks", "Completed sync, total number of explorer blocks: {}", self.service.db.blockchain.blocks.len()); + + Ok(()) + } + + /// Handles blockchain reorganizations (reorgs) during the explorer node's startup synchronization + /// with Darkfi nodes, ensuring the explorer provides a consistent and accurate view of the blockchain. + /// + /// A reorg occurs when the blocks stored by the blockchain nodes diverge from those stored by the explorer. + /// This function resolves inconsistencies by identifying the point of divergence, searching backward through + /// block heights, and comparing block hashes between the explorer database and the blockchain node. Once a + /// common block height is found, the explorer is re-aligned to that height. + /// + /// If no common block can be found, the explorer resets to the "genesis height," removing all blocks, + /// transactions, and metrics from its database to resynchronize with the canonical chain from the nodes. + /// + /// Returns the last height at which the explorer's state was successfully re-aligned with the blockchain. + async fn process_sync_blocks_reorg( + &self, + last_synced_height: u32, + last_darkfid_height: u32, + ) -> Result { + // Log reorg detection in the case that explorer height is greater or equal to height of darkfi node + if last_synced_height >= last_darkfid_height { + info!(target: "blockchain-explorer::rpc_blocks::process_sync_blocks_reorg", + "Reorg detected with heights: explorer.{last_synced_height} >= darkfid.{last_darkfid_height}"); + } + + // Declare a mutable variable to track the current height while searching for a common block + let mut cur_height = last_synced_height; + // Search for an explorer block that matches a darkfi node block + while cur_height > 0 { + let synced_block = self.service.get_block_by_height(cur_height)?; + debug!(target: "blockchain-explorer::rpc_blocks::process_sync_blocks_reorg", "Searching for common block: {}", cur_height); + + // Check if we found a synced block for current height being searched + if let Some(synced_block) = synced_block { + // Fetch the block from darkfi node to check for a match + match self.get_darkfid_block_by_height(cur_height).await { + Ok(darkfid_block) => { + // If hashes match, we've found the point of divergence + if synced_block.header_hash == darkfid_block.hash().to_string() { + // If hashes match but the cur_height differs from the last synced height, reset the explorer state + if cur_height != last_synced_height { + self.service.reset_explorer_state(cur_height)?; + debug!(target: "blockchain-explorer::rpc_blocks::process_sync_blocks_reorg", "Successfully completed reorg to height: {cur_height}"); + } + break; + } else { + // Log reorg detection with height and header hash mismatch details + if cur_height == last_synced_height { + info!( + target: "blockchain-explorer::rpc_blocks::process_sync_blocks_reorg", + "Reorg detected at height {}: explorer.{} != darkfid.{}", + cur_height, + synced_block.header_hash, + darkfid_block.hash().to_string() + ); + } + } + } + // Continue searching for blocks that do not exist on darkfi nodes + Err(Error::JsonRpcError((-32121, _))) => (), + Err(e) => { + return Err(handle_database_error( + "rpc_blocks::process_sync_blocks_reorg", + "[process_sync_blocks_reorg] RPC client request failed", + e, + )) + } + } + } + + // Move to previous block to search for a match + cur_height = cur_height.saturating_sub(1); + } + + // Check if genesis block reorg is needed + if cur_height == 0 { + self.service.reset_explorer_state(0)?; + } + + // Return the last height we reorged to + Ok(cur_height) } // RPCAPI: @@ -269,12 +381,12 @@ pub async fn subscribe_blocks( ex: Arc>, ) -> Result<(StoppableTaskPtr, StoppableTaskPtr)> { // Grab last confirmed block - let (last_confirmed, _) = explorer.get_last_confirmed_block().await?; + let (last_darkfid_height, last_darkfid_hash) = explorer.get_last_confirmed_block().await?; // Grab last synced block - let last_synced = match explorer.service.last_block() { - Ok(Some((height, _))) => height, - Ok(None) => 0, + let (mut height, hash) = match explorer.service.last_block() { + Ok(Some((height, hash))) => (height, hash), + Ok(None) => (0, "".to_string()), Err(e) => { return Err(Error::DatabaseError(format!( "[subscribe_blocks] Retrieving last synced block failed: {e:?}" @@ -282,12 +394,20 @@ pub async fn subscribe_blocks( } }; - if last_confirmed != last_synced { - warn!(target: "blockchain-explorer::rpc_blocks::subscribe_blocks", "Warning: Last synced block is not the last confirmed block."); + // Evaluates whether there is a mismatch between the last confirmed block and the last synced block + let blocks_mismatch = (last_darkfid_height != height || last_darkfid_hash != hash) && + last_darkfid_height != 0 && + height != 0; + + // Check if there is a mismatch, throwing an error to prevent operating in a potentially inconsistent state + if blocks_mismatch { + warn!(target: "blockchain-explorer::rpc_blocks::subscribe_blocks", + "Warning: Last synced block is not the last confirmed block: \ + last_darkfid_height={last_darkfid_height}, last_synced_height={height}, last_darkfid_hash={last_darkfid_hash}, last_synced_hash={hash}"); warn!(target: "blockchain-explorer::rpc_blocks::subscribe_blocks", "You should first fully sync the blockchain, and then subscribe"); return Err(Error::DatabaseError( "[subscribe_blocks] Blockchain not fully synced".to_string(), - )) + )); } info!(target: "blockchain-explorer::rpc_blocks::subscribe_blocks", "Subscribing to receive notifications of incoming blocks"); @@ -322,7 +442,7 @@ pub async fn subscribe_blocks( loop { match subscription.receive().await { JsonResult::Notification(n) => { - info!(target: "blockchain-explorer::rpc_blocks::subscribe_blocks", "Got Block notification from darkfid subscription"); + debug!(target: "blockchain-explorer::rpc_blocks::subscribe_blocks", "Got Block notification from darkfid subscription"); if n.method != "blockchain.subscribe_blocks" { return Err(Error::UnexpectedJsonRpc(format!( "Got foreign notification from darkfid: {}", @@ -347,7 +467,7 @@ pub async fn subscribe_blocks( let param = param.get::().unwrap(); let bytes = base64::decode(param).unwrap(); - let block_data: BlockInfo = match deserialize_async(&bytes).await { + let darkfid_block: BlockInfo = match deserialize_async(&bytes).await { Ok(b) => b, Err(e) => { return Err(Error::UnexpectedJsonRpc(format!( @@ -355,17 +475,36 @@ pub async fn subscribe_blocks( ))) }, }; - let header_hash = block_data.hash().to_string(); info!(target: "blockchain-explorer::rpc_blocks::subscribe_blocks", "======================================="); - info!(target: "blockchain-explorer::rpc_blocks::subscribe_blocks", "Block header: {header_hash}"); + info!(target: "blockchain-explorer::rpc_blocks::subscribe_blocks", "Block Notification: {}", darkfid_block.hash().to_string()); info!(target: "blockchain-explorer::rpc_blocks::subscribe_blocks", "======================================="); - info!(target: "blockchain-explorer::rpc_blocks::subscribe_blocks", "Deserialized successfully. Storing block..."); - if let Err(e) = explorer.service.put_block(&block_data).await { + // Store darkfi node block height for later use + let darkfid_block_height = darkfid_block.header.height; + + // Check if we need to perform a reorg due to mismatch in block heights + if darkfid_block_height <= height { + info!(target: "blockchain-explorer::rpc_blocks::subscribe_blocks", + "Reorg detected with heights: darkfid.{darkfid_block_height} <= explorer.{height}"); + + // Calculate the reset height + let reset_height = darkfid_block_height.saturating_sub(1); + + // Execute the reorg by resetting the explorer state to reset height + explorer.service.reset_explorer_state(reset_height)?; + info!(target: "blockchain-explorer::rpc_blocks::subscribe_blocks", "Successfully completed reorg to height: {reset_height}"); + } + + if let Err(e) = explorer.service.put_block(&darkfid_block).await { return Err(Error::DatabaseError(format!( "[subscribe_blocks] Put block failed: {e:?}" ))) } + + info!(target: "blockchain-explorer::rpc_blocks::subscribe_blocks", "Successfully stored new block at height: {}", darkfid_block.header.height ); + + // Process the next block + height = darkfid_block.header.height; } } diff --git a/script/research/blockchain-explorer/src/transactions.rs b/script/research/blockchain-explorer/src/transactions.rs index a488b0ab6..fdbfabbd7 100644 --- a/script/research/blockchain-explorer/src/transactions.rs +++ b/script/research/blockchain-explorer/src/transactions.rs @@ -18,7 +18,7 @@ use std::collections::HashMap; -use log::{debug, error, info}; +use log::{debug, error}; use smol::io::Cursor; use tinyjson::JsonValue; @@ -98,7 +98,7 @@ impl ExplorerService { let tree = &self.db.blockchain.sled_db.open_tree(tree_name)?; tree.clear()?; let tree_name_str = std::str::from_utf8(tree_name)?; - info!(target: "blockchain-explorer::blocks", "Successfully reset transaction tree: {tree_name_str}"); + debug!(target: "blockchain-explorer::blocks", "Successfully reset transaction tree: {tree_name_str}"); } Ok(())