fedimint_server/consensus/
transaction.rs
1use fedimint_core::db::DatabaseTransaction;
2use fedimint_core::module::{CoreConsensusVersion, TransactionItemAmount};
3use fedimint_core::transaction::{TRANSACTION_OVERFLOW_ERROR, Transaction, TransactionError};
4use fedimint_core::{Amount, InPoint, OutPoint};
5use fedimint_server_core::ServerModuleRegistry;
6use rayon::iter::{IntoParallelIterator, ParallelIterator};
7
8use crate::metrics::{CONSENSUS_TX_PROCESSED_INPUTS, CONSENSUS_TX_PROCESSED_OUTPUTS};
9
10#[derive(Debug, PartialEq, Eq)]
11pub enum TxProcessingMode {
12 Submission,
13 Consensus,
14}
15
16pub async fn process_transaction_with_dbtx(
17 modules: ServerModuleRegistry,
18 dbtx: &mut DatabaseTransaction<'_>,
19 transaction: &Transaction,
20 version: CoreConsensusVersion,
21 mode: TxProcessingMode,
22) -> Result<(), TransactionError> {
23 let in_count = transaction.inputs.len();
24 let out_count = transaction.outputs.len();
25
26 dbtx.on_commit(move || {
27 CONSENSUS_TX_PROCESSED_INPUTS.observe(in_count as f64);
28 CONSENSUS_TX_PROCESSED_OUTPUTS.observe(out_count as f64);
29 });
30
31 transaction
36 .inputs
37 .clone()
38 .into_par_iter()
39 .try_for_each(|input| {
40 modules
41 .get_expect(input.module_instance_id())
42 .verify_input(&input)
43 })
44 .map_err(|_| TransactionError::InvalidWitnessLength)?;
45
46 let mut funding_verifier = FundingVerifier::default();
47 let mut public_keys = Vec::new();
48
49 let txid = transaction.tx_hash();
50
51 for (input, in_idx) in transaction.inputs.iter().zip(0u64..) {
52 if mode == TxProcessingMode::Submission {
55 modules
56 .get_expect(input.module_instance_id())
57 .verify_input_submission(
58 &mut dbtx
59 .to_ref_with_prefix_module_id(input.module_instance_id())
60 .0,
61 input,
62 )
63 .await
64 .map_err(TransactionError::Input)?;
65 }
66 let meta = modules
67 .get_expect(input.module_instance_id())
68 .process_input(
69 &mut dbtx
70 .to_ref_with_prefix_module_id(input.module_instance_id())
71 .0,
72 input,
73 InPoint { txid, in_idx },
74 )
75 .await
76 .map_err(TransactionError::Input)?;
77
78 funding_verifier.add_input(meta.amount)?;
79 public_keys.push(meta.pub_key);
80 }
81
82 transaction.validate_signatures(&public_keys)?;
83
84 for (output, out_idx) in transaction.outputs.iter().zip(0u64..) {
85 if mode == TxProcessingMode::Submission {
88 modules
89 .get_expect(output.module_instance_id())
90 .verify_output_submission(
91 &mut dbtx
92 .to_ref_with_prefix_module_id(output.module_instance_id())
93 .0,
94 output,
95 OutPoint { txid, out_idx },
96 )
97 .await
98 .map_err(TransactionError::Output)?;
99 }
100
101 let amount = modules
102 .get_expect(output.module_instance_id())
103 .process_output(
104 &mut dbtx
105 .to_ref_with_prefix_module_id(output.module_instance_id())
106 .0,
107 output,
108 OutPoint { txid, out_idx },
109 )
110 .await
111 .map_err(TransactionError::Output)?;
112
113 funding_verifier.add_output(amount)?;
114 }
115
116 funding_verifier.verify_funding(version)?;
117
118 Ok(())
119}
120
121#[allow(clippy::struct_field_names)]
122pub struct FundingVerifier {
123 input_amount: Amount,
124 output_amount: Amount,
125 fee_amount: Amount,
126}
127
128impl FundingVerifier {
129 pub fn add_input(
130 &mut self,
131 input_amount: TransactionItemAmount,
132 ) -> Result<(), TransactionError> {
133 self.input_amount = self
134 .input_amount
135 .checked_add(input_amount.amount)
136 .ok_or(TRANSACTION_OVERFLOW_ERROR)?;
137
138 self.fee_amount = self
139 .fee_amount
140 .checked_add(input_amount.fee)
141 .ok_or(TRANSACTION_OVERFLOW_ERROR)?;
142
143 Ok(())
144 }
145
146 pub fn add_output(
147 &mut self,
148 output_amount: TransactionItemAmount,
149 ) -> Result<(), TransactionError> {
150 self.output_amount = self
151 .output_amount
152 .checked_add(output_amount.amount)
153 .ok_or(TRANSACTION_OVERFLOW_ERROR)?;
154
155 self.fee_amount = self
156 .fee_amount
157 .checked_add(output_amount.fee)
158 .ok_or(TRANSACTION_OVERFLOW_ERROR)?;
159
160 Ok(())
161 }
162
163 pub fn verify_funding(self, version: CoreConsensusVersion) -> Result<(), TransactionError> {
164 let outputs_and_fees = self
165 .output_amount
166 .checked_add(self.fee_amount)
167 .ok_or(TRANSACTION_OVERFLOW_ERROR)?;
168
169 if self.input_amount == outputs_and_fees {
170 return Ok(());
171 }
172
173 if self.input_amount > outputs_and_fees && version >= CoreConsensusVersion::new(2, 1) {
174 return Ok(());
175 }
176
177 Err(TransactionError::UnbalancedTransaction {
178 inputs: self.input_amount,
179 outputs: self.output_amount,
180 fee: self.fee_amount,
181 })
182 }
183}
184
185impl Default for FundingVerifier {
186 fn default() -> Self {
187 FundingVerifier {
188 input_amount: Amount::ZERO,
189 output_amount: Amount::ZERO,
190 fee_amount: Amount::ZERO,
191 }
192 }
193}