diff --git a/consensus/src/transactions.rs b/consensus/src/transactions.rs index 5d12c330..46487fe6 100644 --- a/consensus/src/transactions.rs +++ b/consensus/src/transactions.rs @@ -12,11 +12,7 @@ use std::{ }; use futures::FutureExt; -use monero_serai::ringct::bulletproofs::Bulletproof; -use monero_serai::{ - ringct::RctType, - transaction::{Input, Timelock, Transaction}, -}; +use monero_serai::transaction::{Input, Timelock, Transaction}; use rayon::prelude::*; use tower::{Service, ServiceExt}; use tracing::instrument; @@ -38,6 +34,7 @@ use crate::{ }; pub mod contextual_data; +mod free; /// A struct representing the type of validation that needs to be completed for this transaction. #[derive(Debug, Copy, Clone, Eq, PartialEq)] @@ -104,52 +101,9 @@ impl TransactionVerificationData { 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 { - Transaction::V1 { .. } | Transaction::V2 { proofs: None, .. } => tx_blob.len(), - Transaction::V2 { - proofs: Some(proofs), - .. - } => match proofs.rct_type() { - RctType::AggregateMlsagBorromean | RctType::MlsagBorromean => tx_blob.len(), - RctType::MlsagBulletproofs - | RctType::MlsagBulletproofsCompactAmount - | RctType::ClsagBulletproof => { - tx_blob.len() - + Bulletproof::calculate_bp_clawback(false, tx.prefix().outputs.len()).0 - } - RctType::ClsagBulletproofPlus => { - tx_blob.len() - + Bulletproof::calculate_bp_clawback(true, tx.prefix().outputs.len()).0 - } - }, - }; + let tx_weight = free::tx_weight(&tx, &tx_blob); - let mut fee = 0_u64; - - match &tx { - Transaction::V1 { prefix, .. } => { - for input in &prefix.inputs { - if let Input::ToKey { amount, .. } = input { - fee = fee - .checked_add(amount.unwrap_or(0)) - .ok_or(TransactionError::InputsOverflow)?; - } - } - - for output in &prefix.outputs { - fee.checked_sub(output.amount.unwrap_or(0)) - .ok_or(TransactionError::OutputsTooHigh)?; - } - } - Transaction::V2 { proofs, .. } => { - fee = proofs - .as_ref() - .ok_or(TransactionError::TransactionVersionInvalid)? - .base - .fee; - } - }; + let fee = free::tx_fee(&tx)?; Ok(TransactionVerificationData { tx_hash, diff --git a/consensus/src/transactions/free.rs b/consensus/src/transactions/free.rs new file mode 100644 index 00000000..5ffd16e8 --- /dev/null +++ b/consensus/src/transactions/free.rs @@ -0,0 +1,64 @@ +use monero_serai::{ + ringct::{bulletproofs::Bulletproof, RctType}, + transaction::{Input, Transaction}, +}; + +use cuprate_consensus_rules::transactions::TransactionError; + +/// Calculates the weight of a [`Transaction`]. +/// +/// This is more efficient that [`Transaction::weight`] if you already have the transaction blob. +pub fn tx_weight(tx: &Transaction, tx_blob: &[u8]) -> usize { + // the tx weight is only different from the blobs length for bp(+) txs. + + match &tx { + Transaction::V1 { .. } | Transaction::V2 { proofs: None, .. } => tx_blob.len(), + Transaction::V2 { + proofs: Some(proofs), + .. + } => match proofs.rct_type() { + RctType::AggregateMlsagBorromean | RctType::MlsagBorromean => tx_blob.len(), + RctType::MlsagBulletproofs + | RctType::MlsagBulletproofsCompactAmount + | RctType::ClsagBulletproof => { + tx_blob.len() + + Bulletproof::calculate_bp_clawback(false, tx.prefix().outputs.len()).0 + } + RctType::ClsagBulletproofPlus => { + tx_blob.len() + + Bulletproof::calculate_bp_clawback(true, tx.prefix().outputs.len()).0 + } + }, + } +} + +/// Calculates the fee of the [`Transaction`]. +pub fn tx_fee(tx: &Transaction) -> Result { + let mut fee = 0_u64; + + match &tx { + Transaction::V1 { prefix, .. } => { + for input in &prefix.inputs { + if let Input::ToKey { amount, .. } = input { + fee = fee + .checked_add(amount.unwrap_or(0)) + .ok_or(TransactionError::InputsOverflow)?; + } + } + + for output in &prefix.outputs { + fee.checked_sub(output.amount.unwrap_or(0)) + .ok_or(TransactionError::OutputsTooHigh)?; + } + } + Transaction::V2 { proofs, .. } => { + fee = proofs + .as_ref() + .ok_or(TransactionError::TransactionVersionInvalid)? + .base + .fee; + } + }; + + Ok(fee) +} diff --git a/test-utils/src/data/free.rs b/test-utils/src/data/free.rs index 71a4dc51..ccf1bff7 100644 --- a/test-utils/src/data/free.rs +++ b/test-utils/src/data/free.rs @@ -8,11 +8,11 @@ //---------------------------------------------------------------------------------------------------- Import use std::sync::OnceLock; -use hex_literal::hex; -use monero_serai::{block::Block, transaction::Transaction}; - use cuprate_helper::map::combine_low_high_bits_to_u128; use cuprate_types::{VerifiedBlockInformation, VerifiedTransactionInformation}; +use hex_literal::hex; +use monero_serai::transaction::Input; +use monero_serai::{block::Block, transaction::Transaction}; use crate::data::constants::{ BLOCK_43BD1F, BLOCK_5ECB7E, BLOCK_F91043, TX_2180A8, TX_3BC7FF, TX_84D48D, TX_9E3F73, @@ -104,13 +104,41 @@ fn to_tx_verification_data(tx_blob: impl AsRef<[u8]>) -> VerifiedTransactionInfo VerifiedTransactionInformation { tx_weight: tx.weight(), // TODO: - fee: 0, + fee: tx_fee(&tx), tx_hash: tx.hash(), tx_blob, tx, } } +/// Calculates the fee of the [`Transaction`]. +/// +/// # Panics +/// This will panic if the inputs overflow or the transaction outputs too much. +/// +pub fn tx_fee(tx: &Transaction) -> u64 { + let mut fee = 0_u64; + + match &tx { + Transaction::V1 { prefix, .. } => { + for input in &prefix.inputs { + if let Input::ToKey { amount, .. } = input { + fee = fee.checked_add(amount.unwrap_or(0)).unwrap(); + } + } + + for output in &prefix.outputs { + fee.checked_sub(output.amount.unwrap_or(0)).unwrap(); + } + } + Transaction::V2 { proofs, .. } => { + fee = proofs.as_ref().unwrap().base.fee; + } + }; + + fee +} + //---------------------------------------------------------------------------------------------------- Blocks /// Generate a block accessor function with this signature: /// `fn() -> &'static VerifiedBlockInformation` @@ -256,7 +284,6 @@ macro_rules! transaction_verification_data_fn { #[doc = concat!("assert_eq!(tx.tx_blob, ", stringify!($tx_blob), ");")] #[doc = concat!("assert_eq!(tx.tx_weight, ", $weight, ");")] #[doc = concat!("assert_eq!(tx.tx_hash, hex!(\"", $hash, "\"));")] - // #[doc = "assert_eq!(tx.fee, tx.tx.rct_signatures.base.fee);"] /// ``` pub fn $fn_name() -> &'static VerifiedTransactionInformation { static TX: OnceLock = OnceLock::new(); diff --git a/test-utils/src/data/mod.rs b/test-utils/src/data/mod.rs index 49ea89aa..696c6865 100644 --- a/test-utils/src/data/mod.rs +++ b/test-utils/src/data/mod.rs @@ -32,4 +32,6 @@ pub use constants::{ }; mod free; -pub use free::{block_v16_tx0, block_v1_tx2, block_v9_tx3, tx_v1_sig0, tx_v1_sig2, tx_v2_rct3}; +pub use free::{ + block_v16_tx0, block_v1_tx2, block_v9_tx3, tx_fee, tx_v1_sig0, tx_v1_sig2, tx_v2_rct3, +}; diff --git a/test-utils/src/rpc/client.rs b/test-utils/src/rpc/client.rs index 09aa3738..fbe6fb9e 100644 --- a/test-utils/src/rpc/client.rs +++ b/test-utils/src/rpc/client.rs @@ -11,6 +11,8 @@ use monero_simple_request_rpc::SimpleRequestRpc; use cuprate_types::{VerifiedBlockInformation, VerifiedTransactionInformation}; +use crate::data::tx_fee; + //---------------------------------------------------------------------------------------------------- Constants /// The default URL used for Monero RPC connections. pub const LOCALHOST_RPC_URL: &str = "http://127.0.0.1:18081"; @@ -172,8 +174,7 @@ impl HttpRpcClient { tx_blob: tx.serialize(), tx_weight: tx.weight(), tx_hash, - // TODO: fix this. - fee: 0, + fee: tx_fee(&tx), tx, } }) diff --git a/types/Cargo.toml b/types/Cargo.toml index 496d1eea..99fa978b 100644 --- a/types/Cargo.toml +++ b/types/Cargo.toml @@ -21,7 +21,6 @@ cuprate-fixed-bytes = { path = "../net/fixed-bytes" } bytes = { workspace = true } curve25519-dalek = { workspace = true } monero-serai = { workspace = true } - serde = { workspace = true, features = ["derive"], optional = true } borsh = { workspace = true, optional = true }