cuprate_consensus/transactions/
free.rs

1use monero_serai::{
2    ringct::{bulletproofs::Bulletproof, RctType},
3    transaction::{Input, Transaction},
4};
5
6use cuprate_consensus_rules::{transactions::TransactionError, ConsensusError};
7use cuprate_types::{CachedVerificationState, TransactionVerificationData, TxVersion};
8
9/// Creates a new [`TransactionVerificationData`] from a [`Transaction`].
10///
11/// # Errors
12///
13/// This function will return [`Err`] if the transaction is malformed, although returning [`Ok`] does
14/// not necessarily mean the tx is correctly formed.
15pub fn new_tx_verification_data(
16    tx: Transaction,
17) -> Result<TransactionVerificationData, ConsensusError> {
18    let tx_hash = tx.hash();
19    let tx_blob = tx.serialize();
20
21    let tx_weight = tx_weight(&tx, &tx_blob);
22
23    let fee = tx_fee(&tx)?;
24
25    Ok(TransactionVerificationData {
26        tx_hash,
27        version: TxVersion::from_raw(tx.version())
28            .ok_or(TransactionError::TransactionVersionInvalid)?,
29        tx_blob,
30        tx_weight,
31        fee,
32        cached_verification_state: CachedVerificationState::NotVerified,
33        tx,
34    })
35}
36
37/// Calculates the weight of a [`Transaction`].
38///
39/// This is more efficient that [`Transaction::weight`] if you already have the transaction blob.
40pub(crate) fn tx_weight(tx: &Transaction, tx_blob: &[u8]) -> usize {
41    // the tx weight is only different from the blobs length for bp(+) txs.
42
43    match &tx {
44        Transaction::V1 { .. } | Transaction::V2 { proofs: None, .. } => tx_blob.len(),
45        Transaction::V2 {
46            proofs: Some(proofs),
47            ..
48        } => match proofs.rct_type() {
49            RctType::AggregateMlsagBorromean | RctType::MlsagBorromean => tx_blob.len(),
50            RctType::MlsagBulletproofs
51            | RctType::MlsagBulletproofsCompactAmount
52            | RctType::ClsagBulletproof => {
53                tx_blob.len()
54                    + Bulletproof::calculate_bp_clawback(false, tx.prefix().outputs.len()).0
55            }
56            RctType::ClsagBulletproofPlus => {
57                tx_blob.len()
58                    + Bulletproof::calculate_bp_clawback(true, tx.prefix().outputs.len()).0
59            }
60        },
61    }
62}
63
64/// Calculates the fee of the [`Transaction`].
65pub(crate) fn tx_fee(tx: &Transaction) -> Result<u64, TransactionError> {
66    let mut fee = 0_u64;
67
68    match &tx {
69        Transaction::V1 { prefix, .. } => {
70            for input in &prefix.inputs {
71                if let Input::ToKey { amount, .. } = input {
72                    fee = fee
73                        .checked_add(amount.unwrap_or(0))
74                        .ok_or(TransactionError::InputsOverflow)?;
75                }
76            }
77
78            for output in &prefix.outputs {
79                fee = fee
80                    .checked_sub(output.amount.unwrap_or(0))
81                    .ok_or(TransactionError::OutputsTooHigh)?;
82            }
83        }
84        Transaction::V2 { proofs, .. } => {
85            fee = proofs
86                .as_ref()
87                .ok_or(TransactionError::TransactionVersionInvalid)?
88                .base
89                .fee;
90        }
91    };
92
93    Ok(fee)
94}