fedimint_server/consensus/
transaction.rs1use 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
121pub struct FundingVerifier {
122 input_amount: Amount,
123 output_amount: Amount,
124 fee_amount: Amount,
125}
126
127impl FundingVerifier {
128 pub fn add_input(
129 &mut self,
130 input_amount: TransactionItemAmount,
131 ) -> Result<(), TransactionError> {
132 self.input_amount = self
133 .input_amount
134 .checked_add(input_amount.amount)
135 .ok_or(TRANSACTION_OVERFLOW_ERROR)?;
136
137 self.fee_amount = self
138 .fee_amount
139 .checked_add(input_amount.fee)
140 .ok_or(TRANSACTION_OVERFLOW_ERROR)?;
141
142 Ok(())
143 }
144
145 pub fn add_output(
146 &mut self,
147 output_amount: TransactionItemAmount,
148 ) -> Result<(), TransactionError> {
149 self.output_amount = self
150 .output_amount
151 .checked_add(output_amount.amount)
152 .ok_or(TRANSACTION_OVERFLOW_ERROR)?;
153
154 self.fee_amount = self
155 .fee_amount
156 .checked_add(output_amount.fee)
157 .ok_or(TRANSACTION_OVERFLOW_ERROR)?;
158
159 Ok(())
160 }
161
162 pub fn verify_funding(self, version: CoreConsensusVersion) -> Result<(), TransactionError> {
163 let outputs_and_fees = self
164 .output_amount
165 .checked_add(self.fee_amount)
166 .ok_or(TRANSACTION_OVERFLOW_ERROR)?;
167
168 if self.input_amount == outputs_and_fees {
169 return Ok(());
170 }
171
172 if self.input_amount > outputs_and_fees && version >= CoreConsensusVersion::new(2, 1) {
173 return Ok(());
174 }
175
176 Err(TransactionError::UnbalancedTransaction {
177 inputs: self.input_amount,
178 outputs: self.output_amount,
179 fee: self.fee_amount,
180 })
181 }
182}
183
184impl Default for FundingVerifier {
185 fn default() -> Self {
186 FundingVerifier {
187 input_amount: Amount::ZERO,
188 output_amount: Amount::ZERO,
189 fee_amount: Amount::ZERO,
190 }
191 }
192}