use crate::blocks::block::{Block, UnminedBlock}; use crate::common::binary_conversions::hex_to_u64; use crate::common::check_genesis::genesis_checkup; use crate::common::types::Transaction; use crate::encode; use crate::miner::fairness::fairness_difficulty; use crate::records::block_height::get_block_height::get_height; use crate::records::memory::chain_state::{ cached_chain_height, cached_tip_hash, cached_tip_header, }; use crate::records::memory::network_mapping::NodeInfo; use crate::records::unpack_block::unpack_header::load_block_header; use crate::records::wallet_registry::resolve_pubkey_from_short_address; use crate::sled::Db; use crate::verifications::verification_service::VerificationService; use crate::wallets::structures::Wallet; use crate::Arc; use crate::Utc; const ALLOWED_FUTURE_BLOCK_SECONDS: u32 = 1; impl Block { pub async fn verify( &self, db: &Db, verification_service: Arc, ) -> Result, String> { // block verification checks header validity first, then // delegates transaction verification to the shared service // get transactionsfrom block let transactions = &self.transactions; // verifiy the number of transactions is not more than 1000001 let total_transactions = transactions.len() as u32; if total_transactions > 15_000_001 { return Err(format!( "Too many transactions in the block: {total_transactions}" )); } // get current timestamp let current_timestamp = Utc::now().timestamp() as u32; // get header from block let header = &self.vrf_block; // validate miner let miner = &header.unmined_block.miner; let miner_pubkey = resolve_pubkey_from_short_address(db, miner) .map_err(|e| e.to_string())? .ok_or_else(|| "This miner address is not registered".to_string())?; let miner_pubkey_hex = encode(&miner_pubkey); let block_number = current_chain_height(db).await + 1; if !NodeInfo::address_checkup(miner, block_number).await { return Err("This address is not eligable to mine".to_string()); } // get variables from the block let timestamp = header.unmined_block.timestamp; let previous_hash = header.unmined_block.previous_hash.clone(); let difficulty = header.unmined_block.next_block_difficulty; let vrf = header.vrf; let proof = &header.proof; // hash the header to validate the difficulty let unmined_header_hash = header.unmined_block.hash().await; // validate vrf number if !Wallet::vrf_verify_with_public_key(vrf, &unmined_header_hash, &miner_pubkey_hex, proof) .await { return Err("Invalid vrf.".to_string()); } // hash the header to validate the difficulty let header_hash = header.hash().await; // get u64 value let hash = hex_to_u64(&header_hash).await?; // validate hash Self::validate_hash_difficulty(db, hash).await?; // Allow the same one-second clock skew tolerated during handshake, // while the parent timestamp check below still enforces block spacing. if timestamp > current_timestamp.saturating_add(ALLOWED_FUTURE_BLOCK_SECONDS) { return Err("Timestamp in the block is in the future.".to_string()); } // We need to check if the genesis block exists to do // any previous block validations or we will get an error // trying to validate the genesis block as the block 0 // won't load. if genesis_checkup().await { Self::not_genesis_block_checkup(db, miner, timestamp, previous_hash, difficulty) .await?; } let results = Self::send_transactions_to_validate( miner.to_string(), self.transactions.clone(), db.clone(), verification_service, ) .await?; Ok(results) } async fn not_genesis_block_checkup( db: &Db, miner: &str, timestamp: u32, previous_hash: String, difficulty: u64, ) -> Result<(), String> { // non-genesis blocks must reference the current tip, // satisfy fairness rules, and carry the correct next difficulty // load the last block this is the current height // as we are always validating a height higher than // what is recorded. let previous_height = current_chain_height(db).await; let previous_block = match cached_tip_header(previous_height).await { Some(header) => header, None => load_block_header(previous_height).await?, }; // check if miner is eligible based on // fairness difficulty checker if !fairness_difficulty(previous_height, miner).await { return Err( "You have not passed the fairness difficulty, You cannot mine this block." .to_string(), ); } // get previous block hash let calculated_previous_hash = match cached_tip_hash(previous_height).await { Some(hash) => hash, None => previous_block.hash().await, }; // Validate recorded previous_hash is equal to // the previous block hash if calculated_previous_hash != previous_hash { return Err("Incorrect previous_block_hash.".to_string()); } // validate that the current timestamp is greater the previous // block timestamp plus 2 seconds let current_timestamp = Utc::now().timestamp() as u32; if current_timestamp < previous_block.unmined_block.timestamp + 1 { return Err("Mining to quickly".to_string()); } // validate that the timestamp of the current block we are // validating is greater than the previous block timestamp // plus 2 seconds if timestamp < previous_block.unmined_block.timestamp + 2 { return Err("Mining to quickly".to_string()); } // get next block difficulty let difficulty_adjustment = UnminedBlock::adjust_difficulty( timestamp, db, previous_block.unmined_block.next_block_difficulty, ) .await; // validate the listed next block difficulty matches what // we calculated it should be if difficulty_adjustment != difficulty { let e = "Incorrect value for next block difficulty".to_string(); return Err(e); } Ok(()) } async fn send_transactions_to_validate( miner: String, transactions: Vec, db: crate::sled::Db, verification_service: Arc, ) -> Result, String> { // transaction verification is centralized in the shared // verification service so block assembly stays lightweight verification_service .verify_block_transactions(miner, transactions, db) .await } async fn validate_hash_difficulty(db: &Db, hash: u64) -> Result<(), String> { // compare the reduced header hash against the previous // block's recorded target difficulty threshold if !genesis_checkup().await { let difficulty_target = 2000000000000000_u64; if hash <= difficulty_target { return Ok(()); } else { return Err( "Block hash does not satisfy the genesis difficulty threshold.".to_string(), ); } } let current_block_number = current_chain_height(db).await + 1; let previous_block_height = current_block_number - 1; let previous_block = match cached_tip_header(previous_block_height).await { Some(header) => header, None => load_block_header(previous_block_height).await?, }; let difficulty_target = previous_block.unmined_block.next_block_difficulty; if hash >= difficulty_target { Err(format!( "Block hash does not satisfy the required difficulty threshold: hash_value={hash} required_below={difficulty_target}" )) } else { Ok(()) } } } async fn current_chain_height(db: &Db) -> u32 { cached_chain_height() .await .unwrap_or_else(|| get_height(db)) }