1use super::*;
16
17macro_rules! ensure_is_unique {
20 ($name:expr, $self:expr, $method:ident, $iter:expr) => {
21 if has_duplicates($iter) {
23 bail!("Found a duplicate {} in the transaction", $name);
24 }
25 for item in $iter {
27 if $self.transition_store().$method(item)? {
28 bail!("The {} '{}' already exists in the ledger", $name, item)
29 }
30 }
31 };
32}
33
34impl<N: Network, C: ConsensusStorage<N>> VM<N, C> {
35 #[inline]
37 pub fn check_transaction<R: CryptoRng + Rng>(
38 &self,
39 transaction: &Transaction<N>,
40 rejected_id: Option<Field<N>>,
41 rng: &mut R,
42 ) -> Result<()> {
43 let timer = timer!("VM::check_transaction");
44
45 if self.block_store().contains_transaction_id(&transaction.id())? {
49 bail!("Transaction '{}' already exists in the ledger", transaction.id())
50 }
51
52 match transaction.to_root() {
54 Ok(root) if *transaction.id() != root => bail!("Incorrect transaction ID ({})", transaction.id()),
56 Ok(_) => (),
57 Err(error) => {
58 bail!("Failed to compute the Merkle root of the transaction: {error}\n{transaction}");
59 }
60 };
61 lap!(timer, "Verify the transaction ID");
62
63 ensure_is_unique!("transition ID", self, contains_transition_id, transaction.transition_ids());
67
68 ensure_is_unique!("input ID", self, contains_input_id, transaction.input_ids());
72 ensure_is_unique!("serial number", self, contains_serial_number, transaction.serial_numbers());
74 ensure_is_unique!("tag", self, contains_tag, transaction.tags());
76
77 ensure_is_unique!("output ID", self, contains_output_id, transaction.output_ids());
81 ensure_is_unique!("commitment", self, contains_commitment, transaction.commitments());
83 ensure_is_unique!("nonce", self, contains_nonce, transaction.nonces());
85
86 ensure_is_unique!("transition public key", self, contains_tpk, transaction.transition_public_keys());
90 ensure_is_unique!("transition commitment", self, contains_tcm, transaction.transition_commitments());
92
93 lap!(timer, "Check for duplicate elements");
94
95 self.check_fee(transaction, rejected_id)?;
97
98 match transaction {
100 Transaction::Deploy(id, owner, deployment, _) => {
101 let Ok(deployment_id) = deployment.to_deployment_id() else {
103 bail!("Failed to compute the Merkle root for a deployment transaction '{id}'")
104 };
105 ensure!(owner.verify(deployment_id), "Invalid owner signature for deployment transaction '{id}'");
107 if deployment.edition() != N::EDITION {
109 bail!("Invalid deployment transaction '{id}' - expected edition {}", N::EDITION)
110 }
111 if self.transaction_store().contains_program_id(deployment.program_id())? {
113 bail!("Program ID '{}' is already deployed", deployment.program_id())
114 }
115 self.check_deployment_internal(deployment, rng)?;
117 }
118 Transaction::Execute(id, execution, _) => {
119 let Ok(execution_id) = execution.to_execution_id() else {
121 bail!("Failed to compute the Merkle root for an execution transaction '{id}'")
122 };
123 if self.block_store().contains_rejected_deployment_or_execution_id(&execution_id)? {
125 bail!("Transaction '{id}' contains a previously rejected execution")
126 }
127 self.check_execution_internal(execution)?;
129 }
130 Transaction::Fee(..) => { }
131 }
132
133 finish!(timer, "Verify the transaction");
134 Ok(())
135 }
136
137 #[inline]
139 pub fn check_fee(&self, transaction: &Transaction<N>, rejected_id: Option<Field<N>>) -> Result<()> {
140 match transaction {
141 Transaction::Deploy(id, _, deployment, fee) => {
142 ensure!(rejected_id.is_none(), "Transaction '{id}' should not have a rejected ID (deployment)");
144 let Ok(deployment_id) = deployment.to_deployment_id() else {
146 bail!("Failed to compute the Merkle root for deployment transaction '{id}'")
147 };
148 let (cost, _) = deployment_cost(deployment)?;
150 if *fee.base_amount()? < cost {
152 bail!("Transaction '{id}' has an insufficient base fee (deployment) - requires {cost} microcredits")
153 }
154 self.check_fee_internal(fee, deployment_id)?;
156 }
157 Transaction::Execute(id, execution, fee) => {
158 ensure!(rejected_id.is_none(), "Transaction '{id}' should not have a rejected ID (execution)");
160 let Ok(execution_id) = execution.to_execution_id() else {
162 bail!("Failed to compute the Merkle root for execution transaction '{id}'")
163 };
164 let is_fee_required = !(execution.len() == 1 && transaction.contains_split());
166 if let Some(fee) = fee {
168 if is_fee_required {
170 let (cost, _) = execution_cost(self, execution)?;
172 if *fee.base_amount()? < cost {
174 bail!(
175 "Transaction '{id}' has an insufficient base fee (execution) - requires {cost} microcredits"
176 )
177 }
178 } else {
179 ensure!(*fee.base_amount()? == 0, "Transaction '{id}' has a non-zero base fee (execution)");
181 }
182 self.check_fee_internal(fee, execution_id)?;
184 } else {
185 ensure!(!is_fee_required, "Transaction '{id}' is missing a fee (execution)");
187 }
188 }
189 Transaction::Fee(id, fee) => {
193 match rejected_id {
195 Some(rejected_id) => self.check_fee_internal(fee, rejected_id)?,
196 None => bail!("Transaction '{id}' is missing a rejected ID (fee)"),
197 }
198 }
199 }
200 Ok(())
201 }
202}
203
204impl<N: Network, C: ConsensusStorage<N>> VM<N, C> {
205 #[inline]
210 fn check_deployment_internal<R: CryptoRng + Rng>(&self, deployment: &Deployment<N>, rng: &mut R) -> Result<()> {
211 macro_rules! logic {
212 ($process:expr, $network:path, $aleo:path) => {{
213 let deployment = cast_ref!(&deployment as Deployment<$network>);
215 $process.verify_deployment::<$aleo, _>(&deployment, rng)
217 }};
218 }
219
220 let timer = timer!("VM::check_deployment");
222 let result = process!(self, logic).map_err(|error| anyhow!("Deployment verification failed - {error}"));
223 finish!(timer);
224 result
225 }
226
227 #[inline]
232 fn check_execution_internal(&self, execution: &Execution<N>) -> Result<()> {
233 let timer = timer!("VM::check_execution");
234
235 let verification = self.process.read().verify_execution(execution);
237 lap!(timer, "Verify the execution");
238
239 let result = match verification {
241 Ok(()) => match self.block_store().contains_state_root(&execution.global_state_root()) {
243 Ok(true) => Ok(()),
244 Ok(false) => bail!("Execution verification failed: global state root not found"),
245 Err(error) => bail!("Execution verification failed: {error}"),
246 },
247 Err(error) => bail!("Execution verification failed: {error}"),
248 };
249 finish!(timer, "Check the global state root");
250 result
251 }
252
253 #[inline]
258 fn check_fee_internal(&self, fee: &Fee<N>, deployment_or_execution_id: Field<N>) -> Result<()> {
259 let timer = timer!("VM::check_fee");
260
261 let fee_amount = fee.amount()?;
263 ensure!(*fee_amount <= N::MAX_FEE, "Fee verification failed: fee exceeds the maximum limit");
264
265 let verification = self.process.read().verify_fee(fee, deployment_or_execution_id);
267 lap!(timer, "Verify the fee");
268
269 if fee.is_fee_public() {
273 let Some(payer) = fee.payer() else {
275 bail!("Fee verification failed: fee is public, but the payer is missing");
276 };
277 let Some(Value::Plaintext(Plaintext::Literal(Literal::U64(balance), _))) =
279 self.finalize_store().get_value_speculative(
280 ProgramID::from_str("credits.aleo")?,
281 Identifier::from_str("account")?,
282 &Plaintext::from(Literal::Address(payer)),
283 )?
284 else {
285 bail!("Fee verification failed: fee is public, but the payer account balance is missing");
286 };
287 ensure!(balance >= fee_amount, "Fee verification failed: insufficient balance");
289 }
290
291 let result = match verification {
293 Ok(()) => match self.block_store().contains_state_root(&fee.global_state_root()) {
294 Ok(true) => Ok(()),
295 Ok(false) => bail!("Fee verification failed: global state root not found"),
296 Err(error) => bail!("Fee verification failed: {error}"),
297 },
298 Err(error) => bail!("Fee verification failed: {error}"),
299 };
300 finish!(timer, "Check the global state root");
301 result
302 }
303}
304
305#[cfg(test)]
306mod tests {
307 use super::*;
308
309 use crate::vm::test_helpers::sample_finalize_state;
310 use console::{
311 account::{Address, ViewKey},
312 types::Field,
313 };
314 use ledger_block::{Block, Header, Metadata, Transaction};
315
316 type CurrentNetwork = test_helpers::CurrentNetwork;
317
318 #[test]
319 fn test_verify() {
320 let rng = &mut TestRng::default();
321 let vm = crate::vm::test_helpers::sample_vm_with_genesis_block(rng);
322
323 let deployment_transaction = crate::vm::test_helpers::sample_deployment_transaction(rng);
325 vm.check_transaction(&deployment_transaction, None, rng).unwrap();
327
328 let execution_transaction = crate::vm::test_helpers::sample_execution_transaction_with_private_fee(rng);
330 vm.check_transaction(&execution_transaction, None, rng).unwrap();
332
333 let execution_transaction = crate::vm::test_helpers::sample_execution_transaction_with_public_fee(rng);
335 vm.check_transaction(&execution_transaction, None, rng).unwrap();
337 }
338
339 #[test]
340 fn test_verify_deployment() {
341 let rng = &mut TestRng::default();
342 let vm = crate::vm::test_helpers::sample_vm();
343
344 let program = crate::vm::test_helpers::sample_program();
346
347 let deployment = vm.deploy_raw(&program, rng).unwrap();
349
350 vm.check_deployment_internal(&deployment, rng).unwrap();
352
353 let serialized_deployment = deployment.to_string();
355 let deployment_transaction: Deployment<CurrentNetwork> = serde_json::from_str(&serialized_deployment).unwrap();
356 vm.check_deployment_internal(&deployment_transaction, rng).unwrap();
357 }
358
359 #[test]
360 fn test_verify_execution() {
361 let rng = &mut TestRng::default();
362 let vm = crate::vm::test_helpers::sample_vm_with_genesis_block(rng);
363
364 let transactions = [
366 crate::vm::test_helpers::sample_execution_transaction_with_private_fee(rng),
367 crate::vm::test_helpers::sample_execution_transaction_with_public_fee(rng),
368 ];
369
370 for transaction in transactions {
371 match transaction {
372 Transaction::Execute(_, execution, _) => {
373 assert!(execution.proof().is_some());
375 vm.check_execution_internal(&execution).unwrap();
377
378 let serialized_execution = execution.to_string();
380 let recovered_execution: Execution<CurrentNetwork> =
381 serde_json::from_str(&serialized_execution).unwrap();
382 vm.check_execution_internal(&recovered_execution).unwrap();
383 }
384 _ => panic!("Expected an execution transaction"),
385 }
386 }
387 }
388
389 #[test]
390 fn test_verify_fee() {
391 let rng = &mut TestRng::default();
392 let vm = crate::vm::test_helpers::sample_vm_with_genesis_block(rng);
393
394 let transactions = [
396 crate::vm::test_helpers::sample_execution_transaction_with_private_fee(rng),
397 crate::vm::test_helpers::sample_execution_transaction_with_public_fee(rng),
398 ];
399
400 for transaction in transactions {
401 match transaction {
402 Transaction::Execute(_, execution, Some(fee)) => {
403 let execution_id = execution.to_execution_id().unwrap();
404
405 assert!(fee.proof().is_some());
407 vm.check_fee_internal(&fee, execution_id).unwrap();
409
410 let serialized_fee = fee.to_string();
412 let recovered_fee: Fee<CurrentNetwork> = serde_json::from_str(&serialized_fee).unwrap();
413 vm.check_fee_internal(&recovered_fee, execution_id).unwrap();
414 }
415 _ => panic!("Expected an execution with a fee"),
416 }
417 }
418 }
419
420 #[test]
421 fn test_check_transaction_execution() {
422 let rng = &mut TestRng::default();
423
424 let vm = crate::vm::test_helpers::sample_vm();
426 let genesis = crate::vm::test_helpers::sample_genesis_block(rng);
428 vm.add_next_block(&genesis).unwrap();
430
431 let valid_transaction = crate::vm::test_helpers::sample_execution_transaction_with_private_fee(rng);
433 vm.check_transaction(&valid_transaction, None, rng).unwrap();
434
435 let valid_transaction = crate::vm::test_helpers::sample_execution_transaction_with_public_fee(rng);
437 vm.check_transaction(&valid_transaction, None, rng).unwrap();
438
439 let valid_transaction = crate::vm::test_helpers::sample_execution_transaction_without_fee(rng);
441 vm.check_transaction(&valid_transaction, None, rng).unwrap();
442 }
443
444 #[test]
445 fn test_verify_deploy_and_execute() {
446 let rng = &mut TestRng::default();
448
449 let caller_private_key = crate::vm::test_helpers::sample_genesis_private_key(rng);
451 let caller_view_key = ViewKey::try_from(&caller_private_key).unwrap();
452 let address = Address::try_from(&caller_private_key).unwrap();
453
454 let genesis = crate::vm::test_helpers::sample_genesis_block(rng);
456
457 let records = genesis.records().collect::<indexmap::IndexMap<_, _>>();
459
460 let credits = records.values().next().unwrap().decrypt(&caller_view_key).unwrap();
462
463 let vm = crate::vm::test_helpers::sample_vm();
465 vm.add_next_block(&genesis).unwrap();
467
468 let program = crate::vm::test_helpers::sample_program();
470 let deployment_transaction = vm.deploy(&caller_private_key, &program, Some(credits), 10, None, rng).unwrap();
471
472 let (ratifications, transactions, aborted_transaction_ids, ratified_finalize_operations) =
474 vm.speculate(sample_finalize_state(1), Some(0u64), vec![], None, [deployment_transaction].iter()).unwrap();
475 assert!(aborted_transaction_ids.is_empty());
476
477 let deployment_metadata = Metadata::new(
479 CurrentNetwork::ID,
480 1,
481 1,
482 0,
483 0,
484 CurrentNetwork::GENESIS_COINBASE_TARGET,
485 CurrentNetwork::GENESIS_PROOF_TARGET,
486 genesis.last_coinbase_target(),
487 genesis.last_coinbase_timestamp(),
488 CurrentNetwork::GENESIS_TIMESTAMP + 1,
489 )
490 .unwrap();
491
492 let deployment_header = Header::from(
493 vm.block_store().current_state_root(),
494 transactions.to_transactions_root().unwrap(),
495 transactions.to_finalize_root(ratified_finalize_operations).unwrap(),
496 ratifications.to_ratifications_root().unwrap(),
497 Field::zero(),
498 Field::zero(),
499 deployment_metadata,
500 )
501 .unwrap();
502
503 let deployment_block = Block::new_beacon(
505 &caller_private_key,
506 genesis.hash(),
507 deployment_header,
508 ratifications,
509 None,
510 transactions,
511 aborted_transaction_ids,
512 rng,
513 )
514 .unwrap();
515
516 vm.add_next_block(&deployment_block).unwrap();
518
519 let records = deployment_block.records().collect::<indexmap::IndexMap<_, _>>();
521
522 let inputs = [
524 Value::<CurrentNetwork>::from_str(&address.to_string()).unwrap(),
525 Value::<CurrentNetwork>::from_str("10u64").unwrap(),
526 ]
527 .into_iter();
528
529 let credits = Some(records.values().next().unwrap().decrypt(&caller_view_key).unwrap());
531
532 let transaction =
534 vm.execute(&caller_private_key, ("testing.aleo", "initialize"), inputs, credits, 10, None, rng).unwrap();
535
536 vm.check_transaction(&transaction, None, rng).unwrap();
538 }
539
540 #[test]
541 fn test_failed_credits_deployment() {
542 let rng = &mut TestRng::default();
543 let vm = crate::vm::test_helpers::sample_vm();
544
545 let program = Program::credits().unwrap();
547
548 assert!(vm.deploy_raw(&program, rng).is_err());
550
551 let program = Program::from_str(
553 r"
554program credits.aleo;
555
556record token:
557 owner as address.private;
558 amount as u64.private;
559
560function compute:
561 input r0 as u32.private;
562 add r0 r0 into r1;
563 output r1 as u32.public;",
564 )
565 .unwrap();
566
567 assert!(vm.deploy_raw(&program, rng).is_err());
569 }
570}