link rules to monero-book.

need to do transactions.
This commit is contained in:
Boog900 2024-01-07 01:15:33 +00:00
parent 40e64cc9c3
commit 13957a5e7f
No known key found for this signature in database
GPG key ID: 5401367FB7302004
9 changed files with 124 additions and 58 deletions

View file

@ -30,22 +30,29 @@ pub enum BlockError {
PreviousIDIncorrect,
#[error("The blocks timestamp is invalid.")]
TimeStampInvalid,
#[error("The block contains a duplicate transaction.")]
DuplicateTransaction,
#[error("Hard-fork error: {0}")]
HardForkError(#[from] HardForkError),
#[error("Miner transaction error: {0}")]
MinerTxError(#[from] MinerTxError),
}
/// A trait to represent the RandomX VM.
pub trait RandomX {
type Error;
fn calculate_hash(&self, buf: &[u8]) -> Result<[u8; 32], Self::Error>;
}
/// Returns if this height is a RandomX seed height.
pub fn is_randomx_seed_height(height: u64) -> bool {
height % RX_SEEDHASH_EPOCH_BLOCKS == 0
}
/// Returns the RandomX seed height for this block.
///
/// ref: https://monero-book.cuprate.org/consensus_rules/blocks.html#randomx-seed
pub fn randomx_seed_height(height: u64) -> u64 {
if height <= RX_SEEDHASH_EPOCH_BLOCKS + RX_SEEDHASH_EPOCH_LAG {
0
@ -55,6 +62,8 @@ pub fn randomx_seed_height(height: u64) -> u64 {
}
/// Calculates the POW hash of this block.
///
/// ref: https://monero-book.cuprate.org/consensus_rules/blocks.html#pow-function
pub fn calculate_pow_hash<R: RandomX>(
randomx_vm: &R,
buf: &[u8],
@ -82,7 +91,7 @@ pub fn calculate_pow_hash<R: 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
/// ref: https://monero-book.cuprate.org/consensus_rules/blocks.html#checking-pow-hash
pub fn check_block_pow(hash: &[u8; 32], difficulty: u128) -> Result<(), BlockError> {
let int_hash = U256::from_little_endian(hash);
@ -102,7 +111,7 @@ pub fn check_block_pow(hash: &[u8; 32], difficulty: u128) -> Result<(), BlockErr
/// Sanity check on the block blob size.
///
/// https://cuprate.github.io/monero-book/consensus_rules/blocks.html#block-weight-and-size
/// ref: https://monero-book.cuprate.org/consensus_rules/blocks.html#block-weight-and-size
fn block_size_sanity_check(
block_blob_len: usize,
effective_median: usize,
@ -114,20 +123,9 @@ fn block_size_sanity_check(
}
}
/// 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
/// ref: https://monero-book.cuprate.org/consensus_rules/blocks.html#block-weight-and-size
fn check_block_weight(
block_weight: usize,
median_for_block_reward: usize,
@ -139,9 +137,20 @@ fn check_block_weight(
}
}
/// Sanity check on number of txs in the block.
///
/// ref: https://monero-book.cuprate.org/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(())
}
}
/// Verifies the previous id is the last blocks hash
///
/// https://cuprate.github.io/monero-book/consensus_rules/blocks.html#previous-id
/// ref: https://monero-book.cuprate.org/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)
@ -152,7 +161,7 @@ fn check_prev_id(block: &Block, top_hash: &[u8; 32]) -> Result<(), BlockError> {
/// Checks the blocks timestamp is in the valid range.
///
/// https://cuprate.github.io/monero-book/consensus_rules/blocks.html#timestamp
/// ref: https://monero-book.cuprate.org/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_unix_timestamp() + BLOCK_FUTURE_TIME_LIMIT
@ -163,21 +172,52 @@ fn check_timestamp(block: &Block, median_timestamp: u64) -> Result<(), BlockErro
}
}
/// Checks that all txs in the block have a unique hash.
///
/// ref: https://monero-book.cuprate.org/consensus_rules/blocks.html#no-duplicate-transactions
fn check_txs_unique(txs: &[[u8; 32]]) -> Result<(), BlockError> {
txs.windows(2).try_for_each(|window| {
if window[0] != window[1] {
Err(BlockError::DuplicateTransaction)?;
}
Ok(())
})
}
/// This struct contains the data needed to verify a block, implementers MUST make sure
/// the data in this struct is calculated correctly.
#[derive(Debug, Clone)]
pub struct ContextToVerifyBlock {
/// ref: https://monero-book.cuprate.org/consensus_rules/blocks/weights.html#median-weight-for-coinbase-checks
pub median_weight_for_block_reward: usize,
/// ref: https://monero-book.cuprate.org/consensus_rules/blocks/weights.html#effective-median-weight
pub effective_median_weight: usize,
/// The top hash of the blockchain, aka the block hash of the previous block to the one we are verifying.
pub top_hash: [u8; 32],
/// Contains the median timestamp over the last 60 blocks, if there is less than 60 blocks this should be [`None`]
pub median_block_timestamp: Option<u64>,
/// The current chain height.
pub chain_height: u64,
/// The current hard-fork.
pub current_hf: HardFork,
/// ref: https://monero-book.cuprate.org/consensus_rules/blocks/difficulty.html#calculating-difficulty
pub next_difficulty: u128,
/// The amount of coins already minted.
pub already_generated_coins: u64,
}
/// Checks the block is valid returning the blocks hard-fork vote and the amount of coins generated.
/// Checks the block is valid returning the blocks hard-fork `VOTE` and the amount of coins generated in this block.
///
/// This does not check the POW nor does it calculate the POW hash, this is because checking POW is very expensive and
/// to allow the computation of the POW hashes to be done separately. This also does not check the transactions in the
/// block are valid.
///
/// Missed block checks in this function:
///
/// https://monero-book.cuprate.org/consensus_rules/blocks.html#key-images
/// https://monero-book.cuprate.org/consensus_rules/blocks.html#checking-pow-hash
///
///
/// 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,
@ -201,6 +241,7 @@ pub fn check_block(
block_size_sanity_check(block_blob_len, block_chain_ctx.effective_median_weight)?;
check_amount_txs(block.txs.len())?;
check_txs_unique(&block.txs)?;
let generated_coins = check_miner_tx(
&block.miner_tx,

View file

@ -36,8 +36,8 @@ pub fn decomposed_amounts() -> &'static [u64; 172] {
///
/// This is also used during miner tx verification.
///
/// https://cuprate.github.io/monero-book/consensus_rules/transactions/pre_rct.html#output-amount
/// https://cuprate.github.io/monero-book/consensus_rules/blocks/miner_tx.html#output-amounts
/// ref: https://monero-book.cuprate.org/consensus_rules/transactions/ring_signatures.html#output-amount
/// ref: https://monero-book.cuprate.org/consensus_rules/blocks/miner_tx.html#output-amounts
#[inline]
pub fn is_decomposed_amount(amount: &u64) -> bool {
decomposed_amounts().binary_search(amount).is_ok()

View file

@ -25,7 +25,7 @@ fn genesis_miner_tx(network: &Network) -> Transaction {
/// Generates the Monero genesis block.
///
/// ref: https://cuprate.github.io/monero-docs/consensus_rules/genesis_block.html
/// ref: https://monero-book.cuprate.org/consensus_rules/genesis_block.html
pub fn generate_genesis_block(network: &Network) -> Block {
Block {
header: BlockHeader {

View file

@ -17,8 +17,12 @@ use std::{
mod tests;
/// Target block time for hf 1.
///
/// ref: https://monero-book.cuprate.org/consensus_rules/blocks/difficulty.html#target-seconds
const BLOCK_TIME_V1: Duration = Duration::from_secs(60);
/// Target block time from v2.
///
/// ref: https://monero-book.cuprate.org/consensus_rules/blocks/difficulty.html#target-seconds
const BLOCK_TIME_V2: Duration = Duration::from_secs(120);
pub const NUMB_OF_HARD_FORKS: usize = 16;
@ -60,7 +64,7 @@ impl HFsInfo {
/// Returns the main-net hard-fork information.
///
/// https://cuprate.github.io/monero-book/consensus_rules/hardforks.html#Mainnet-Hard-Forks
/// ref: https://monero-book.cuprate.org/consensus_rules/hardforks.html#Mainnet-Hard-Forks
pub const fn main_net() -> HFsInfo {
Self([
HFInfo::new(0, 0),
@ -82,6 +86,9 @@ impl HFsInfo {
])
}
/// Returns the test-net hard-fork information.
///
/// ref: https://monero-book.cuprate.org/consensus_rules/hardforks.html#Testnet-Hard-Forks
pub const fn test_net() -> HFsInfo {
Self([
HFInfo::new(0, 0),
@ -103,6 +110,9 @@ impl HFsInfo {
])
}
/// Returns the test-net hard-fork information.
///
/// ref: https://monero-book.cuprate.org/consensus_rules/hardforks.html#Stagenet-Hard-Forks
pub const fn stage_net() -> HFsInfo {
Self([
HFInfo::new(0, 0),
@ -152,7 +162,7 @@ pub enum HardFork {
impl HardFork {
/// Returns the hard-fork for a blocks `major_version` field.
///
/// https://cuprate.github.io/monero-docs/consensus_rules/hardforks.html#blocks-version-and-vote
/// https://monero-book.cuprate.org/consensus_rules/hardforks.html#blocks-version-and-vote
pub fn from_version(version: u8) -> Result<HardFork, HardForkError> {
Ok(match version {
1 => HardFork::V1,
@ -177,7 +187,7 @@ impl HardFork {
/// Returns the hard-fork for a blocks `minor_version` (vote) field.
///
/// https://cuprate.github.io/monero-docs/consensus_rules/hardforks.html#blocks-version-and-vote
/// https://monero-book.cuprate.org/consensus_rules/hardforks.html#blocks-version-and-vote
pub fn from_vote(vote: u8) -> HardFork {
if vote == 0 {
// A vote of 0 is interpreted as 1 as that's what Monero used to default to.
@ -200,6 +210,8 @@ impl HardFork {
}
/// Returns the target block time for this hardfork.
///
/// ref: https://monero-book.cuprate.org/consensus_rules/blocks/difficulty.html#target-seconds
pub fn block_time(&self) -> Duration {
match self {
HardFork::V1 => BLOCK_TIME_V1,
@ -209,7 +221,7 @@ impl HardFork {
/// Checks a blocks version and vote, assuming that `self` is the current hard-fork.
///
/// https://cuprate.github.io/monero-book/consensus_rules/blocks.html#version-and-vote
/// ref: https://monero-book.cuprate.org/consensus_rules/hardforks.html#blocks-version-and-vote
pub fn check_block_version_vote(
&self,
version: &HardFork,
@ -280,7 +292,7 @@ impl HFVotes {
/// Returns the total votes for a hard-fork.
///
/// https://cuprate.github.io/monero-docs/consensus_rules/hardforks.html#accepting-a-fork
/// ref: https://monero-book.cuprate.org/consensus_rules/hardforks.html#accepting-a-fork
pub fn votes_for_hf(&self, hf: &HardFork) -> u64 {
self.votes[*hf as usize - 1..].iter().sum()
}
@ -293,7 +305,7 @@ impl HFVotes {
/// Checks if a future hard fork should be activated, returning the next hard-fork that should be
/// activated.
///
/// https://cuprate.github.io/monero-book/consensus_rules/hardforks.html#accepting-a-fork
/// ref: https://monero-book.cuprate.org/consensus_rules/hardforks.html#accepting-a-fork
pub fn current_fork(
&self,
current_hf: &HardFork,
@ -323,7 +335,7 @@ impl HFVotes {
/// Returns the votes needed for a hard-fork.
///
/// https://cuprate.github.io/monero-book/consensus_rules/hardforks.html#accepting-a-fork
/// ref: https://monero-book.cuprate.org/consensus_rules/hardforks.html#accepting-a-fork
pub fn votes_needed(threshold: u64, window: u64) -> u64 {
(threshold * window).div_ceil(100)
}

View file

@ -32,6 +32,7 @@ fn check_point_canonically_encoded(point: &curve25519_dalek::edwards::Compressed
.is_some()
}
/// Returns the current UNIX timestamp.
pub fn current_unix_timestamp() -> u64 {
SystemTime::now()
.duration_since(UNIX_EPOCH)
@ -39,6 +40,8 @@ pub fn current_unix_timestamp() -> u64 {
.as_secs()
}
/// An internal function that returns an iterator or a parallel iterator if the
/// `rayon` feature is enabled.
#[cfg(feature = "rayon")]
fn try_par_iter<T>(t: T) -> T::Iter
where
@ -47,6 +50,8 @@ where
t.into_par_iter()
}
/// An internal function that returns an iterator or a parallel iterator if the
/// `rayon` feature is enabled.
#[cfg(not(feature = "rayon"))]
fn try_par_iter<T>(t: T) -> impl std::iter::Iterator<Item = T::Item>
where

View file

@ -39,6 +39,8 @@ const MINER_TX_TIME_LOCKED_BLOCKS: u64 = 60;
/// Calculates the base block reward without taking away the penalty for expanding
/// the block.
///
/// ref: https://monero-book.cuprate.org/consensus_rules/blocks/reward.html#calculating-base-block-reward
fn calculate_base_reward(already_generated_coins: u64, hf: &HardFork) -> u64 {
let target_mins = hf.block_time().as_secs() / 60;
let emission_speed_factor = 20 - (target_mins - 1);
@ -47,6 +49,8 @@ fn calculate_base_reward(already_generated_coins: u64, hf: &HardFork) -> u64 {
}
/// Calculates the miner reward for this block.
///
/// ref: https://monero-book.cuprate.org/consensus_rules/blocks/reward.html#calculating-block-reward
pub fn calculate_block_reward(
block_weight: usize,
median_bw: usize,
@ -71,7 +75,7 @@ pub fn calculate_block_reward(
/// Checks the miner transactions version.
///
/// https://cuprate.github.io/monero-book/consensus_rules/blocks/miner_tx.html#version
/// ref: https://monero-book.cuprate.org/consensus_rules/blocks/miner_tx.html#version
fn check_miner_tx_version(tx_version: &TxVersion, hf: &HardFork) -> Result<(), MinerTxError> {
// The TxVersion enum checks if the version is not 1 or 2
if hf >= &HardFork::V12 && tx_version != &TxVersion::RingCT {
@ -83,8 +87,7 @@ fn check_miner_tx_version(tx_version: &TxVersion, hf: &HardFork) -> Result<(), M
/// Checks the miner transactions inputs.
///
/// https://cuprate.github.io/monero-book/consensus_rules/blocks/miner_tx.html#input
/// https://cuprate.github.io/monero-book/consensus_rules/blocks/miner_tx.html#height
/// ref: https://monero-book.cuprate.org/consensus_rules/blocks/miner_tx.html#input
fn check_inputs(inputs: &[Input], chain_height: u64) -> Result<(), MinerTxError> {
if inputs.len() != 1 {
return Err(MinerTxError::IncorrectNumbOfInputs);
@ -104,13 +107,13 @@ fn check_inputs(inputs: &[Input], chain_height: u64) -> Result<(), MinerTxError>
/// Checks the miner transaction has a correct time lock.
///
/// https://cuprate.github.io/monero-book/consensus_rules/blocks/miner_tx.html#unlock-time
/// ref: https://monero-book.cuprate.org/consensus_rules/blocks/miner_tx.html#unlock-time
fn check_time_lock(time_lock: &Timelock, chain_height: u64) -> Result<(), MinerTxError> {
match time_lock {
Timelock::Block(till_height) => {
// Lock times above this amount are timestamps not blocks.
// This is just for safety though and shouldn't actually be hit.
if till_height >= &500_000_000 {
if till_height > &500_000_000 {
Err(MinerTxError::InvalidLockTime)?;
}
if u64::try_from(*till_height).unwrap() != chain_height + MINER_TX_TIME_LOCKED_BLOCKS {
@ -125,7 +128,7 @@ fn check_time_lock(time_lock: &Timelock, chain_height: u64) -> Result<(), MinerT
/// Sums the outputs checking for overflow.
///
/// https://cuprate.github.io/monero-book/consensus_rules/blocks/miner_tx.html#output-amounts
/// ref: https://monero-book.cuprate.org/consensus_rules/blocks/miner_tx.html#output-amounts
fn sum_outputs(outputs: &[Output], hf: &HardFork) -> Result<u64, MinerTxError> {
let mut sum: u64 = 0;
for out in outputs {
@ -140,7 +143,7 @@ fn sum_outputs(outputs: &[Output], hf: &HardFork) -> Result<u64, MinerTxError> {
/// Checks the total outputs amount is correct returning the amount of coins collected by the miner.
///
/// https://cuprate.github.io/monero-book/consensus_rules/blocks/miner_tx.html#total-outputs
/// ref: https://monero-book.cuprate.org/consensus_rules/blocks/miner_tx.html#total-outputs
fn check_total_output_amt(
total_output: u64,
reward: u64,
@ -160,6 +163,12 @@ fn check_total_output_amt(
}
}
/// Checks all miner transactions rules.
///
/// Excluding:
/// https://monero-book.cuprate.org/consensus_rules/blocks/miner_tx.html#v2-output-pool
///
/// as this needs to be done in a database.
pub fn check_miner_tx(
tx: &Transaction,
total_fees: u64,
@ -172,6 +181,7 @@ pub fn check_miner_tx(
let tx_version = TxVersion::from_raw(tx.prefix.version).ok_or(MinerTxError::VersionInvalid)?;
check_miner_tx_version(&tx_version, hf)?;
// ref: https://monero-book.cuprate.org/consensus_rules/blocks/miner_tx.html#ringct-type
if hf >= &HardFork::V12 && tx.rct_signatures.rct_type() != RctType::Null {
return Err(MinerTxError::RCTTypeNotNULL);
}

View file

@ -1,17 +1,18 @@
#![cfg(feature = "binaries")]
use std::collections::{HashMap, HashSet};
use std::ops::Deref;
use std::time::Duration;
use std::{ops::Range, path::PathBuf, sync::Arc};
use std::{
collections::{HashMap, HashSet},
ops::Range,
path::PathBuf,
sync::Arc,
};
use clap::Parser;
use futures::{
channel::{mpsc, oneshot},
SinkExt, StreamExt, TryFutureExt,
SinkExt, StreamExt,
};
use monero_serai::{block::Block, transaction::Transaction};
use rayon::prelude::*;
use tokio::sync::RwLock;
use tower::{Service, ServiceExt};
use tracing::level_filters::LevelFilter;
@ -221,7 +222,7 @@ where
let mut randomx_vms: Option<HashMap<u64, RandomXVM>> = Some(HashMap::new());
tokio::spawn(async move {
while let Some(mut blocks) = incoming_blocks.next().await {
while let Some(blocks) = incoming_blocks.next().await {
let unwrapped_rx_vms = randomx_vms.as_mut().unwrap();
let blocks = rayon_spawn_async(move || {
@ -243,12 +244,9 @@ where
unwrapped_rx_vms.retain(|seed_height, _| seeds_needed.contains(seed_height));
for seed_height in seeds_needed {
if !unwrapped_rx_vms.contains_key(&seed_height) {
unwrapped_rx_vms.insert(
seed_height,
RandomXVM::new(rx_seed_cache.get_seeds_hash(seed_height)).unwrap(),
);
}
unwrapped_rx_vms.entry(seed_height).or_insert_with(|| {
RandomXVM::new(rx_seed_cache.get_seeds_hash(seed_height)).unwrap()
});
}
let arc_rx_vms = Arc::new(randomx_vms.take().unwrap());

View file

@ -10,16 +10,14 @@ use monero_serai::block::Block;
use monero_serai::transaction::Input;
use tower::{Service, ServiceExt};
use monero_consensus::blocks::{BlockError, RandomX};
use monero_consensus::miner_tx::MinerTxError;
use monero_consensus::{
blocks::{calculate_pow_hash, check_block, check_block_pow},
blocks::{calculate_pow_hash, check_block, check_block_pow, BlockError, RandomX},
miner_tx::MinerTxError,
ConsensusError, HardFork,
};
use crate::{
context::{BlockChainContextRequest, BlockChainContextResponse},
helper::rayon_spawn_async,
transactions::{TransactionVerificationData, VerifyTxRequest, VerifyTxResponse},
ExtendedConsensusError, TxNotInPool, TxPoolRequest, TxPoolResponse,
};
@ -294,10 +292,10 @@ where
}
async fn verify_main_chain_block<C, TxV, TxP>(
block: Block,
context_svc: C,
tx_verifier_svc: TxV,
tx_pool: TxP,
_block: Block,
_context_svc: C,
_tx_verifier_svc: TxV,
_tx_pool: TxP,
) -> Result<VerifyBlockResponse, ExtendedConsensusError>
where
C: Service<
@ -313,6 +311,9 @@ where
+ Send
+ 'static,
{
todo!("Single main chain block.");
/*
tracing::debug!("getting blockchain context");
let BlockChainContextResponse::Context(checked_context) = context_svc
.oneshot(BlockChainContextRequest::Get)
@ -379,4 +380,5 @@ where
hf_vote,
cumulative_difficulty: context.cumulative_difficulty + context.next_difficulty,
}))
*/
}

View file

@ -7,7 +7,6 @@ pub struct RandomXVM {
vms: ThreadLocal<VMInner>,
cache: RandomXCache,
flags: RandomXFlag,
seed: [u8; 32],
}
impl RandomXVM {
@ -20,7 +19,6 @@ impl RandomXVM {
vms: ThreadLocal::new(),
cache,
flags,
seed,
})
}
}