mirror of
https://github.com/hinto-janai/cuprate.git
synced 2024-12-23 12:09:57 +00:00
187 lines
5.5 KiB
Rust
187 lines
5.5 KiB
Rust
|
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))
|
||
|
}
|