From 756d240d333d59b478061148b8411be489558cef Mon Sep 17 00:00:00 2001 From: Yulong Wu Date: Fri, 18 Oct 2024 12:21:07 +0100 Subject: [PATCH 1/3] Add test - transfer locked buckets between threads --- .../assets/blueprints/Cargo.lock | 7 ++ .../assets/blueprints/Cargo.toml | 1 + .../assets/blueprints/threading/Cargo.toml | 10 +++ .../assets/blueprints/threading/src/lib.rs | 30 +++++++ radix-engine-tests/tests/system/mod.rs | 1 + radix-engine-tests/tests/system/threading.rs | 87 +++++++++++++++++++ 6 files changed, 136 insertions(+) create mode 100644 radix-engine-tests/assets/blueprints/threading/Cargo.toml create mode 100644 radix-engine-tests/assets/blueprints/threading/src/lib.rs create mode 100644 radix-engine-tests/tests/system/threading.rs diff --git a/radix-engine-tests/assets/blueprints/Cargo.lock b/radix-engine-tests/assets/blueprints/Cargo.lock index efb280d4a8..773b1bf18f 100644 --- a/radix-engine-tests/assets/blueprints/Cargo.lock +++ b/radix-engine-tests/assets/blueprints/Cargo.lock @@ -1242,6 +1242,13 @@ dependencies = [ "scrypto", ] +[[package]] +name = "threading" +version = "1.0.0" +dependencies = [ + "scrypto", +] + [[package]] name = "threadpool" version = "1.8.1" diff --git a/radix-engine-tests/assets/blueprints/Cargo.toml b/radix-engine-tests/assets/blueprints/Cargo.toml index 3837073cc6..7c9f72ff39 100644 --- a/radix-engine-tests/assets/blueprints/Cargo.toml +++ b/radix-engine-tests/assets/blueprints/Cargo.toml @@ -55,6 +55,7 @@ members = [ "royalty-auth", "scrypto_env", "system", + "threading", "transaction_limits", "transaction_runtime", "tx_processor_access", diff --git a/radix-engine-tests/assets/blueprints/threading/Cargo.toml b/radix-engine-tests/assets/blueprints/threading/Cargo.toml new file mode 100644 index 0000000000..ac91523b44 --- /dev/null +++ b/radix-engine-tests/assets/blueprints/threading/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "threading" +version = "1.0.0" +edition = "2021" + +[dependencies] +scrypto = { path = "../../../../scrypto" } + +[lib] +crate-type = ["cdylib", "lib"] \ No newline at end of file diff --git a/radix-engine-tests/assets/blueprints/threading/src/lib.rs b/radix-engine-tests/assets/blueprints/threading/src/lib.rs new file mode 100644 index 0000000000..8d06894e5c --- /dev/null +++ b/radix-engine-tests/assets/blueprints/threading/src/lib.rs @@ -0,0 +1,30 @@ +use scrypto::prelude::*; + +#[derive(ScryptoSbor, NonFungibleData)] +pub struct MyData {} + +#[blueprint] +mod threading { + + struct Threading { + vault: Vault, + } + + impl Threading { + pub fn new(bucket: Bucket) -> ComponentAddress { + Self { + vault: Vault::with_bucket(bucket), + } + .instantiate() + .prepare_to_globalize(OwnerRole::None) + .globalize() + .address() + } + + pub fn create_locked_bucket(&mut self, amount: Decimal) -> (Bucket, Proof) { + let bucket = self.vault.take(amount); + let proof = bucket.create_proof_of_all(); + (bucket, proof) + } + } +} diff --git a/radix-engine-tests/tests/system/mod.rs b/radix-engine-tests/tests/system/mod.rs index f4e0a9b16f..1faf5393e2 100644 --- a/radix-engine-tests/tests/system/mod.rs +++ b/radix-engine-tests/tests/system/mod.rs @@ -73,6 +73,7 @@ mod system_lock_fee; mod system_module_methods; mod system_reference; mod system_role_assignment; +mod threading; mod toolkit_receipt; mod track; mod transaction_limits; diff --git a/radix-engine-tests/tests/system/threading.rs b/radix-engine-tests/tests/system/threading.rs new file mode 100644 index 0000000000..f402736469 --- /dev/null +++ b/radix-engine-tests/tests/system/threading.rs @@ -0,0 +1,87 @@ +use radix_engine_tests::common::PackageLoader; +use scrypto_test::prelude::*; + +#[test] +fn can_transfer_locked_bucket_between_threads() { + let mut ledger = LedgerSimulatorBuilder::new().build(); + let (pk1, sk1, account1) = ledger.new_allocated_account(); + + // Prepares a component that can return a locked bucket (and a proof). + // + // When a bucket is inserted into the worktop, it's added as-is if there is no corresponding bucket + // allocated for the resource address, otherwise it's "merged" into the existing bucket, which will check + // lock status. + let package_address = ledger.publish_package_simple(PackageLoader::get("threading")); + let component_address = ledger + .execute_manifest( + ManifestBuilder::new() + .lock_fee_from_faucet() + .call_method(FAUCET, "free", ()) + .take_all_from_worktop(XRD, "bucket") + .call_function_with_name_lookup(package_address, "Threading", "new", |lookup| { + (lookup.bucket("bucket"),) + }) + .build(), + [], + ) + .expect_commit_success() + .new_component_addresses()[0]; + + // Flow: + // 1. root creates a locked bucket + // 2. root sends child the bucket + // 3. child returns the bucket + // 4. root frees the bucket + // 5. root deposit the bucket into an account + let start_epoch_inclusive = ledger.get_current_epoch(); + let end_epoch_exclusive = start_epoch_inclusive.after(1).unwrap(); + let transaction = TransactionV2Builder::new() + .add_signed_child( + "child", + PartialTransactionV2Builder::new() + .intent_header(IntentHeaderV2 { + network_id: NetworkDefinition::simulator().id, + start_epoch_inclusive, + end_epoch_exclusive, + min_proposer_timestamp_inclusive: None, + max_proposer_timestamp_exclusive: None, + intent_discriminator: 1, + }) + .manifest_builder(|builder| { + builder + // EntireWorktop will ensure the buckets are passed as-is. + .yield_to_parent((ManifestExpression::EntireWorktop,)) + }) + .sign(&sk1) + .build(), + ) + .intent_header(IntentHeaderV2 { + network_id: NetworkDefinition::simulator().id, + start_epoch_inclusive, + end_epoch_exclusive, + min_proposer_timestamp_inclusive: None, + max_proposer_timestamp_exclusive: None, + intent_discriminator: 2, + }) + .manifest_builder(|builder| { + builder + .lock_fee(account1, 3) + .call_method(component_address, "create_locked_bucket", (dec!(1),)) + // EntireWorktop will ensure the buckets are passed as-is. + .yield_to_child("child", (ManifestExpression::EntireWorktop,)) + // Free the bucket + .drop_all_proofs() + .try_deposit_entire_worktop_or_abort(account1, None) + }) + .transaction_header(TransactionHeaderV2 { + notary_public_key: pk1.into(), + notary_is_signatory: false, + tip_basis_points: 0, + }) + .sign(&sk1) + .notarize(&sk1) + .build(); + + let receipt = ledger.execute_transaction(&transaction, ExecutionConfig::for_test_transaction()); + receipt.expect_commit_success(); +} From 9d93b20dac9ee7bedab69a48f1a7ee0d3e16d213 Mon Sep 17 00:00:00 2001 From: Yulong Wu Date: Wed, 23 Oct 2024 13:57:15 +0100 Subject: [PATCH 2/3] Add test - pass references, address reservations and named addresses --- .../assets/blueprints/threading/src/lib.rs | 19 ++ radix-engine-tests/tests/system/threading.rs | 181 ++++++++++++++++++ 2 files changed, 200 insertions(+) diff --git a/radix-engine-tests/assets/blueprints/threading/src/lib.rs b/radix-engine-tests/assets/blueprints/threading/src/lib.rs index 8d06894e5c..f371a903cf 100644 --- a/radix-engine-tests/assets/blueprints/threading/src/lib.rs +++ b/radix-engine-tests/assets/blueprints/threading/src/lib.rs @@ -21,10 +21,29 @@ mod threading { .address() } + pub fn new2(reservation: GlobalAddressReservation) -> ComponentAddress { + Self { + vault: Vault::new(XRD), + } + .instantiate() + .prepare_to_globalize(OwnerRole::None) + .with_address(reservation) + .globalize() + .address() + } + pub fn create_locked_bucket(&mut self, amount: Decimal) -> (Bucket, Proof) { let bucket = self.vault.take(amount); let proof = bucket.create_proof_of_all(); (bucket, proof) } + + pub fn call(node_id: NodeId) { + let address = unsafe { ComponentAddress::new_unchecked(node_id.into()) }; + let component: Global = address.into(); + component.method(); + } + + pub fn method(&self) {} } } diff --git a/radix-engine-tests/tests/system/threading.rs b/radix-engine-tests/tests/system/threading.rs index f402736469..26e224c00a 100644 --- a/radix-engine-tests/tests/system/threading.rs +++ b/radix-engine-tests/tests/system/threading.rs @@ -85,3 +85,184 @@ fn can_transfer_locked_bucket_between_threads() { let receipt = ledger.execute_transaction(&transaction, ExecutionConfig::for_test_transaction()); receipt.expect_commit_success(); } + +// Arguably, we may disallow transferring references +#[test] +fn can_pass_global_and_direct_access_references() { + let mut ledger = LedgerSimulatorBuilder::new().build(); + let (pk1, sk1, account1) = ledger.new_allocated_account(); + let (_, _, account) = ledger.new_allocated_account(); + let vault = ledger.get_component_vaults(account, XRD).pop().unwrap(); + + let start_epoch_inclusive = ledger.get_current_epoch(); + let end_epoch_exclusive = start_epoch_inclusive.after(1).unwrap(); + let transaction = TransactionV2Builder::new() + .add_signed_child( + "child", + PartialTransactionV2Builder::new() + .intent_header(IntentHeaderV2 { + network_id: NetworkDefinition::simulator().id, + start_epoch_inclusive, + end_epoch_exclusive, + min_proposer_timestamp_inclusive: None, + max_proposer_timestamp_exclusive: None, + intent_discriminator: 1, + }) + .manifest_builder(|builder| { + builder + // Unfortunately, there is no way to grab the received references + .yield_to_parent(()) + }) + .sign(&sk1) + .build(), + ) + .intent_header(IntentHeaderV2 { + network_id: NetworkDefinition::simulator().id, + start_epoch_inclusive, + end_epoch_exclusive, + min_proposer_timestamp_inclusive: None, + max_proposer_timestamp_exclusive: None, + intent_discriminator: 2, + }) + .manifest_builder(|builder| { + builder.lock_fee(account1, 3).yield_to_child( + "child", + ( + ManifestAddress::Static(account.into_node_id()), + ManifestAddress::Static(vault), + ), + ) + }) + .transaction_header(TransactionHeaderV2 { + notary_public_key: pk1.into(), + notary_is_signatory: false, + tip_basis_points: 0, + }) + .sign(&sk1) + .notarize(&sk1) + .build(); + + let receipt = ledger.execute_transaction(&transaction, ExecutionConfig::for_test_transaction()); + receipt.expect_commit_success(); +} + +#[test] +fn can_not_pass_address_reservation() { + let mut ledger = LedgerSimulatorBuilder::new().with_kernel_trace().build(); + let (pk1, sk1, account1) = ledger.new_allocated_account(); + let package_address = ledger.publish_package_simple(PackageLoader::get("threading")); + + let start_epoch_inclusive = ledger.get_current_epoch(); + let end_epoch_exclusive = start_epoch_inclusive.after(1).unwrap(); + let transaction = TransactionV2Builder::new() + .add_signed_child( + "child", + PartialTransactionV2Builder::new() + .intent_header(IntentHeaderV2 { + network_id: NetworkDefinition::simulator().id, + start_epoch_inclusive, + end_epoch_exclusive, + min_proposer_timestamp_inclusive: None, + max_proposer_timestamp_exclusive: None, + intent_discriminator: 1, + }) + .manifest_builder(|builder| builder.yield_to_parent(())) + .sign(&sk1) + .build(), + ) + .intent_header(IntentHeaderV2 { + network_id: NetworkDefinition::simulator().id, + start_epoch_inclusive, + end_epoch_exclusive, + min_proposer_timestamp_inclusive: None, + max_proposer_timestamp_exclusive: None, + intent_discriminator: 2, + }) + .manifest_builder(|builder| { + builder + .lock_fee(account1, 3) + .allocate_global_address( + package_address, + "Threading", + "address_reservation", + "address", + ) + .yield_to_child_with_name_lookup("child", |lookup| { + (lookup.address_reservation("address_reservation"),) + }) + }) + .transaction_header(TransactionHeaderV2 { + notary_public_key: pk1.into(), + notary_is_signatory: false, + tip_basis_points: 0, + }) + .sign(&sk1) + .notarize(&sk1) + .build(); + + let receipt = ledger.execute_transaction(&transaction, ExecutionConfig::for_test_transaction()); + receipt.expect_specific_failure(|e| { + matches!(e, RuntimeError::SystemError(SystemError::NotAnObject)) + }); +} + +#[test] +fn can_pass_named_address() { + let mut ledger = LedgerSimulatorBuilder::new().with_kernel_trace().build(); + let (pk1, sk1, account1) = ledger.new_allocated_account(); + let package_address = ledger.publish_package_simple(PackageLoader::get("threading")); + + let start_epoch_inclusive = ledger.get_current_epoch(); + let end_epoch_exclusive = start_epoch_inclusive.after(1).unwrap(); + let transaction = TransactionV2Builder::new() + .add_signed_child( + "child", + PartialTransactionV2Builder::new() + .intent_header(IntentHeaderV2 { + network_id: NetworkDefinition::simulator().id, + start_epoch_inclusive, + end_epoch_exclusive, + min_proposer_timestamp_inclusive: None, + max_proposer_timestamp_exclusive: None, + intent_discriminator: 1, + }) + .manifest_builder(|builder| builder.yield_to_parent(())) + .sign(&sk1) + .build(), + ) + .intent_header(IntentHeaderV2 { + network_id: NetworkDefinition::simulator().id, + start_epoch_inclusive, + end_epoch_exclusive, + min_proposer_timestamp_inclusive: None, + max_proposer_timestamp_exclusive: None, + intent_discriminator: 2, + }) + .manifest_builder(|builder| { + builder + .lock_fee(account1, 3) + .allocate_global_address( + package_address, + "Threading", + "address_reservation", + "address", + ) + .yield_to_child_with_name_lookup("child", |lookup| { + (lookup.named_address("address"),) + }) + .call_function_with_name_lookup(package_address, "Threading", "new2", |lookup| { + (lookup.address_reservation("address_reservation"),) + }) + }) + .transaction_header(TransactionHeaderV2 { + notary_public_key: pk1.into(), + notary_is_signatory: false, + tip_basis_points: 0, + }) + .sign(&sk1) + .notarize(&sk1) + .build(); + + let receipt = ledger.execute_transaction(&transaction, ExecutionConfig::for_test_transaction()); + receipt.expect_commit_success(); +} From 8aa2681f4d4fc0d77f3447ad3e32981a7d0ff02c Mon Sep 17 00:00:00 2001 From: Yulong Wu Date: Fri, 25 Oct 2024 14:49:18 +0100 Subject: [PATCH 3/3] Add test - passing proof between threads --- radix-engine-tests/tests/system/threading.rs | 56 +++++++++++++++++++ .../src/builder/transaction_builder.rs | 13 +++++ 2 files changed, 69 insertions(+) diff --git a/radix-engine-tests/tests/system/threading.rs b/radix-engine-tests/tests/system/threading.rs index 26e224c00a..9f3458bb83 100644 --- a/radix-engine-tests/tests/system/threading.rs +++ b/radix-engine-tests/tests/system/threading.rs @@ -1,6 +1,8 @@ use radix_engine_tests::common::PackageLoader; use scrypto_test::prelude::*; +// Some of the tests in this file are to demonstrate the current behavior. + #[test] fn can_transfer_locked_bucket_between_threads() { let mut ledger = LedgerSimulatorBuilder::new().build(); @@ -266,3 +268,57 @@ fn can_pass_named_address() { let receipt = ledger.execute_transaction(&transaction, ExecutionConfig::for_test_transaction()); receipt.expect_commit_success(); } + +#[should_panic( + expected = "Transaction should be convertible to executable: ManifestValidationError(ProofCannotBePassedToAnotherIntent)" +)] +#[test] +fn can_not_pass_proof_between_threads() { + let mut ledger = LedgerSimulatorBuilder::new().with_kernel_trace().build(); + let (pk1, sk1, account1) = ledger.new_allocated_account(); + + let start_epoch_inclusive = ledger.get_current_epoch(); + let end_epoch_exclusive = start_epoch_inclusive.after(1).unwrap(); + let transaction = TransactionV2Builder::new() + .add_signed_child( + "child", + PartialTransactionV2Builder::new() + .intent_header(IntentHeaderV2 { + network_id: NetworkDefinition::simulator().id, + start_epoch_inclusive, + end_epoch_exclusive, + min_proposer_timestamp_inclusive: None, + max_proposer_timestamp_exclusive: None, + intent_discriminator: 1, + }) + .manifest_builder(|builder| builder.yield_to_parent(())) + .sign(&sk1) + .build(), + ) + .intent_header(IntentHeaderV2 { + network_id: NetworkDefinition::simulator().id, + start_epoch_inclusive, + end_epoch_exclusive, + min_proposer_timestamp_inclusive: None, + max_proposer_timestamp_exclusive: None, + intent_discriminator: 2, + }) + .manifest_builder_no_validate(|builder| { + // Note that we have to disable validation + builder + .lock_fee(account1, 3) + .create_proof_from_account_of_amount(account1, XRD, 10) + .create_proof_from_auth_zone_of_amount(XRD, 10, "proof") + .yield_to_child_with_name_lookup("child", |lookup| (lookup.proof("proof"),)) + }) + .transaction_header(TransactionHeaderV2 { + notary_public_key: pk1.into(), + notary_is_signatory: false, + tip_basis_points: 0, + }) + .sign(&sk1) + .notarize(&sk1) + .build_no_validate(); + + ledger.execute_transaction(&transaction, ExecutionConfig::for_test_transaction()); +} diff --git a/radix-transactions/src/builder/transaction_builder.rs b/radix-transactions/src/builder/transaction_builder.rs index f6bed1e594..19957c4796 100644 --- a/radix-transactions/src/builder/transaction_builder.rs +++ b/radix-transactions/src/builder/transaction_builder.rs @@ -514,6 +514,19 @@ impl TransactionV2Builder { self } + pub fn manifest_builder_no_validate( + mut self, + build_manifest: impl FnOnce(TransactionManifestV2Builder) -> TransactionManifestV2Builder, + ) -> Self { + let mut manifest_builder = TransactionManifestV2Builder::new_typed(); + for (child_name, (hash, _, _)) in self.child_partial_transactions.iter() { + manifest_builder = manifest_builder.use_child(child_name, *hash); + } + self.transaction_intent_manifest = + Some(build_manifest(manifest_builder).build_no_validate()); + self + } + /// ## Panics: /// * If called with a manifest which references different children to those provided by [`add_signed_child`][Self::add_signed_child]. pub fn manifest(mut self, manifest: TransactionManifestV2) -> Self {