cuprate-hinto-janai/consensus/rules/src/blocks.rs

187 lines
5.5 KiB
Rust
Raw Normal View History

use monero_serai::block::Block;
use primitive_types::U256;
use cryptonight_cuprate::*;
use crate::{
current_time,
hard_forks::HardForkError,
miner_tx::{check_miner_tx, MinerTxError},
HardFork,
};
const BLOCK_SIZE_SANITY_LEEWAY: usize = 100;
const BLOCK_FUTURE_TIME_LIMIT: u64 = 60 * 60 * 2;
#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
pub enum BlockError {
#[error("The blocks POW is invalid.")]
POWInvalid,
#[error("The block is too big.")]
TooLarge,
#[error("The block has too many transactions.")]
TooManyTxs,
#[error("The blocks previous ID is incorrect.")]
PreviousIDIncorrect,
#[error("The blocks timestamp is invalid.")]
TimeStampInvalid,
#[error("Hard-fork error: {0}")]
HardForkError(#[from] HardForkError),
#[error("Miner transaction error: {0}")]
MinerTxError(#[from] MinerTxError),
}
/// Calculates the POW hash of this block.
pub fn calculate_pow_hash(buf: &[u8], height: u64, hf: &HardFork) -> Result<[u8; 32], BlockError> {
if height == 202612 {
return Ok(
hex::decode("84f64766475d51837ac9efbef1926486e58563c95a19fef4aec3254f03000000")
.unwrap()
.try_into()
.unwrap(),
);
}
Ok(if hf < &HardFork::V7 {
cryptonight_hash_v0(buf)
} else if hf == &HardFork::V7 {
cryptonight_hash_v1(buf).map_err(|_| BlockError::POWInvalid)?
} else if hf < &HardFork::V10 {
cryptonight_hash_v2(buf)
} else if hf < &HardFork::V12 {
cryptonight_hash_r(buf, height)
} else {
todo!("RandomX")
})
}
/// Returns if the blocks POW hash is valid for the current difficulty.
///
/// See: https://cuprate.github.io/monero-book/consensus_rules/blocks/difficulty.html#checking-a-blocks-proof-of-work
pub fn check_block_pow(hash: &[u8; 32], difficulty: u128) -> Result<(), BlockError> {
let int_hash = U256::from_little_endian(hash);
let difficulty = U256::from(difficulty);
if int_hash.checked_mul(difficulty).is_none() {
Err(BlockError::POWInvalid)
} else {
Ok(())
}
}
/// Sanity check on the block blob size.
///
/// https://cuprate.github.io/monero-book/consensus_rules/blocks.html#block-weight-and-size
fn block_size_sanity_check(
block_blob_len: usize,
effective_median: usize,
) -> Result<(), BlockError> {
if block_blob_len > effective_median * 2 + BLOCK_SIZE_SANITY_LEEWAY {
Err(BlockError::TooLarge)
} else {
Ok(())
}
}
/// Sanity check on number of txs in the block.
///
/// https://cuprate.github.io/monero-book/consensus_rules/blocks.html#amount-of-transactions
fn check_amount_txs(number_none_miner_txs: usize) -> Result<(), BlockError> {
if number_none_miner_txs + 1 > 0x10000000 {
Err(BlockError::TooManyTxs)
} else {
Ok(())
}
}
/// Sanity check on the block weight.
///
/// https://cuprate.github.io/monero-book/consensus_rules/blocks.html#block-weight-and-siz
fn check_block_weight(
block_weight: usize,
median_for_block_reward: usize,
) -> Result<(), BlockError> {
if block_weight > median_for_block_reward * 2 {
Err(BlockError::TooLarge)
} else {
Ok(())
}
}
/// Verifies the previous id is the last blocks hash
///
/// https://cuprate.github.io/monero-book/consensus_rules/blocks.html#previous-id
fn check_prev_id(block: &Block, top_hash: &[u8; 32]) -> Result<(), BlockError> {
if &block.header.previous != top_hash {
Err(BlockError::PreviousIDIncorrect)
} else {
Ok(())
}
}
/// Checks the blocks timestamp is in the valid range.
///
/// https://cuprate.github.io/monero-book/consensus_rules/blocks.html#timestamp
fn check_timestamp(block: &Block, median_timestamp: u64) -> Result<(), BlockError> {
if block.header.timestamp < median_timestamp
|| block.header.timestamp > current_time() + BLOCK_FUTURE_TIME_LIMIT
{
Err(BlockError::TimeStampInvalid)
} else {
Ok(())
}
}
#[derive(Debug, Clone)]
pub struct ContextToVerifyBlock {
pub median_weight_for_block_reward: usize,
pub effective_median_weight: usize,
pub top_hash: [u8; 32],
pub median_block_timestamp: Option<u64>,
pub chain_height: u64,
pub current_hf: HardFork,
pub next_difficulty: u128,
pub already_generated_coins: u64,
}
/// Checks the block is valid returning the blocks hard-fork vote and the amount of coins generated.
///
/// Does not check the proof of work as that check is expensive and should be done last.
pub fn check_block(
block: &Block,
total_fees: u64,
block_weight: usize,
block_blob_len: usize,
block_chain_ctx: &ContextToVerifyBlock,
) -> Result<(HardFork, u64), BlockError> {
let (version, vote) = HardFork::from_block_header(&block.header)?;
block_chain_ctx
.current_hf
.check_block_version_vote(&version, &vote)?;
if let Some(median_timestamp) = block_chain_ctx.median_block_timestamp {
check_timestamp(block, median_timestamp)?;
}
check_prev_id(block, &block_chain_ctx.top_hash)?;
check_block_weight(block_weight, block_chain_ctx.median_weight_for_block_reward)?;
block_size_sanity_check(block_blob_len, block_chain_ctx.effective_median_weight)?;
check_amount_txs(block.txs.len())?;
let generated_coins = check_miner_tx(
&block.miner_tx,
total_fees,
block_chain_ctx.chain_height,
block_weight,
block_chain_ctx.median_weight_for_block_reward,
block_chain_ctx.already_generated_coins,
&block_chain_ctx.current_hf,
)?;
Ok((vote, generated_coins))
}