Contractless/src/verifications/async_funcs/verify_block.rs

211 lines
7.8 KiB
Rust
Raw Normal View History

2026-05-24 17:56:57 +00:00
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::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;
2026-06-01 19:51:23 +00:00
const ALLOWED_FUTURE_BLOCK_SECONDS: u32 = 1;
2026-05-24 17:56:57 +00:00
impl Block {
pub async fn verify(
&self,
db: &Db,
verification_service: Arc<VerificationService>,
) -> Result<Vec<String>, 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 = get_height(db) + 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?;
2026-06-01 19:51:23 +00:00
// 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) {
2026-05-24 17:56:57 +00:00
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 = get_height(db);
let previous_block = 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 = 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 + 2 {
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<Transaction>,
db: crate::sled::Db,
verification_service: Arc<VerificationService>,
) -> Result<Vec<String>, 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 = 1200000000000000_u64;
2026-05-24 17:56:57 +00:00
if hash <= difficulty_target {
return Ok(());
} else {
return Err(
"Block hash does not satisfy the genesis difficulty threshold.".to_string(),
);
}
}
let current_block_number = get_height(db) + 1;
let previous_block_height = current_block_number - 1;
let previous_block = 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(())
}
}
}