Contractless/src/verifications/async_funcs/verify_block.rs

231 lines
8.5 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;
2026-06-13 19:51:54 +00:00
use crate::records::memory::chain_state::{
cached_chain_height, cached_tip_hash, cached_tip_header,
};
2026-05-24 17:56:57 +00:00
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-19 18:36:01 +00:00
const ALLOWED_FUTURE_BLOCK_SECONDS: u32 = 3;
2026-06-01 19:51:23 +00:00
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);
2026-06-13 19:51:54 +00:00
let block_number = current_chain_height(db).await + 1;
2026-05-24 17:56:57 +00:00
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-19 18:36:01 +00:00
// Allow the same three-second clock skew tolerated during handshake,
2026-06-01 19:51:23 +00:00
// 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,
2026-06-27 16:28:13 +00:00
timestamp,
2026-05-24 17:56:57 +00:00
)
.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.
2026-06-13 19:51:54 +00:00
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?,
};
2026-05-24 17:56:57 +00:00
// 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
2026-06-13 19:51:54 +00:00
let calculated_previous_hash = match cached_tip_hash(previous_height).await {
Some(hash) => hash,
None => previous_block.hash().await,
};
2026-05-24 17:56:57 +00:00
// 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
2026-06-19 18:36:01 +00:00
// block timestamp plus 1 second
2026-05-24 17:56:57 +00:00
let current_timestamp = Utc::now().timestamp() as u32;
2026-06-18 17:38:52 +00:00
if current_timestamp < previous_block.unmined_block.timestamp + 1 {
2026-05-24 17:56:57 +00:00
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>,
2026-06-27 16:28:13 +00:00
block_timestamp: u32,
2026-05-24 17:56:57 +00:00
) -> Result<Vec<String>, String> {
// transaction verification is centralized in the shared
// verification service so block assembly stays lightweight
verification_service
2026-06-27 16:28:13 +00:00
.verify_block_transactions(miner, transactions, db, block_timestamp)
2026-05-24 17:56:57 +00:00
.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 {
2026-06-05 03:11:23 +00:00
let difficulty_target = 2000000000000000_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(),
);
}
}
2026-06-13 19:51:54 +00:00
let current_block_number = current_chain_height(db).await + 1;
2026-05-24 17:56:57 +00:00
let previous_block_height = current_block_number - 1;
2026-06-13 19:51:54 +00:00
let previous_block = match cached_tip_header(previous_block_height).await {
Some(header) => header,
None => load_block_header(previous_block_height).await?,
};
2026-05-24 17:56:57 +00:00
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(())
}
}
}
2026-06-13 19:51:54 +00:00
async fn current_chain_height(db: &Db) -> u32 {
cached_chain_height()
.await
.unwrap_or_else(|| get_height(db))
}