From 730bc8fb426543acac527a3268c305871dd6c84a Mon Sep 17 00:00:00 2001 From: Boog900 <54e72d8a-345f-4599-bd90-c6b9bc7d0ec5@aleeas.com> Date: Mon, 8 Jan 2024 01:26:44 +0000 Subject: [PATCH] link transaction rules to monero-book --- consensus/rules/src/blocks.rs | 19 +- consensus/rules/src/transactions.rs | 201 ++++++++++++++---- .../rules/src/transactions/contextual_data.rs | 4 +- consensus/rules/src/transactions/ring_ct.rs | 30 +-- .../rules/src/transactions/ring_signatures.rs | 10 +- consensus/src/context/weight.rs | 19 +- consensus/src/transactions.rs | 17 +- 7 files changed, 209 insertions(+), 91 deletions(-) diff --git a/consensus/rules/src/blocks.rs b/consensus/rules/src/blocks.rs index 5e92346..ac1f1db 100644 --- a/consensus/rules/src/blocks.rs +++ b/consensus/rules/src/blocks.rs @@ -15,6 +15,10 @@ const BLOCK_FUTURE_TIME_LIMIT: u64 = 60 * 60 * 2; const BLOCK_202612_POW_HASH: [u8; 32] = hex_literal::hex!("84f64766475d51837ac9efbef1926486e58563c95a19fef4aec3254f03000000"); +pub const PENALTY_FREE_ZONE_1: usize = 20000; +pub const PENALTY_FREE_ZONE_2: usize = 60000; +pub const PENALTY_FREE_ZONE_5: usize = 300000; + const RX_SEEDHASH_EPOCH_BLOCKS: u64 = 2048; const RX_SEEDHASH_EPOCH_LAG: u64 = 64; @@ -109,6 +113,19 @@ pub fn check_block_pow(hash: &[u8; 32], difficulty: u128) -> Result<(), BlockErr } } +/// Returns the penalty free zone +/// +/// https://cuprate.github.io/monero-book/consensus_rules/blocks/weight_limit.html#penalty-free-zone +pub fn penalty_free_zone(hf: &HardFork) -> usize { + if hf == &HardFork::V1 { + PENALTY_FREE_ZONE_1 + } else if hf >= &HardFork::V2 && hf < &HardFork::V5 { + PENALTY_FREE_ZONE_2 + } else { + PENALTY_FREE_ZONE_5 + } +} + /// Sanity check on the block blob size. /// /// ref: https://monero-book.cuprate.org/consensus_rules/blocks.html#block-weight-and-size @@ -177,7 +194,7 @@ fn check_timestamp(block: &Block, median_timestamp: u64) -> Result<(), BlockErro /// ref: https://monero-book.cuprate.org/consensus_rules/blocks.html#no-duplicate-transactions fn check_txs_unique(txs: &[[u8; 32]]) -> Result<(), BlockError> { txs.windows(2).try_for_each(|window| { - if window[0] != window[1] { + if window[0] == window[1] { Err(BlockError::DuplicateTransaction)?; } Ok(()) diff --git a/consensus/rules/src/transactions.rs b/consensus/rules/src/transactions.rs index 37a5648..138893e 100644 --- a/consensus/rules/src/transactions.rs +++ b/consensus/rules/src/transactions.rs @@ -1,9 +1,12 @@ +use monero_serai::ringct::RctType; use std::{cmp::Ordering, collections::HashSet, sync::Arc}; use monero_serai::transaction::{Input, Output, Timelock, Transaction}; use multiexp::BatchVerifier; -use crate::{check_point_canonically_encoded, is_decomposed_amount, HardFork}; +use crate::{ + blocks::penalty_free_zone, check_point_canonically_encoded, is_decomposed_amount, HardFork, +}; mod contextual_data; mod ring_ct; @@ -12,10 +15,15 @@ mod ring_signatures; pub use contextual_data::*; pub use ring_ct::RingCTError; +const MAX_BULLETPROOFS_OUTPUTS: usize = 16; +const MAX_TX_BLOB_SIZE: usize = 1_000_000; + #[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)] pub enum TransactionError { #[error("The transactions version is incorrect.")] TransactionVersionInvalid, + #[error("The transactions is too big.")] + TooBig, //-------------------------------------------------------- OUTPUTS #[error("Output is not a valid point.")] OutputNotValidPoint, @@ -23,12 +31,16 @@ pub enum TransactionError { OutputTypeInvalid, #[error("The transaction is v1 with a 0 amount output.")] ZeroOutputForV1, + #[error("The transaction is v2 with a non 0 amount output.")] + NonZeroOutputForV2, #[error("The transaction has an output which is not decomposed.")] AmountNotDecomposed, #[error("The transactions outputs overflow.")] OutputsOverflow, #[error("The transactions outputs too much.")] OutputsTooHigh, + #[error("The transactions has too many outputs.")] + InvalidNumberOfOutputs, //-------------------------------------------------------- INPUTS #[error("One or more inputs don't have the expected number of decoys.")] InputDoesNotHaveExpectedNumbDecoys, @@ -57,16 +69,25 @@ pub enum TransactionError { RingSignatureIncorrect, //-------------------------------------------------------- RingCT #[error("RingCT Error: {0}.")] - RingCTError(#[from] ring_ct::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: u64) -> Option { Some(match version { 1 => TxVersion::RingSignatures, @@ -80,7 +101,7 @@ impl TxVersion { /// Checks the output keys are canonically encoded points. /// -/// https://cuprate.github.io/monero-book/consensus_rules/transactions.html#output-keys-canonical +/// https://monero-book.cuprate.org/consensus_rules/transactions/outputs.html#output-keys-canonical fn check_output_keys(outputs: &[Output]) -> Result<(), TransactionError> { for out in outputs { if !check_point_canonically_encoded(&out.key) { @@ -91,12 +112,12 @@ fn check_output_keys(outputs: &[Output]) -> Result<(), TransactionError> { Ok(()) } -/// Checks the output types are allowed. +/// Checks the output types are allowed for the given hard-fork. /// /// This is also used during miner-tx verification. /// -/// https://cuprate.github.io/monero-book/consensus_rules/transactions.html#output-type -/// https://cuprate.github.io/monero-book/consensus_rules/blocks/miner_tx.html#output-type +/// https://monero-book.cuprate.org/consensus_rules/transactions/outputs.html#output-type +/// https://monero-book.cuprate.org/consensus_rules/blocks/miner_tx.html#output-type pub(crate) fn check_output_types( outputs: &[Output], hf: &HardFork, @@ -120,9 +141,9 @@ pub(crate) fn check_output_types( Ok(()) } -/// Checks the outputs amount for version 1 txs. +/// Checks the individual outputs amount for version 1 txs. /// -/// https://cuprate.github.io/monero-book/consensus_rules/transactions/pre_rct.html#output-amount +/// ref: https://monero-book.cuprate.org/consensus_rules/transactions/outputs.html#output-amount fn check_output_amount_v1(amount: u64, hf: &HardFork) -> Result<(), TransactionError> { if amount == 0 { return Err(TransactionError::ZeroOutputForV1); @@ -135,20 +156,35 @@ fn check_output_amount_v1(amount: u64, hf: &HardFork) -> Result<(), TransactionE Ok(()) } +/// Checks the individual outputs amount for version 2 txs. +/// +/// ref: https://monero-book.cuprate.org/consensus_rules/transactions/outputs.html#output-amount +fn check_output_amount_v2(amount: u64) -> Result<(), TransactionError> { + if amount == 0 { + Ok(()) + } else { + Err(TransactionError::NonZeroOutputForV2) + } +} + /// Sums the outputs, checking for overflow and other consensus rules. /// -/// Should only be used on v1 transactions. -/// -/// https://cuprate.github.io/monero-book/consensus_rules/transactions/pre_rct.html#inputs-and-outputs-must-not-overflow -/// https://cuprate.github.io/monero-book/consensus_rules/transactions/pre_rct.html#output-amount -fn sum_outputs_v1(outputs: &[Output], hf: &HardFork) -> Result { +/// ref: https://monero-book.cuprate.org/consensus_rules/transactions/outputs.html#output-amount +/// && https://monero-book.cuprate.org/consensus_rules/transactions/outputs.html#outputs-must-not-overflow +fn sum_outputs( + outputs: &[Output], + hf: &HardFork, + tx_version: &TxVersion, +) -> Result { let mut sum: u64 = 0; for out in outputs { let raw_amount = out.amount.unwrap_or(0); - check_output_amount_v1(raw_amount, hf)?; - + match tx_version { + TxVersion::RingSignatures => check_output_amount_v1(raw_amount, hf)?, + TxVersion::RingCT => check_output_amount_v2(raw_amount)?, + } sum = sum .checked_add(raw_amount) .ok_or(TransactionError::OutputsOverflow)?; @@ -157,26 +193,60 @@ fn sum_outputs_v1(outputs: &[Output], hf: &HardFork) -> Result Result<(), TransactionError> { + if tx_version == &TxVersion::RingSignatures { + return Ok(()); + } + + if hf >= &HardFork::V12 && outputs < 2 { + return Err(TransactionError::InvalidNumberOfOutputs); + } + + match rct_type { + RctType::Bulletproofs | RctType::BulletproofsCompactAmount | RctType::BulletproofsPlus => { + if outputs <= MAX_BULLETPROOFS_OUTPUTS { + Ok(()) + } else { + Err(TransactionError::InvalidNumberOfOutputs) + } + } + _ => Ok(()), + } +} + /// Checks the outputs against all output consensus rules, returning the sum of the output amounts. +/// +/// ref: https://monero-book.cuprate.org/consensus_rules/transactions/outputs.html +/// && https://monero-book.cuprate.org/consensus_rules/transactions/ring_ct/bulletproofs.html#max-outputs +/// && https://monero-book.cuprate.org/consensus_rules/transactions/ring_ct/bulletproofs+.html#max-outputs fn check_outputs_semantics( outputs: &[Output], hf: &HardFork, tx_version: &TxVersion, + rct_type: &RctType, ) -> Result { check_output_types(outputs, hf)?; check_output_keys(outputs)?; + check_number_of_outputs(outputs.len(), hf, tx_version, rct_type)?; - match tx_version { - TxVersion::RingSignatures => sum_outputs_v1(outputs, hf), - TxVersion::RingCT => Ok(0), // RCT outputs are checked to be zero in RCT checks. - } + sum_outputs(outputs, hf, tx_version) } //----------------------------------------------------------------------------------------------------------- TIME LOCKS /// Checks if an outputs unlock time has passed. /// -/// https://cuprate.github.io/monero-book/consensus_rules/transactions/unlock_time.html#unlock-time +/// https://monero-book.cuprate.org/consensus_rules/transactions/unlock_time.html fn output_unlocked( time_lock: &Timelock, current_chain_height: u64, @@ -194,18 +264,17 @@ fn output_unlocked( } } -/// Returns if a locked output, which uses a block height, can be spend. +/// Returns if a locked output, which uses a block height, can be spent. /// -/// https://cuprate.github.io/monero-book/consensus_rules/transactions/unlock_time.html#block-height +/// ref: https://monero-book.cuprate.org/consensus_rules/transactions/unlock_time.html#block-height fn check_block_time_lock(unlock_height: u64, current_chain_height: u64) -> bool { // current_chain_height = 1 + top height unlock_height <= current_chain_height } -/// /// /// Returns if a locked output, which uses a block height, can be spend. /// -/// https://cuprate.github.io/monero-book/consensus_rules/transactions/unlock_time.html#timestamp +/// ref: https://monero-book.cuprate.org/consensus_rules/transactions/unlock_time.html#timestamp fn check_timestamp_time_lock( unlock_timestamp: u64, current_time_lock_timestamp: u64, @@ -216,9 +285,10 @@ fn check_timestamp_time_lock( /// Checks all the time locks are unlocked. /// -/// `current_time_lock_timestamp` must be: https://cuprate.github.io/monero-book/consensus_rules/transactions/unlock_time.html#getting-the-current-time +/// `current_time_lock_timestamp` must be: https://monero-book.cuprate.org/consensus_rules/transactions/unlock_time.html#getting-the-current-time /// -/// https://cuprate.github.io/monero-book/consensus_rules/transactions/unlock_time.html#unlock-time +/// https://monero-book.cuprate.org/consensus_rules/transactions/unlock_time.html +/// https://monero-book.cuprate.org/consensus_rules/transactions/inputs.html#the-output-must-not-be-locked fn check_all_time_locks( time_locks: &[Timelock], current_chain_height: u64, @@ -243,8 +313,8 @@ fn check_all_time_locks( /// Checks the decoys are allowed. /// -/// https://cuprate.github.io/monero-book/consensus_rules/transactions.html#minimum-decoys -/// https://cuprate.github.io/monero-book/consensus_rules/transactions.html#equal-number-of-decoys +/// ref: https://monero-book.cuprate.org/consensus_rules/transactions/inputs.html#minimum-decoys +/// && https://monero-book.cuprate.org/consensus_rules/transactions/inputs.html#equal-number-of-decoys fn check_decoy_info(decoy_info: &DecoyInfo, hf: &HardFork) -> Result<(), TransactionError> { if hf == &HardFork::V15 { // Hard-fork 15 allows both v14 and v16 rules @@ -276,13 +346,13 @@ fn check_decoy_info(decoy_info: &DecoyInfo, hf: &HardFork) -> Result<(), Transac Ok(()) } -/// Checks the inputs key images for torsion and for duplicates in the transaction. +/// Checks the inputs key images for torsion and for duplicates in the spent_kis list. /// /// The `spent_kis` parameter is not meant to be a complete list of key images, just a list of related transactions /// key images, for example transactions in a block. The chain will be checked for duplicates later. /// -/// https://cuprate.github.io/monero-book/consensus_rules/transactions.html#unique-key-image -/// https://cuprate.github.io/monero-book/consensus_rules/transactions.html#torsion-free-key-image +/// ref: https://monero-book.cuprate.org/consensus_rules/transactions/inputs.html#unique-key-image +/// && https://monero-book.cuprate.org/consensus_rules/transactions/inputs.html#torsion-free-key-image fn check_key_images( input: &Input, spent_kis: &mut HashSet<[u8; 32]>, @@ -305,7 +375,7 @@ fn check_key_images( /// Checks that the input is of type [`Input::ToKey`] aka txin_to_key. /// -/// https://cuprate.github.io/monero-book/consensus_rules/transactions.html#input-type +/// ref: https://monero-book.cuprate.org/consensus_rules/transactions/inputs.html#input-type fn check_input_type(input: &Input) -> Result<(), TransactionError> { match input { Input::ToKey { .. } => Ok(()), @@ -315,7 +385,7 @@ fn check_input_type(input: &Input) -> Result<(), TransactionError> { /// Checks that the input has decoys. /// -/// https://cuprate.github.io/monero-book/consensus_rules/transactions.html#inputs-must-have-decoys +/// ref: https://monero-book.cuprate.org/consensus_rules/transactions/inputs.html#no-empty-decoys fn check_input_has_decoys(input: &Input) -> Result<(), TransactionError> { match input { Input::ToKey { key_offsets, .. } => { @@ -331,7 +401,7 @@ fn check_input_has_decoys(input: &Input) -> Result<(), TransactionError> { /// Checks that the ring members for the input are unique after hard-fork 6. /// -/// https://cuprate.github.io/monero-book/consensus_rules/transactions.html#unique-ring-members +/// ref: https://monero-book.cuprate.org/consensus_rules/transactions/inputs.html#unique-ring-members fn check_ring_members_unique(input: &Input, hf: &HardFork) -> Result<(), TransactionError> { if hf >= &HardFork::V6 { match input { @@ -351,7 +421,7 @@ fn check_ring_members_unique(input: &Input, hf: &HardFork) -> Result<(), Transac /// Checks that from hf 7 the inputs are sorted by key image. /// -/// https://cuprate.github.io/monero-book/consensus_rules/transactions.html#sorted-inputs +/// ref: https://monero-book.cuprate.org/consensus_rules/transactions/inputs.html#sorted-inputs fn check_inputs_sorted(inputs: &[Input], hf: &HardFork) -> Result<(), TransactionError> { let get_ki = |inp: &Input| match inp { Input::ToKey { key_image, .. } => Ok(key_image.compress().to_bytes()), @@ -373,7 +443,7 @@ fn check_inputs_sorted(inputs: &[Input], hf: &HardFork) -> Result<(), Transactio /// Checks the youngest output is at least 10 blocks old. /// -/// https://cuprate.github.io/monero-book/consensus_rules/transactions.html#10-block-lock +/// ref: https://monero-book.cuprate.org/consensus_rules/transactions/inputs.html#10-block-lock fn check_10_block_lock( youngest_used_out_height: u64, current_chain_height: u64, @@ -392,7 +462,7 @@ fn check_10_block_lock( /// Sums the inputs checking for overflow. /// -/// https://cuprate.github.io/monero-book/consensus_rules/transactions/pre_rct.html#inputs-and-outputs-must-not-overflow +/// ref: https://monero-book.cuprate.org/consensus_rules/transactions/inputs.html#inputs-must-not-overflow fn sum_inputs_check_overflow(inputs: &[Input]) -> Result { let mut sum: u64 = 0; for inp in inputs { @@ -409,7 +479,13 @@ fn sum_inputs_check_overflow(inputs: &[Input]) -> Result Ok(sum) } +/// Checks the inputs semantically validity, returning the sum of the inputs. +/// +/// Semantic rules are rules that don't require blockchain context, the hard-fork does not require blockchain context as: +/// - The tx-pool will use the current hard-fork +/// - When syncing the hard-fork is in the block header. fn check_inputs_semantics(inputs: &[Input], hf: &HardFork) -> Result { + // https://monero-book.cuprate.org/consensus_rules/transactions/inputs.html#no-empty-inputs if inputs.is_empty() { return Err(TransactionError::NoInputs); } @@ -426,6 +502,14 @@ fn check_inputs_semantics(inputs: &[Input], hf: &HardFork) -> Result, version: &TxVersion, @@ -468,7 +554,6 @@ fn check_tx_version( return Err(TransactionError::TransactionVersionInvalid); } - // TODO: Doc is wrong here let min = min_tx_version(hf); if version < &min && decoy_info.not_mixable == 0 { return Err(TransactionError::TransactionVersionInvalid); @@ -483,6 +568,7 @@ fn check_tx_version( Ok(()) } +/// Returns the default maximum tx version for the given hard-fork. fn max_tx_version(hf: &HardFork) -> TxVersion { if hf <= &HardFork::V3 { TxVersion::RingSignatures @@ -491,6 +577,7 @@ fn max_tx_version(hf: &HardFork) -> TxVersion { } } +/// Returns the default minimum tx version for the given hard-fork. fn min_tx_version(hf: &HardFork) -> TxVersion { if hf >= &HardFork::V6 { TxVersion::RingCT @@ -499,16 +586,42 @@ fn min_tx_version(hf: &HardFork) -> TxVersion { } } +fn transaction_weight_limit(hf: &HardFork) -> usize { + penalty_free_zone(hf) / 2 - 600 +} + +/// Checks the transaction is semantically valid. +/// +/// Semantic rules are rules that don't require blockchain context, the hard-fork does not require blockchain context as: +/// - The tx-pool will use the current hard-fork +/// - When syncing the hard-fork is in the block header. +/// +/// To fully verify a transaction this must be accompanied with [`check_transaction_contextual`] +/// pub fn check_transaction_semantic( tx: &Transaction, + tx_blob_size: usize, + tx_weight: usize, tx_hash: &[u8; 32], hf: &HardFork, verifier: &mut BatchVerifier<(), dalek_ff_group::EdwardsPoint>, ) -> Result { + // https://monero-book.cuprate.org/consensus_rules/transactions.html#transaction-size + if tx_blob_size > MAX_TX_BLOB_SIZE + || (hf >= &HardFork::V8 && tx_weight > transaction_weight_limit(hf)) + { + Err(TransactionError::TooBig)?; + } + let tx_version = TxVersion::from_raw(tx.prefix.version) .ok_or(TransactionError::TransactionVersionInvalid)?; - let outputs_sum = check_outputs_semantics(&tx.prefix.outputs, hf, &tx_version)?; + let outputs_sum = check_outputs_semantics( + &tx.prefix.outputs, + hf, + &tx_version, + &tx.rct_signatures.rct_type(), + )?; let inputs_sum = check_inputs_semantics(&tx.prefix.inputs, hf)?; let fee = match tx_version { @@ -528,6 +641,12 @@ pub fn check_transaction_semantic( Ok(fee) } +/// Checks the transaction is contextually valid. +/// +/// To fully verify a transaction this must be accompanied with [`check_transaction_semantic`] +/// +/// `current_time_lock_timestamp` must be: https://monero-book.cuprate.org/consensus_rules/transactions/unlock_time.html#getting-the-current-time + pub fn check_transaction_contextual( tx: &Transaction, tx_ring_members_info: &TxRingMembersInfo, @@ -556,7 +675,7 @@ pub fn check_transaction_contextual( )?; match tx_version { - TxVersion::RingSignatures => ring_signatures::verify_inputs_signatures( + TxVersion::RingSignatures => ring_signatures::check_input_signatures( &tx.prefix.inputs, &tx.signatures, &tx_ring_members_info.rings, diff --git a/consensus/rules/src/transactions/contextual_data.rs b/consensus/rules/src/transactions/contextual_data.rs index 2d50a19..afba03a 100644 --- a/consensus/rules/src/transactions/contextual_data.rs +++ b/consensus/rules/src/transactions/contextual_data.rs @@ -135,7 +135,7 @@ impl Rings { #[derive(Debug)] pub struct TxRingMembersInfo { pub rings: Rings, - /// Information on the structure of the decoys, will be [`None`] for txs before [`HardFork::V1`] + /// Information on the structure of the decoys, must be [`None`] for txs before [`HardFork::V1`] pub decoy_info: Option, pub youngest_used_out_height: u64, pub time_locked_outs: Vec, @@ -277,7 +277,7 @@ impl DecoyInfo { /// Returns the default minimum amount of decoys for a hard-fork. /// **There are exceptions to this always being the minimum decoys** /// -/// https://cuprate.github.io/monero-book/consensus_rules/transactions/decoys.html#minimum-amount-of-decoys +/// ref: https://monero-book.cuprate.org/consensus_rules/transactions/inputs.html#default-minimum-decoys pub(crate) fn minimum_decoys(hf: &HardFork) -> usize { use HardFork as HF; match hf { diff --git a/consensus/rules/src/transactions/ring_ct.rs b/consensus/rules/src/transactions/ring_ct.rs index 56d2686..ea3606a 100644 --- a/consensus/rules/src/transactions/ring_ct.rs +++ b/consensus/rules/src/transactions/ring_ct.rs @@ -6,7 +6,7 @@ use monero_serai::{ mlsag::{AggregateRingMatrixBuilder, MlsagError, RingMatrix}, RctPrunable, RctSignatures, RctType, }, - transaction::{Input, Output, Transaction}, + transaction::{Input, Transaction}, H, }; use multiexp::BatchVerifier; @@ -27,8 +27,6 @@ const GRANDFATHERED_TRANSACTIONS: [[u8; 32]; 2] = [ pub enum RingCTError { #[error("The RingCT type used is not allowed.")] TypeNotAllowed, - #[error("One or more of the outputs do not have a zero amount.")] - OutputNotZero, #[error("RingCT simple: sum pseudo-outs does not equal outputs.")] SimpleAmountDoNotBalance, #[error("The borromean range proof is invalid.")] @@ -61,19 +59,6 @@ fn check_rct_type(ty: &RctType, hf: HardFork, tx_hash: &[u8; 32]) -> Result<(), } } -/// Checks all the outputs have a zero amount. -/// -/// https://monero-book.cuprate.org/consensus_rules/ring_ct.html#output-amount -fn check_output_amount(outputs: &[Output]) -> Result<(), RingCTError> { - outputs.iter().try_for_each(|out| { - if out.amount.is_none() { - Ok(()) - } else { - Err(RingCTError::OutputNotZero) - } - }) -} - /// Checks that the pseudo-outs sum to the same point as the output commitments. /// /// https://monero-book.cuprate.org/consensus_rules/ring_ct.html#pseudo-outs-outpks-balance @@ -133,28 +118,29 @@ fn check_output_range_proofs( } } -pub fn ring_ct_semantic_checks( +pub(crate) fn ring_ct_semantic_checks( tx: &Transaction, tx_hash: &[u8; 32], verifier: &mut BatchVerifier<(), dalek_ff_group::EdwardsPoint>, hf: &HardFork, ) -> Result<(), RingCTError> { - check_output_amount(&tx.prefix.outputs)?; - check_rct_type(&tx.rct_signatures.rct_type(), *hf, tx_hash)?; + let rct_type = tx.rct_signatures.rct_type(); + + check_rct_type(&rct_type, *hf, tx_hash)?; check_output_range_proofs(&tx.rct_signatures, verifier)?; - if tx.rct_signatures.rct_type() != RctType::MlsagAggregate { + if rct_type != RctType::MlsagAggregate { simple_type_balances(&tx.rct_signatures)?; } Ok(()) } -/// Check the input signatures, MLSAG, CLSAG. +/// Check the input signatures: MLSAG, CLSAG. /// /// https://monero-book.cuprate.org/consensus_rules/ring_ct/mlsag.html /// https://monero-book.cuprate.org/consensus_rules/ring_ct/clsag.html -pub fn check_input_signatures( +pub(crate) fn check_input_signatures( msg: &[u8; 32], inputs: &[Input], rct_sig: &RctSignatures, diff --git a/consensus/rules/src/transactions/ring_signatures.rs b/consensus/rules/src/transactions/ring_signatures.rs index f0e4a34..e4ec652 100644 --- a/consensus/rules/src/transactions/ring_signatures.rs +++ b/consensus/rules/src/transactions/ring_signatures.rs @@ -1,9 +1,9 @@ //! Version 1 ring signature verification. //! //! Some checks have to be done at deserialization or with data we don't have so we can't do them here, those checks are: -//! https://cuprate.github.io/monero-book/consensus_rules/transactions/pre_rct.html#signatures-must-be-canonical +//! https://monero-book.cuprate.org/consensus_rules/transactions/ring_signatures.html#signatures-must-be-canonical //! this happens at deserialization in monero-serai. -//! https://cuprate.github.io/monero-book/consensus_rules/transactions/pre_rct.html#amount-of-signatures-in-a-ring +//! https://monero-book.cuprate.org/consensus_rules/transactions/ring_signatures.html#amount-of-signatures-in-a-ring //! and this happens during ring signature verification in monero-serai. //! use monero_serai::{ring_signatures::RingSignature, transaction::Input}; @@ -16,9 +16,8 @@ use crate::try_par_iter; /// Verifies the ring signature. /// -/// https://cuprate.github.io/monero-book/consensus_rules/transactions/pre_rct.html#the-ring-signature-must-be-valid -/// https://cuprate.github.io/monero-book/consensus_rules/transactions/pre_rct.html#amount-of-ring-signatures -pub fn verify_inputs_signatures( +/// ref: https://monero-book.cuprate.org/consensus_rules/transactions/ring_signatures.html +pub fn check_input_signatures( inputs: &[Input], signatures: &[RingSignature], rings: &Rings, @@ -26,6 +25,7 @@ pub fn verify_inputs_signatures( ) -> Result<(), TransactionError> { match rings { Rings::Legacy(rings) => { + // https://monero-book.cuprate.org/consensus_rules/transactions/ring_signatures.html#amount-of-ring-signatures // rings.len() != inputs.len() can't happen but check any way. if signatures.len() != inputs.len() || rings.len() != inputs.len() { return Err(TransactionError::RingSignatureIncorrect); diff --git a/consensus/src/context/weight.rs b/consensus/src/context/weight.rs index c8c0d5e..1545fa3 100644 --- a/consensus/src/context/weight.rs +++ b/consensus/src/context/weight.rs @@ -16,6 +16,8 @@ use rayon::prelude::*; use tower::ServiceExt; use tracing::instrument; +use monero_consensus::blocks::{penalty_free_zone, PENALTY_FREE_ZONE_5}; + use crate::{ helper::{median, rayon_spawn_async}, Database, DatabaseRequest, DatabaseResponse, ExtendedConsensusError, HardFork, @@ -24,26 +26,9 @@ use crate::{ #[cfg(test)] pub(super) mod tests; -const PENALTY_FREE_ZONE_1: usize = 20000; -const PENALTY_FREE_ZONE_2: usize = 60000; -const PENALTY_FREE_ZONE_5: usize = 300000; - const SHORT_TERM_WINDOW: u64 = 100; const LONG_TERM_WINDOW: u64 = 100000; -/// Returns the penalty free zone -/// -/// https://cuprate.github.io/monero-book/consensus_rules/blocks/weight_limit.html#penalty-free-zone -pub fn penalty_free_zone(hf: &HardFork) -> usize { - if hf == &HardFork::V1 { - PENALTY_FREE_ZONE_1 - } else if hf >= &HardFork::V2 && hf < &HardFork::V5 { - PENALTY_FREE_ZONE_2 - } else { - PENALTY_FREE_ZONE_5 - } -} - /// Configuration for the block weight cache. /// #[derive(Debug, Clone)] diff --git a/consensus/src/transactions.rs b/consensus/src/transactions.rs index d31d047..8c01919 100644 --- a/consensus/src/transactions.rs +++ b/consensus/src/transactions.rs @@ -8,6 +8,7 @@ use std::{ }; use futures::FutureExt; +use monero_serai::ringct::RctType; use monero_serai::transaction::Transaction; use rayon::prelude::*; use tower::{Service, ServiceExt}; @@ -50,16 +51,26 @@ impl TransactionVerificationData { verifier: Arc, ) -> Result { let tx_hash = tx.hash(); + let tx_blob = tx.serialize(); + + // the tx weight is only different from the blobs length for bp(+) txs. + let tx_weight = match tx.rct_signatures.rct_type() { + RctType::Bulletproofs + | RctType::BulletproofsCompactAmount + | RctType::Clsag + | RctType::BulletproofsPlus => tx.weight(), + _ => tx_blob.len(), + }; let fee = verifier.queue_statement(|verifier| { - check_transaction_semantic(&tx, &tx_hash, hf, verifier) + check_transaction_semantic(&tx, tx_blob.len(), tx_weight, &tx_hash, hf, verifier) .map_err(ConsensusError::Transaction) })?; Ok(TransactionVerificationData { tx_hash, - tx_blob: tx.serialize(), - tx_weight: tx.weight(), + tx_blob, + tx_weight, fee, rings_member_info: std::sync::Mutex::new(None), version: TxVersion::from_raw(tx.prefix.version)