208 lines
7.6 KiB
Rust
208 lines
7.6 KiB
Rust
|
|
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;
|
||
|
|
|
||
|
|
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?;
|
||
|
|
|
||
|
|
// verify timestamp is not in the future
|
||
|
|
if timestamp > current_timestamp {
|
||
|
|
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 = 3000000000000000_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 = 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(())
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|