/* This file is part of DarkFi (https://dark.fi) * * Copyright (C) 2020-2026 Dyne.org foundation * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ use std::{collections::HashMap, sync::Arc}; use darkfi_sdk::{crypto::MerkleTree, fee::minimum_fee}; use kvdb_overlay::Database; use num_bigint::BigUint; use smol::lock::RwLock; use tracing::{debug, error, info, warn}; use crate::{ blockchain::{ block_store::{BlockDifficulty, BlockInfo, BlockRanks}, Blockchain, BlockchainOverlay, HeaderHash, }, error::TxVerifyFailed, tx::Transaction, util::time::Timestamp, zk::VerifyingKey, Error, Result, }; /// DarkFi consensus module pub mod consensus; use consensus::{Consensus, Fork, Proposal}; /// DarkFi PoW module pub mod pow; use pow::PoWModule; /// RandomX infrastructure pub mod randomx_factory; pub use randomx_factory::RandomXFactory; /// Verification functions pub mod verification; use verification::{ verify_block, verify_checkpoint_block, verify_genesis_block, verify_producer_transaction, verify_transaction, verify_transactions, }; /// Fee calculation helpers pub mod fees; /// Helper utilities pub mod utils; use utils::{best_fork_index, block_rank, deploy_native_contracts}; /// Configuration for initializing [`Validator`] #[derive(Clone)] pub struct ValidatorConfig { /// Currently configured confirmation security threshold pub confirmation_threshold: usize, /// Currently configured max in-memory forks to maintain. pub max_forks: usize, /// Currently configured PoW target pub pow_target: u32, /// Optional fixed difficulty, for testing purposes pub pow_fixed_difficulty: Option, /// Genesis block pub genesis_block: BlockInfo, /// Flag to enable tx fee verification pub verify_fees: bool, } /// Atomic pointer to validator. pub type ValidatorPtr = Arc>; /// This struct represents a DarkFi validator node. pub struct Validator { /// Canonical (confirmed) blockchain pub blockchain: Blockchain, /// Hot/Live data used by the consensus algorithm pub consensus: Consensus, /// Flag signalling if the node is synced pub synced: bool, /// Flag to enable tx fee verification pub verify_fees: bool, } impl Validator { pub async fn new(kvdb: &Database, config: &ValidatorConfig) -> Result { info!(target: "validator::new", "Initializing Validator"); info!(target: "validator::new", "Initializing Blockchain"); let blockchain = Blockchain::new(kvdb)?; // Create an overlay over whole blockchain so we can write // stuff. let overlay = BlockchainOverlay::new(&blockchain)?; // Deploy native wasm contracts deploy_native_contracts(&overlay, config.pow_target).await?; // Update the contracts states monotree in case native // contracts zkas has changed. let diff = overlay.lock().unwrap().overlay.lock().unwrap().diff(&[])?; overlay.lock().unwrap().contracts.update_state_monotree(&diff)?; // Add genesis block if blockchain is empty if blockchain.genesis().is_err() { info!(target: "validator::new", "Appending genesis block"); verify_genesis_block(&overlay, &[diff], &config.genesis_block, config.pow_target) .await?; }; // Write the changes to the actual chain db overlay.lock().unwrap().overlay.lock().unwrap().apply()?; info!(target: "validator::new", "Initializing Consensus"); let consensus = Consensus::new( blockchain.clone(), config.confirmation_threshold, config.max_forks, config.pow_target, config.pow_fixed_difficulty.clone(), )?; // Create the actual state let state = Arc::new(RwLock::new(Self { blockchain, consensus, synced: false, verify_fees: config.verify_fees, })); info!(target: "validator::new", "Finished initializing validator"); Ok(state) } /// Auxiliary function to compute provided transaction's required /// fee, against current best fork. The function takes a boolean /// called `verify_fee` to overwrite the nodes configured /// `verify_fees` flag. /// /// Note: Always remember to purge new trees from the database if /// not needed. pub async fn calculate_fee(&self, tx: &Transaction, verify_fee: bool) -> Result { // Grab the best fork to verify against let index = best_fork_index(&self.consensus.forks)?; let fork = self.consensus.forks[index].full_clone()?; // Map of ZK proof verifying keys for the transaction let mut vks: HashMap<[u8; 32], HashMap> = HashMap::new(); for call in &tx.calls { vks.insert(call.data.contract_id.to_bytes(), HashMap::new()); } // Grab forks' next block height let next_block_height = fork.get_next_block_height()?; // Verify transaction to grab the gas used let verify_result = verify_transaction( &fork.overlay, next_block_height, self.consensus.module.target, tx, &mut MerkleTree::new(1), &mut vks, verify_fee, ) .await?; Ok(minimum_fee(verify_result.total_gas_used())?) } /// The node retrieves a transaction, validates its state /// transition agains best fork, and appends it to the pending txs /// store if its valid. /// /// Note: Always remember to purge new trees from the database if /// not needed. pub async fn append_tx(&mut self, tx: &Transaction, write: bool) -> Result<()> { let tx_hash = tx.hash(); // Check if we have already seen this tx in the pending store if self.blockchain.transactions.contains_pending(&tx_hash)? { debug!(target: "validator::append_tx", "We have already seen pending tx: {tx_hash}"); return Err(TxVerifyFailed::AlreadySeenTx(tx_hash.as_string()).into()) } // Grab the best fork to verify against let index = best_fork_index(&self.consensus.forks)?; let fork = self.consensus.forks[index].full_clone()?; // Check if we have already seen this tx. This checks both the // fork cache and the actual database. if fork.overlay.lock().unwrap().transactions.contains(&tx_hash)? { debug!(target: "validator::append_tx", "We have already seen tx: {tx_hash}"); return Err(TxVerifyFailed::AlreadySeenTx(tx_hash.as_string()).into()) } // Verify state transition info!(target: "validator::append_tx", "Starting state transition validation for tx: {tx_hash}"); // Map of ZK proof verifying keys for the transaction let mut vks: HashMap<[u8; 32], HashMap> = HashMap::new(); for call in &tx.calls { vks.insert(call.data.contract_id.to_bytes(), HashMap::new()); } // Grab forks' next block height let next_block_height = fork.get_next_block_height()?; // Verify transaction verify_transaction( &fork.overlay, next_block_height, self.consensus.module.target, tx, &mut MerkleTree::new(1), &mut vks, self.verify_fees, ) .await?; // Add transaction to pending txs store if write { self.blockchain.add_pending_txs(std::slice::from_ref(tx))?; info!(target: "validator::append_tx", "Appended tx {tx_hash} to pending txs store"); } Ok(()) } /// The node tries to append provided proposal to its consensus /// state. pub async fn append_proposal( &mut self, proposal: &Proposal, timestamp_bound: Option, ) -> Result<()> { self.consensus.append_proposal(proposal, timestamp_bound, self.verify_fees).await } /// The node checks if best fork can be confirmed. /// If proposals can be confirmed, node appends them to canonical, /// and resets the current forks. pub async fn confirmation(&mut self) -> Result> { info!(target: "validator::confirmation", "Performing confirmation check"); // Grab best fork index that can be confirmed let confirmed_fork = self.consensus.confirmation().await?; if confirmed_fork.is_none() { info!(target: "validator::confirmation", "No proposals can be confirmed"); return Ok(vec![]) } // Grab the actual best fork let confirmed_fork = confirmed_fork.unwrap(); let fork = &mut self.consensus.forks[confirmed_fork]; // Find the excess over confirmation threshold let excess = (fork.proposals.len() - self.consensus.confirmation_threshold) + 1; // Grab confirmed proposals and update fork's sequences let rest_proposals = fork.proposals.split_off(excess); let rest_diffs = fork.diffs.split_off(excess); let confirmed_proposals = fork.proposals.clone(); let diffs = fork.diffs.clone(); fork.proposals = rest_proposals; fork.diffs = rest_diffs; // Grab confirmed proposals blocks let confirmed_blocks = fork.overlay.lock().unwrap().get_blocks_by_hash(&confirmed_proposals)?; // Apply confirmed proposals diffs and update PoW module let mut module = self.consensus.module.clone(); let mut confirmed_txs = vec![]; let mut state_inverse_diffs_heights = vec![]; let mut state_inverse_diffs = vec![]; info!(target: "validator::confirmation", "Confirming proposals:"); for (index, proposal) in confirmed_proposals.iter().enumerate() { info!(target: "validator::confirmation", "\t{proposal} ({}) - {}", confirmed_blocks[index].header.pow_data, confirmed_blocks[index].header.height); fork.overlay.lock().unwrap().overlay.lock().unwrap().apply_diff(&diffs[index])?; let next_difficulty = module.next_difficulty()?; module.append(&confirmed_blocks[index].header, &next_difficulty)?; confirmed_txs.extend_from_slice(&confirmed_blocks[index].txs); state_inverse_diffs_heights.push(confirmed_blocks[index].header.height); state_inverse_diffs.push(diffs[index].inverse()); } self.consensus.module = module; // Store the block inverse diffs self.blockchain .blocks .insert_state_inverse_diff(&state_inverse_diffs_heights, &state_inverse_diffs)?; // Reset forks starting with the confirmed blocks self.consensus.reset_forks(&confirmed_proposals, &confirmed_fork, &confirmed_txs).await?; info!(target: "validator::confirmation", "Confirmation completed!"); Ok(confirmed_blocks) } /// Apply provided set of [`BlockInfo`] without doing formal /// verification. A set of [`HeaderHash`] is also provided, to /// verify that the provided block hash matches the expected header /// one. /// /// Note: this function should only be used for blocks received /// using a checkpoint, since in that case we enforce the node to /// follow the sequence, assuming all its blocks are valid. /// Additionally, it will update any forks to a single empty one, /// holding the updated module. Always remember to purge new trees /// from the database if not needed. pub async fn add_checkpoint_blocks( &mut self, blocks: &[BlockInfo], headers: &[HeaderHash], ) -> Result<()> { // Check provided sequences are the same length if blocks.len() != headers.len() { return Err(Error::InvalidInputLengths) } debug!(target: "validator::add_checkpoint_blocks", "Instantiating BlockchainOverlay"); let overlay = BlockchainOverlay::new(&self.blockchain)?; // Retrieve last block difficulty to access current ranks let last_difficulty = self.blockchain.last_block_difficulty()?; let mut current_targets_rank = last_difficulty.ranks.targets_rank; let mut current_hashes_rank = last_difficulty.ranks.hashes_rank; // Grab current PoW module to validate each block let mut module = self.consensus.module.clone(); // Keep track of all blocks transactions to remove them from // pending txs store. let mut removed_txs = vec![]; // Keep track of all block database state diffs and their // inverse. let mut diffs_heights = vec![]; let mut diffs = vec![]; let mut inverse_diffs = vec![]; // Validate and insert each block for (index, block) in blocks.iter().enumerate() { // Verify block match verify_checkpoint_block(&overlay, &diffs, block, &headers[index], module.target) .await { Ok(()) => { /* Do nothing */ } // Skip already existing block Err(Error::BlockAlreadyExists(_)) => continue, Err(e) => { error!(target: "validator::add_checkpoint_blocks", "Erroneous block found in set: {e}"); return Err(Error::BlockIsInvalid(block.hash().as_string())) } }; // Calculate block rank let (next_difficulty, target_distance_sq, hash_distance_sq) = block_rank(&mut module, block)?; // Update current ranks current_targets_rank += target_distance_sq.clone(); current_hashes_rank += hash_distance_sq.clone(); // Generate block difficulty and update PoW module let cumulative_difficulty = module.cumulative_difficulty.clone() + next_difficulty.clone(); let ranks = BlockRanks::new( target_distance_sq, current_targets_rank.clone(), hash_distance_sq, current_hashes_rank.clone(), ); let block_difficulty = BlockDifficulty::new( block.header.height, block.header.timestamp, next_difficulty, cumulative_difficulty, ranks, ); module.append_difficulty(&overlay, &block.header, block_difficulty)?; // Store block transactions for tx in &block.txs { removed_txs.push(tx.clone()); } // Store block database state diff and its inverse diffs_heights.push(block.header.height); let diff = overlay.lock().unwrap().overlay.lock().unwrap().diff(&diffs)?; inverse_diffs.push(diff.inverse()); diffs.push(diff); } debug!(target: "validator::add_checkpoint_blocks", "Applying overlay changes"); overlay.lock().unwrap().overlay.lock().unwrap().apply()?; // Store the block diffs self.blockchain.blocks.insert_state_inverse_diff(&diffs_heights, &inverse_diffs)?; // Remove blocks transactions from pending txs store self.blockchain.remove_pending_txs(&removed_txs)?; // Update PoW module self.consensus.module = module.clone(); // Update forks self.consensus.forks = vec![Fork::new(self.blockchain.clone(), module).await?]; Ok(()) } /// Validate a set of [`BlockInfo`] in sequence and apply them if /// all are valid. /// /// Note: this function should only be used in tests when we don't /// want to perform consensus logic and always remember to purge /// new trees from the database if not needed. pub async fn add_test_blocks(&mut self, blocks: &[BlockInfo]) -> Result<()> { debug!(target: "validator::add_test_blocks", "Instantiating BlockchainOverlay"); let overlay = BlockchainOverlay::new(&self.blockchain)?; // Retrieve last block let mut previous = &overlay.lock().unwrap().last_block()?; // Retrieve last block difficulty to access current ranks let last_difficulty = self.blockchain.last_block_difficulty()?; let mut current_targets_rank = last_difficulty.ranks.targets_rank; let mut current_hashes_rank = last_difficulty.ranks.hashes_rank; // Grab current PoW module to validate each block let mut module = self.consensus.module.clone(); // Keep track of all blocks transactions to remove them from // pending txs store. let mut removed_txs = vec![]; // Keep track of all block database state diffs and their // inverse. let mut diffs_heights = vec![]; let mut diffs = vec![]; let mut inverse_diffs = vec![]; // All blocks must be before the future timestamp upper bound let timestamp_bound = Some(module.future_timestamp_upper_bound()?); // Validate and insert each block for block in blocks { // Verify block match verify_block( &overlay, &diffs, &mut module, block, previous, timestamp_bound, self.verify_fees, ) .await { Ok(()) => { /* Do nothing */ } // Skip already existing block Err(Error::BlockAlreadyExists(_)) => { previous = block; continue } Err(e) => { error!(target: "validator::add_test_blocks", "Erroneous block found in set: {e}"); return Err(Error::BlockIsInvalid(block.hash().as_string())) } }; // Calculate block rank let (next_difficulty, target_distance_sq, hash_distance_sq) = block_rank(&mut module, block)?; // Update current ranks current_targets_rank += target_distance_sq.clone(); current_hashes_rank += hash_distance_sq.clone(); // Generate block difficulty and update PoW module let cumulative_difficulty = module.cumulative_difficulty.clone() + next_difficulty.clone(); let ranks = BlockRanks::new( target_distance_sq, current_targets_rank.clone(), hash_distance_sq, current_hashes_rank.clone(), ); let block_difficulty = BlockDifficulty::new( block.header.height, block.header.timestamp, next_difficulty, cumulative_difficulty, ranks, ); module.append_difficulty(&overlay, &block.header, block_difficulty)?; // Store block transactions for tx in &block.txs { removed_txs.push(tx.clone()); } // Store block database state diff and its inverse diffs_heights.push(block.header.height); let diff = overlay.lock().unwrap().overlay.lock().unwrap().diff(&diffs)?; inverse_diffs.push(diff.inverse()); diffs.push(diff); // Use last inserted block as next iteration previous previous = block; } debug!(target: "validator::add_test_blocks", "Applying overlay changes"); overlay.lock().unwrap().overlay.lock().unwrap().apply()?; // Store the block diffs self.blockchain.blocks.insert_state_inverse_diff(&diffs_heights, &inverse_diffs)?; // Remove blocks transactions from pending txs store self.blockchain.remove_pending_txs(&removed_txs)?; // Update PoW module self.consensus.module = module; Ok(()) } /// Validate a set of [`Transaction`] in sequence and apply them if /// all are valid. In case any of the transactions fail, they will /// be returned to the caller. The function takes a boolean called /// `write` which tells it to actually write the state transitions /// to the database, and a boolean called `verify_fees` to /// overwrite the nodes configured `verify_fees` flag. /// /// Returns the total gas used and total paid fees for the given /// transactions. /// /// Note: This function should only be used in tests and always /// remember to purge new trees from the database if not needed. pub async fn add_test_transactions( &self, txs: &[Transaction], verifying_block_height: u32, block_target: u32, write: bool, verify_fees: bool, ) -> Result<(u64, u64)> { debug!(target: "validator::add_transactions", "Instantiating BlockchainOverlay"); let overlay = BlockchainOverlay::new(&self.blockchain)?; // Verify all transactions and get erroneous ones let verify_result = verify_transactions( &overlay, verifying_block_height, block_target, txs, &mut MerkleTree::new(1), verify_fees, ) .await; let lock = overlay.lock().unwrap(); let mut overlay = lock.overlay.lock().unwrap(); let gas_values = verify_result?; if !write { debug!(target: "validator::add_transactions", "Skipping apply of state updates because write=false"); return Ok(gas_values) } debug!(target: "validator::add_transactions", "Applying overlay changes"); overlay.apply()?; Ok(gas_values) } /// Validate a producer `Transaction` and apply it if valid. In /// case the transactions fail, ir will be returned to the caller. /// The function takes a boolean called `write` which tells it to /// actually write the state transitions to the database. /// /// Note: This function should only be used in tests and always /// remember to purge new trees from the database if not needed. pub async fn add_test_producer_transaction( &self, tx: &Transaction, verifying_block_height: u32, block_target: u32, write: bool, ) -> Result<()> { debug!(target: "validator::add_test_producer_transaction", "Instantiating BlockchainOverlay"); let overlay = BlockchainOverlay::new(&self.blockchain)?; // Verify transaction let mut erroneous_txs = vec![]; if let Err(e) = verify_producer_transaction( &overlay, verifying_block_height, block_target, tx, &mut MerkleTree::new(1), ) .await { warn!(target: "validator::add_test_producer_transaction", "Transaction verification failed: {e}"); erroneous_txs.push(tx.clone()); } let lock = overlay.lock().unwrap(); let mut overlay = lock.overlay.lock().unwrap(); if !erroneous_txs.is_empty() { warn!(target: "validator::add_test_producer_transaction", "Erroneous transactions found in set"); return Err(TxVerifyFailed::ErroneousTxs(erroneous_txs).into()) } if !write { debug!(target: "validator::add_test_producer_transaction", "Skipping apply of state updates because write=false"); return Ok(()) } debug!(target: "validator::add_test_producer_transaction", "Applying overlay changes"); overlay.apply()?; Ok(()) } /// Retrieve all existing blocks and try to apply them /// to an in memory overlay to verify their correctness. /// Be careful as this will try to load everything in memory. /// /// Note: Always remember to purge new trees from the database if /// not needed. pub async fn validate_blockchain( &self, pow_target: u32, pow_fixed_difficulty: Option, ) -> Result<()> { // An empty blockchain is considered valid let mut blocks_count = self.blockchain.len()? as u32; info!(target: "validator::validate_blockchain", "Validating {blocks_count} blocks..."); if blocks_count == 0 { info!(target: "validator::validate_blockchain", "Blockchain validated successfully!"); return Ok(()) } // Create an in memory blockchain overlay let (kvdb, _folder) = Database::open_temp()?; let blockchain = Blockchain::new(&kvdb)?; let overlay = BlockchainOverlay::new(&blockchain)?; // Set previous let mut previous = self.blockchain.genesis_block()?; // Deploy native wasm contracts deploy_native_contracts(&overlay, pow_target).await?; // Update the contracts states monotree let diff = overlay.lock().unwrap().overlay.lock().unwrap().diff(&[])?; overlay.lock().unwrap().contracts.update_state_monotree(&diff)?; // Validate genesis block verify_genesis_block(&overlay, &[diff], &previous, pow_target).await?; info!(target: "validator::validate_blockchain", "Genesis block validated successfully!"); // Write the changes to the in memory db overlay.lock().unwrap().overlay.lock().unwrap().apply()?; // Create a PoW module to validate each block let mut module = PoWModule::new(blockchain, pow_target, pow_fixed_difficulty, Some(0))?; // Keep track of all block database state diffs let mut diffs = vec![]; // All blocks must be before the future timestamp upper bound let timestamp_bound = Some(module.future_timestamp_upper_bound()?); // Validate and insert each block info!(target: "validator::validate_blockchain", "Validating rest blocks..."); blocks_count -= 1; let mut index = 1; while index <= blocks_count { // Grab block let block = self.blockchain.get_blocks_by_heights(&[index])?[0].clone(); // Verify block if let Err(e) = verify_block( &overlay, &diffs, &mut module, &block, &previous, timestamp_bound, self.verify_fees, ) .await { error!(target: "validator::validate_blockchain", "Erroneous block found in set: {e}"); return Err(Error::BlockIsInvalid(block.hash().as_string())) }; // Update PoW module module.append(&block.header, &module.next_difficulty()?)?; // Store block database state diff let diff = overlay.lock().unwrap().overlay.lock().unwrap().diff(&diffs)?; diffs.push(diff); // Use last inserted block as next iteration previous previous = block; info!(target: "validator::validate_blockchain", "Block {index}/{blocks_count} validated successfully!"); index += 1; } info!(target: "validator::validate_blockchain", "Blockchain validated successfully!"); Ok(()) } /// Auxiliary function to grab current mining RandomX key, /// based on next block height. /// If no forks exist, returns the canonical key. pub async fn current_mining_randomx_key(&self) -> Result { self.consensus.current_mining_randomx_key().await } /// Auxiliary function to grab best current fork full clone. pub async fn best_current_fork(&self) -> Result { self.consensus.best_current_fork().await } /// Auxiliary function to retrieve current best fork next block /// height. pub async fn best_fork_next_block_height(&self) -> Result { let index = best_fork_index(&self.consensus.forks)?; let fork = &self.consensus.forks[index]; let next_block_height = fork.get_next_block_height()?; Ok(next_block_height) } /// Auxiliary function to reset the validator blockchain and /// consensus states to the provided block height. pub async fn reset_to_height(&mut self, height: u32) -> Result<()> { info!(target: "validator::reset_to_height", "Resetting validator to height: {height}"); // Reset our databasse to provided height self.blockchain.reset_to_height(height)?; // Reset consensus PoW module self.consensus.reset_pow_module().await?; // Purge current forks self.consensus.purge_forks().await?; info!(target: "validator::reset_to_height", "Validator reset successfully!"); Ok(()) } /// Auxiliary function to rebuild the block difficulties database /// based on current validator blockchain. /// Be careful as this will try to load everything in memory. pub async fn rebuild_block_difficulties( &self, pow_target: u32, pow_fixed_difficulty: Option, ) -> Result<()> { info!(target: "validator::rebuild_block_difficulties", "Rebuilding validator block difficulties..."); // Clear the block difficulties tree self.blockchain.blocks.difficulty.clear()?; // An empty blockchain doesn't have difficulty records let mut blocks_count = self.blockchain.len()? as u32; info!(target: "validator::rebuild_block_difficulties", "Rebuilding {blocks_count} block difficulties..."); if blocks_count == 0 { info!(target: "validator::rebuild_block_difficulties", "Validator block difficulties rebuilt successfully!"); return Ok(()) } // Create a PoW module and an in memory overlay to compute each // block difficulty. let mut module = PoWModule::new(self.blockchain.clone(), pow_target, pow_fixed_difficulty, Some(0))?; // Grab genesis block difficulty to access current ranks let genesis_block = self.blockchain.genesis_block()?; let last_difficulty = BlockDifficulty::genesis(genesis_block.header.timestamp); let mut targets_rank = last_difficulty.ranks.targets_rank; let mut hashes_rank = last_difficulty.ranks.hashes_rank; // Grab each block to compute its difficulty blocks_count -= 1; let mut index = 1; while index <= blocks_count { // Grab block let block = self.blockchain.get_blocks_by_heights(&[index])?[0].clone(); // Calculate block rank let (next_difficulty, target_distance_sq, hash_distance_sq) = block_rank(&mut module, &block)?; // Update chain ranks targets_rank += target_distance_sq.clone(); hashes_rank += hash_distance_sq.clone(); // Generate block difficulty and update PoW module let cumulative_difficulty = module.cumulative_difficulty.clone() + next_difficulty.clone(); let ranks = BlockRanks::new( target_distance_sq, targets_rank.clone(), hash_distance_sq, hashes_rank.clone(), ); let block_difficulty = BlockDifficulty::new( block.header.height, block.header.timestamp, next_difficulty, cumulative_difficulty, ranks, ); module.append(&block.header, &block_difficulty.difficulty)?; // Add difficulty to database self.blockchain.blocks.insert_difficulty(&[block_difficulty])?; info!(target: "validator::rebuild_block_difficulties", "Block {index}/{blocks_count} difficulty added successfully!"); index += 1; } // Flush the database self.blockchain.kvdb.flush_default_mode()?; info!(target: "validator::rebuild_block_difficulties", "Validator block difficulties rebuilt successfully!"); Ok(()) } }