Consensus: move more types to types (#250)

* move `HardFork` to `types`

* fmt

* fix tests & doc

* fmt

* fix clippy

* move transaction verification data

* misc fixes

* doc fixes

* update README.md

* review fixes
This commit is contained in:
Boog900 2024-08-08 23:56:13 +00:00 committed by GitHub
parent fafa20c20f
commit be2f3f2672
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
23 changed files with 381 additions and 288 deletions

4
Cargo.lock generated
View file

@ -573,6 +573,7 @@ dependencies = [
"crypto-bigint", "crypto-bigint",
"cuprate-cryptonight", "cuprate-cryptonight",
"cuprate-helper", "cuprate-helper",
"cuprate-types",
"curve25519-dalek", "curve25519-dalek",
"hex", "hex",
"hex-literal", "hex-literal",
@ -860,7 +861,10 @@ dependencies = [
"cuprate-fixed-bytes", "cuprate-fixed-bytes",
"curve25519-dalek", "curve25519-dalek",
"monero-serai", "monero-serai",
"proptest",
"proptest-derive",
"serde", "serde",
"thiserror",
] ]
[[package]] [[package]]

View file

@ -16,7 +16,7 @@ use tower::{Service, ServiceExt};
use cuprate_consensus::{ use cuprate_consensus::{
context::{BlockChainContextRequest, BlockChainContextResponse}, context::{BlockChainContextRequest, BlockChainContextResponse},
transactions::TransactionVerificationData, transactions::new_tx_verification_data,
}; };
use cuprate_consensus_rules::{miner_tx::MinerTxError, ConsensusError}; use cuprate_consensus_rules::{miner_tx::MinerTxError, ConsensusError};
use cuprate_types::{VerifiedBlockInformation, VerifiedTransactionInformation}; use cuprate_types::{VerifiedBlockInformation, VerifiedTransactionInformation};
@ -257,7 +257,7 @@ where
.remove(tx) .remove(tx)
.ok_or(FastSyncError::TxsIncludedWithBlockIncorrect)?; .ok_or(FastSyncError::TxsIncludedWithBlockIncorrect)?;
let data = TransactionVerificationData::new(tx)?; let data = new_tx_verification_data(tx)?;
verified_txs.push(VerifiedTransactionInformation { verified_txs.push(VerifiedTransactionInformation {
tx_blob: data.tx_blob, tx_blob: data.tx_blob,
tx_weight: data.tx_weight, tx_weight: data.tx_weight,

View file

@ -7,11 +7,12 @@ authors = ["Boog900"]
[features] [features]
default = [] default = []
proptest = ["dep:proptest", "dep:proptest-derive"] proptest = ["dep:proptest", "dep:proptest-derive", "cuprate-types/proptest"]
rayon = ["dep:rayon"] rayon = ["dep:rayon"]
[dependencies] [dependencies]
cuprate-helper = { path = "../../helper", default-features = false, features = ["std"] } cuprate-helper = { path = "../../helper", default-features = false, features = ["std"] }
cuprate-types = { path = "../../types", default-features = false }
cuprate-cryptonight = {path = "../../cryptonight"} cuprate-cryptonight = {path = "../../cryptonight"}
monero-serai = { workspace = true, features = ["std"] } monero-serai = { workspace = true, features = ["std"] }

View file

@ -6,7 +6,7 @@ use monero_serai::block::Block;
use cuprate_cryptonight::*; use cuprate_cryptonight::*;
use crate::{ use crate::{
current_unix_timestamp, check_block_version_vote, current_unix_timestamp,
hard_forks::HardForkError, hard_forks::HardForkError,
miner_tx::{check_miner_tx, MinerTxError}, miner_tx::{check_miner_tx, MinerTxError},
HardFork, HardFork,
@ -249,11 +249,10 @@ pub fn check_block(
block_blob_len: usize, block_blob_len: usize,
block_chain_ctx: &ContextToVerifyBlock, block_chain_ctx: &ContextToVerifyBlock,
) -> Result<(HardFork, u64), BlockError> { ) -> Result<(HardFork, u64), BlockError> {
let (version, vote) = HardFork::from_block_header(&block.header)?; let (version, vote) =
HardFork::from_block_header(&block.header).map_err(|_| HardForkError::HardForkUnknown)?;
block_chain_ctx check_block_version_vote(&block_chain_ctx.current_hf, &version, &vote)?;
.current_hf
.check_block_version_vote(&version, &vote)?;
if let Some(median_timestamp) = block_chain_ctx.median_block_timestamp { if let Some(median_timestamp) = block_chain_ctx.median_block_timestamp {
check_timestamp(block, median_timestamp)?; check_timestamp(block, median_timestamp)?;

View file

@ -1,40 +1,37 @@
//! # Hard-Forks //! # Hard-Forks
//! //!
//! Monero use hard-forks to update it's protocol, this module contains a [`HardFork`] enum which is //! Monero use hard-forks to update it's protocol, this module contains a [`HFVotes`] struct which
//! an identifier for every current hard-fork. //! keeps track of current blockchain voting, and has a method [`HFVotes::current_fork`] to check
//! //! if the next hard-fork should be activated.
//! This module also contains a [`HFVotes`] struct which keeps track of current blockchain voting, and
//! has a method [`HFVotes::current_fork`] to check if the next hard-fork should be activated.
//!
use monero_serai::block::BlockHeader;
use std::{ use std::{
collections::VecDeque, collections::VecDeque,
fmt::{Display, Formatter}, fmt::{Display, Formatter},
time::Duration,
}; };
pub use cuprate_types::{HardFork, HardForkError};
#[cfg(test)] #[cfg(test)]
mod tests; 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; pub const NUMB_OF_HARD_FORKS: usize = 16;
#[derive(Debug, Copy, Clone, PartialEq, Eq, thiserror::Error)] /// Checks a blocks version and vote, assuming that `hf` is the current hard-fork.
pub enum HardForkError { ///
#[error("The hard-fork is unknown")] /// ref: <https://monero-book.cuprate.org/consensus_rules/hardforks.html#blocks-version-and-vote>
HardForkUnknown, pub fn check_block_version_vote(
#[error("The block is on an incorrect hard-fork")] hf: &HardFork,
VersionIncorrect, version: &HardFork,
#[error("The block's vote is for a previous hard-fork")] vote: &HardFork,
VoteTooLow, ) -> Result<(), HardForkError> {
// self = current hf
if hf != version {
Err(HardForkError::VersionIncorrect)?;
}
if hf > vote {
Err(HardForkError::VoteTooLow)?;
}
Ok(())
} }
/// Information about a given hard-fork. /// Information about a given hard-fork.
@ -135,113 +132,6 @@ impl HFsInfo {
} }
} }
/// An identifier for every hard-fork Monero has had.
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Copy, Clone)]
#[cfg_attr(any(feature = "proptest", test), derive(proptest_derive::Arbitrary))]
#[repr(u8)]
pub enum HardFork {
V1 = 1,
V2,
V3,
V4,
V5,
V6,
V7,
V8,
V9,
V10,
V11,
V12,
V13,
V14,
V15,
// remember to update from_vote!
V16,
}
impl HardFork {
/// Returns the hard-fork for a blocks `major_version` field.
///
/// <https://monero-book.cuprate.org/consensus_rules/hardforks.html#blocks-version-and-vote>
#[inline]
pub fn from_version(version: u8) -> Result<HardFork, HardForkError> {
Ok(match version {
1 => HardFork::V1,
2 => HardFork::V2,
3 => HardFork::V3,
4 => HardFork::V4,
5 => HardFork::V5,
6 => HardFork::V6,
7 => HardFork::V7,
8 => HardFork::V8,
9 => HardFork::V9,
10 => HardFork::V10,
11 => HardFork::V11,
12 => HardFork::V12,
13 => HardFork::V13,
14 => HardFork::V14,
15 => HardFork::V15,
16 => HardFork::V16,
_ => return Err(HardForkError::HardForkUnknown),
})
}
/// Returns the hard-fork for a blocks `minor_version` (vote) field.
///
/// <https://monero-book.cuprate.org/consensus_rules/hardforks.html#blocks-version-and-vote>
#[inline]
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.
return HardFork::V1;
}
// This must default to the latest hard-fork!
Self::from_version(vote).unwrap_or(HardFork::V16)
}
#[inline]
pub fn from_block_header(header: &BlockHeader) -> Result<(HardFork, HardFork), HardForkError> {
Ok((
HardFork::from_version(header.hardfork_version)?,
HardFork::from_vote(header.hardfork_signal),
))
}
/// Returns the next hard-fork.
pub fn next_fork(&self) -> Option<HardFork> {
HardFork::from_version(*self as u8 + 1).ok()
}
/// 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,
_ => BLOCK_TIME_V2,
}
}
/// Checks a blocks version and vote, assuming that `self` is the current hard-fork.
///
/// ref: <https://monero-book.cuprate.org/consensus_rules/hardforks.html#blocks-version-and-vote>
pub fn check_block_version_vote(
&self,
version: &HardFork,
vote: &HardFork,
) -> Result<(), HardForkError> {
// self = current hf
if self != version {
Err(HardForkError::VersionIncorrect)?;
}
if self > vote {
Err(HardForkError::VoteTooLow)?;
}
Ok(())
}
}
/// A struct holding the current voting state of the blockchain. /// A struct holding the current voting state of the blockchain.
#[derive(Debug, Clone, Eq, PartialEq)] #[derive(Debug, Clone, Eq, PartialEq)]
pub struct HFVotes { pub struct HFVotes {

View file

@ -9,7 +9,7 @@ pub mod miner_tx;
pub mod transactions; pub mod transactions;
pub use decomposed_amount::is_decomposed_amount; pub use decomposed_amount::is_decomposed_amount;
pub use hard_forks::{HFVotes, HFsInfo, HardFork}; pub use hard_forks::{check_block_version_vote, HFVotes, HFsInfo, HardFork};
pub use transactions::TxVersion; pub use transactions::TxVersion;
#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)] #[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]

View file

@ -1,6 +1,8 @@
use monero_serai::transaction::{Input, Output, Timelock, Transaction}; use monero_serai::transaction::{Input, Output, Timelock, Transaction};
use crate::{is_decomposed_amount, transactions::check_output_types, HardFork, TxVersion}; use cuprate_types::TxVersion;
use crate::{is_decomposed_amount, transactions::check_output_types, HardFork};
#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)] #[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
pub enum MinerTxError { pub enum MinerTxError {

View file

@ -1,8 +1,11 @@
use std::cmp::Ordering; use std::cmp::Ordering;
use monero_serai::ringct::RctType; use monero_serai::{
ringct::RctType,
transaction::{Input, Output, Timelock, Transaction},
};
use monero_serai::transaction::{Input, Output, Timelock, Transaction}; pub use cuprate_types::TxVersion;
use crate::{ use crate::{
batch_verifier::BatchVerifier, blocks::penalty_free_zone, check_point_canonically_encoded, batch_verifier::BatchVerifier, blocks::penalty_free_zone, check_point_canonically_encoded,
@ -75,31 +78,6 @@ pub enum TransactionError {
RingCTError(#[from] RingCTError), RingCTError(#[from] RingCTError),
} }
/// An enum representing all valid Monero transaction versions.
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)]
pub enum TxVersion {
/// Legacy ring signatures.
RingSignatures,
/// RingCT
RingCT,
}
impl TxVersion {
/// Converts a `raw` version value to a [`TxVersion`].
///
/// This will return `None` on invalid values.
///
/// ref: <https://monero-book.cuprate.org/consensus_rules/transactions.html#version>
/// && <https://monero-book.cuprate.org/consensus_rules/blocks/miner_tx.html#version>
pub fn from_raw(version: u8) -> Option<TxVersion> {
Some(match version {
1 => TxVersion::RingSignatures,
2 => TxVersion::RingCT,
_ => return None,
})
}
}
//----------------------------------------------------------------------------------------------------------- OUTPUTS //----------------------------------------------------------------------------------------------------------- OUTPUTS
/// Checks the output keys are canonically encoded points. /// Checks the output keys are canonically encoded points.

View file

@ -16,20 +16,22 @@ use tower::{Service, ServiceExt};
use cuprate_helper::asynch::rayon_spawn_async; use cuprate_helper::asynch::rayon_spawn_async;
use cuprate_types::{ use cuprate_types::{
AltBlockInformation, VerifiedBlockInformation, VerifiedTransactionInformation, AltBlockInformation, TransactionVerificationData, VerifiedBlockInformation,
VerifiedTransactionInformation,
}; };
use cuprate_consensus_rules::{ use cuprate_consensus_rules::{
blocks::{ blocks::{
calculate_pow_hash, check_block, check_block_pow, randomx_seed_height, BlockError, RandomX, calculate_pow_hash, check_block, check_block_pow, randomx_seed_height, BlockError, RandomX,
}, },
hard_forks::HardForkError,
miner_tx::MinerTxError, miner_tx::MinerTxError,
ConsensusError, HardFork, ConsensusError, HardFork,
}; };
use crate::{ use crate::{
context::{BlockChainContextRequest, BlockChainContextResponse, RawBlockChainContext}, context::{BlockChainContextRequest, BlockChainContextResponse, RawBlockChainContext},
transactions::{TransactionVerificationData, VerifyTxRequest, VerifyTxResponse}, transactions::{VerifyTxRequest, VerifyTxResponse},
Database, ExtendedConsensusError, Database, ExtendedConsensusError,
}; };
@ -71,8 +73,8 @@ impl PreparedBlockExPow {
/// - Hard-fork values are invalid /// - Hard-fork values are invalid
/// - Miner transaction is missing a miner input /// - Miner transaction is missing a miner input
pub fn new(block: Block) -> Result<PreparedBlockExPow, ConsensusError> { pub fn new(block: Block) -> Result<PreparedBlockExPow, ConsensusError> {
let (hf_version, hf_vote) = let (hf_version, hf_vote) = HardFork::from_block_header(&block.header)
HardFork::from_block_header(&block.header).map_err(BlockError::HardForkError)?; .map_err(|_| BlockError::HardForkError(HardForkError::HardForkUnknown))?;
let Some(Input::Gen(height)) = block.miner_transaction.prefix().inputs.first() else { let Some(Input::Gen(height)) = block.miner_transaction.prefix().inputs.first() else {
Err(ConsensusError::Block(BlockError::MinerTxError( Err(ConsensusError::Block(BlockError::MinerTxError(
@ -125,8 +127,8 @@ impl PreparedBlock {
block: Block, block: Block,
randomx_vm: Option<&R>, randomx_vm: Option<&R>,
) -> Result<PreparedBlock, ConsensusError> { ) -> Result<PreparedBlock, ConsensusError> {
let (hf_version, hf_vote) = let (hf_version, hf_vote) = HardFork::from_block_header(&block.header)
HardFork::from_block_header(&block.header).map_err(BlockError::HardForkError)?; .map_err(|_| BlockError::HardForkError(HardForkError::HardForkUnknown))?;
let [Input::Gen(height)] = &block.miner_transaction.prefix().inputs[..] else { let [Input::Gen(height)] = &block.miner_transaction.prefix().inputs[..] else {
Err(ConsensusError::Block(BlockError::MinerTxError( Err(ConsensusError::Block(BlockError::MinerTxError(

View file

@ -15,7 +15,10 @@ use cuprate_consensus_rules::{
ConsensusError, ConsensusError,
}; };
use cuprate_helper::asynch::rayon_spawn_async; use cuprate_helper::asynch::rayon_spawn_async;
use cuprate_types::{AltBlockInformation, Chain, ChainId, VerifiedTransactionInformation}; use cuprate_types::{
AltBlockInformation, Chain, ChainId, TransactionVerificationData,
VerifiedTransactionInformation,
};
use crate::{ use crate::{
block::{free::pull_ordered_transactions, PreparedBlock}, block::{free::pull_ordered_transactions, PreparedBlock},
@ -25,7 +28,6 @@ use crate::{
weight::{self, BlockWeightsCache}, weight::{self, BlockWeightsCache},
AltChainContextCache, AltChainRequestToken, BLOCKCHAIN_TIMESTAMP_CHECK_WINDOW, AltChainContextCache, AltChainRequestToken, BLOCKCHAIN_TIMESTAMP_CHECK_WINDOW,
}, },
transactions::TransactionVerificationData,
BlockChainContextRequest, BlockChainContextResponse, ExtendedConsensusError, BlockChainContextRequest, BlockChainContextResponse, ExtendedConsensusError,
VerifyBlockResponse, VerifyBlockResponse,
}; };

View file

@ -16,7 +16,7 @@ use cuprate_helper::asynch::rayon_spawn_async;
use crate::{ use crate::{
block::{free::pull_ordered_transactions, PreparedBlock, PreparedBlockExPow}, block::{free::pull_ordered_transactions, PreparedBlock, PreparedBlockExPow},
context::rx_vms::RandomXVM, context::rx_vms::RandomXVM,
transactions::TransactionVerificationData, transactions::new_tx_verification_data,
BlockChainContextRequest, BlockChainContextResponse, ExtendedConsensusError, BlockChainContextRequest, BlockChainContextResponse, ExtendedConsensusError,
VerifyBlockResponse, VerifyBlockResponse,
}; };
@ -185,7 +185,7 @@ where
let txs = txs let txs = txs
.into_par_iter() .into_par_iter()
.map(|tx| { .map(|tx| {
let tx = TransactionVerificationData::new(tx)?; let tx = new_tx_verification_data(tx)?;
Ok::<_, ConsensusError>((tx.tx_hash, tx)) Ok::<_, ConsensusError>((tx.tx_hash, tx))
}) })
.collect::<Result<HashMap<_, _>, _>>()?; .collect::<Result<HashMap<_, _>, _>>()?;

View file

@ -3,7 +3,9 @@ use std::collections::HashMap;
use monero_serai::block::Block; use monero_serai::block::Block;
use crate::{transactions::TransactionVerificationData, ExtendedConsensusError}; use cuprate_types::TransactionVerificationData;
use crate::ExtendedConsensusError;
/// Returns a list of transactions, pulled from `txs` in the order they are in the [`Block`]. /// Returns a list of transactions, pulled from `txs` in the order they are in the [`Block`].
/// ///

View file

@ -95,8 +95,7 @@ impl HardForkState {
panic!("Database sent incorrect response!"); panic!("Database sent incorrect response!");
}; };
let current_hardfork = let current_hardfork = ext_header.version;
HardFork::from_version(ext_header.version).expect("Stored block has invalid hardfork");
let mut hfs = HardForkState { let mut hfs = HardForkState {
config, config,

View file

@ -61,8 +61,8 @@ pub struct DummyBlockExtendedHeader {
impl From<DummyBlockExtendedHeader> for ExtendedBlockHeader { impl From<DummyBlockExtendedHeader> for ExtendedBlockHeader {
fn from(value: DummyBlockExtendedHeader) -> Self { fn from(value: DummyBlockExtendedHeader) -> Self {
ExtendedBlockHeader { ExtendedBlockHeader {
version: value.version.unwrap_or(HardFork::V1) as u8, version: value.version.unwrap_or(HardFork::V1),
vote: value.vote.unwrap_or(HardFork::V1) as u8, vote: value.vote.unwrap_or(HardFork::V1).as_u8(),
timestamp: value.timestamp.unwrap_or_default(), timestamp: value.timestamp.unwrap_or_default(),
cumulative_difficulty: value.cumulative_difficulty.unwrap_or_default(), cumulative_difficulty: value.cumulative_difficulty.unwrap_or_default(),
block_weight: value.block_weight.unwrap_or_default(), block_weight: value.block_weight.unwrap_or_default(),

View file

@ -7,7 +7,7 @@ use std::{
future::Future, future::Future,
ops::Deref, ops::Deref,
pin::Pin, pin::Pin,
sync::{Arc, Mutex as StdMutex}, sync::Arc,
task::{Context, Poll}, task::{Context, Poll},
}; };
@ -22,10 +22,13 @@ use cuprate_consensus_rules::{
check_decoy_info, check_transaction_contextual, check_transaction_semantic, check_decoy_info, check_transaction_contextual, check_transaction_semantic,
output_unlocked, TransactionError, output_unlocked, TransactionError,
}, },
ConsensusError, HardFork, TxVersion, ConsensusError, HardFork,
}; };
use cuprate_helper::asynch::rayon_spawn_async; use cuprate_helper::asynch::rayon_spawn_async;
use cuprate_types::blockchain::{BlockchainReadRequest, BlockchainResponse}; use cuprate_types::{
blockchain::{BlockchainReadRequest, BlockchainResponse},
CachedVerificationState, TransactionVerificationData, TxVersion,
};
use crate::{ use crate::{
batch_verifier::MultiThreadedBatchVerifier, batch_verifier::MultiThreadedBatchVerifier,
@ -36,6 +39,8 @@ use crate::{
pub mod contextual_data; pub mod contextual_data;
mod free; mod free;
pub use free::new_tx_verification_data;
/// A struct representing the type of validation that needs to be completed for this transaction. /// A struct representing the type of validation that needs to be completed for this transaction.
#[derive(Debug, Copy, Clone, Eq, PartialEq)] #[derive(Debug, Copy, Clone, Eq, PartialEq)]
enum VerificationNeeded { enum VerificationNeeded {
@ -45,79 +50,6 @@ enum VerificationNeeded {
Contextual, Contextual,
} }
/// Represents if a transaction has been fully validated and under what conditions
/// the transaction is valid in the future.
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum CachedVerificationState {
/// The transaction has not been validated.
NotVerified,
/// The transaction is valid* if the block represented by this hash is in the blockchain and the [`HardFork`]
/// is the same.
///
/// *V1 transactions require checks on their ring-length even if this hash is in the blockchain.
ValidAtHashAndHF([u8; 32], HardFork),
/// The transaction is valid* if the block represented by this hash is in the blockchain _and_ this
/// given time lock is unlocked. The time lock here will represent the youngest used time based lock
/// (If the transaction uses any time based time locks). This is because time locks are not monotonic
/// so unlocked outputs could become re-locked.
///
/// *V1 transactions require checks on their ring-length even if this hash is in the blockchain.
ValidAtHashAndHFWithTimeBasedLock([u8; 32], HardFork, Timelock),
}
impl CachedVerificationState {
/// Returns the block hash this is valid for if in state [`CachedVerificationState::ValidAtHashAndHF`] or [`CachedVerificationState::ValidAtHashAndHFWithTimeBasedLock`].
fn verified_at_block_hash(&self) -> Option<[u8; 32]> {
match self {
CachedVerificationState::NotVerified => None,
CachedVerificationState::ValidAtHashAndHF(hash, _)
| CachedVerificationState::ValidAtHashAndHFWithTimeBasedLock(hash, _, _) => Some(*hash),
}
}
}
/// Data needed to verify a transaction.
#[derive(Debug)]
pub struct TransactionVerificationData {
/// The transaction we are verifying
pub tx: Transaction,
/// The [`TxVersion`] of this tx.
pub version: TxVersion,
/// The serialised transaction.
pub tx_blob: Vec<u8>,
/// The weight of the transaction.
pub tx_weight: usize,
/// The fee this transaction has paid.
pub fee: u64,
/// The hash of this transaction.
pub tx_hash: [u8; 32],
/// The verification state of this transaction.
pub cached_verification_state: StdMutex<CachedVerificationState>,
}
impl TransactionVerificationData {
/// Creates a new [`TransactionVerificationData`] from the given [`Transaction`].
pub fn new(tx: Transaction) -> Result<TransactionVerificationData, ConsensusError> {
let tx_hash = tx.hash();
let tx_blob = tx.serialize();
let tx_weight = free::tx_weight(&tx, &tx_blob);
let fee = free::tx_fee(&tx)?;
Ok(TransactionVerificationData {
tx_hash,
tx_blob,
tx_weight,
fee,
cached_verification_state: StdMutex::new(CachedVerificationState::NotVerified),
version: TxVersion::from_raw(tx.version())
.ok_or(TransactionError::TransactionVersionInvalid)?,
tx,
})
}
}
/// A request to verify a transaction. /// A request to verify a transaction.
pub enum VerifyTxRequest { pub enum VerifyTxRequest {
/// Verifies a batch of prepared txs. /// Verifies a batch of prepared txs.
@ -252,7 +184,7 @@ where
tracing::debug!(parent: &span, "prepping transactions for verification."); tracing::debug!(parent: &span, "prepping transactions for verification.");
let txs = rayon_spawn_async(|| { let txs = rayon_spawn_async(|| {
txs.into_par_iter() txs.into_par_iter()
.map(|tx| TransactionVerificationData::new(tx).map(Arc::new)) .map(|tx| new_tx_verification_data(tx).map(Arc::new))
.collect::<Result<Vec<_>, _>>() .collect::<Result<Vec<_>, _>>()
}) })
.await?; .await?;
@ -399,7 +331,7 @@ fn transactions_needing_verification(
.push((tx.clone(), VerificationNeeded::SemanticAndContextual)); .push((tx.clone(), VerificationNeeded::SemanticAndContextual));
continue; continue;
} }
CachedVerificationState::ValidAtHashAndHF(hash, hf) => { CachedVerificationState::ValidAtHashAndHF { block_hash, hf } => {
if current_hf != hf { if current_hf != hf {
drop(guard); drop(guard);
full_validation_transactions full_validation_transactions
@ -407,13 +339,17 @@ fn transactions_needing_verification(
continue; continue;
} }
if !hashes_in_main_chain.contains(hash) { if !hashes_in_main_chain.contains(block_hash) {
drop(guard); drop(guard);
full_validation_transactions.push((tx.clone(), VerificationNeeded::Contextual)); full_validation_transactions.push((tx.clone(), VerificationNeeded::Contextual));
continue; continue;
} }
} }
CachedVerificationState::ValidAtHashAndHFWithTimeBasedLock(hash, hf, lock) => { CachedVerificationState::ValidAtHashAndHFWithTimeBasedLock {
block_hash,
hf,
time_lock,
} => {
if current_hf != hf { if current_hf != hf {
drop(guard); drop(guard);
full_validation_transactions full_validation_transactions
@ -421,14 +357,14 @@ fn transactions_needing_verification(
continue; continue;
} }
if !hashes_in_main_chain.contains(hash) { if !hashes_in_main_chain.contains(block_hash) {
drop(guard); drop(guard);
full_validation_transactions.push((tx.clone(), VerificationNeeded::Contextual)); full_validation_transactions.push((tx.clone(), VerificationNeeded::Contextual));
continue; continue;
} }
// If the time lock is still locked then the transaction is invalid. // If the time lock is still locked then the transaction is invalid.
if !output_unlocked(lock, current_chain_height, time_for_time_lock, hf) { if !output_unlocked(time_lock, current_chain_height, time_for_time_lock, hf) {
return Err(ConsensusError::Transaction( return Err(ConsensusError::Transaction(
TransactionError::OneOrMoreRingMembersLocked, TransactionError::OneOrMoreRingMembersLocked,
)); ));
@ -517,10 +453,15 @@ where
txs.iter() txs.iter()
.zip(txs_ring_member_info) .zip(txs_ring_member_info)
.for_each(|((tx, _), ring)| { .for_each(|((tx, _), ring)| {
if ring.time_locked_outs.is_empty() { *tx.cached_verification_state.lock().unwrap() = if ring.time_locked_outs.is_empty()
*tx.cached_verification_state.lock().unwrap() = {
CachedVerificationState::ValidAtHashAndHF(top_hash, hf); // no outputs with time-locks used.
CachedVerificationState::ValidAtHashAndHF {
block_hash: top_hash,
hf,
}
} else { } else {
// an output with a time-lock was used, check if it was time-based.
let youngest_timebased_lock = ring let youngest_timebased_lock = ring
.time_locked_outs .time_locked_outs
.iter() .iter()
@ -530,16 +471,20 @@ where
}) })
.min(); .min();
*tx.cached_verification_state.lock().unwrap() =
if let Some(time) = youngest_timebased_lock { if let Some(time) = youngest_timebased_lock {
CachedVerificationState::ValidAtHashAndHFWithTimeBasedLock( // time-based lock used.
top_hash, CachedVerificationState::ValidAtHashAndHFWithTimeBasedLock {
block_hash: top_hash,
hf, hf,
Timelock::Time(time), time_lock: Timelock::Time(time),
) }
} else { } else {
CachedVerificationState::ValidAtHashAndHF(top_hash, hf) // no time-based locked output was used.
}; CachedVerificationState::ValidAtHashAndHF {
block_hash: top_hash,
hf,
}
}
} }
}); });

View file

@ -1,9 +1,40 @@
use std::sync::Mutex as StdMutex;
use monero_serai::{ use monero_serai::{
ringct::{bulletproofs::Bulletproof, RctType}, ringct::{bulletproofs::Bulletproof, RctType},
transaction::{Input, Transaction}, transaction::{Input, Transaction},
}; };
use cuprate_consensus_rules::transactions::TransactionError; use cuprate_consensus_rules::{transactions::TransactionError, ConsensusError};
use cuprate_types::{CachedVerificationState, TransactionVerificationData, TxVersion};
/// Creates a new [`TransactionVerificationData`] from a [`Transaction`].
///
/// # Errors
///
/// This function will return [`Err`] if the transaction is malformed, although returning [`Ok`] does
/// not necessarily mean the tx is correctly formed.
pub fn new_tx_verification_data(
tx: Transaction,
) -> Result<TransactionVerificationData, ConsensusError> {
let tx_hash = tx.hash();
let tx_blob = tx.serialize();
let tx_weight = tx_weight(&tx, &tx_blob);
let fee = tx_fee(&tx)?;
Ok(TransactionVerificationData {
tx_hash,
version: TxVersion::from_raw(tx.version())
.ok_or(TransactionError::TransactionVersionInvalid)?,
tx_blob,
tx_weight,
fee,
cached_verification_state: StdMutex::new(CachedVerificationState::NotVerified),
tx,
})
}
/// Calculates the weight of a [`Transaction`]. /// Calculates the weight of a [`Transaction`].
/// ///

View file

@ -8,7 +8,7 @@ use cuprate_database::{
RuntimeError, StorableVec, {DatabaseRo, DatabaseRw}, RuntimeError, StorableVec, {DatabaseRo, DatabaseRw},
}; };
use cuprate_helper::map::{combine_low_high_bits_to_u128, split_u128_into_low_high_bits}; use cuprate_helper::map::{combine_low_high_bits_to_u128, split_u128_into_low_high_bits};
use cuprate_types::{ExtendedBlockHeader, VerifiedBlockInformation}; use cuprate_types::{ExtendedBlockHeader, HardFork, VerifiedBlockInformation};
use crate::{ use crate::{
ops::{ ops::{
@ -182,6 +182,7 @@ pub fn get_block_extended_header(
/// Same as [`get_block_extended_header`] but with a [`BlockHeight`]. /// Same as [`get_block_extended_header`] but with a [`BlockHeight`].
#[doc = doc_error!()] #[doc = doc_error!()]
#[allow(clippy::missing_panics_doc)] // The panic is only possible with a corrupt DB
#[inline] #[inline]
pub fn get_block_extended_header_from_height( pub fn get_block_extended_header_from_height(
block_height: &BlockHeight, block_height: &BlockHeight,
@ -200,7 +201,8 @@ pub fn get_block_extended_header_from_height(
#[allow(clippy::cast_possible_truncation)] #[allow(clippy::cast_possible_truncation)]
Ok(ExtendedBlockHeader { Ok(ExtendedBlockHeader {
cumulative_difficulty, cumulative_difficulty,
version: block.header.hardfork_version, version: HardFork::from_version(block.header.hardfork_version)
.expect("Stored block must have a valid hard-fork"),
vote: block.header.hardfork_signal, vote: block.header.hardfork_signal,
timestamp: block.header.timestamp, timestamp: block.header.timestamp,
block_weight: block_info.weight as usize, block_weight: block_info.weight as usize,
@ -369,7 +371,7 @@ mod test {
let b1 = block_header_from_hash; let b1 = block_header_from_hash;
let b2 = block; let b2 = block;
assert_eq!(b1, block_header_from_height); assert_eq!(b1, block_header_from_height);
assert_eq!(b1.version, b2.block.header.hardfork_version); assert_eq!(b1.version.as_u8(), b2.block.header.hardfork_version);
assert_eq!(b1.vote, b2.block.header.hardfork_signal); assert_eq!(b1.vote, b2.block.header.hardfork_signal);
assert_eq!(b1.timestamp, b2.block.header.timestamp); assert_eq!(b1.timestamp, b2.block.header.timestamp);
assert_eq!(b1.cumulative_difficulty, b2.cumulative_difficulty); assert_eq!(b1.cumulative_difficulty, b2.cumulative_difficulty);

View file

@ -13,6 +13,7 @@ default = ["blockchain", "epee", "serde"]
blockchain = [] blockchain = []
epee = ["dep:cuprate-epee-encoding"] epee = ["dep:cuprate-epee-encoding"]
serde = ["dep:serde"] serde = ["dep:serde"]
proptest = ["dep:proptest", "dep:proptest-derive"]
[dependencies] [dependencies]
cuprate-epee-encoding = { path = "../net/epee-encoding", optional = true } cuprate-epee-encoding = { path = "../net/epee-encoding", optional = true }
@ -23,5 +24,9 @@ curve25519-dalek = { workspace = true }
monero-serai = { workspace = true } monero-serai = { workspace = true }
serde = { workspace = true, features = ["derive"], optional = true } serde = { workspace = true, features = ["derive"], optional = true }
borsh = { workspace = true, optional = true } borsh = { workspace = true, optional = true }
thiserror = { workspace = true }
proptest = { workspace = true, optional = true }
proptest-derive = { workspace = true, optional = true }
[dev-dependencies] [dev-dependencies]

View file

@ -9,3 +9,4 @@ This crate is a kitchen-sink for data types that are shared across Cuprate.
| `blockchain` | Enables the `blockchain` module, containing the blockchain database request/response types | `blockchain` | Enables the `blockchain` module, containing the blockchain database request/response types
| `serde` | Enables `serde` on types where applicable | `serde` | Enables `serde` on types where applicable
| `epee` | Enables `cuprate-epee-encoding` on types where applicable | `epee` | Enables `cuprate-epee-encoding` on types where applicable
| `proptest` | Enables `proptest::arbitrary::Arbitrary` on some types

131
types/src/hard_fork.rs Normal file
View file

@ -0,0 +1,131 @@
//! The [`HardFork`] type.
use std::time::Duration;
use monero_serai::block::BlockHeader;
/// 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);
/// An error working with a [`HardFork`].
#[derive(Debug, Copy, Clone, PartialEq, Eq, thiserror::Error)]
pub enum HardForkError {
/// The raw-HF value is not a valid [`HardFork`].
#[error("The hard-fork is unknown")]
HardForkUnknown,
/// The [`HardFork`] version is incorrect.
#[error("The block is on an incorrect hard-fork")]
VersionIncorrect,
/// The block's [`HardFork`] vote was below the current [`HardFork`].
#[error("The block's vote is for a previous hard-fork")]
VoteTooLow,
}
/// An identifier for every hard-fork Monero has had.
#[allow(missing_docs)]
#[derive(Default, Debug, PartialEq, Eq, PartialOrd, Ord, Copy, Clone, Hash)]
#[cfg_attr(any(feature = "proptest"), derive(proptest_derive::Arbitrary))]
#[repr(u8)]
pub enum HardFork {
#[default]
V1 = 1,
V2,
V3,
V4,
V5,
V6,
V7,
V8,
V9,
V10,
V11,
V12,
V13,
V14,
V15,
// remember to update from_vote!
V16,
}
impl HardFork {
/// Returns the hard-fork for a blocks [`BlockHeader::hardfork_version`] field.
///
/// ref: <https://monero-book.cuprate.org/consensus_rules/hardforks.html#blocks-version-and-vote>
///
/// # Errors
///
/// Will return [`Err`] if the version is not a valid [`HardFork`].
#[inline]
pub const fn from_version(version: u8) -> Result<Self, HardForkError> {
Ok(match version {
1 => Self::V1,
2 => Self::V2,
3 => Self::V3,
4 => Self::V4,
5 => Self::V5,
6 => Self::V6,
7 => Self::V7,
8 => Self::V8,
9 => Self::V9,
10 => Self::V10,
11 => Self::V11,
12 => Self::V12,
13 => Self::V13,
14 => Self::V14,
15 => Self::V15,
16 => Self::V16,
_ => return Err(HardForkError::HardForkUnknown),
})
}
/// Returns the hard-fork for a blocks [`BlockHeader::hardfork_signal`] (vote) field.
///
/// <https://monero-book.cuprate.org/consensus_rules/hardforks.html#blocks-version-and-vote>
#[inline]
pub fn from_vote(vote: u8) -> Self {
if vote == 0 {
// A vote of 0 is interpreted as 1 as that's what Monero used to default to.
return Self::V1;
}
// This must default to the latest hard-fork!
Self::from_version(vote).unwrap_or(Self::V16)
}
/// Returns the [`HardFork`] version and vote from this block header.
///
/// # Errors
///
/// Will return [`Err`] if the [`BlockHeader::hardfork_version`] is not a valid [`HardFork`].
#[inline]
pub fn from_block_header(header: &BlockHeader) -> Result<(Self, Self), HardForkError> {
Ok((
Self::from_version(header.hardfork_version)?,
Self::from_vote(header.hardfork_signal),
))
}
/// Returns the raw hard-fork value, as it would appear in [`BlockHeader::hardfork_version`].
pub const fn as_u8(&self) -> u8 {
*self as u8
}
/// Returns the next hard-fork.
pub fn next_fork(&self) -> Option<Self> {
Self::from_version(*self as u8 + 1).ok()
}
/// Returns the target block time for this hardfork.
///
/// ref: <https://monero-book.cuprate.org/consensus_rules/blocks/difficulty.html#target-seconds>
pub const fn block_time(&self) -> Duration {
match self {
Self::V1 => BLOCK_TIME_V1,
_ => BLOCK_TIME_V2,
}
}
}

View file

@ -80,9 +80,15 @@
// Documentation for each module is located in the respective file. // Documentation for each module is located in the respective file.
mod block_complete_entry; mod block_complete_entry;
mod hard_fork;
mod transaction_verification_data;
mod types; mod types;
pub use block_complete_entry::{BlockCompleteEntry, PrunedTxBlobEntry, TransactionBlobs}; pub use block_complete_entry::{BlockCompleteEntry, PrunedTxBlobEntry, TransactionBlobs};
pub use hard_fork::{HardFork, HardForkError};
pub use transaction_verification_data::{
CachedVerificationState, TransactionVerificationData, TxVersion,
};
pub use types::{ pub use types::{
AltBlockInformation, Chain, ChainId, ExtendedBlockHeader, OutputOnChain, AltBlockInformation, Chain, ChainId, ExtendedBlockHeader, OutputOnChain,
VerifiedBlockInformation, VerifiedTransactionInformation, VerifiedBlockInformation, VerifiedTransactionInformation,
@ -91,5 +97,4 @@ pub use types::{
//---------------------------------------------------------------------------------------------------- Feature-gated //---------------------------------------------------------------------------------------------------- Feature-gated
#[cfg(feature = "blockchain")] #[cfg(feature = "blockchain")]
pub mod blockchain; pub mod blockchain;
//---------------------------------------------------------------------------------------------------- Private //---------------------------------------------------------------------------------------------------- Private

View file

@ -0,0 +1,94 @@
//! Contains [`TransactionVerificationData`] and the related types.
use std::sync::Mutex;
use monero_serai::transaction::{Timelock, Transaction};
use crate::HardFork;
/// An enum representing all valid Monero transaction versions.
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)]
pub enum TxVersion {
/// Legacy ring signatures.
RingSignatures,
/// Ring-CT
RingCT,
}
impl TxVersion {
/// Converts a `raw` version value to a [`TxVersion`].
///
/// This will return `None` on invalid values.
///
/// ref: <https://monero-book.cuprate.org/consensus_rules/transactions.html#version>
/// && <https://monero-book.cuprate.org/consensus_rules/blocks/miner_tx.html#version>
pub const fn from_raw(version: u8) -> Option<Self> {
Some(match version {
1 => Self::RingSignatures,
2 => Self::RingCT,
_ => return None,
})
}
}
/// Represents if a transaction has been fully validated and under what conditions
/// the transaction is valid in the future.
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum CachedVerificationState {
/// The transaction has not been validated.
NotVerified,
/// The transaction is valid* if the block represented by this hash is in the blockchain and the [`HardFork`]
/// is the same.
///
/// *V1 transactions require checks on their ring-length even if this hash is in the blockchain.
ValidAtHashAndHF {
/// The block hash that was in the chain when this transaction was validated.
block_hash: [u8; 32],
/// The hf this transaction was validated against.
hf: HardFork,
},
/// The transaction is valid* if the block represented by this hash is in the blockchain _and_ this
/// given time lock is unlocked. The time lock here will represent the youngest used time based lock
/// (If the transaction uses any time based time locks). This is because time locks are not monotonic
/// so unlocked outputs could become re-locked.
///
/// *V1 transactions require checks on their ring-length even if this hash is in the blockchain.
ValidAtHashAndHFWithTimeBasedLock {
/// The block hash that was in the chain when this transaction was validated.
block_hash: [u8; 32],
/// The hf this transaction was validated against.
hf: HardFork,
/// The youngest used time based lock.
time_lock: Timelock,
},
}
impl CachedVerificationState {
/// Returns the block hash this is valid for if in state [`CachedVerificationState::ValidAtHashAndHF`] or [`CachedVerificationState::ValidAtHashAndHFWithTimeBasedLock`].
pub const fn verified_at_block_hash(&self) -> Option<[u8; 32]> {
match self {
Self::NotVerified => None,
Self::ValidAtHashAndHF { block_hash, .. }
| Self::ValidAtHashAndHFWithTimeBasedLock { block_hash, .. } => Some(*block_hash),
}
}
}
/// Data needed to verify a transaction.
#[derive(Debug)]
pub struct TransactionVerificationData {
/// The transaction we are verifying
pub tx: Transaction,
/// The [`TxVersion`] of this tx.
pub version: TxVersion,
/// The serialised transaction.
pub tx_blob: Vec<u8>,
/// The weight of the transaction.
pub tx_weight: usize,
/// The fee this transaction has paid.
pub fee: u64,
/// The hash of this transaction.
pub tx_hash: [u8; 32],
/// The verification state of this transaction.
pub cached_verification_state: Mutex<CachedVerificationState>,
}

View file

@ -7,6 +7,8 @@ use monero_serai::{
transaction::{Timelock, Transaction}, transaction::{Timelock, Transaction},
}; };
use crate::HardFork;
//---------------------------------------------------------------------------------------------------- ExtendedBlockHeader //---------------------------------------------------------------------------------------------------- ExtendedBlockHeader
/// Extended header data of a block. /// Extended header data of a block.
/// ///
@ -15,13 +17,11 @@ use monero_serai::{
pub struct ExtendedBlockHeader { pub struct ExtendedBlockHeader {
/// The block's major version. /// The block's major version.
/// ///
/// This can also be represented with `cuprate_consensus::HardFork`.
///
/// This is the same value as [`monero_serai::block::BlockHeader::hardfork_version`]. /// This is the same value as [`monero_serai::block::BlockHeader::hardfork_version`].
pub version: u8, pub version: HardFork,
/// The block's hard-fork vote. /// The block's hard-fork vote.
/// ///
/// This can also be represented with `cuprate_consensus::HardFork`. /// This can't be represented with [`HardFork`] as raw-votes can be out of the range of [`HardFork`]s.
/// ///
/// This is the same value as [`monero_serai::block::BlockHeader::hardfork_signal`]. /// This is the same value as [`monero_serai::block::BlockHeader::hardfork_signal`].
pub vote: u8, pub vote: u8,