mirror of
https://github.com/Cuprate/cuprate.git
synced 2024-11-16 15:58:17 +00:00
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:
parent
fafa20c20f
commit
be2f3f2672
23 changed files with 381 additions and 288 deletions
4
Cargo.lock
generated
4
Cargo.lock
generated
|
@ -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]]
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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"] }
|
||||||
|
|
|
@ -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)?;
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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)]
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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(
|
||||||
|
|
|
@ -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,
|
||||||
};
|
};
|
||||||
|
|
|
@ -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<_, _>, _>>()?;
|
||||||
|
|
|
@ -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`].
|
||||||
///
|
///
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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(),
|
||||||
|
|
|
@ -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 {
|
// time-based lock used.
|
||||||
CachedVerificationState::ValidAtHashAndHFWithTimeBasedLock(
|
CachedVerificationState::ValidAtHashAndHFWithTimeBasedLock {
|
||||||
top_hash,
|
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,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -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`].
|
||||||
///
|
///
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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]
|
|
@ -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
131
types/src/hard_fork.rs
Normal 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,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
||||||
|
|
94
types/src/transaction_verification_data.rs
Normal file
94
types/src/transaction_verification_data.rs
Normal 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>,
|
||||||
|
}
|
|
@ -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,
|
||||||
|
|
Loading…
Reference in a new issue