/ synthesizer / process / src / verify_fee.rs
verify_fee.rs
  1  // Copyright (c) 2025 ADnet Contributors
  2  // This file is part of the AlphaVM library.
  3  
  4  // Licensed under the Apache License, Version 2.0 (the "License");
  5  // you may not use this file except in compliance with the License.
  6  // You may obtain a copy of the License at:
  7  
  8  // http://www.apache.org/licenses/LICENSE-2.0
  9  
 10  // Unless required by applicable law or agreed to in writing, software
 11  // distributed under the License is distributed on an "AS IS" BASIS,
 12  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 13  // See the License for the specific language governing permissions and
 14  // limitations under the License.
 15  
 16  use super::*;
 17  
 18  impl<N: Network> Process<N> {
 19      /// Verifies the given fee is valid.
 20      /// Note: This does *not* check that the global state root exists in the ledger.
 21      #[inline]
 22      pub fn verify_fee(
 23          &self,
 24          consensus_version: ConsensusVersion,
 25          varuna_version: VarunaVersion,
 26          inclusion_version: InclusionVersion,
 27          fee: &Fee<N>,
 28          deployment_or_execution_id: Field<N>,
 29      ) -> Result<()> {
 30          let timer = timer!("Process::verify_fee");
 31  
 32          // Retrieve the stack.
 33          let stack = self.get_stack(fee.program_id())?;
 34          // Retrieve the function from the stack.
 35          let function = stack.get_function(fee.function_name())?;
 36  
 37          dev_println!("Verifying fee from {}/{}...", fee.program_id(), fee.function_name());
 38  
 39          #[cfg(debug_assertions)]
 40          {
 41              // Ensure the number of function calls in this function is 1.
 42              if stack.get_number_of_calls(function.name())? != 1 {
 43                  bail!("The number of function calls in '{}/{}' should be 1", stack.program_id(), function.name())
 44              }
 45              // Debug-mode only, as the `Transition` constructor recomputes the transition ID at initialization.
 46              debug_assert_eq!(
 47                  **fee.id(),
 48                  N::hash_bhp512(&(fee.to_root()?, *fee.tcm()).to_bits_le())?,
 49                  "Transition ID of the fee is incorrect"
 50              );
 51          }
 52  
 53          // Determine if the fee is private.
 54          let is_fee_private = fee.is_fee_private();
 55          // Determine if the fee is public.
 56          let is_fee_public = fee.is_fee_public();
 57          // Ensure the fee has the correct program ID and function.
 58          ensure!(is_fee_private || is_fee_public, "Incorrect program ID or function name for fee transition");
 59          // Ensure the number of inputs is within the allowed range.
 60          ensure!(fee.inputs().len() <= N::MAX_INPUTS, "Fee exceeded maximum number of inputs");
 61          // Ensure the number of outputs is within the allowed range.
 62          ensure!(fee.outputs().len() <= N::MAX_INPUTS, "Fee exceeded maximum number of outputs");
 63  
 64          // Ensure the input and output types are equivalent to the ones defined in the function.
 65          // We only need to check that the variant type matches because we already check the hashes in
 66          // the `Input::verify` and `Output::verify` functions.
 67          let fee_input_variants = fee.inputs().iter().map(Input::variant).collect::<Vec<_>>();
 68          let fee_output_variants = fee.outputs().iter().map(Output::variant).collect::<Vec<_>>();
 69          ensure!(function.input_variants() == fee_input_variants, "The fee input variants do not match");
 70          ensure!(function.output_variants() == fee_output_variants, "The fee output variants do not match");
 71  
 72          // Retrieve the candidate deployment or execution ID.
 73          let Ok(candidate_id) = fee.deployment_or_execution_id() else {
 74              bail!("Failed to get the deployment or execution ID in the fee transition")
 75          };
 76          // Ensure the candidate ID is the deployment or execution ID.
 77          if candidate_id != deployment_or_execution_id {
 78              bail!("Incorrect deployment or execution ID in the fee transition")
 79          }
 80          lap!(timer, "Verify the deployment or execution ID");
 81  
 82          // Verify the fee transition is well-formed.
 83          match is_fee_private {
 84              true => self.verify_fee_private(consensus_version, varuna_version, inclusion_version, &fee)?,
 85              false => self.verify_fee_public(varuna_version, inclusion_version, &fee)?,
 86          }
 87          finish!(timer, "Verify the fee transition");
 88          Ok(())
 89      }
 90  }
 91  
 92  impl<N: Network> Process<N> {
 93      /// Verifies the transition for `credits.alpha/fee_private` is well-formed.
 94      fn verify_fee_private(
 95          &self,
 96          consensus_version: ConsensusVersion,
 97          varuna_version: VarunaVersion,
 98          inclusion_version: InclusionVersion,
 99          fee: &&Fee<N>,
100      ) -> Result<()> {
101          let timer = timer!("Process::verify_fee_private");
102  
103          // Retrieve the network ID.
104          let network_id = U16::new(N::ID);
105          // Compute the function ID.
106          let function_id = compute_function_id(&network_id, fee.program_id(), fee.function_name())?;
107  
108          // Ensure the fee contains 1 input record.
109          ensure!(
110              fee.inputs().iter().filter(|input| matches!(input, Input::Record(..))).count() == 1,
111              "The fee transition must contain *1* input record"
112          );
113          // Ensure the number of inputs is correct.
114          let num_inputs = fee.inputs().len();
115          ensure!(num_inputs == 4, "The number of inputs in the fee transition should be 4, found {num_inputs}",);
116          // Ensure each input is valid.
117          if fee.inputs().iter().enumerate().any(|(index, input)| !input.verify(function_id, fee.tcm(), index)) {
118              bail!("Failed to verify a fee input")
119          }
120          lap!(timer, "Verify the inputs");
121  
122          // Ensure the number of outputs is correct.
123          ensure!(
124              fee.outputs().len() == 1,
125              "The number of outputs in the fee transition should be 1, found {}",
126              fee.outputs().len()
127          );
128          // Ensure each output is valid.
129          for (index, output) in fee.outputs().iter().enumerate() {
130              // If the consensus version are before `ConsensusVersion::V8`, ensure the output record is on Version 0.
131              // if the consensus version is on or after `ConsensusVersion::V8`, ensure the output record is on Version 1.
132              if let Some((_, record)) = output.record() {
133                  if (ConsensusVersion::V1..=ConsensusVersion::V7).contains(&consensus_version) {
134                      #[cfg(not(any(test, feature = "test")))]
135                      ensure!(record.version().is_zero(), "Output record must be Version 0 before Consensus V8");
136                      #[cfg(any(test, feature = "test"))]
137                      ensure!(
138                          record.version().is_one(),
139                          "Output record must be Version 1 before Consensus V8  in tests."
140                      );
141                  } else {
142                      ensure!(record.version().is_one(), "Output record must be Version 1 on or after Consensus V8");
143                  }
144              }
145              // Ensure the output is valid.
146              if !output.verify(function_id, fee.tcm(), num_inputs + index) {
147                  bail!("Failed to verify a fee output")
148              }
149          }
150          lap!(timer, "Verify the outputs");
151  
152          // Compute the x- and y-coordinate of `tpk`.
153          let (tpk_x, tpk_y) = fee.tpk().to_xy_coordinates();
154  
155          // Retrieve the address belonging to the program ID.
156          let stack = self.get_stack(fee.program_id())?;
157          let program_address = stack.program_address();
158  
159          // Compute the x- and y-coordinate of `parent`.
160          let (parent_x, parent_y) = program_address.to_xy_coordinates();
161  
162          // Construct the public inputs to verify the proof.
163          let mut inputs = vec![N::Field::one(), *tpk_x, *tpk_y, **fee.tcm(), **fee.scm()];
164          // Extend the inputs with the input IDs.
165          inputs.extend(fee.inputs().iter().flat_map(|input| input.verifier_inputs()));
166          // Extend the verifier inputs with the public inputs for 'self.caller'.
167          inputs.extend([*Field::<N>::one(), *parent_x, *parent_y]);
168          // Extend the inputs with the output IDs.
169          inputs.extend(fee.outputs().iter().flat_map(|output| output.verifier_inputs()));
170          lap!(timer, "Construct the verifier inputs");
171  
172          dev_println!("Fee public inputs ({} elements): {:#?}", inputs.len(), inputs);
173  
174          // Retrieve the verifying key.
175          let verifying_key = stack.get_verifying_key(fee.function_name())?;
176  
177          // Ensure the fee proof is valid.
178          Trace::verify_fee_proof(varuna_version, inclusion_version, (verifying_key, vec![inputs]), fee)?;
179          finish!(timer, "Verify the fee proof");
180          Ok(())
181      }
182  
183      /// Verifies the transition for `credits.alpha/fee_public` is well-formed.
184      /// Attention: This method does *not* verify the account balance is sufficient.
185      fn verify_fee_public(
186          &self,
187          varuna_version: VarunaVersion,
188          inclusion_version: InclusionVersion,
189          fee: &&Fee<N>,
190      ) -> Result<()> {
191          let timer = timer!("Process::verify_fee_public");
192  
193          // Retrieve the network ID.
194          let network_id = U16::new(N::ID);
195          // Compute the function ID.
196          let function_id = compute_function_id(&network_id, fee.program_id(), fee.function_name())?;
197  
198          // Ensure the fee contains all public inputs.
199          ensure!(
200              fee.inputs().iter().all(|input| matches!(input, Input::Public(..))),
201              "The fee transition must contain *only* public inputs"
202          );
203          // Ensure the number of inputs is correct.
204          let num_inputs = fee.inputs().len();
205          ensure!(num_inputs == 3, "The number of inputs in the fee transition should be 3, found {num_inputs}",);
206          // Ensure each input is valid.
207          if fee.inputs().iter().enumerate().any(|(index, input)| !input.verify(function_id, fee.tcm(), index)) {
208              bail!("Failed to verify a fee input")
209          }
210          lap!(timer, "Verify the inputs");
211  
212          // Ensure there is one output.
213          ensure!(
214              fee.outputs().len() == 1,
215              "The number of outputs in the fee transition should be 1, found {}",
216              fee.outputs().len()
217          );
218          // Ensure each output is valid.
219          if fee
220              .outputs()
221              .iter()
222              .enumerate()
223              .any(|(index, output)| !output.verify(function_id, fee.tcm(), num_inputs + index))
224          {
225              bail!("Failed to verify a fee output")
226          }
227          lap!(timer, "Verify the outputs");
228  
229          // Compute the x- and y-coordinate of `tpk`.
230          let (tpk_x, tpk_y) = fee.tpk().to_xy_coordinates();
231  
232          // Retrieve the address belonging to the program ID.
233          let stack = self.get_stack(fee.program_id())?;
234          let program_address = stack.program_address();
235  
236          // Compute the x- and y-coordinate of `parent`.
237          let (parent_x, parent_y) = program_address.to_xy_coordinates();
238  
239          // Construct the public inputs to verify the proof.
240          let mut inputs = vec![N::Field::one(), *tpk_x, *tpk_y, **fee.tcm(), **fee.scm()];
241          // Extend the inputs with the input IDs.
242          inputs.extend(fee.inputs().iter().flat_map(|input| input.verifier_inputs()));
243          // Extend the verifier inputs with the public inputs for 'self.caller'
244          inputs.extend([*Field::<N>::one(), *parent_x, *parent_y]);
245          // Extend the inputs with the output IDs.
246          inputs.extend(fee.outputs().iter().flat_map(|output| output.verifier_inputs()));
247          lap!(timer, "Construct the verifier inputs");
248  
249          dev_println!("Fee public inputs ({} elements): {:#?}", inputs.len(), inputs);
250  
251          // Retrieve the verifying key.
252          let verifying_key = stack.get_verifying_key(fee.function_name())?;
253  
254          // Ensure the fee proof is valid.
255          Trace::verify_fee_proof(varuna_version, inclusion_version, (verifying_key, vec![inputs]), fee)?;
256          finish!(timer, "Verify the fee proof");
257          Ok(())
258      }
259  }
260  
261  #[cfg(test)]
262  mod tests {
263      use super::*;
264      use alphavm_ledger_block::Transaction;
265      use console::prelude::TestRng;
266  
267      #[test]
268      fn test_verify_fee() {
269          let rng = &mut TestRng::default();
270  
271          // Fetch transactions.
272          let transactions = [
273              alphavm_ledger_test_helpers::sample_deployment_transaction(1, Uniform::rand(rng), true, rng),
274              alphavm_ledger_test_helpers::sample_deployment_transaction(1, Uniform::rand(rng), false, rng),
275              alphavm_ledger_test_helpers::sample_deployment_transaction(2, Uniform::rand(rng), true, rng),
276              alphavm_ledger_test_helpers::sample_deployment_transaction(2, Uniform::rand(rng), false, rng),
277              alphavm_ledger_test_helpers::sample_execution_transaction_with_fee(true, rng, 0),
278              alphavm_ledger_test_helpers::sample_execution_transaction_with_fee(false, rng, 0),
279              alphavm_ledger_test_helpers::sample_fee_private_transaction(rng),
280              alphavm_ledger_test_helpers::sample_fee_public_transaction(rng),
281          ];
282  
283          // Construct a new process.
284          let process = Process::load().unwrap();
285  
286          for transaction in transactions {
287              match transaction {
288                  Transaction::Deploy(_, _, _, deployment, fee) => {
289                      // Compute the deployment ID.
290                      let deployment_id = deployment.to_deployment_id().unwrap();
291                      // Verify the fee.
292                      process
293                          .verify_fee(ConsensusVersion::V8, VarunaVersion::V1, InclusionVersion::V0, &fee, deployment_id)
294                          .unwrap();
295                  }
296                  Transaction::Execute(_, _, execution, fee) => {
297                      // Compute the execution ID.
298                      let execution_id = execution.to_execution_id().unwrap();
299                      // Verify the fee.
300                      process
301                          .verify_fee(
302                              ConsensusVersion::V8,
303                              VarunaVersion::V1,
304                              InclusionVersion::V0,
305                              &fee.unwrap(),
306                              execution_id,
307                          )
308                          .unwrap();
309                  }
310                  Transaction::Fee(_, fee) => match fee.is_fee_private() {
311                      true => process
312                          .verify_fee_private(ConsensusVersion::V8, VarunaVersion::V1, InclusionVersion::V0, &&fee)
313                          .unwrap(),
314                      false => process.verify_fee_public(VarunaVersion::V1, InclusionVersion::V0, &&fee).unwrap(),
315                  },
316              }
317          }
318      }
319  }