fedimint_server/consensus/
transaction.rs

1use fedimint_core::db::DatabaseTransaction;
2use fedimint_core::module::registry::ServerModuleRegistry;
3use fedimint_core::module::TransactionItemAmount;
4use fedimint_core::transaction::{Transaction, TransactionError, TRANSACTION_OVERFLOW_ERROR};
5use fedimint_core::{Amount, OutPoint};
6use rayon::iter::{IntoParallelIterator, ParallelIterator};
7
8use crate::metrics::{CONSENSUS_TX_PROCESSED_INPUTS, CONSENSUS_TX_PROCESSED_OUTPUTS};
9
10pub async fn process_transaction_with_dbtx(
11    modules: ServerModuleRegistry,
12    dbtx: &mut DatabaseTransaction<'_>,
13    transaction: &Transaction,
14) -> Result<(), TransactionError> {
15    let in_count = transaction.inputs.len();
16    let out_count = transaction.outputs.len();
17
18    dbtx.on_commit(move || {
19        CONSENSUS_TX_PROCESSED_INPUTS.observe(in_count as f64);
20        CONSENSUS_TX_PROCESSED_OUTPUTS.observe(out_count as f64);
21    });
22
23    // We can not return the error here as errors are not returned in a specified
24    // order and the client still expects consensus on the error. Since the
25    // error is not extensible at the moment we need to incorrectly return the
26    // InvalidWitnessLength variant.
27    transaction
28        .inputs
29        .clone()
30        .into_par_iter()
31        .try_for_each(|input| {
32            modules
33                .get_expect(input.module_instance_id())
34                .verify_input(&input)
35        })
36        .map_err(|_| TransactionError::InvalidWitnessLength)?;
37
38    let mut funding_verifier = FundingVerifier::default();
39    let mut public_keys = Vec::new();
40
41    for input in &transaction.inputs {
42        let meta = modules
43            .get_expect(input.module_instance_id())
44            .process_input(
45                &mut dbtx
46                    .to_ref_with_prefix_module_id(input.module_instance_id())
47                    .0,
48                input,
49            )
50            .await
51            .map_err(TransactionError::Input)?;
52
53        funding_verifier.add_input(meta.amount)?;
54        public_keys.push(meta.pub_key);
55    }
56
57    transaction.validate_signatures(&public_keys)?;
58
59    let txid = transaction.tx_hash();
60
61    for (output, out_idx) in transaction.outputs.iter().zip(0u64..) {
62        let amount = modules
63            .get_expect(output.module_instance_id())
64            .process_output(
65                &mut dbtx
66                    .to_ref_with_prefix_module_id(output.module_instance_id())
67                    .0,
68                output,
69                OutPoint { txid, out_idx },
70            )
71            .await
72            .map_err(TransactionError::Output)?;
73
74        funding_verifier.add_output(amount)?;
75    }
76
77    funding_verifier.verify_funding()?;
78
79    Ok(())
80}
81
82pub struct FundingVerifier {
83    input_amount: Amount,
84    output_amount: Amount,
85    fee_amount: Amount,
86}
87
88impl FundingVerifier {
89    pub fn add_input(
90        &mut self,
91        input_amount: TransactionItemAmount,
92    ) -> Result<(), TransactionError> {
93        self.input_amount = self
94            .input_amount
95            .checked_add(input_amount.amount)
96            .ok_or(TRANSACTION_OVERFLOW_ERROR)?;
97        self.fee_amount = self
98            .fee_amount
99            .checked_add(input_amount.fee)
100            .ok_or(TRANSACTION_OVERFLOW_ERROR)?;
101        Ok(())
102    }
103
104    pub fn add_output(
105        &mut self,
106        output_amount: TransactionItemAmount,
107    ) -> Result<(), TransactionError> {
108        self.output_amount = self
109            .output_amount
110            .checked_add(output_amount.amount)
111            .ok_or(TRANSACTION_OVERFLOW_ERROR)?;
112        self.fee_amount = self
113            .fee_amount
114            .checked_add(output_amount.fee)
115            .ok_or(TRANSACTION_OVERFLOW_ERROR)?;
116        Ok(())
117    }
118
119    pub fn verify_funding(self) -> Result<(), TransactionError> {
120        let outputs_and_fees = self
121            .output_amount
122            .checked_add(self.fee_amount)
123            .ok_or(TRANSACTION_OVERFLOW_ERROR)?;
124        if self.input_amount == outputs_and_fees {
125            Ok(())
126        } else {
127            Err(TransactionError::UnbalancedTransaction {
128                inputs: self.input_amount,
129                outputs: self.output_amount,
130                fee: self.fee_amount,
131            })
132        }
133    }
134}
135
136impl Default for FundingVerifier {
137    fn default() -> Self {
138        FundingVerifier {
139            input_amount: Amount::ZERO,
140            output_amount: Amount::ZERO,
141            fee_amount: Amount::ZERO,
142        }
143    }
144}