diff --git a/radix-clis/src/resim/mod.rs b/radix-clis/src/resim/mod.rs index b62d3107862..bd716791fd9 100644 --- a/radix-clis/src/resim/mod.rs +++ b/radix-clis/src/resim/mod.rs @@ -222,7 +222,7 @@ pub fn handle_manifest( .map(|e| NonFungibleGlobalId::from_public_key(&e.public_key())) .collect::>(); let nonce = get_nonce()?; - let transaction = TestTransaction::new_v1_from_nonce(manifest, nonce); + let transaction = TestTransaction::new_v1_from_nonce(manifest, nonce, initial_proofs); let receipt = execute_and_commit_transaction( &mut db, @@ -231,7 +231,7 @@ pub fn handle_manifest( transaction .prepare() .map_err(Error::TransactionPrepareError)? - .get_executable(initial_proofs), + .get_executable(), ); if print_receipt { diff --git a/radix-engine-tests/benches/transfer.rs b/radix-engine-tests/benches/transfer.rs index f6c4de2b07a..bf60a7db288 100644 --- a/radix-engine-tests/benches/transfer.rs +++ b/radix-engine-tests/benches/transfer.rs @@ -36,10 +36,14 @@ fn bench_transfer(c: &mut Criterion) { &mut substate_db, &vm_modules, &ExecutionConfig::for_notarized_transaction(NetworkDefinition::simulator()), - TestTransaction::new_v1_from_nonce(manifest, 1) - .prepare() - .unwrap() - .get_executable(btreeset![NonFungibleGlobalId::from_public_key(&public_key)]), + TestTransaction::new_v1_from_nonce( + manifest, + 1, + btreeset![NonFungibleGlobalId::from_public_key(&public_key)], + ) + .prepare() + .unwrap() + .get_executable(), ) .expect_commit(true) .new_component_addresses()[0]; @@ -62,10 +66,14 @@ fn bench_transfer(c: &mut Criterion) { &mut substate_db, &vm_modules, &ExecutionConfig::for_notarized_transaction(NetworkDefinition::simulator()), - TestTransaction::new_v1_from_nonce(manifest.clone(), nonce) - .prepare() - .unwrap() - .get_executable(btreeset![NonFungibleGlobalId::from_public_key(&public_key)]), + TestTransaction::new_v1_from_nonce( + manifest.clone(), + nonce, + btreeset![NonFungibleGlobalId::from_public_key(&public_key)], + ) + .prepare() + .unwrap() + .get_executable(), ) .expect_commit(true); } @@ -85,10 +93,14 @@ fn bench_transfer(c: &mut Criterion) { &mut substate_db, &vm_modules, &ExecutionConfig::for_notarized_transaction(NetworkDefinition::simulator()), - TestTransaction::new_v1_from_nonce(manifest.clone(), nonce) - .prepare() - .unwrap() - .get_executable(btreeset![NonFungibleGlobalId::from_public_key(&public_key)]), + TestTransaction::new_v1_from_nonce( + manifest.clone(), + nonce, + btreeset![NonFungibleGlobalId::from_public_key(&public_key)], + ) + .prepare() + .unwrap() + .get_executable(), ); receipt.expect_commit_success(); nonce += 1; diff --git a/radix-engine-tests/tests/blueprints/account_locker.rs b/radix-engine-tests/tests/blueprints/account_locker.rs index 0bf43282371..e2069a89340 100644 --- a/radix-engine-tests/tests/blueprints/account_locker.rs +++ b/radix-engine-tests/tests/blueprints/account_locker.rs @@ -3059,9 +3059,10 @@ pub impl DefaultLedgerSimulator { }); let nonce = self.next_transaction_nonce(); - let test_transaction = TestTransaction::new_v1_from_nonce(manifest, nonce); + let test_transaction = + TestTransaction::new_v1_from_nonce(manifest, nonce, Default::default()); let prepared_transaction = test_transaction.prepare().unwrap(); - let executable = prepared_transaction.get_executable(Default::default()); + let executable = prepared_transaction.get_executable(); self.execute_transaction(executable, execution_config) } } diff --git a/radix-engine-tests/tests/blueprints/mod.rs b/radix-engine-tests/tests/blueprints/mod.rs index fb3682918e0..888ecc05384 100644 --- a/radix-engine-tests/tests/blueprints/mod.rs +++ b/radix-engine-tests/tests/blueprints/mod.rs @@ -24,7 +24,6 @@ mod reentrancy; mod resource; mod static_dependencies; mod time; -mod transaction_tracker; mod tx_processor; mod tx_processor_access; mod validator; diff --git a/radix-engine-tests/tests/kernel/kernel_open_substate.rs b/radix-engine-tests/tests/kernel/kernel_open_substate.rs index c76b3ff8b72..60c2fd7413a 100644 --- a/radix-engine-tests/tests/kernel/kernel_open_substate.rs +++ b/radix-engine-tests/tests/kernel/kernel_open_substate.rs @@ -34,10 +34,11 @@ pub fn test_open_substate_of_invisible_package_address() { let transaction = TestTransaction::new_v1_from_nonce( ManifestBuilder::new().lock_fee_from_faucet().build(), 1, + btreeset![], ) .prepare() .unwrap(); - let executable = transaction.get_executable(btreeset![]); + let executable = transaction.get_executable(); // Create database and bootstrap let mut database = InMemorySubstateDatabase::standard(); diff --git a/radix-engine-tests/tests/kernel/panics.rs b/radix-engine-tests/tests/kernel/panics.rs index 64049584d8b..11248e10a25 100644 --- a/radix-engine-tests/tests/kernel/panics.rs +++ b/radix-engine-tests/tests/kernel/panics.rs @@ -42,8 +42,13 @@ impl KernelApi for MockKernel { type CallbackObject = System; } -impl KernelThreadApi for MockKernel { +impl KernelStackApi for MockKernel { type CallFrameData = Actor; + + fn kernel_switch_stack(&mut self, _id: usize) -> Result<(), RuntimeError> { + panic1!() + } + fn kernel_set_call_frame_data(&mut self, _data: Actor) -> Result<(), RuntimeError> { panic1!() } diff --git a/radix-engine-tests/tests/kernel/transaction_multi_threaded.rs b/radix-engine-tests/tests/kernel/transaction_multi_threaded.rs index 9b4eb48c1aa..add280a5adf 100644 --- a/radix-engine-tests/tests/kernel/transaction_multi_threaded.rs +++ b/radix-engine-tests/tests/kernel/transaction_multi_threaded.rs @@ -44,12 +44,14 @@ mod multi_threaded_test { &mut substate_db, &vm_modules, &ExecutionConfig::for_test_transaction(), - TestTransaction::new_v1(manifest, hash(format!("Account creation: {i}"))) - .prepare() - .unwrap() - .get_executable(btreeset![NonFungibleGlobalId::from_public_key( - &public_key - )]), + TestTransaction::new_v1( + manifest, + hash(format!("Account creation: {i}")), + btreeset![NonFungibleGlobalId::from_public_key(&public_key)], + ) + .prepare() + .unwrap() + .get_executable(), ) .expect_commit(true) .new_component_addresses()[0]; @@ -71,10 +73,14 @@ mod multi_threaded_test { &mut substate_db, &vm_modules, &ExecutionConfig::for_test_transaction(), - TestTransaction::new_v1(manifest.clone(), hash(format!("Fill account: {}", nonce))) - .prepare() - .expect("Expected transaction to be preparable") - .get_executable(btreeset![NonFungibleGlobalId::from_public_key(&public_key)]), + TestTransaction::new_v1( + manifest.clone(), + hash(format!("Fill account: {}", nonce)), + btreeset![NonFungibleGlobalId::from_public_key(&public_key)], + ) + .prepare() + .expect("Expected transaction to be preparable") + .get_executable(), ) .expect_commit(true); } @@ -96,12 +102,14 @@ mod multi_threaded_test { &substate_db, &vm_modules, &ExecutionConfig::for_test_transaction(), - TestTransaction::new_v1(manifest.clone(), hash(format!("Transfer"))) - .prepare() - .expect("Expected transaction to be preparable") - .get_executable(btreeset![NonFungibleGlobalId::from_public_key( - &public_key, - )]), + TestTransaction::new_v1( + manifest.clone(), + hash(format!("Transfer")), + btreeset![NonFungibleGlobalId::from_public_key(&public_key,)], + ) + .prepare() + .expect("Expected transaction to be preparable") + .get_executable(), ); receipt.expect_commit_success(); println!("receipt = {:?}", receipt); diff --git a/radix-engine-tests/tests/protocol/transaction.rs b/radix-engine-tests/tests/protocol/transaction.rs index a9db4de1cad..715ad7776a3 100644 --- a/radix-engine-tests/tests/protocol/transaction.rs +++ b/radix-engine-tests/tests/protocol/transaction.rs @@ -19,14 +19,19 @@ fn bottlenose_protocol_should_not_support_v2_transactions() { let manifest = ManifestBuilder::new_v2() .lock_standard_test_fee(account) .build(); - (manifest, ledger.next_transaction_nonce()) + ( + manifest, + ledger.next_transaction_nonce(), + vec![], + btreeset![NonFungibleGlobalId::from_public_key(&public_key)], + ) }) .collect(); let receipt = ledger.execute_transaction( TestTransaction::new_v2_from_nonce(intents) .prepare() .expect("expected transaction to be preparable") - .get_executable(btreeset![NonFungibleGlobalId::from_public_key(&public_key)]), + .get_executable(), ExecutionConfig::for_test_transaction(), ); diff --git a/radix-engine-tests/tests/system/mod.rs b/radix-engine-tests/tests/system/mod.rs index f064353e333..d9519eb8efa 100644 --- a/radix-engine-tests/tests/system/mod.rs +++ b/radix-engine-tests/tests/system/mod.rs @@ -50,6 +50,9 @@ mod royalty; mod royalty_auth; mod royalty_edge_cases; mod schema_sanity_check; +mod subintent_auth; +mod subintent_leaks; +mod subintent_txn_shape; mod system; mod system_access_rule; mod system_actor_collection; @@ -68,6 +71,7 @@ mod toolkit_receipt; mod track; mod transaction_limits; mod transaction_runtime; +mod transaction_tracker; mod typed_substate_layout; mod vault; mod vault_burn; diff --git a/radix-engine-tests/tests/system/subintent_auth.rs b/radix-engine-tests/tests/system/subintent_auth.rs new file mode 100644 index 00000000000..8597e7e4c9a --- /dev/null +++ b/radix-engine-tests/tests/system/subintent_auth.rs @@ -0,0 +1,192 @@ +use radix_common::constants::TRANSACTION_PROCESSOR_PACKAGE; +use radix_common::crypto::Hash; +use radix_common::prelude::{manifest_encode, FromPublicKey, NonFungibleGlobalId, XRD}; +use radix_common::ManifestSbor; +use radix_engine::errors::{RuntimeError, SystemModuleError}; +use radix_engine::system::system_modules::auth::AuthError; +use radix_engine::transaction::ExecutionConfig; +use radix_engine_interface::blueprints::transaction_processor::{ + TRANSACTION_PROCESSOR_BLUEPRINT, TRANSACTION_PROCESSOR_RUN_IDENT, +}; +use radix_engine_interface::macros::dec; +use radix_rust::btreeset; +use radix_rust::prelude::{index_map_new, IndexMap}; +use radix_transactions::builder::ManifestBuilder; +use radix_transactions::model::{InstructionV1, ManifestIntent, TestTransaction}; +use scrypto_test::ledger_simulator::LedgerSimulatorBuilder; + +#[test] +fn should_not_be_able_to_use_root_auth_in_subintent() { + // Arrange + let mut ledger = LedgerSimulatorBuilder::new().build(); + let (public_key, _, account) = ledger.new_allocated_account(); + + // Act + let intents = vec![ + { + let manifest = ManifestBuilder::new_v2() + .lock_standard_test_fee(account) + .yield_to_child(ManifestIntent(0), ()) + .build(); + + ( + manifest, + ledger.next_transaction_nonce(), + vec![1], + btreeset![NonFungibleGlobalId::from_public_key(&public_key)], + ) + }, + { + let manifest = ManifestBuilder::new_v2() + .withdraw_from_account(account, XRD, dec!(10)) + .deposit_entire_worktop(account) + .build(); + + ( + manifest, + ledger.next_transaction_nonce(), + vec![], + btreeset!(), + ) + }, + ]; + + let receipt = ledger.execute_transaction( + TestTransaction::new_v2_from_nonce(intents) + .prepare() + .expect("expected transaction to be preparable") + .get_executable(), + ExecutionConfig::for_test_transaction(), + ); + + // Assert + receipt.expect_specific_failure(|e| { + matches!( + e, + RuntimeError::SystemModuleError(SystemModuleError::AuthError(..)) + ) + }); +} + +#[test] +fn should_be_able_to_use_separate_auth_in_subintent() { + // Arrange + let mut ledger = LedgerSimulatorBuilder::new().build(); + let (public_key, _, account) = ledger.new_allocated_account(); + let (public_key2, _, account2) = ledger.new_allocated_account(); + + // Act + let intents = vec![ + { + let manifest = ManifestBuilder::new_v2() + .lock_standard_test_fee(account) + .yield_to_child(ManifestIntent(0), ()) + .build(); + + ( + manifest, + ledger.next_transaction_nonce(), + vec![1], + btreeset![NonFungibleGlobalId::from_public_key(&public_key)], + ) + }, + { + let manifest = ManifestBuilder::new_v2() + .withdraw_from_account(account2, XRD, dec!(10)) + .deposit_entire_worktop(account2) + .build(); + + ( + manifest, + ledger.next_transaction_nonce(), + vec![], + btreeset![NonFungibleGlobalId::from_public_key(&public_key2)], + ) + }, + ]; + + let receipt = ledger.execute_transaction( + TestTransaction::new_v2_from_nonce(intents) + .prepare() + .expect("expected transaction to be preparable") + .get_executable(), + ExecutionConfig::for_test_transaction(), + ); + + // Assert + receipt.expect_commit_success(); +} + +#[derive(Debug, Eq, PartialEq, ManifestSbor)] +pub struct ManifestTransactionProcessorRunInput { + pub manifest_encoded_instructions: Vec, + pub global_address_reservations: Vec<()>, + pub references: Vec<()>, + pub blobs: IndexMap>, +} + +#[test] +fn should_not_be_able_to_call_tx_processor_in_subintent() { + // Arrange + let mut ledger = LedgerSimulatorBuilder::new().build(); + let (public_key, _, account) = ledger.new_allocated_account(); + + // Act + let intents = vec![ + { + let manifest = ManifestBuilder::new_v2() + .lock_standard_test_fee(account) + .yield_to_child(ManifestIntent(0), ()) + .build(); + + ( + manifest, + ledger.next_transaction_nonce(), + vec![1], + btreeset![NonFungibleGlobalId::from_public_key(&public_key)], + ) + }, + { + let instructions: Vec = Vec::new(); + let manifest_encoded_instructions = manifest_encode(&instructions).unwrap(); + let manifest = ManifestBuilder::new_v2() + .call_function( + TRANSACTION_PROCESSOR_PACKAGE, + TRANSACTION_PROCESSOR_BLUEPRINT, + TRANSACTION_PROCESSOR_RUN_IDENT, + ManifestTransactionProcessorRunInput { + manifest_encoded_instructions, + global_address_reservations: vec![], + references: vec![], + blobs: index_map_new(), + }, + ) + .build(); + + ( + manifest, + ledger.next_transaction_nonce(), + vec![], + btreeset![], + ) + }, + ]; + + let receipt = ledger.execute_transaction( + TestTransaction::new_v2_from_nonce(intents) + .prepare() + .expect("expected transaction to be preparable") + .get_executable(), + ExecutionConfig::for_test_transaction(), + ); + + // Assert + receipt.expect_specific_failure(|e| { + matches!( + e, + RuntimeError::SystemModuleError(SystemModuleError::AuthError(AuthError::Unauthorized( + .. + ))) + ) + }); +} diff --git a/radix-engine-tests/tests/system/subintent_leaks.rs b/radix-engine-tests/tests/system/subintent_leaks.rs new file mode 100644 index 00000000000..460252df38d --- /dev/null +++ b/radix-engine-tests/tests/system/subintent_leaks.rs @@ -0,0 +1,113 @@ +use radix_common::constants::XRD; +use radix_common::prelude::{FromPublicKey, NonFungibleGlobalId}; +use radix_engine::blueprints::resource::FungibleResourceManagerError; +use radix_engine::errors::{ApplicationError, RuntimeError}; +use radix_engine::transaction::ExecutionConfig; +use radix_engine_interface::macros::dec; +use radix_rust::btreeset; +use radix_transactions::model::{ManifestIntent, TestTransaction}; +use radix_transactions::prelude::ManifestBuilder; +use scrypto_test::ledger_simulator::LedgerSimulatorBuilder; + +#[test] +fn bucket_leak_in_subintent_should_fail() { + // Arrange + let mut ledger = LedgerSimulatorBuilder::new().build(); + let (public_key, _, account) = ledger.new_allocated_account(); + let (public_key2, _, account2) = ledger.new_allocated_account(); + + // Act + let intents = vec![ + { + let manifest = ManifestBuilder::new_v2() + .lock_standard_test_fee(account) + .yield_to_child(ManifestIntent(0), ()) + .build(); + + ( + manifest, + ledger.next_transaction_nonce(), + vec![1], + btreeset![NonFungibleGlobalId::from_public_key(&public_key)], + ) + }, + { + let manifest = ManifestBuilder::new_v2() + .withdraw_from_account(account2, XRD, dec!(10)) + .build(); + + ( + manifest, + ledger.next_transaction_nonce(), + vec![], + btreeset![NonFungibleGlobalId::from_public_key(&public_key2)], + ) + }, + ]; + + let receipt = ledger.execute_transaction( + TestTransaction::new_v2_from_nonce(intents) + .prepare() + .expect("expected transaction to be preparable") + .get_executable(), + ExecutionConfig::for_test_transaction(), + ); + + // Assert + receipt.expect_specific_failure(|e| { + matches!( + e, + RuntimeError::ApplicationError(ApplicationError::FungibleResourceManagerError( + FungibleResourceManagerError::DropNonEmptyBucket + )) + ) + }); +} + +#[test] +fn proofs_in_subintent_should_autodrop() { + // Arrange + let mut ledger = LedgerSimulatorBuilder::new().build(); + let (public_key, _, account) = ledger.new_allocated_account(); + let (public_key2, _, account2) = ledger.new_allocated_account(); + + // Act + let intents = vec![ + { + let manifest = ManifestBuilder::new_v2() + .lock_standard_test_fee(account) + .yield_to_child(ManifestIntent(0), ()) + .build(); + + ( + manifest, + ledger.next_transaction_nonce(), + vec![1], + btreeset![NonFungibleGlobalId::from_public_key(&public_key)], + ) + }, + { + let manifest = ManifestBuilder::new_v2() + .create_proof_from_account_of_amount(account2, XRD, dec!(10)) + .build(); + + ( + manifest, + ledger.next_transaction_nonce(), + vec![], + btreeset![NonFungibleGlobalId::from_public_key(&public_key2)], + ) + }, + ]; + + let receipt = ledger.execute_transaction( + TestTransaction::new_v2_from_nonce(intents) + .prepare() + .expect("expected transaction to be preparable") + .get_executable(), + ExecutionConfig::for_test_transaction(), + ); + + // Assert + receipt.expect_commit_success(); +} diff --git a/radix-engine-tests/tests/system/subintent_txn_shape.rs b/radix-engine-tests/tests/system/subintent_txn_shape.rs new file mode 100644 index 00000000000..a9c5b33ef89 --- /dev/null +++ b/radix-engine-tests/tests/system/subintent_txn_shape.rs @@ -0,0 +1,61 @@ +use radix_common::prelude::{FromPublicKey, NonFungibleGlobalId}; +use radix_engine::transaction::ExecutionConfig; +use radix_rust::btreeset; +use radix_transactions::model::{ManifestIntent, TestTransaction}; +use radix_transactions::prelude::ManifestBuilder; +use scrypto_test::ledger_simulator::LedgerSimulatorBuilder; + +#[test] +fn simple_subintent_should_work() { + test_subintent_txn_shape(vec![vec![1], vec![]]); +} + +#[test] +fn multiple_flat_subintents_should_work() { + test_subintent_txn_shape(vec![vec![1, 2, 3, 4], vec![], vec![], vec![], vec![]]); +} + +#[test] +fn multiple_deep_subintents_should_work() { + test_subintent_txn_shape(vec![vec![1], vec![2], vec![3], vec![4], vec![]]); +} + +fn test_subintent_txn_shape(children: Vec>) { + // Arrange + let mut ledger = LedgerSimulatorBuilder::new().build(); + let (public_key, _, account) = ledger.new_allocated_account(); + + // Act + let mut intents = vec![]; + + for (index, intent_children) in children.into_iter().enumerate() { + let mut builder = ManifestBuilder::new_v2(); + if index == 0 { + builder = builder.lock_standard_test_fee(account); + } + for (index, _) in intent_children.iter().enumerate() { + builder = builder.yield_to_child(ManifestIntent(index as u32), ()); + } + + let manifest = builder.build(); + + let signatures = btreeset![NonFungibleGlobalId::from_public_key(&public_key)]; + intents.push(( + manifest, + ledger.next_transaction_nonce(), + intent_children, + signatures, + )) + } + + let receipt = ledger.execute_transaction( + TestTransaction::new_v2_from_nonce(intents) + .prepare() + .expect("expected transaction to be preparable") + .get_executable(), + ExecutionConfig::for_test_transaction(), + ); + + // Assert + receipt.expect_commit_success(); +} diff --git a/radix-engine-tests/tests/system/transaction_limits.rs b/radix-engine-tests/tests/system/transaction_limits.rs index 057cf6267a0..8b1994a8f84 100644 --- a/radix-engine-tests/tests/system/transaction_limits.rs +++ b/radix-engine-tests/tests/system/transaction_limits.rs @@ -44,7 +44,7 @@ fn test_read_non_existent_entries_from_kv_store_exceeding_limit() { ) .build(); - let transactions = TestTransaction::new_v1_from_nonce(manifest, 10); + let transactions = TestTransaction::new_v1_from_nonce(manifest, 10, btreeset!()); let prepared = transactions.prepare().unwrap(); let execution_config = { @@ -61,8 +61,7 @@ fn test_read_non_existent_entries_from_kv_store_exceeding_limit() { execution_config }; - let receipt = - ledger.execute_transaction(prepared.get_executable(btreeset!()), execution_config); + let receipt = ledger.execute_transaction(prepared.get_executable(), execution_config); // Assert receipt.expect_specific_failure(|e| { @@ -111,7 +110,7 @@ fn test_write_entries_to_kv_store_exceeding_limit() { ) .build(); - let transactions = TestTransaction::new_v1_from_nonce(manifest, 10); + let transactions = TestTransaction::new_v1_from_nonce(manifest, 10, btreeset!()); let prepared = transactions.prepare().unwrap(); let execution_config = { let mut execution_config = ExecutionConfig::for_test_transaction(); @@ -128,8 +127,7 @@ fn test_write_entries_to_kv_store_exceeding_limit() { execution_config }; - let receipt = - ledger.execute_transaction(prepared.get_executable(btreeset!()), execution_config); + let receipt = ledger.execute_transaction(prepared.get_executable(), execution_config); // Assert receipt.expect_specific_failure(|e| { @@ -162,7 +160,7 @@ fn test_write_entries_to_heap_kv_store_exceeding_limit() { ) .build(); - let transactions = TestTransaction::new_v1_from_nonce(manifest, 10); + let transactions = TestTransaction::new_v1_from_nonce(manifest, 10, btreeset!()); let prepared = transactions.prepare().unwrap(); let execution_config = { let mut execution_config = ExecutionConfig::for_test_transaction(); @@ -178,8 +176,7 @@ fn test_write_entries_to_heap_kv_store_exceeding_limit() { execution_config }; - let receipt = - ledger.execute_transaction(prepared.get_executable(btreeset!()), execution_config); + let receipt = ledger.execute_transaction(prepared.get_executable(), execution_config); // Assert receipt.expect_specific_failure(|e| { diff --git a/radix-engine-tests/tests/blueprints/transaction_tracker.rs b/radix-engine-tests/tests/system/transaction_tracker.rs similarity index 100% rename from radix-engine-tests/tests/blueprints/transaction_tracker.rs rename to radix-engine-tests/tests/system/transaction_tracker.rs diff --git a/radix-engine/src/blueprints/transaction_processor/blueprint.rs b/radix-engine/src/blueprints/transaction_processor/blueprint.rs index 2ec63e6ac2b..8f9d2928f1a 100644 --- a/radix-engine/src/blueprints/transaction_processor/blueprint.rs +++ b/radix-engine/src/blueprints/transaction_processor/blueprint.rs @@ -1,4 +1,4 @@ -use crate::blueprints::transaction_processor::TxnProcessor; +use crate::blueprints::transaction_processor::{ResumeResult, TxnProcessorThread}; use crate::errors::RuntimeError; use crate::internal_prelude::{Sbor, ScryptoEncode, ScryptoSbor}; use crate::kernel::kernel_api::{KernelNodeApi, KernelSubstateApi}; @@ -57,13 +57,17 @@ impl TransactionProcessorBlueprint { TransactionProcessorV1MinorVersion::Zero => usize::MAX, TransactionProcessorV1MinorVersion::One => MAX_TOTAL_BLOB_SIZE_PER_INVOCATION, }; - let txn_processor = TxnProcessor::::init( + let mut txn_processor_single_thread = TxnProcessorThread::::init( Rc::new(manifest_encoded_instructions), global_address_reservations, Rc::new(blobs), max_total_size_of_blobs, api, )?; - txn_processor.execute(api) + let resume_result = txn_processor_single_thread.resume(api)?; + if !matches!(resume_result, ResumeResult::Done) { + panic!("Unexpected yield occurred in v1 transaction processing"); + } + Ok(txn_processor_single_thread.outputs) } } diff --git a/radix-engine/src/blueprints/transaction_processor/instructions.rs b/radix-engine/src/blueprints/transaction_processor/instructions.rs index 0b15045b7c0..f189962e9e3 100644 --- a/radix-engine/src/blueprints/transaction_processor/instructions.rs +++ b/radix-engine/src/blueprints/transaction_processor/instructions.rs @@ -14,7 +14,12 @@ use radix_native_sdk::runtime::LocalAuthZone; use radix_rust::prelude::*; use radix_transactions::data::transform; use radix_transactions::manifest::*; -use radix_transactions::prelude::*; +use radix_transactions::model::{InstructionV1, InstructionV2}; + +pub enum Yield { + ToChild(usize), + ToParent, +} pub trait TxnInstruction { fn execute + KernelNodeApi + KernelSubstateApi, L: Default>( @@ -22,7 +27,7 @@ pub trait TxnInstruction { worktop: &mut Worktop, objects: &mut TxnProcessorObjects, api: &mut Y, - ) -> Result; + ) -> Result<(InstructionOutput, Option), RuntimeError>; } impl TxnInstruction for InstructionV1 { @@ -31,8 +36,8 @@ impl TxnInstruction for InstructionV1 { worktop: &mut Worktop, objects: &mut TxnProcessorObjects, api: &mut Y, - ) -> Result { - match self { + ) -> Result<(InstructionOutput, Option), RuntimeError> { + let output = match self { InstructionV1::TakeAllFromWorktop(i) => i.execute(worktop, objects, api), InstructionV1::TakeFromWorktop(i) => i.execute(worktop, objects, api), InstructionV1::TakeNonFungiblesFromWorktop(i) => i.execute(worktop, objects, api), @@ -67,11 +72,80 @@ impl TxnInstruction for InstructionV1 { InstructionV1::DropNamedProofs(i) => i.execute(worktop, objects, api), InstructionV1::DropAllProofs(i) => i.execute(worktop, objects, api), InstructionV1::AllocateGlobalAddress(i) => i.execute(worktop, objects, api), - } + }?; + Ok((output, None)) + } +} + +impl TxnInstruction for InstructionV2 { + fn execute + KernelNodeApi + KernelSubstateApi, L: Default>( + self, + worktop: &mut Worktop, + objects: &mut TxnProcessorObjects, + api: &mut Y, + ) -> Result<(InstructionOutput, Option), RuntimeError> { + let output = match self { + InstructionV2::TakeAllFromWorktop(i) => i.execute(worktop, objects, api), + InstructionV2::TakeFromWorktop(i) => i.execute(worktop, objects, api), + InstructionV2::TakeNonFungiblesFromWorktop(i) => i.execute(worktop, objects, api), + InstructionV2::ReturnToWorktop(i) => i.execute(worktop, objects, api), + InstructionV2::AssertWorktopContainsAny(i) => i.execute(worktop, objects, api), + InstructionV2::AssertWorktopContains(i) => i.execute(worktop, objects, api), + InstructionV2::AssertWorktopContainsNonFungibles(i) => i.execute(worktop, objects, api), + InstructionV2::AssertWorktopIsEmpty(_) => todo!(), + InstructionV2::PopFromAuthZone(i) => i.execute(worktop, objects, api), + InstructionV2::PushToAuthZone(i) => i.execute(worktop, objects, api), + InstructionV2::CreateProofFromAuthZoneOfAmount(i) => i.execute(worktop, objects, api), + InstructionV2::CreateProofFromAuthZoneOfNonFungibles(i) => { + i.execute(worktop, objects, api) + } + InstructionV2::CreateProofFromAuthZoneOfAll(i) => i.execute(worktop, objects, api), + InstructionV2::CreateProofFromBucketOfAmount(i) => i.execute(worktop, objects, api), + InstructionV2::CreateProofFromBucketOfNonFungibles(i) => { + i.execute(worktop, objects, api) + } + InstructionV2::CreateProofFromBucketOfAll(i) => i.execute(worktop, objects, api), + InstructionV2::DropAuthZoneProofs(i) => i.execute(worktop, objects, api), + InstructionV2::DropAuthZoneRegularProofs(i) => i.execute(worktop, objects, api), + InstructionV2::DropAuthZoneSignatureProofs(i) => i.execute(worktop, objects, api), + InstructionV2::BurnResource(i) => i.execute(worktop, objects, api), + InstructionV2::CloneProof(i) => i.execute(worktop, objects, api), + InstructionV2::DropProof(i) => i.execute(worktop, objects, api), + InstructionV2::CallFunction(i) => i.execute(worktop, objects, api), + InstructionV2::CallMethod(i) => i.execute(worktop, objects, api), + InstructionV2::CallRoyaltyMethod(i) => i.execute(worktop, objects, api), + InstructionV2::CallMetadataMethod(i) => i.execute(worktop, objects, api), + InstructionV2::CallRoleAssignmentMethod(i) => i.execute(worktop, objects, api), + InstructionV2::CallDirectVaultMethod(i) => i.execute(worktop, objects, api), + InstructionV2::DropNamedProofs(i) => i.execute(worktop, objects, api), + InstructionV2::DropAllProofs(i) => i.execute(worktop, objects, api), + InstructionV2::AllocateGlobalAddress(i) => i.execute(worktop, objects, api), + InstructionV2::VerifyParent(_) => todo!(), + InstructionV2::YieldToChild(i) => { + return Ok(( + InstructionOutput::None, + Some(Yield::ToChild(i.child_index.0 as usize)), + )); + } + InstructionV2::YieldToParent(_) => { + return Ok((InstructionOutput::None, Some(Yield::ToParent))); + } + }?; + + Ok((output, None)) } } -impl TxnInstruction for TakeAllFromWorktop { +pub trait TxnNormalInstruction { + fn execute + KernelNodeApi + KernelSubstateApi, L: Default>( + self, + worktop: &mut Worktop, + objects: &mut TxnProcessorObjects, + api: &mut Y, + ) -> Result; +} + +impl TxnNormalInstruction for TakeAllFromWorktop { fn execute + KernelNodeApi + KernelSubstateApi, L: Default>( self, worktop: &mut Worktop, @@ -84,7 +158,7 @@ impl TxnInstruction for TakeAllFromWorktop { } } -impl TxnInstruction for TakeFromWorktop { +impl TxnNormalInstruction for TakeFromWorktop { fn execute + KernelNodeApi + KernelSubstateApi, L: Default>( self, worktop: &mut Worktop, @@ -97,7 +171,7 @@ impl TxnInstruction for TakeFromWorktop { } } -impl TxnInstruction for TakeNonFungiblesFromWorktop { +impl TxnNormalInstruction for TakeNonFungiblesFromWorktop { fn execute + KernelNodeApi + KernelSubstateApi, L: Default>( self, worktop: &mut Worktop, @@ -114,7 +188,7 @@ impl TxnInstruction for TakeNonFungiblesFromWorktop { } } -impl TxnInstruction for ReturnToWorktop { +impl TxnNormalInstruction for ReturnToWorktop { fn execute + KernelNodeApi + KernelSubstateApi, L: Default>( self, worktop: &mut Worktop, @@ -127,7 +201,7 @@ impl TxnInstruction for ReturnToWorktop { } } -impl TxnInstruction for AssertWorktopContainsAny { +impl TxnNormalInstruction for AssertWorktopContainsAny { fn execute + KernelNodeApi + KernelSubstateApi, L: Default>( self, worktop: &mut Worktop, @@ -139,7 +213,7 @@ impl TxnInstruction for AssertWorktopContainsAny { } } -impl TxnInstruction for AssertWorktopContains { +impl TxnNormalInstruction for AssertWorktopContains { fn execute + KernelNodeApi + KernelSubstateApi, L: Default>( self, worktop: &mut Worktop, @@ -151,7 +225,7 @@ impl TxnInstruction for AssertWorktopContains { } } -impl TxnInstruction for AssertWorktopContainsNonFungibles { +impl TxnNormalInstruction for AssertWorktopContainsNonFungibles { fn execute + KernelNodeApi + KernelSubstateApi, L: Default>( self, worktop: &mut Worktop, @@ -167,7 +241,7 @@ impl TxnInstruction for AssertWorktopContainsNonFungibles { } } -impl TxnInstruction for PopFromAuthZone { +impl TxnNormalInstruction for PopFromAuthZone { fn execute + KernelNodeApi + KernelSubstateApi, L: Default>( self, _worktop: &mut Worktop, @@ -182,7 +256,7 @@ impl TxnInstruction for PopFromAuthZone { } } -impl TxnInstruction for PushToAuthZone { +impl TxnNormalInstruction for PushToAuthZone { fn execute + KernelNodeApi + KernelSubstateApi, L: Default>( self, _worktop: &mut Worktop, @@ -195,7 +269,7 @@ impl TxnInstruction for PushToAuthZone { } } -impl TxnInstruction for CreateProofFromAuthZoneOfAmount { +impl TxnNormalInstruction for CreateProofFromAuthZoneOfAmount { fn execute + KernelNodeApi + KernelSubstateApi, L: Default>( self, _worktop: &mut Worktop, @@ -208,7 +282,7 @@ impl TxnInstruction for CreateProofFromAuthZoneOfAmount { } } -impl TxnInstruction for CreateProofFromAuthZoneOfNonFungibles { +impl TxnNormalInstruction for CreateProofFromAuthZoneOfNonFungibles { fn execute + KernelNodeApi + KernelSubstateApi, L: Default>( self, _worktop: &mut Worktop, @@ -225,7 +299,7 @@ impl TxnInstruction for CreateProofFromAuthZoneOfNonFungibles { } } -impl TxnInstruction for CreateProofFromAuthZoneOfAll { +impl TxnNormalInstruction for CreateProofFromAuthZoneOfAll { fn execute + KernelNodeApi + KernelSubstateApi, L: Default>( self, _worktop: &mut Worktop, @@ -238,7 +312,7 @@ impl TxnInstruction for CreateProofFromAuthZoneOfAll { } } -impl TxnInstruction for CreateProofFromBucketOfAmount { +impl TxnNormalInstruction for CreateProofFromBucketOfAmount { fn execute + KernelNodeApi + KernelSubstateApi, L: Default>( self, _worktop: &mut Worktop, @@ -252,7 +326,7 @@ impl TxnInstruction for CreateProofFromBucketOfAmount { } } -impl TxnInstruction for CreateProofFromBucketOfNonFungibles { +impl TxnNormalInstruction for CreateProofFromBucketOfNonFungibles { fn execute + KernelNodeApi + KernelSubstateApi, L: Default>( self, _worktop: &mut Worktop, @@ -266,7 +340,7 @@ impl TxnInstruction for CreateProofFromBucketOfNonFungibles { } } -impl TxnInstruction for CreateProofFromBucketOfAll { +impl TxnNormalInstruction for CreateProofFromBucketOfAll { fn execute + KernelNodeApi + KernelSubstateApi, L: Default>( self, _worktop: &mut Worktop, @@ -280,7 +354,7 @@ impl TxnInstruction for CreateProofFromBucketOfAll { } } -impl TxnInstruction for DropAuthZoneProofs { +impl TxnNormalInstruction for DropAuthZoneProofs { fn execute + KernelNodeApi + KernelSubstateApi, L: Default>( self, _worktop: &mut Worktop, @@ -292,7 +366,7 @@ impl TxnInstruction for DropAuthZoneProofs { } } -impl TxnInstruction for DropAuthZoneRegularProofs { +impl TxnNormalInstruction for DropAuthZoneRegularProofs { fn execute + KernelNodeApi + KernelSubstateApi, L: Default>( self, _worktop: &mut Worktop, @@ -304,7 +378,7 @@ impl TxnInstruction for DropAuthZoneRegularProofs { } } -impl TxnInstruction for DropAuthZoneSignatureProofs { +impl TxnNormalInstruction for DropAuthZoneSignatureProofs { fn execute + KernelNodeApi + KernelSubstateApi, L: Default>( self, _worktop: &mut Worktop, @@ -316,7 +390,7 @@ impl TxnInstruction for DropAuthZoneSignatureProofs { } } -impl TxnInstruction for BurnResource { +impl TxnNormalInstruction for BurnResource { fn execute + KernelNodeApi + KernelSubstateApi, L: Default>( self, worktop: &mut Worktop, @@ -332,7 +406,7 @@ impl TxnInstruction for BurnResource { } } -impl TxnInstruction for CloneProof { +impl TxnNormalInstruction for CloneProof { fn execute + KernelNodeApi + KernelSubstateApi, L: Default>( self, _worktop: &mut Worktop, @@ -346,7 +420,7 @@ impl TxnInstruction for CloneProof { } } -impl TxnInstruction for DropProof { +impl TxnNormalInstruction for DropProof { fn execute + KernelNodeApi + KernelSubstateApi, L: Default>( self, _worktop: &mut Worktop, @@ -384,7 +458,7 @@ fn handle_invocation + KernelSubstateApi, L: Defau Ok(InstructionOutput::CallReturn(result.into())) } -impl TxnInstruction for CallFunction { +impl TxnNormalInstruction for CallFunction { fn execute + KernelNodeApi + KernelSubstateApi, L: Default>( self, worktop: &mut Worktop, @@ -403,7 +477,7 @@ impl TxnInstruction for CallFunction { } } -impl TxnInstruction for CallMethod { +impl TxnNormalInstruction for CallMethod { fn execute + KernelNodeApi + KernelSubstateApi, L: Default>( self, worktop: &mut Worktop, @@ -421,7 +495,7 @@ impl TxnInstruction for CallMethod { } } -impl TxnInstruction for CallRoyaltyMethod { +impl TxnNormalInstruction for CallRoyaltyMethod { fn execute + KernelNodeApi + KernelSubstateApi, L: Default>( self, worktop: &mut Worktop, @@ -440,7 +514,7 @@ impl TxnInstruction for CallRoyaltyMethod { } } -impl TxnInstruction for CallMetadataMethod { +impl TxnNormalInstruction for CallMetadataMethod { fn execute + KernelNodeApi + KernelSubstateApi, L: Default>( self, worktop: &mut Worktop, @@ -459,7 +533,7 @@ impl TxnInstruction for CallMetadataMethod { } } -impl TxnInstruction for CallRoleAssignmentMethod { +impl TxnNormalInstruction for CallRoleAssignmentMethod { fn execute + KernelNodeApi + KernelSubstateApi, L: Default>( self, worktop: &mut Worktop, @@ -478,7 +552,7 @@ impl TxnInstruction for CallRoleAssignmentMethod { } } -impl TxnInstruction for CallDirectVaultMethod { +impl TxnNormalInstruction for CallDirectVaultMethod { fn execute + KernelNodeApi + KernelSubstateApi, L: Default>( self, worktop: &mut Worktop, @@ -495,7 +569,7 @@ impl TxnInstruction for CallDirectVaultMethod { } } -impl TxnInstruction for DropNamedProofs { +impl TxnNormalInstruction for DropNamedProofs { fn execute + KernelNodeApi + KernelSubstateApi, L: Default>( self, _worktop: &mut Worktop, @@ -510,7 +584,7 @@ impl TxnInstruction for DropNamedProofs { } } -impl TxnInstruction for DropAllProofs { +impl TxnNormalInstruction for DropAllProofs { fn execute + KernelNodeApi + KernelSubstateApi, L: Default>( self, _worktop: &mut Worktop, @@ -526,7 +600,7 @@ impl TxnInstruction for DropAllProofs { } } -impl TxnInstruction for AllocateGlobalAddress { +impl TxnNormalInstruction for AllocateGlobalAddress { fn execute + KernelNodeApi + KernelSubstateApi, L: Default>( self, _worktop: &mut Worktop, diff --git a/radix-engine/src/blueprints/transaction_processor/mod.rs b/radix-engine/src/blueprints/transaction_processor/mod.rs index c07d0c93e42..f293a45ceb3 100644 --- a/radix-engine/src/blueprints/transaction_processor/mod.rs +++ b/radix-engine/src/blueprints/transaction_processor/mod.rs @@ -1,9 +1,8 @@ mod blueprint; mod instructions; mod package; -mod tx_processor; +pub use crate::system::transaction::txn_processor::*; pub use blueprint::*; pub use instructions::*; pub use package::*; -pub use tx_processor::*; diff --git a/radix-engine/src/kernel/kernel.rs b/radix-engine/src/kernel/kernel.rs index db9e6d84061..7fba828e239 100644 --- a/radix-engine/src/kernel/kernel.rs +++ b/radix-engine/src/kernel/kernel.rs @@ -17,9 +17,10 @@ use sbor::rust::mem; macro_rules! as_read_only { ($kernel:expr) => {{ + let (current_frame, prev_frame) = $kernel.stacks.cur_and_prev(); KernelReadOnly { - current_frame: &$kernel.current_frame, - prev_frame: $kernel.prev_frame_stack.last(), + current_frame, + prev_frame, heap: &$kernel.substate_io.heap, callback: $kernel.callback, } @@ -104,12 +105,7 @@ impl<'h, M: KernelTransactionCallbackObject, S: SubstateDatabase> BootLoader<'h, &mut self.track, &mut self.id_allocator, &mut system, - // TODO: Fix to take call frame inits for each intent - { - let mut call_frame_inits = call_frame_inits; - let first_call_frame_init = call_frame_inits.drain(..).next().unwrap(); - first_call_frame_init - }, + call_frame_inits, ); // Execution @@ -118,7 +114,9 @@ impl<'h, M: KernelTransactionCallbackObject, S: SubstateDatabase> BootLoader<'h, let output = M::start(&mut kernel, executable)?; // Sanity check call frame - assert!(kernel.prev_frame_stack.is_empty()); + for stack in &kernel.stacks.stacks { + assert!(stack.prev_frames.is_empty()); + } // Sanity check heap assert!(kernel.substate_io.heap.is_empty()); @@ -136,6 +134,125 @@ impl<'h, M: KernelTransactionCallbackObject, S: SubstateDatabase> BootLoader<'h, } } +pub struct KernelStack { + current_frame: CallFrame, + prev_frames: Vec>, +} + +impl KernelStack { + pub fn new(init: CallFrameInit) -> Self { + Self { + current_frame: CallFrame::new_root(init), + prev_frames: vec![], + } + } +} + +pub struct KernelStacks { + stack_pointer: usize, + stacks: Vec>, +} + +impl KernelStacks { + pub fn new(call_frames: Vec>) -> Self { + let stacks = call_frames + .into_iter() + .map(|call_frame| KernelStack::new(call_frame)) + .collect(); + Self { + stack_pointer: 0usize, + stacks, + } + } + + pub fn push(&mut self, frame: CallFrame) { + let stack = self.stacks.get_mut(self.stack_pointer).unwrap(); + let parent = mem::replace(&mut stack.current_frame, frame); + stack.prev_frames.push(parent); + } + + pub fn pop(&mut self) { + let stack = self.stacks.get_mut(self.stack_pointer).unwrap(); + let parent = stack.prev_frames.pop().unwrap(); + let _ = core::mem::replace(&mut stack.current_frame, parent); + } + + pub fn switch(&mut self, id: usize) { + self.stack_pointer = id; + } + + pub fn cur_mut_and_other_mut( + &mut self, + other_stack: usize, + ) -> ( + &mut CallFrame, + &mut CallFrame, + ) { + let mut mut_stacks: Vec<_> = self + .stacks + .iter_mut() + .enumerate() + .filter(|(id, _)| (*id).eq(&self.stack_pointer) || (*id).eq(&other_stack)) + .map(|stack| Some(stack)) + .collect(); + + let (id0, stack0) = mut_stacks[0].take().unwrap(); + let (_id1, stack1) = mut_stacks[1].take().unwrap(); + if id0.eq(&self.stack_pointer) { + (&mut stack0.current_frame, &mut stack1.current_frame) + } else { + (&mut stack1.current_frame, &mut stack0.current_frame) + } + } + + pub fn cur_and_prev( + &self, + ) -> ( + &CallFrame, + Option<&CallFrame>, + ) { + let stack = self.stacks.get(self.stack_pointer).unwrap(); + (&stack.current_frame, stack.prev_frames.last()) + } + + pub fn cur_mut_and_prev( + &mut self, + ) -> ( + &mut CallFrame, + Option<&CallFrame>, + ) { + let stack = self.stacks.get_mut(self.stack_pointer).unwrap(); + (&mut stack.current_frame, stack.prev_frames.last()) + } + + pub fn cur_mut_and_prev_mut( + &mut self, + ) -> ( + &mut CallFrame, + Option<&mut CallFrame>, + ) { + let stack = self.stacks.get_mut(self.stack_pointer).unwrap(); + (&mut stack.current_frame, stack.prev_frames.last_mut()) + } + + pub fn cur(&self) -> &CallFrame { + &self.stacks.get(self.stack_pointer).unwrap().current_frame + } + + pub fn cur_mut(&mut self) -> &mut CallFrame { + &mut self + .stacks + .get_mut(self.stack_pointer) + .unwrap() + .current_frame + } + + pub fn prev_frames_mut(&mut self) -> &mut Vec> { + let stack = self.stacks.get_mut(self.stack_pointer).unwrap(); + &mut stack.prev_frames + } +} + pub struct Kernel< 'g, // Lifetime of values outliving all frames M, // Upstream System layer @@ -144,12 +261,7 @@ pub struct Kernel< M: KernelCallbackObject, S: CommitableSubstateStore, { - /// Stack - current_frame: CallFrame, - // This stack could potentially be removed and just use the native stack - // but keeping this call_frames stack may potentially prove useful if implementing - // execution pause and/or for better debuggability - prev_frame_stack: Vec>, + stacks: KernelStacks, substate_io: SubstateIO<'g, S>, @@ -171,7 +283,16 @@ impl< id_allocator: &'g mut IdAllocator, callback: &'g mut M, ) -> Self { - Self::new(store, id_allocator, callback, Default::default()) + Self::new( + store, + id_allocator, + callback, + vec![CallFrameInit { + data: M::CallFrameData::default(), + direct_accesses: Default::default(), + global_addresses: Default::default(), + }], + ) } } @@ -180,9 +301,10 @@ impl<'g, M: KernelCallbackObject, S: CommitableSubstateStore + BootStore> Kernel store: &'g mut S, id_allocator: &'g mut IdAllocator, callback: &'g mut M, - call_frame_init: CallFrameInit, + call_frame_inits: Vec>, ) -> Self { Kernel { + stacks: KernelStacks::new(call_frame_inits), substate_io: SubstateIO { heap: Heap::new(), store, @@ -192,8 +314,6 @@ impl<'g, M: KernelCallbackObject, S: CommitableSubstateStore + BootStore> Kernel pinned_to_heap: BTreeSet::new(), }, id_allocator, - current_frame: CallFrame::new_root(call_frame_init), - prev_frame_stack: vec![], callback, } } @@ -273,7 +393,8 @@ where fn kernel_pin_node(&mut self, node_id: NodeId) -> Result<(), RuntimeError> { M::on_pin_node(&node_id, &mut as_read_only!(self))?; - self.current_frame + self.stacks + .cur_mut() .pin_node(&mut self.substate_io, node_id) .map_err(|e| { RuntimeError::KernelError(KernelError::CallFrameError( @@ -301,15 +422,17 @@ where &mut read_only, )?; + let (cur_frame, prev_frame) = self.stacks.cur_mut_and_prev(); + let mut handler = KernelHandler { callback: self.callback, - prev_frame: self.prev_frame_stack.last(), + prev_frame, on_io_access: |api, io_access| { M::on_create_node(CreateNodeEvent::IOAccess(&io_access), api) }, }; - self.current_frame + cur_frame .create_node(&mut self.substate_io, &mut handler, node_id, node_substates) .map_err(|e| match e { CallbackError::Error(e) => RuntimeError::KernelError(KernelError::CallFrameError( @@ -338,15 +461,17 @@ where &mut read_only, )?; + let (cur_frame, prev_frame) = self.stacks.cur_mut_and_prev(); + let mut handler = KernelHandler { callback: self.callback, - prev_frame: self.prev_frame_stack.last(), + prev_frame, on_io_access: |api, io_access| { M::on_create_node(CreateNodeEvent::IOAccess(&io_access), api) }, }; - self.current_frame + cur_frame .create_node( &mut self.substate_io, &mut handler, @@ -365,16 +490,18 @@ where } { + let (cur_frame, prev_frame) = self.stacks.cur_mut_and_prev(); + let mut handler = KernelHandler { callback: self.callback, - prev_frame: self.prev_frame_stack.last(), + prev_frame, on_io_access: |api, io_access| { M::on_move_module(MoveModuleEvent::IOAccess(&io_access), api) }, }; for (dest_partition_number, (src_node_id, src_partition_number)) in partitions { - self.current_frame + cur_frame .move_partition( &mut self.substate_io, &mut handler, @@ -402,15 +529,16 @@ where M::on_drop_node_mut(node_id, self)?; + let (cur_frame, prev_frame) = self.stacks.cur_mut_and_prev(); + let mut handler = KernelHandler { callback: self.callback, - prev_frame: self.prev_frame_stack.last(), + prev_frame, on_io_access: |api, io_access| { M::on_drop_node(DropNodeEvent::IOAccess(&io_access), api) }, }; - let dropped_node = self - .current_frame + let dropped_node = cur_frame .drop_node(&mut self.substate_io, node_id, &mut handler) .map_err(|e| match e { CallbackError::Error(e) => RuntimeError::KernelError(KernelError::CallFrameError( @@ -441,7 +569,7 @@ impl<'g, M: KernelCallbackObject, S: CommitableSubstateStore> KernelInternalApi type System = M; fn kernel_get_node_visibility(&self, node_id: &NodeId) -> NodeVisibility { - self.current_frame.get_node_visibility(node_id) + self.stacks.cur().get_node_visibility(node_id) } fn kernel_get_thread_id(&self) -> usize { @@ -450,20 +578,21 @@ impl<'g, M: KernelCallbackObject, S: CommitableSubstateStore> KernelInternalApi } fn kernel_get_current_depth(&self) -> usize { - self.current_frame.depth() + self.stacks.cur().depth() } fn kernel_get_system_state(&mut self) -> SystemState<'_, M> { - let caller_actor = match self.prev_frame_stack.last() { + let (cur, prev) = self.stacks.cur_and_prev(); + let caller_actor = match prev { Some(call_frame) => call_frame.data(), None => { // This will only occur on initialization - self.current_frame.data() + cur.data() } }; SystemState { system: &mut self.callback, - current_call_frame: self.current_frame.data(), + current_call_frame: cur.data(), caller_call_frame: caller_actor, } } @@ -545,7 +674,8 @@ where ) -> Result<(), RuntimeError> { M::on_mark_substate_as_transient(&node_id, &partition_num, &key, &mut as_read_only!(self))?; - self.current_frame + self.stacks + .cur_mut() .mark_substate_as_transient(&mut self.substate_io, node_id, partition_num, key) .map_err(|e| { RuntimeError::KernelError(KernelError::CallFrameError( @@ -574,15 +704,17 @@ where &mut as_read_only!(self), )?; + let (cur_frame, prev_frame) = self.stacks.cur_mut_and_prev(); + let mut handler = KernelHandler { callback: self.callback, - prev_frame: self.prev_frame_stack.last(), + prev_frame, on_io_access: |api, io_access| { M::on_open_substate(OpenSubstateEvent::IOAccess(&io_access), api) }, }; - let maybe_lock_handle = self.current_frame.open_substate( + let maybe_lock_handle = cur_frame.open_substate( &mut self.substate_io, node_id, partition_num, @@ -601,15 +733,17 @@ where M::on_substate_lock_fault(*node_id, partition_num, &substate_key, self)?; if retry { + let (cur_frame, prev_frame) = self.stacks.cur_mut_and_prev(); + let mut handler = KernelHandler { callback: self.callback, - prev_frame: self.prev_frame_stack.last(), + prev_frame, on_io_access: |api, io_access| { M::on_open_substate(OpenSubstateEvent::IOAccess(&io_access), api) }, }; - self.current_frame + cur_frame .open_substate( &mut self.substate_io, &node_id, @@ -666,7 +800,8 @@ where &mut self, lock_handle: SubstateHandle, ) -> Result { - self.current_frame + self.stacks + .cur() .get_handle_info(lock_handle) .ok_or(RuntimeError::KernelError( KernelError::SubstateHandleDoesNotExist(lock_handle), @@ -678,16 +813,16 @@ where &mut self, lock_handle: SubstateHandle, ) -> Result<&IndexedScryptoValue, RuntimeError> { + let (cur_frame, prev_frame) = self.stacks.cur_mut_and_prev(); let mut handler = KernelHandler { callback: self.callback, - prev_frame: self.prev_frame_stack.last(), + prev_frame, on_io_access: |api, io_access| { M::on_read_substate(ReadSubstateEvent::IOAccess(&io_access), api) }, }; - let value = self - .current_frame + let value = cur_frame .read_substate(&mut self.substate_io, lock_handle, &mut handler) .map_err(|e| match e { CallbackError::Error(e) => RuntimeError::KernelError(KernelError::CallFrameError( @@ -714,15 +849,17 @@ where &mut read_only, )?; + let (cur_frame, prev_frame) = self.stacks.cur_mut_and_prev(); + let mut handler = KernelHandler { callback: self.callback, - prev_frame: self.prev_frame_stack.last(), + prev_frame, on_io_access: |api, io_access| { M::on_write_substate(WriteSubstateEvent::IOAccess(&io_access), api) }, }; - self.current_frame + cur_frame .write_substate(&mut self.substate_io, lock_handle, value, &mut handler) .map_err(|e| match e { CallbackError::Error(e) => RuntimeError::KernelError(KernelError::CallFrameError( @@ -743,7 +880,8 @@ where let mut read_only = as_read_only!(self); M::on_close_substate(CloseSubstateEvent::Start(lock_handle), &mut read_only)?; - self.current_frame + self.stacks + .cur_mut() .close_substate(&mut self.substate_io, lock_handle) .map_err(|e| { RuntimeError::KernelError(KernelError::CallFrameError( @@ -767,15 +905,17 @@ where &mut as_read_only!(self), )?; + let (cur_frame, prev_frame) = self.stacks.cur_mut_and_prev(); + let mut handler = KernelHandler { callback: self.callback, - prev_frame: self.prev_frame_stack.last(), + prev_frame, on_io_access: |api, io_access| { M::on_set_substate(SetSubstateEvent::IOAccess(&io_access), api) }, }; - self.current_frame + cur_frame .set_substate( &mut self.substate_io, node_id, @@ -806,16 +946,17 @@ where &mut as_read_only!(self), )?; + let (cur_frame, prev_frame) = self.stacks.cur_mut_and_prev(); + let mut handler = KernelHandler { callback: self.callback, - prev_frame: self.prev_frame_stack.last(), + prev_frame, on_io_access: |api, io_access| { M::on_remove_substate(RemoveSubstateEvent::IOAccess(&io_access), api) }, }; - let substate = self - .current_frame + let substate = cur_frame .remove_substate( &mut self.substate_io, node_id, @@ -842,16 +983,17 @@ where ) -> Result, RuntimeError> { M::on_scan_keys(ScanKeysEvent::Start, &mut as_read_only!(self))?; + let (cur_frame, prev_frame) = self.stacks.cur_mut_and_prev(); + let mut handler = KernelHandler { callback: self.callback, - prev_frame: self.prev_frame_stack.last(), + prev_frame, on_io_access: |api, io_access| { M::on_scan_keys(ScanKeysEvent::IOAccess(&io_access), api) }, }; - let keys = self - .current_frame + let keys = cur_frame .scan_keys::( &mut self.substate_io, node_id, @@ -878,16 +1020,17 @@ where ) -> Result, RuntimeError> { M::on_drain_substates(DrainSubstatesEvent::Start(limit), &mut as_read_only!(self))?; + let (cur_frame, prev_frame) = self.stacks.cur_mut_and_prev(); + let mut handler = KernelHandler { callback: self.callback, - prev_frame: self.prev_frame_stack.last(), + prev_frame, on_io_access: |api, io_access| { M::on_drain_substates(DrainSubstatesEvent::IOAccess(&io_access), api) }, }; - let substates = self - .current_frame + let substates = cur_frame .drain_substates::( &mut self.substate_io, node_id, @@ -914,16 +1057,17 @@ where ) -> Result, RuntimeError> { M::on_scan_sorted_substates(ScanSortedSubstatesEvent::Start, &mut as_read_only!(self))?; + let (cur_frame, prev_frame) = self.stacks.cur_mut_and_prev(); + let mut handler = KernelHandler { callback: self.callback, - prev_frame: self.prev_frame_stack.last(), + prev_frame, on_io_access: |api, io_access| { M::on_scan_sorted_substates(ScanSortedSubstatesEvent::IOAccess(&io_access), api) }, }; - let substates = self - .current_frame + let substates = cur_frame .scan_sorted( &mut self.substate_io, node_id, @@ -963,14 +1107,14 @@ where { let frame = CallFrame::new_child_from_parent( &self.substate_io, - &mut self.current_frame, + self.stacks.cur_mut(), callee, message, ) .map_err(CallFrameError::CreateFrameError) .map_err(KernelError::CallFrameError)?; - let parent = mem::replace(&mut self.current_frame, frame); - self.prev_frame_stack.push(parent); + + self.stacks.push(frame); } // Execute @@ -979,10 +1123,11 @@ where M::on_execution_start(self)?; // Auto drop locks - for handle in self.current_frame.open_substates() { + for handle in self.stacks.cur().open_substates() { M::on_close_substate(CloseSubstateEvent::Start(handle), self)?; } - self.current_frame + self.stacks + .cur_mut() .close_all_substates(&mut self.substate_io); // Run @@ -990,10 +1135,11 @@ where let message = CallFrameMessage::from_output(&output); // Auto-drop locks again in case module forgot to drop - for handle in self.current_frame.open_substates() { + for handle in self.stacks.cur().open_substates() { M::on_close_substate(CloseSubstateEvent::Start(handle), self)?; } - self.current_frame + self.stacks + .cur_mut() .close_all_substates(&mut self.substate_io); // Handle execution finish @@ -1004,24 +1150,24 @@ where // Move { - let parent = self.prev_frame_stack.last_mut().unwrap(); + let (cur_frame, prev_frame) = self.stacks.cur_mut_and_prev_mut(); // Move resource CallFrame::pass_message( &self.substate_io, - &mut self.current_frame, - parent, + cur_frame, + prev_frame.unwrap(), message.clone(), ) .map_err(CallFrameError::PassMessageError) .map_err(KernelError::CallFrameError)?; // Auto-drop - let owned_nodes = self.current_frame.owned_nodes(); + let owned_nodes = cur_frame.owned_nodes(); M::auto_drop(owned_nodes, self)?; // Now, check if any own has been left! - let owned_nodes = self.current_frame.owned_nodes(); + let owned_nodes = self.stacks.cur().owned_nodes(); if !owned_nodes.is_empty() { return Err(RuntimeError::KernelError(KernelError::OrphanedNodes( owned_nodes, @@ -1030,10 +1176,7 @@ where } // Pop call frame - { - let parent = self.prev_frame_stack.pop().unwrap(); - let _ = core::mem::replace(&mut self.current_frame, parent); - } + self.stacks.pop(); M::after_invoke(&output, self)?; @@ -1041,15 +1184,21 @@ where } } -impl<'g, M: KernelCallbackObject, S: CommitableSubstateStore> KernelThreadApi for Kernel<'g, M, S> { +impl<'g, M: KernelCallbackObject, S: CommitableSubstateStore> KernelStackApi for Kernel<'g, M, S> { type CallFrameData = M::CallFrameData; + + fn kernel_switch_stack(&mut self, id: usize) -> Result<(), RuntimeError> { + self.stacks.switch(id); + Ok(()) + } + fn kernel_set_call_frame_data(&mut self, data: M::CallFrameData) -> Result<(), RuntimeError> { - *self.current_frame.data_mut() = data; + *self.stacks.cur_mut().data_mut() = data; Ok(()) } fn kernel_get_owned_nodes(&mut self) -> Result, RuntimeError> { - Ok(self.current_frame.owned_nodes()) + Ok(self.stacks.cur().owned_nodes()) } } @@ -1058,28 +1207,36 @@ impl<'g, M: KernelCallbackObject, S: CommitableSubstateStore> KernelApi for Kern } #[cfg(feature = "radix_engine_tests")] -impl<'g, M: KernelCallbackObject, S: CommitableSubstateStore> Kernel<'g, M, S> { +impl<'g, M, S> Kernel<'g, M, S> +where + M: KernelCallbackObject, + S: CommitableSubstateStore, +{ pub fn kernel_create_kernel_for_testing( substate_io: SubstateIO<'g, S>, id_allocator: &'g mut IdAllocator, - current_frame: CallFrame, - prev_frame_stack: Vec>, callback: &'g mut M, ) -> Kernel<'g, M, S> { Self { - current_frame, - prev_frame_stack, + stacks: KernelStacks::new(vec![CallFrameInit { + data: M::CallFrameData::default(), + direct_accesses: Default::default(), + global_addresses: Default::default(), + }]), substate_io, id_allocator, callback, } } +} +#[cfg(feature = "radix_engine_tests")] +impl<'g, M: KernelCallbackObject, S: CommitableSubstateStore> Kernel<'g, M, S> { pub fn kernel_current_frame( &self, ) -> &CallFrame<::CallFrameData, ::LockData> { - &self.current_frame + self.stacks.cur() } pub fn kernel_current_frame_mut( @@ -1091,18 +1248,7 @@ impl<'g, M: KernelCallbackObject, S: CommitableSubstateStore> Kernel<'g, M, S> { ::LockData, >, ) { - (&self.substate_io, &mut self.current_frame) - } - - pub fn kernel_prev_frame_stack( - &self, - ) -> &Vec< - CallFrame< - ::CallFrameData, - ::LockData, - >, - > { - &self.prev_frame_stack + (&self.substate_io, self.stacks.cur_mut()) } pub fn kernel_prev_frame_stack_mut( @@ -1113,7 +1259,7 @@ impl<'g, M: KernelCallbackObject, S: CommitableSubstateStore> Kernel<'g, M, S> { ::LockData, >, > { - &mut self.prev_frame_stack + self.stacks.prev_frames_mut() } pub fn kernel_substate_io(&self) -> &SubstateIO<'g, S> { diff --git a/radix-engine/src/kernel/kernel_api.rs b/radix-engine/src/kernel/kernel_api.rs index 6d7af225238..19128e5a82d 100644 --- a/radix-engine/src/kernel/kernel_api.rs +++ b/radix-engine/src/kernel/kernel_api.rs @@ -171,9 +171,14 @@ pub trait KernelInvokeApi { ) -> Result; } -/// API for managing threads and their associated call frame stack -pub trait KernelThreadApi { +/// API for managing multiple call frame stacks +pub trait KernelStackApi { type CallFrameData; + + /// Achieves a context switch by switching the underlying callframe/stack + fn kernel_switch_stack(&mut self, id: usize) -> Result<(), RuntimeError>; + + /// Sets the call frame data for the current call frame fn kernel_set_call_frame_data(&mut self, data: Self::CallFrameData) -> Result<(), RuntimeError>; @@ -222,7 +227,7 @@ pub trait KernelApi: KernelNodeApi + KernelSubstateApi<::LockData> + KernelInvokeApi<::CallFrameData> - + KernelThreadApi::CallFrameData> + + KernelStackApi::CallFrameData> + KernelInternalApi { type CallbackObject: KernelCallbackObject; diff --git a/radix-engine/src/system/mod.rs b/radix-engine/src/system/mod.rs index 485a114cf26..967dfef667b 100644 --- a/radix-engine/src/system/mod.rs +++ b/radix-engine/src/system/mod.rs @@ -14,4 +14,5 @@ pub mod system_modules; pub mod system_substate_schemas; pub mod system_substates; pub mod system_type_checker; +pub mod transaction; pub mod type_info; diff --git a/radix-engine/src/system/system_callback.rs b/radix-engine/src/system/system_callback.rs index 3908b7e04fa..5cdb80f4096 100644 --- a/radix-engine/src/system/system_callback.rs +++ b/radix-engine/src/system/system_callback.rs @@ -8,10 +8,7 @@ use crate::blueprints::identity::IDENTITY_CREATE_PREALLOCATED_ED25519_ID; use crate::blueprints::identity::IDENTITY_CREATE_PREALLOCATED_SECP256K1_ID; use crate::blueprints::resource::fungible_vault::{DepositEvent, PayFeeEvent}; use crate::blueprints::resource::*; -use crate::blueprints::transaction_processor::{ - TransactionProcessorRunInputEfficientEncodable, TxnProcessor, - MAX_TOTAL_BLOB_SIZE_PER_INVOCATION, -}; +use crate::blueprints::transaction_processor::TransactionProcessorRunInputEfficientEncodable; use crate::blueprints::transaction_tracker::*; use crate::errors::*; use crate::internal_prelude::*; @@ -35,6 +32,7 @@ use crate::system::system_modules::transaction_runtime::TransactionRuntimeModule use crate::system::system_modules::{EnabledModules, SystemModuleMixer}; use crate::system::system_substates::KeyValueEntrySubstate; use crate::system::system_type_checker::{BlueprintTypeTarget, KVStoreTypeTarget}; +use crate::system::transaction::multithread_txn_processor::MultiThreadedTxnProcessor; use crate::track::*; use crate::transaction::*; use radix_blueprint_schema_init::RefTypes; @@ -134,69 +132,19 @@ impl VersionedSystemLogic { output } VersionedSystemLogic::V2 => { - let intents = executable.intents(); - for intent in intents { - let mut system_service = SystemService::new(api); - let virtual_resources = intent - .auth_zone_init - .simulate_every_proof_under_resources - .clone(); - let virtual_non_fungibles = - intent.auth_zone_init.initial_non_fungible_id_proofs.clone(); - let auth_zone = AuthModule::create_auth_zone( - &mut system_service, - None, - virtual_resources, - virtual_non_fungibles, - )?; - - api.kernel_set_call_frame_data(Actor::Function(FunctionActor { - blueprint_id: BlueprintId::new( - &TRANSACTION_PROCESSOR_PACKAGE, - TRANSACTION_PROCESSOR_BLUEPRINT, - ), - ident: TRANSACTION_PROCESSOR_RUN_IDENT.to_string(), - auth_zone, - }))?; - } - - { - let mut system_service = SystemService::new(api); - let intent = intents.get(0).unwrap(); - - let txn_processor = TxnProcessor::::init( - intent.encoded_instructions.clone(), - global_address_reservations.clone(), - intent.blobs.clone(), - MAX_TOTAL_BLOB_SIZE_PER_INVOCATION, - &mut system_service, - )?; - let output = txn_processor.execute(&mut system_service)?; - - let owned_nodes = api.kernel_get_owned_nodes()?; - System::auto_drop(owned_nodes, api)?; - - let actor = api.kernel_get_system_state().current_call_frame; - match actor { - Actor::Function(FunctionActor { auth_zone, .. }) => { - let auth_zone = auth_zone.clone(); - let mut system_service = SystemService::new(api); - AuthModule::teardown_auth_zone(&mut system_service, auth_zone)?; - } - _ => { - panic!("unexpected"); - } - } - - let owned_nodes = api.kernel_get_owned_nodes()?; - if !owned_nodes.is_empty() { - return Err(RuntimeError::KernelError(KernelError::OrphanedNodes( - owned_nodes, - ))); - } - - output - } + let mut txn_threads = + MultiThreadedTxnProcessor::init(executable, global_address_reservations, api)?; + txn_threads.execute(api)?; + let output = txn_threads + .threads + .get_mut(0) + .unwrap() + .0 + .outputs + .drain(..) + .collect(); + txn_threads.cleanup(api)?; + output } }; diff --git a/radix-engine/src/system/transaction/mod.rs b/radix-engine/src/system/transaction/mod.rs new file mode 100644 index 00000000000..6ee6f696b82 --- /dev/null +++ b/radix-engine/src/system/transaction/mod.rs @@ -0,0 +1,2 @@ +pub mod multithread_txn_processor; +pub mod txn_processor; diff --git a/radix-engine/src/system/transaction/multithread_txn_processor.rs b/radix-engine/src/system/transaction/multithread_txn_processor.rs new file mode 100644 index 00000000000..716a7ae1cda --- /dev/null +++ b/radix-engine/src/system/transaction/multithread_txn_processor.rs @@ -0,0 +1,138 @@ +use crate::blueprints::transaction_processor::{ + ResumeResult, TxnProcessorThread, MAX_TOTAL_BLOB_SIZE_PER_INVOCATION, +}; +use crate::errors::{KernelError, RuntimeError}; +use crate::kernel::kernel_callback_api::KernelCallbackObject; +use crate::system::actor::{Actor, FunctionActor}; +use crate::system::system::SystemService; +use crate::system::system_callback::{System, SystemBasedKernelApi}; +use crate::system::system_modules::auth::AuthModule; +use radix_common::constants::TRANSACTION_PROCESSOR_PACKAGE; +use radix_common::prelude::{BlueprintId, GlobalAddressReservation}; +use radix_engine_interface::blueprints::transaction_processor::{ + TRANSACTION_PROCESSOR_BLUEPRINT, TRANSACTION_PROCESSOR_RUN_IDENT, +}; +use radix_rust::prelude::*; +use radix_transactions::model::{ExecutableTransaction, InstructionV2}; +use sbor::prelude::ToString; + +/// Multi-thread transaction processor for executing multiple subintents +pub struct MultiThreadedTxnProcessor { + pub threads: Vec<(TxnProcessorThread, Vec)>, +} + +impl MultiThreadedTxnProcessor { + pub fn init( + executable: ExecutableTransaction, + global_address_reservations: Vec, + api: &mut Y, + ) -> Result { + let mut txn_processors = vec![]; + + // Setup + let intents = executable.intents(); + for (thread_id, intent) in intents.iter().enumerate() { + api.kernel_switch_stack(thread_id)?; + + let mut system_service = SystemService::new(api); + let virtual_resources = intent + .auth_zone_init + .simulate_every_proof_under_resources + .clone(); + let virtual_non_fungibles = + intent.auth_zone_init.initial_non_fungible_id_proofs.clone(); + let auth_zone = AuthModule::create_auth_zone( + &mut system_service, + None, + virtual_resources, + virtual_non_fungibles, + )?; + + api.kernel_set_call_frame_data(Actor::Function(FunctionActor { + blueprint_id: BlueprintId::new( + &TRANSACTION_PROCESSOR_PACKAGE, + TRANSACTION_PROCESSOR_BLUEPRINT, + ), + ident: TRANSACTION_PROCESSOR_RUN_IDENT.to_string(), + auth_zone, + }))?; + + let mut system_service = SystemService::new(api); + let txn_processor = TxnProcessorThread::::init( + intent.encoded_instructions.clone(), + global_address_reservations.clone(), + intent.blobs.clone(), + MAX_TOTAL_BLOB_SIZE_PER_INVOCATION, + &mut system_service, + )?; + + txn_processors.push((txn_processor, intent.children_intent_indices.clone())); + } + Ok(Self { + threads: txn_processors, + }) + } + + pub fn execute(&mut self, api: &mut Y) -> Result<(), RuntimeError> { + let mut cur_thread = 0; + let mut parent_stack = vec![]; + + loop { + api.kernel_switch_stack(cur_thread)?; + let (txn_thread, children_mapping) = self.threads.get_mut(cur_thread).unwrap(); + + let mut system_service = SystemService::new(api); + match txn_thread.resume(&mut system_service)? { + ResumeResult::YieldToChild(child) => { + let child = *children_mapping.get(child).unwrap(); + parent_stack.push(cur_thread); + cur_thread = child; + } + ResumeResult::YieldToParent => { + cur_thread = parent_stack.pop().unwrap(); + } + ResumeResult::Done => { + if let Some(parent) = parent_stack.pop() { + cur_thread = parent; + } else { + break; + } + } + } + } + + assert!(parent_stack.is_empty()); + + Ok(()) + } + + pub fn cleanup(self, api: &mut Y) -> Result<(), RuntimeError> { + for (thread_id, _intent) in self.threads.iter().enumerate() { + api.kernel_switch_stack(thread_id)?; + + let owned_nodes = api.kernel_get_owned_nodes()?; + System::auto_drop(owned_nodes, api)?; + + let actor = api.kernel_get_system_state().current_call_frame; + match actor { + Actor::Function(FunctionActor { auth_zone, .. }) => { + let auth_zone = auth_zone.clone(); + let mut system_service = SystemService::new(api); + AuthModule::teardown_auth_zone(&mut system_service, auth_zone)?; + } + _ => { + panic!("unexpected"); + } + } + + let owned_nodes = api.kernel_get_owned_nodes()?; + if !owned_nodes.is_empty() { + return Err(RuntimeError::KernelError(KernelError::OrphanedNodes( + owned_nodes, + ))); + } + } + + Ok(()) + } +} diff --git a/radix-engine/src/blueprints/transaction_processor/tx_processor.rs b/radix-engine/src/system/transaction/txn_processor.rs similarity index 91% rename from radix-engine/src/blueprints/transaction_processor/tx_processor.rs rename to radix-engine/src/system/transaction/txn_processor.rs index ba89c5cfc15..0ff953f527b 100644 --- a/radix-engine/src/blueprints/transaction_processor/tx_processor.rs +++ b/radix-engine/src/system/transaction/txn_processor.rs @@ -1,5 +1,5 @@ use crate::blueprints::resource::WorktopSubstate; -use crate::blueprints::transaction_processor::TxnInstruction; +use crate::blueprints::transaction_processor::{TxnInstruction, Yield}; use crate::errors::ApplicationError; use crate::errors::RuntimeError; use crate::internal_prelude::*; @@ -42,14 +42,21 @@ impl From for RuntimeError { } } -pub struct TxnProcessor { - instructions: Vec, +pub enum ResumeResult { + YieldToChild(usize), + YieldToParent, + Done, +} + +pub struct TxnProcessorThread { + instructions: VecDeque, worktop: Worktop, objects: TxnProcessorObjects, - outputs: Vec, + pub instruction_index: usize, + pub outputs: Vec, } -impl TxnProcessor { +impl TxnProcessorThread { pub fn init + KernelNodeApi + KernelSubstateApi, L: Default>( manifest_encoded_instructions: Rc>, global_address_reservations: Vec, @@ -94,29 +101,36 @@ impl TxnProcessor { let outputs = Vec::new(); Ok(Self { - instructions, + instructions: instructions.into_iter().collect(), + instruction_index: 0usize, worktop, objects, outputs, }) } - pub fn execute< - Y: SystemApi + KernelNodeApi + KernelSubstateApi, - L: Default, - >( - mut self, + pub fn resume + KernelNodeApi + KernelSubstateApi, L: Default>( + &mut self, api: &mut Y, - ) -> Result, RuntimeError> { - for (index, instruction) in self.instructions.into_iter().enumerate() { - api.update_instruction_index(index)?; - let result = instruction.execute(&mut self.worktop, &mut self.objects, api)?; - self.outputs.push(result); + ) -> Result { + while let Some(instruction) = self.instructions.pop_front() { + api.update_instruction_index(self.instruction_index)?; + let (output, yield_instruction) = + instruction.execute(&mut self.worktop, &mut self.objects, api)?; + self.outputs.push(output); + self.instruction_index += 1; + if let Some(yield_instruction) = yield_instruction { + let result = match yield_instruction { + Yield::ToChild(child) => ResumeResult::YieldToChild(child), + Yield::ToParent => ResumeResult::YieldToParent, + }; + return Ok(result); + } } self.worktop.drop(api)?; - Ok(self.outputs) + Ok(ResumeResult::Done) } } diff --git a/radix-transactions/src/model/v1/test_transaction.rs b/radix-transactions/src/model/v1/test_transaction.rs index fe991b5e4b7..dc832c2cee1 100644 --- a/radix-transactions/src/model/v1/test_transaction.rs +++ b/radix-transactions/src/model/v1/test_transaction.rs @@ -17,6 +17,7 @@ pub struct TestIntentV1 { pub instructions: InstructionsV1, pub blobs: BlobsV1, pub hash: Hash, + pub initial_proofs: BTreeSet, } #[derive(ManifestSbor)] @@ -24,6 +25,8 @@ pub struct TestIntentV2 { pub instructions: InstructionsV2, pub blobs: BlobsV1, pub hash: Hash, + pub initial_proofs: BTreeSet, + pub children_intent_indices: Vec, } #[derive(ManifestSbor)] @@ -38,42 +41,84 @@ pub struct PreparedTestIntent { pub references: IndexSet, pub blobs: Rc>>, pub hash: Hash, + pub children_intent_indices: Vec, + pub initial_proofs: BTreeSet, } impl TestTransaction { /// The nonce needs to be globally unique amongst test transactions on your ledger - pub fn new_v1_from_nonce(manifest: TransactionManifestV1, nonce: u32) -> Self { - Self::new_v1(manifest, hash(format!("Test transaction: {}", nonce))) + pub fn new_v1_from_nonce( + manifest: TransactionManifestV1, + nonce: u32, + initial_proofs: BTreeSet, + ) -> Self { + Self::new_v1( + manifest, + hash(format!("Test transaction: {}", nonce)), + initial_proofs, + ) } - pub fn new_v1(manifest: TransactionManifestV1, hash: Hash) -> Self { + pub fn new_v1( + manifest: TransactionManifestV1, + hash: Hash, + initial_proofs: BTreeSet, + ) -> Self { let (instructions, blobs) = manifest.for_intent(); Self::V1(TestIntentV1 { instructions, blobs, hash, + initial_proofs, }) } - pub fn new_v2_from_nonce(intents: Vec<(TransactionManifestV2, u32)>) -> Self { + pub fn new_v2_from_nonce( + intents: Vec<( + TransactionManifestV2, + u32, + Vec, + BTreeSet, + )>, + ) -> Self { let intents = intents .into_iter() - .map(|(manifest, nonce)| (manifest, hash(format!("Test transaction: {}", nonce)))) + .map( + |(manifest, nonce, children_intent_indices, initial_proofs)| { + ( + manifest, + hash(format!("Test transaction: {}", nonce)), + children_intent_indices, + initial_proofs, + ) + }, + ) .collect(); Self::new_v2(intents) } - pub fn new_v2(intents: Vec<(TransactionManifestV2, Hash)>) -> Self { + pub fn new_v2( + intents: Vec<( + TransactionManifestV2, + Hash, + Vec, + BTreeSet, + )>, + ) -> Self { let intents = intents .into_iter() - .map(|(manifest, hash)| { - let (instructions, blobs, ..) = manifest.for_intent(); - TestIntentV2 { - instructions, - blobs, - hash, - } - }) + .map( + |(manifest, hash, children_intent_indices, initial_proofs)| { + let (instructions, blobs, ..) = manifest.for_intent(); + TestIntentV2 { + instructions, + blobs, + hash, + children_intent_indices, + initial_proofs, + } + }, + ) .collect(); Self::V2(intents) @@ -89,6 +134,8 @@ impl TestTransaction { references: prepared_instructions.references, blobs: intent.blobs.prepare_partial()?.blobs_by_hash, hash: intent.hash, + children_intent_indices: vec![], + initial_proofs: intent.initial_proofs, })) } Self::V2(intents) => { @@ -102,6 +149,8 @@ impl TestTransaction { references: prepared_instructions.references.deref().clone(), blobs: intent.blobs.prepare_partial()?.blobs_by_hash, hash: intent.hash, + children_intent_indices: intent.children_intent_indices, + initial_proofs: intent.initial_proofs, }); } @@ -112,16 +161,13 @@ impl TestTransaction { } impl PreparedTestTransaction { - pub fn get_executable( - &self, - initial_proofs: BTreeSet, - ) -> ExecutableTransaction { + pub fn get_executable(&self) -> ExecutableTransaction { match self { PreparedTestTransaction::V1(intent) => { - let num_of_signature_validations = initial_proofs.len() + 1; + let num_of_signature_validations = intent.initial_proofs.len() + 1; ExecutableTransaction::new_v1( intent.encoded_instructions.clone(), - AuthZoneInit::proofs(initial_proofs), + AuthZoneInit::proofs(intent.initial_proofs.clone()), intent.references.clone(), intent.blobs.clone(), ExecutionContext { @@ -152,7 +198,11 @@ impl PreparedTestTransaction { + intent.blobs.values().map(|x| x.len()).sum::() }) .sum(); - let num_of_signature_validations = initial_proofs.len() + 1; + let num_of_signature_validations = intents + .iter() + .map(|intent| intent.initial_proofs.len()) + .sum(); + let context = ExecutionContext { unique_hash: intents.get(0).unwrap().hash, intent_hash_nullifications: vec![], @@ -170,20 +220,17 @@ impl PreparedTestTransaction { start_timestamp_inclusive: None, end_timestamp_exclusive: None, }; - let mut root_auth_zone = Some(AuthZoneInit::proofs(initial_proofs)); let intents = intents .iter() .map(|intent| { - let auth_zone_init = root_auth_zone - .take() - .unwrap_or(AuthZoneInit::proofs(Default::default())); + let auth_zone_init = AuthZoneInit::proofs(intent.initial_proofs.clone()); ExecutableIntent { encoded_instructions: intent.encoded_instructions.clone(), auth_zone_init, references: intent.references.clone(), blobs: intent.blobs.clone(), - children_intent_indices: vec![], + children_intent_indices: intent.children_intent_indices.clone(), } }) .collect(); diff --git a/scrypto-test/src/environment/builder.rs b/scrypto-test/src/environment/builder.rs index cf356fb37c7..eced465acad 100644 --- a/scrypto-test/src/environment/builder.rs +++ b/scrypto-test/src/environment/builder.rs @@ -282,8 +282,6 @@ where pinned_to_heap: Default::default(), }, id_allocator, - CallFrame::new_root(Default::default()), - vec![], system_config, ) }, diff --git a/scrypto-test/src/ledger_simulator/inject_costing_err.rs b/scrypto-test/src/ledger_simulator/inject_costing_err.rs index 6f1d88acee7..739bf211153 100644 --- a/scrypto-test/src/ledger_simulator/inject_costing_err.rs +++ b/scrypto-test/src/ledger_simulator/inject_costing_err.rs @@ -493,9 +493,14 @@ impl<'a, M: SystemCallbackObject + 'a, K: KernelApi>> - KernelThreadApi for WrappedKernelApi<'a, M, K> + KernelStackApi for WrappedKernelApi<'a, M, K> { type CallFrameData = Actor; + + fn kernel_switch_stack(&mut self, id: usize) -> Result<(), RuntimeError> { + self.api.kernel_switch_stack(id) + } + fn kernel_set_call_frame_data(&mut self, data: Actor) -> Result<(), RuntimeError> { self.api.kernel_set_call_frame_data(data) } diff --git a/scrypto-test/src/ledger_simulator/ledger_simulator.rs b/scrypto-test/src/ledger_simulator/ledger_simulator.rs index 08f80ceefcc..a62a1051b39 100644 --- a/scrypto-test/src/ledger_simulator/ledger_simulator.rs +++ b/scrypto-test/src/ledger_simulator/ledger_simulator.rs @@ -1136,10 +1136,14 @@ impl LedgerSimulator { { let nonce = self.next_transaction_nonce(); self.execute_transaction( - TestTransaction::new_v1_from_nonce(manifest, nonce) - .prepare() - .expect("expected transaction to be preparable") - .get_executable(initial_proofs.into_iter().collect()), + TestTransaction::new_v1_from_nonce( + manifest, + nonce, + initial_proofs.into_iter().collect(), + ) + .prepare() + .expect("expected transaction to be preparable") + .get_executable(), ExecutionConfig::for_test_transaction(), ) } @@ -1155,10 +1159,14 @@ impl LedgerSimulator { { let nonce = self.next_transaction_nonce(); self.execute_transaction( - TestTransaction::new_v1_from_nonce(manifest, nonce) - .prepare() - .expect("expected transaction to be preparable") - .get_executable(initial_proofs.into_iter().collect()), + TestTransaction::new_v1_from_nonce( + manifest, + nonce, + initial_proofs.into_iter().collect(), + ) + .prepare() + .expect("expected transaction to be preparable") + .get_executable(), execution_config, ) } @@ -1179,10 +1187,14 @@ impl LedgerSimulator { ..Default::default() }); self.execute_transaction( - TestTransaction::new_v1_from_nonce(manifest, nonce) - .prepare() - .expect("expected transaction to be preparable") - .get_executable(initial_proofs.into_iter().collect()), + TestTransaction::new_v1_from_nonce( + manifest, + nonce, + initial_proofs.into_iter().collect(), + ) + .prepare() + .expect("expected transaction to be preparable") + .get_executable(), config, ) } @@ -1197,10 +1209,14 @@ impl LedgerSimulator { T: IntoIterator, { let nonce = self.next_transaction_nonce(); - let txn = TestTransaction::new_v1_from_nonce(manifest, nonce) - .prepare() - .expect("expected transaction to be preparable"); - let executable = txn.get_executable(initial_proofs.into_iter().collect()); + let txn = TestTransaction::new_v1_from_nonce( + manifest, + nonce, + initial_proofs.into_iter().collect(), + ) + .prepare() + .expect("expected transaction to be preparable"); + let executable = txn.get_executable(); let vm_init = self.vm_modules.create_vm_init();