1use super::*;
17
18impl<N: Network> Process<N> {
19 #[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 let stack = self.get_stack(fee.program_id())?;
34 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 if stack.contains_dynamic_call(function.name())? {
43 bail!("The function '{}/{}' should not have dynamic calls", stack.program_id(), function.name())
44 }
45 if stack.get_minimum_number_of_calls(function.name())? != 1 {
47 bail!("The number of function calls in '{}/{}' should be 1", stack.program_id(), function.name())
48 }
49 let expected_id = N::hash_bhp512(&(fee.to_root()?, *fee.tcm()).to_bits_le())?;
51 debug_assert_eq!(**fee.id(), expected_id, "Transition ID of the fee is incorrect");
52 }
53
54 let is_fee_private = fee.is_fee_private();
56 let is_fee_public = fee.is_fee_public();
58 ensure!(is_fee_private || is_fee_public, "Incorrect program ID or function name for fee transition");
60 ensure!(fee.inputs().len() <= N::MAX_INPUTS, "Fee exceeded maximum number of inputs");
62 ensure!(fee.outputs().len() <= N::MAX_INPUTS, "Fee exceeded maximum number of outputs");
64
65 ensure!(
69 function.input_types().len() == fee.inputs().len(),
70 "The number of fee inputs is incorrect: expected {}, found {}",
71 function.input_types().len(),
72 fee.inputs().len()
73 );
74 for (function_input, fee_input) in function.input_types().iter().zip_eq(fee.inputs().iter()) {
75 ensure!(fee_input.is_type(function_input), "The fee input variants do not match");
76 }
77 ensure!(
78 function.output_types().len() == fee.outputs().len(),
79 "The number of fee outputs is incorrect: expected {}, found {}",
80 function.output_types().len(),
81 fee.outputs().len()
82 );
83 for (function_output, fee_output) in function.output_types().iter().zip_eq(fee.outputs().iter()) {
84 ensure!(fee_output.is_type(function_output), "The fee output variants do not match");
85 }
86
87 let Ok(candidate_id) = fee.deployment_or_execution_id() else {
89 bail!("Failed to get the deployment or execution ID in the fee transition")
90 };
91 if candidate_id != deployment_or_execution_id {
93 bail!("Incorrect deployment or execution ID in the fee transition")
94 }
95 lap!(timer, "Verify the deployment or execution ID");
96
97 match is_fee_private {
99 true => self.verify_fee_private(consensus_version, varuna_version, inclusion_version, &fee)?,
100 false => self.verify_fee_public(varuna_version, inclusion_version, &fee)?,
101 }
102 finish!(timer, "Verify the fee transition");
103 Ok(())
104 }
105}
106
107impl<N: Network> Process<N> {
108 fn verify_fee_private(
110 &self,
111 consensus_version: ConsensusVersion,
112 varuna_version: VarunaVersion,
113 inclusion_version: InclusionVersion,
114 fee: &&Fee<N>,
115 ) -> Result<()> {
116 let timer = timer!("Process::verify_fee_private");
117
118 let network_id = U16::new(N::ID);
120 let function_id = compute_function_id(&network_id, fee.program_id(), fee.function_name())?;
122
123 ensure!(
125 fee.inputs().iter().filter(|input| matches!(input, Input::Record(..))).count() == 1,
126 "The fee transition must contain *1* input record"
127 );
128 let num_inputs = fee.inputs().len();
130 ensure!(num_inputs == 4, "The number of inputs in the fee transition should be 4, found {num_inputs}",);
131 if fee.inputs().iter().enumerate().any(|(index, input)| !input.verify(function_id, fee.tcm(), index)) {
133 bail!("Failed to verify a fee input")
134 }
135 lap!(timer, "Verify the inputs");
136
137 ensure!(
139 fee.outputs().len() == 1,
140 "The number of outputs in the fee transition should be 1, found {}",
141 fee.outputs().len()
142 );
143 for (index, output) in fee.outputs().iter().enumerate() {
145 if let Some((_, record)) = output.record() {
148 if (ConsensusVersion::V1..=ConsensusVersion::V7).contains(&consensus_version) {
149 #[cfg(not(any(test, feature = "test")))]
150 ensure!(record.version().is_zero(), "Output record must be Version 0 before Consensus V8");
151 #[cfg(any(test, feature = "test"))]
152 ensure!(
153 record.version().is_one(),
154 "Output record must be Version 1 before Consensus V8 in tests."
155 );
156 } else {
157 ensure!(record.version().is_one(), "Output record must be Version 1 on or after Consensus V8");
158 }
159 }
160 if !output.verify(function_id, fee.tcm(), num_inputs + index) {
162 bail!("Failed to verify a fee output")
163 }
164 }
165 lap!(timer, "Verify the outputs");
166
167 let (tpk_x, tpk_y) = fee.tpk().to_xy_coordinates();
169
170 let stack = self.get_stack(fee.program_id())?;
172 let program_address = stack.program_address();
173
174 let (parent_x, parent_y) = program_address.to_xy_coordinates();
176
177 let mut inputs = vec![N::Field::one(), *tpk_x, *tpk_y, **fee.tcm(), **fee.scm()];
179 inputs.extend(fee.inputs().iter().flat_map(|input| input.verifier_inputs()));
181 inputs.extend([*Field::<N>::one(), *parent_x, *parent_y]);
183 inputs.extend(fee.outputs().iter().flat_map(|output| output.verifier_inputs()));
185 lap!(timer, "Construct the verifier inputs");
186
187 dev_println!("Fee public inputs ({} elements): {:#?}", inputs.len(), inputs);
188
189 let verifying_key = stack.get_verifying_key(fee.function_name())?;
191
192 Trace::verify_fee_proof(varuna_version, inclusion_version, (verifying_key, vec![inputs]), fee)?;
194 finish!(timer, "Verify the fee proof");
195 Ok(())
196 }
197
198 fn verify_fee_public(
201 &self,
202 varuna_version: VarunaVersion,
203 inclusion_version: InclusionVersion,
204 fee: &&Fee<N>,
205 ) -> Result<()> {
206 let timer = timer!("Process::verify_fee_public");
207
208 let network_id = U16::new(N::ID);
210 let function_id = compute_function_id(&network_id, fee.program_id(), fee.function_name())?;
212
213 ensure!(
215 fee.inputs().iter().all(|input| matches!(input, Input::Public(..))),
216 "The fee transition must contain *only* public inputs"
217 );
218 let num_inputs = fee.inputs().len();
220 ensure!(num_inputs == 3, "The number of inputs in the fee transition should be 3, found {num_inputs}",);
221 if fee.inputs().iter().enumerate().any(|(index, input)| !input.verify(function_id, fee.tcm(), index)) {
223 bail!("Failed to verify a fee input")
224 }
225 lap!(timer, "Verify the inputs");
226
227 ensure!(
229 fee.outputs().len() == 1,
230 "The number of outputs in the fee transition should be 1, found {}",
231 fee.outputs().len()
232 );
233 if fee
235 .outputs()
236 .iter()
237 .enumerate()
238 .any(|(index, output)| !output.verify(function_id, fee.tcm(), num_inputs + index))
239 {
240 bail!("Failed to verify a fee output")
241 }
242 lap!(timer, "Verify the outputs");
243
244 let (tpk_x, tpk_y) = fee.tpk().to_xy_coordinates();
246
247 let stack = self.get_stack(fee.program_id())?;
249 let program_address = stack.program_address();
250
251 let (parent_x, parent_y) = program_address.to_xy_coordinates();
253
254 let mut inputs = vec![N::Field::one(), *tpk_x, *tpk_y, **fee.tcm(), **fee.scm()];
256 inputs.extend(fee.inputs().iter().flat_map(|input| input.verifier_inputs()));
258 inputs.extend([*Field::<N>::one(), *parent_x, *parent_y]);
260 inputs.extend(fee.outputs().iter().flat_map(|output| output.verifier_inputs()));
262 lap!(timer, "Construct the verifier inputs");
263
264 dev_println!("Fee public inputs ({} elements): {:#?}", inputs.len(), inputs);
265
266 let verifying_key = stack.get_verifying_key(fee.function_name())?;
268
269 Trace::verify_fee_proof(varuna_version, inclusion_version, (verifying_key, vec![inputs]), fee)?;
271 finish!(timer, "Verify the fee proof");
272 Ok(())
273 }
274}
275
276#[cfg(test)]
277mod tests {
278 use super::*;
279 use console::prelude::TestRng;
280 use snarkvm_ledger_block::Transaction;
281
282 #[test]
283 fn test_verify_fee() {
284 let rng = &mut TestRng::default();
285
286 let transactions = [
288 snarkvm_ledger_test_helpers::sample_deployment_transaction(1, Uniform::rand(rng), false, true, rng),
289 snarkvm_ledger_test_helpers::sample_deployment_transaction(1, Uniform::rand(rng), false, false, rng),
290 snarkvm_ledger_test_helpers::sample_deployment_transaction(2, Uniform::rand(rng), false, true, rng),
291 snarkvm_ledger_test_helpers::sample_deployment_transaction(2, Uniform::rand(rng), false, false, rng),
292 snarkvm_ledger_test_helpers::sample_deployment_transaction(2, Uniform::rand(rng), true, true, rng),
293 snarkvm_ledger_test_helpers::sample_deployment_transaction(2, Uniform::rand(rng), true, false, rng),
294 snarkvm_ledger_test_helpers::sample_execution_transaction_with_fee(true, rng, 0),
295 snarkvm_ledger_test_helpers::sample_execution_transaction_with_fee(false, rng, 0),
296 snarkvm_ledger_test_helpers::sample_fee_private_transaction(rng),
297 snarkvm_ledger_test_helpers::sample_fee_public_transaction(rng),
298 ];
299
300 let process = Process::load().unwrap();
302
303 for transaction in transactions {
304 match transaction {
305 Transaction::Deploy(_, _, _, deployment, fee) => {
306 let deployment_id = deployment.to_deployment_id().unwrap();
308 process
310 .verify_fee(ConsensusVersion::V8, VarunaVersion::V1, InclusionVersion::V0, &fee, deployment_id)
311 .unwrap();
312 }
313 Transaction::Execute(_, _, execution, fee) => {
314 let execution_id = execution.to_execution_id().unwrap();
316 process
318 .verify_fee(
319 ConsensusVersion::V8,
320 VarunaVersion::V1,
321 InclusionVersion::V0,
322 &fee.unwrap(),
323 execution_id,
324 )
325 .unwrap();
326 }
327 Transaction::Fee(_, fee) => match fee.is_fee_private() {
328 true => process
329 .verify_fee_private(ConsensusVersion::V8, VarunaVersion::V1, InclusionVersion::V0, &&fee)
330 .unwrap(),
331 false => process.verify_fee_public(VarunaVersion::V1, InclusionVersion::V0, &&fee).unwrap(),
332 },
333 }
334 }
335 }
336}