diff --git a/.vscode/settings.json b/.vscode/settings.json index 7363ce89b77..1b6daa2210d 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,20 +1,18 @@ { "rust-analyzer.linkedProjects": [ - // Root Workspace & CLIs. "Cargo.toml", "radix-clis/Cargo.toml", - // Various blueprints we have. - "examples/everything/Cargo.toml", - "examples/hello-world/Cargo.toml", - "examples/no-std/Cargo.toml", - "radix-clis/tests/blueprints/Cargo.toml", - "radix-engine-tests/assets/blueprints/Cargo.toml", - "radix-engine/assets/blueprints/Cargo.toml", - "radix-transaction-scenarios/assets/blueprints/Cargo.toml", - "scrypto-compiler/tests/assets/scenario_1/Cargo.toml", - "scrypto-compiler/tests/assets/scenario_2/Cargo.toml", - "scrypto-test/assets/blueprints/Cargo.toml", - "scrypto-test/tests/blueprints/Cargo.toml", + // "examples/everything/Cargo.toml", + // "examples/hello-world/Cargo.toml", + // "examples/no-std/Cargo.toml", + // "radix-clis/tests/blueprints/Cargo.toml", + // "radix-engine-tests/assets/blueprints/Cargo.toml", + // "radix-engine/assets/blueprints/Cargo.toml", + // "radix-transaction-scenarios/assets/blueprints/Cargo.toml", + // "scrypto-compiler/tests/assets/scenario_1/Cargo.toml", + // "scrypto-compiler/tests/assets/scenario_2/Cargo.toml", + // "scrypto-test/assets/blueprints/Cargo.toml", + // "scrypto-test/tests/blueprints/Cargo.toml", ], "cSpell.words": [ "accesscontroller", diff --git a/radix-clis/src/resim/config.rs b/radix-clis/src/resim/config.rs index 3b867d79ad5..e62aa4e3d23 100644 --- a/radix-clis/src/resim/config.rs +++ b/radix-clis/src/resim/config.rs @@ -1,8 +1,6 @@ use crate::resim::*; use radix_common::prelude::*; -use radix_engine::updates::ProtocolUpdates; -use radix_substate_store_interface::db_key_mapper::*; -use radix_substate_store_interface::interface::*; +use radix_engine::updates::*; use std::env; use std::fs; use std::path::PathBuf; @@ -13,6 +11,7 @@ pub struct SimulatorEnvironment { pub db: RocksdbSubstateStore, // VMs pub scrypto_vm: ScryptoVm, + pub network_definition: NetworkDefinition, } impl SimulatorEnvironment { @@ -23,7 +22,11 @@ impl SimulatorEnvironment { // Create the VMs let scrypto_vm = ScryptoVm::::default(); - let mut env = Self { db, scrypto_vm }; + let mut env = Self { + db, + scrypto_vm, + network_definition: NetworkDefinition::simulator(), + }; env.bootstrap(); Ok(env) @@ -42,17 +45,14 @@ impl SimulatorEnvironment { let vm = VmInit::new(&self.scrypto_vm, NoExtension); // Bootstrap - Bootstrapper::new(NetworkDefinition::simulator(), &mut self.db, vm, false) + Bootstrapper::new(self.network_definition.clone(), &mut self.db, vm, false) .bootstrap_test_default(); - // Run the protocol updates - unlike the test runner, the user has no way in whether they + // Run the protocol updates - for now, unlike the test runner, the user has no way in whether they // get these protocol updates or not. - for state_updates in - ProtocolUpdates::all().generate_state_updates(&self.db, &NetworkDefinition::simulator()) - { - let db_updates = state_updates.create_database_updates::(); - self.db.commit(&db_updates); - } + ProtocolBuilder::for_network(&self.network_definition) + .until_latest_protocol_version() + .commit_each_protocol_update(&mut self.db); } } diff --git a/radix-clis/src/resim/mod.rs b/radix-clis/src/resim/mod.rs index c89f4e04c5d..f43c8e0dc44 100644 --- a/radix-clis/src/resim/mod.rs +++ b/radix-clis/src/resim/mod.rs @@ -169,7 +169,9 @@ pub fn handle_system_transaction( print_receipt: bool, out: &mut O, ) -> Result { - let SimulatorEnvironment { mut db, scrypto_vm } = SimulatorEnvironment::new()?; + let SimulatorEnvironment { + mut db, scrypto_vm, .. + } = SimulatorEnvironment::new()?; let vm_init = VmInit::new(&scrypto_vm, NoExtension); let nonce = get_nonce()?; @@ -240,7 +242,9 @@ pub fn handle_manifest( Ok(None) } None => { - let SimulatorEnvironment { mut db, scrypto_vm } = SimulatorEnvironment::new()?; + let SimulatorEnvironment { + mut db, scrypto_vm, .. + } = SimulatorEnvironment::new()?; let vm_init = VmInit::new(&scrypto_vm, NoExtension); let sks = get_signing_keys(signing_keys)?; diff --git a/radix-engine-tests/src/common.rs b/radix-engine-tests/src/common.rs index 29fe4e58a83..a0f107d8097 100644 --- a/radix-engine-tests/src/common.rs +++ b/radix-engine-tests/src/common.rs @@ -115,11 +115,10 @@ pub mod path_macros { }; } - #[macro_export] - macro_rules! path_local_metering_assets { - ($folder: expr) => { - concat!(env!("CARGO_MANIFEST_DIR"), "/assets/metering/", $folder) - }; + // Not a macro, because it needs to support a variable folder, but here + // for consistency + pub fn path_local_metering_assets(folder: &str) -> String { + format!("{}/assets/metering/{}", env!("CARGO_MANIFEST_DIR"), folder) } pub use crate::include_local_wasm_str; diff --git a/radix-engine-tests/tests/application/metering.rs b/radix-engine-tests/tests/application/metering.rs index 61f7f863d69..008b950dfc5 100644 --- a/radix-engine-tests/tests/application/metering.rs +++ b/radix-engine-tests/tests/application/metering.rs @@ -4,11 +4,10 @@ use radix_engine::transaction::ExecutionConfig; use radix_engine::transaction::TransactionFeeDetails; use radix_engine::transaction::TransactionFeeSummary; use radix_engine::transaction::TransactionReceipt; -use radix_engine::updates::ProtocolUpdates; +use radix_engine::updates::*; use radix_engine_interface::blueprints::access_controller::ACCESS_CONTROLLER_CREATE_PROOF_IDENT; use radix_engine_interface::blueprints::package::PackageDefinition; use radix_engine_tests::common::*; -use radix_engine_tests::path_local_metering_assets; use scrypto::object_modules::ModuleConfig; use scrypto::prelude::metadata; use scrypto::prelude::metadata_init; @@ -20,97 +19,37 @@ use scrypto_test::prelude::*; #[test] #[ignore = "Run this test to update expected costs"] fn update_expected_costs() { - use radix_engine_tests::path_local_metering_assets; - - for (name, updates) in [ - ( - path_local_metering_assets!("babylon"), - ProtocolUpdates::none(), - ), - ( - path_local_metering_assets!("anemone"), - ProtocolUpdates::up_to_anemone(), - ), - ( - path_local_metering_assets!("bottlenose"), - ProtocolUpdates::up_to_bottlenose(), - ), - ] { - run_basic_transfer( - updates.clone(), - PostRunTask::OutputCosting(name, "cost_transfer.csv"), - ); - run_basic_transfer_to_virtual_account( - updates.clone(), - PostRunTask::OutputCosting(name, "cost_transfer_to_virtual_account.csv"), - ); - run_radiswap( - updates.clone(), - PostRunTask::OutputCosting(name, "cost_radiswap.csv"), - ); - run_flash_loan( - updates.clone(), - PostRunTask::OutputCosting(name, "cost_flash_loan.csv"), - ); - run_publish_large_package( - updates.clone(), - PostRunTask::OutputCosting(name, "cost_publish_large_package.csv"), - ); - run_mint_large_size_nfts_from_manifest( - updates.clone(), - PostRunTask::OutputCosting(name, "cost_mint_large_size_nfts_from_manifest.csv"), - ); - run_mint_small_size_nfts_from_manifest( - updates.clone(), - PostRunTask::OutputCosting(name, "cost_mint_small_size_nfts_from_manifest.csv"), - ); - } + run_all(PostRunMode::OutputCosting); } #[test] fn run_cost_tests() { - for (name, updates) in [ - ( - path_local_metering_assets!("babylon"), - ProtocolUpdates::none(), - ), - ( - path_local_metering_assets!("anemone"), - ProtocolUpdates::up_to_anemone(), - ), - ( - path_local_metering_assets!("bottlenose"), - ProtocolUpdates::up_to_bottlenose(), - ), - ] { - run_basic_transfer( - updates.clone(), - PostRunTask::AssertCosting(name, "cost_transfer.csv"), - ); - run_basic_transfer_to_virtual_account( - updates.clone(), - PostRunTask::AssertCosting(name, "cost_transfer_to_virtual_account.csv"), - ); - run_radiswap( - updates.clone(), - PostRunTask::AssertCosting(name, "cost_radiswap.csv"), - ); - run_flash_loan( - updates.clone(), - PostRunTask::AssertCosting(name, "cost_flash_loan.csv"), - ); - run_publish_large_package( - updates.clone(), - PostRunTask::AssertCosting(name, "cost_publish_large_package.csv"), - ); - run_mint_large_size_nfts_from_manifest( - updates.clone(), - PostRunTask::AssertCosting(name, "cost_mint_large_size_nfts_from_manifest.csv"), - ); - run_mint_small_size_nfts_from_manifest( - updates.clone(), - PostRunTask::AssertCosting(name, "cost_mint_small_size_nfts_from_manifest.csv"), - ); + run_all(PostRunMode::AssertCosting); +} + +fn run_all(mode: PostRunMode) { + for protocol_version in ProtocolVersion::all_iterator() { + let folder = path_local_metering_assets(protocol_version.logical_name()); + + let folder = folder.as_str(); + let execute = move |run: &dyn Fn(ProtocolVersion, PostRunTask), file: &'static str| { + run( + protocol_version, + PostRunTask { + mode, + folder: folder.to_string(), + file: file.to_string(), + } + ) + }; + + execute(&run_basic_transfer, "cost_transfer.csv"); + execute(&run_basic_transfer_to_virtual_account, "cost_transfer_to_virtual_account.csv"); + execute(&run_radiswap, "cost_radiswap.csv"); + execute(&run_flash_loan, "cost_flash_loan.csv"); + execute(&run_publish_large_package, "cost_publish_large_package.csv"); + execute(&run_mint_large_size_nfts_from_manifest, "cost_mint_large_size_nfts_from_manifest.csv"); + execute(&run_mint_small_size_nfts_from_manifest, "cost_mint_small_size_nfts_from_manifest.csv"); } } @@ -361,18 +300,28 @@ pub fn write_cost_breakdown( f.write_all(buffer.as_bytes()).unwrap(); } -pub enum PostRunTask { - OutputCosting(&'static str, &'static str), - AssertCosting(&'static str, &'static str), +struct PostRunTask { + mode: PostRunMode, + folder: String, + file: String, +} + +#[derive(Copy, Clone)] +enum PostRunMode { + OutputCosting, + AssertCosting, } impl PostRunTask { pub fn run(&self, fee_summary: &TransactionFeeSummary, fee_details: &TransactionFeeDetails) { - match self { - PostRunTask::OutputCosting(folder, file) => { + let Self { + folder, file, mode + } = self; + match mode { + PostRunMode::OutputCosting => { write_cost_breakdown(fee_summary, fee_details, folder, file); } - PostRunTask::AssertCosting(folder, file) => { + PostRunMode::AssertCosting => { let expected = load_cost_breakdown(folder, file); assert_eq!(&fee_details.execution_cost_breakdown, &expected.0); assert_eq!(&fee_details.finalization_cost_breakdown, &expected.1); @@ -381,10 +330,10 @@ impl PostRunTask { } } -fn run_basic_transfer(protocol_updates: ProtocolUpdates, task: PostRunTask) { +fn run_basic_transfer(protocol_version: ProtocolVersion, task: PostRunTask) { // Arrange let mut ledger = LedgerSimulatorBuilder::new() - .with_custom_protocol_updates(protocol_updates) + .with_custom_protocol(|builder| builder.until(protocol_version)) .without_kernel_trace() .build(); let (public_key1, _, account1) = ledger.new_allocated_account(); @@ -407,10 +356,10 @@ fn run_basic_transfer(protocol_updates: ProtocolUpdates, task: PostRunTask) { task.run(&receipt.fee_summary, &receipt.fee_details.unwrap()); } -fn run_basic_transfer_to_virtual_account(protocol_updates: ProtocolUpdates, task: PostRunTask) { +fn run_basic_transfer_to_virtual_account(protocol_version: ProtocolVersion, task: PostRunTask) { // Arrange let mut ledger = LedgerSimulatorBuilder::new() - .with_custom_protocol_updates(protocol_updates) + .with_custom_protocol(|builder| builder.until(protocol_version)) .without_kernel_trace() .build(); let (public_key1, _, account1) = ledger.new_allocated_account(); @@ -435,9 +384,9 @@ fn run_basic_transfer_to_virtual_account(protocol_updates: ProtocolUpdates, task task.run(&receipt.fee_summary, &receipt.fee_details.unwrap()); } -fn run_radiswap(protocol_updates: ProtocolUpdates, task: PostRunTask) { +fn run_radiswap(protocol_version: ProtocolVersion, task: PostRunTask) { let mut ledger = LedgerSimulatorBuilder::new() - .with_custom_protocol_updates(protocol_updates) + .with_custom_protocol(|builder| builder.until(protocol_version)) .without_kernel_trace() .build(); @@ -544,9 +493,9 @@ fn run_radiswap(protocol_updates: ProtocolUpdates, task: PostRunTask) { task.run(&receipt.fee_summary, &receipt.fee_details.unwrap()); } -fn run_flash_loan(protocol_updates: ProtocolUpdates, task: PostRunTask) { +fn run_flash_loan(protocol_version: ProtocolVersion, task: PostRunTask) { let mut ledger = LedgerSimulatorBuilder::new() - .with_custom_protocol_updates(protocol_updates) + .with_custom_protocol(|builder| builder.until(protocol_version)) .without_kernel_trace() .build(); @@ -635,10 +584,10 @@ fn run_flash_loan(protocol_updates: ProtocolUpdates, task: PostRunTask) { task.run(&receipt.fee_summary, &receipt.fee_details.unwrap()); } -fn run_publish_large_package(protocol_updates: ProtocolUpdates, task: PostRunTask) { +fn run_publish_large_package(protocol_version: ProtocolVersion, task: PostRunTask) { // Arrange let mut ledger = LedgerSimulatorBuilder::new() - .with_custom_protocol_updates(protocol_updates) + .with_custom_protocol(|builder| builder.until(protocol_version)) .without_kernel_trace() .build(); @@ -671,9 +620,9 @@ fn run_publish_large_package(protocol_updates: ProtocolUpdates, task: PostRunTas task.run(&receipt.fee_summary, &receipt.fee_details.unwrap()); } -fn run_mint_small_size_nfts_from_manifest(protocol_updates: ProtocolUpdates, task: PostRunTask) { +fn run_mint_small_size_nfts_from_manifest(protocol_version: ProtocolVersion, task: PostRunTask) { run_mint_nfts_from_manifest( - protocol_updates, + protocol_version, task, TestNonFungibleData { metadata: btreemap!(), @@ -681,11 +630,11 @@ fn run_mint_small_size_nfts_from_manifest(protocol_updates: ProtocolUpdates, tas ) } -fn run_mint_large_size_nfts_from_manifest(protocol_updates: ProtocolUpdates, task: PostRunTask) { +fn run_mint_large_size_nfts_from_manifest(protocol_version: ProtocolVersion, task: PostRunTask) { const N: usize = 50; run_mint_nfts_from_manifest( - protocol_updates, + protocol_version, task, TestNonFungibleData { metadata: btreemap!( @@ -703,13 +652,13 @@ fn run_mint_large_size_nfts_from_manifest(protocol_updates: ProtocolUpdates, tas } fn run_mint_nfts_from_manifest( - protocol_updates: ProtocolUpdates, + protocol_version: ProtocolVersion, task: PostRunTask, nft_data: TestNonFungibleData, ) { // Arrange let mut ledger = LedgerSimulatorBuilder::new() - .with_custom_protocol_updates(protocol_updates) + .with_custom_protocol(|builder| builder.until(protocol_version)) .without_kernel_trace() .build(); let (_, _, account) = ledger.new_allocated_account(); diff --git a/radix-engine-tests/tests/application/stake_reconciliation.rs b/radix-engine-tests/tests/application/stake_reconciliation.rs index 2872c05f1a2..45a26394ebd 100644 --- a/radix-engine-tests/tests/application/stake_reconciliation.rs +++ b/radix-engine-tests/tests/application/stake_reconciliation.rs @@ -1,5 +1,5 @@ use radix_common::prelude::*; -use radix_engine::updates::ProtocolUpdates; +use radix_engine::updates::*; use radix_substate_store_interface::db_key_mapper::{DatabaseKeyMapper, SpreadPrefixKeyMapper}; use scrypto_test::prelude::*; @@ -8,7 +8,7 @@ fn test_stake_reconciliation() { // Arrange let pub_key = Secp256k1PrivateKey::from_u64(1u64).unwrap().public_key(); let mut ledger = LedgerSimulatorBuilder::new() - .with_custom_protocol_updates(ProtocolUpdates::none()) + .with_protocol_version(ProtocolVersion::Genesis) .build(); let (account_pk, _, account) = ledger.new_account(false); diff --git a/radix-engine-tests/tests/blueprints/account_locker.rs b/radix-engine-tests/tests/blueprints/account_locker.rs index 5ae6471e3e2..23606545b4d 100644 --- a/radix-engine-tests/tests/blueprints/account_locker.rs +++ b/radix-engine-tests/tests/blueprints/account_locker.rs @@ -15,7 +15,7 @@ fn account_locker_cant_be_instantiated_before_protocol_update() { // Arrange let mut ledger = LedgerSimulatorBuilder::new() .without_kernel_trace() - .with_custom_protocol_updates(ProtocolUpdates::up_to_anemone()) + .with_protocol_version(ProtocolVersion::Anemone) .build(); let (_, _, account) = ledger.new_account(false); diff --git a/radix-engine-tests/tests/db/jmt_consistency.rs b/radix-engine-tests/tests/db/jmt_consistency.rs index 94124dc409e..5799d9aec84 100644 --- a/radix-engine-tests/tests/db/jmt_consistency.rs +++ b/radix-engine-tests/tests/db/jmt_consistency.rs @@ -6,10 +6,11 @@ use scrypto_test::prelude::*; #[test] fn substate_store_matches_state_tree_after_each_scenario() { let db = StateTreeUpdatingDatabase::new(InMemorySubstateDatabase::standard()); - DefaultTransactionScenarioExecutor::new(db, NetworkDefinition::simulator()) + let network_definition = NetworkDefinition::simulator(); + DefaultTransactionScenarioExecutor::new(db, &network_definition) .on_transaction_executed(|_, _, _, db| { db.validate_state_tree_matches_substate_store().unwrap() }) - .execute_all() + .execute_every_protocol_update_and_scenario() .expect("Must succeed!"); } diff --git a/radix-engine-tests/tests/db/substate_database_overlay.rs b/radix-engine-tests/tests/db/substate_database_overlay.rs index 32eabd042f1..e26a7575f13 100644 --- a/radix-engine-tests/tests/db/substate_database_overlay.rs +++ b/radix-engine-tests/tests/db/substate_database_overlay.rs @@ -1,6 +1,5 @@ use radix_engine::system::system_db_reader::*; use radix_engine::transaction::*; -use radix_engine::updates::*; use radix_substate_store_impls::memory_db::*; use radix_substate_store_impls::substate_database_overlay::*; use radix_substate_store_interface::db_key_mapper::*; @@ -408,53 +407,39 @@ fn transaction_receipts_from_scenarios_are_identical_between_staging_and_non_sta #[test] #[allow(clippy::redundant_closure_call)] fn database_hashes_are_identical_between_staging_and_non_staging_database_at_each_scenario_step() { - macro_rules! non_homogenous_array_map { - ( - [ - $($item: expr),* $(,)? - ] - .map($func: expr) - ) => { - [ - $( - $func($item) - ),* - ] - }; - } - run_scenarios(|(non_staging_database, _), (staging_database, _)| { - let [non_staging_database_hash, staging_database_hash] = non_homogenous_array_map! { - [non_staging_database, staging_database].map(|database| { - let mut accumulator_hash = Hash([0; 32]); - let reader = SystemDatabaseReader::new(database); - for (node_id, partition_number) in reader.partitions_iter() { - let db_node_key = SpreadPrefixKeyMapper::to_db_node_key(&node_id); - let db_partition_key = DbPartitionKey { - node_key: db_node_key, - partition_num: partition_number.0, - }; - - for (substate_key, substate_value) in - SubstateDatabase::list_entries(database, &db_partition_key) - { - let entry_hash = hash( - scrypto_encode(&(node_id, partition_number, substate_key, substate_value)) - .unwrap(), - ); - let mut data = accumulator_hash.to_vec(); - data.extend(entry_hash.to_vec()); - accumulator_hash = hash(data); - } - } - accumulator_hash - }) - }; + let non_staging_database_hash = create_database_contents_hash(non_staging_database); + let staging_database_hash = create_database_contents_hash(staging_database); assert_eq!(non_staging_database_hash, staging_database_hash) }) } +fn create_database_contents_hash(database: &D) -> Hash { + let mut accumulator_hash = Hash([0; 32]); + let reader = SystemDatabaseReader::new(database); + for (node_id, partition_number) in reader.partitions_iter() { + let db_node_key = SpreadPrefixKeyMapper::to_db_node_key(&node_id); + let db_partition_key = DbPartitionKey { + node_key: db_node_key, + partition_num: partition_number.0, + }; + + for (substate_key, substate_value) in + SubstateDatabase::list_entries(database, &db_partition_key) + { + let entry_hash = hash( + scrypto_encode(&(node_id, partition_number, substate_key, substate_value)) + .unwrap(), + ); + let mut data = accumulator_hash.to_vec(); + data.extend(entry_hash.to_vec()); + accumulator_hash = hash(data); + } + } + accumulator_hash +} + /// Runs the scenarios on an [`InMemorySubstateDatabase`] and a [`SingleSubstateDatabaseOverlay`] wrapping /// an [`InMemorySubstateDatabase`]. The passed check function is executed after the execution of /// each scenario. @@ -472,31 +457,20 @@ fn run_scenarios( let ledger_with_overlay = Rc::new(RefCell::new( LedgerSimulatorBuilder::new() .with_custom_database(overlay) - .with_custom_protocol_updates(ProtocolUpdates::none()) + .with_custom_protocol(|builder| builder.until_genesis()) .without_kernel_trace() .build(), )); + let network_definition = NetworkDefinition::simulator(); DefaultTransactionScenarioExecutor::new( InMemorySubstateDatabase::standard(), - NetworkDefinition::simulator(), + &network_definition, ) - .bootstrap(true) - .on_new_protocol_requirement_encountered(|network, protocol_update, db| { - if let ProtocolVersion::ProtocolUpdate(protocol_update) = protocol_update { - // Apply to the executor's DB. - protocol_update - .generate_state_updates(db, network) - .into_iter() - .for_each(|state_updates| { - db.commit(&state_updates.create_database_updates::()) - }); - - // Apply to the ledger's DB. - ledger_with_overlay - .borrow_mut() - .apply_protocol_updates(&[protocol_update]); - } + .on_before_execute_protocol_update(|executor| { + // We copy the protocol updates onto the ledger_with_overlay + executor.clone() + .run_and_commit(ledger_with_overlay.borrow_mut().substate_db_mut()) }) .on_transaction_executed(|_, transaction, receipt, db| { // Execute the same transaction on the ledger simulator. @@ -513,6 +487,6 @@ fn run_scenarios( ), ); }) - .execute_all() + .execute_every_protocol_update_and_scenario() .expect("Must succeed!"); } diff --git a/radix-engine-tests/tests/flash/account.rs b/radix-engine-tests/tests/flash/account.rs index f286b3d2466..9c6cf6faf3b 100644 --- a/radix-engine-tests/tests/flash/account.rs +++ b/radix-engine-tests/tests/flash/account.rs @@ -1,5 +1,4 @@ -use radix_engine::blueprints::account::*; -use radix_engine::updates::*; +use radix_engine::{blueprints::account::*, updates::*}; use scrypto_test::prelude::*; #[test] @@ -7,7 +6,7 @@ fn before_protocol_update_try_deposit_or_refund_fails_if_claimed_authorized_depo // Arrange let mut ledger = LedgerSimulatorBuilder::new() .without_kernel_trace() - .with_custom_protocol_updates(ProtocolUpdates::none()) + .with_protocol_version(ProtocolVersion::Genesis) .build(); let (user_public_key, _, user_account) = ledger.new_account(false); @@ -54,7 +53,7 @@ fn after_protocol_update_try_deposit_or_refund_refunds_resources_if_claimed_auth // Arrange let mut ledger = LedgerSimulatorBuilder::new() .without_kernel_trace() - .with_custom_protocol_updates(ProtocolUpdates::all()) + .with_protocol_version(ProtocolVersion::latest()) .build(); let (user_public_key, _, user_account) = ledger.new_account(false); diff --git a/radix-engine-tests/tests/flash/crypto_utils.rs b/radix-engine-tests/tests/flash/crypto_utils.rs index 264f2298f35..2ff6a1ad940 100644 --- a/radix-engine-tests/tests/flash/crypto_utils.rs +++ b/radix-engine-tests/tests/flash/crypto_utils.rs @@ -2,12 +2,9 @@ use radix_common::prelude::*; use radix_engine::blueprints::package::PackageError; use radix_engine::errors::ApplicationError; use radix_engine::errors::RuntimeError; -use radix_engine::updates::state_updates::generate_bls128_and_keccak256_state_updates; -use radix_engine::updates::ProtocolUpdateEntry; -use radix_engine::updates::ProtocolUpdates; +use radix_engine::updates::*; use radix_engine_tests::common::PackageLoader; use radix_engine_tests::common::*; -use radix_substate_store_interface::db_key_mapper::SpreadPrefixKeyMapper; use scrypto_test::prelude::*; use scrypto_test::prelude::{CustomGenesis, LedgerSimulatorBuilder}; @@ -24,14 +21,15 @@ fn publishing_crypto_utils_with_state_flash_should_succeed() { fn run_flash_test(flash_substates: bool, expect_success: bool) { // Arrange let mut ledger = LedgerSimulatorBuilder::new() - .with_custom_protocol_updates(ProtocolUpdates::none()) .with_custom_genesis(CustomGenesis::default( Epoch::of(1), CustomGenesis::default_consensus_manager_config(), )) + .with_protocol_version(ProtocolVersion::Genesis) .build(); + if flash_substates { - let state_updates = generate_bls128_and_keccak256_state_updates(); + let state_updates = generate_vm_boot_for_bls128_and_keccak256_state_updates(); let db_updates = state_updates.create_database_updates::(); ledger.substate_db_mut().commit(&db_updates); } @@ -66,16 +64,17 @@ fn publishing_crypto_utils_using_test_environment_with_state_flash_should_succee fn run_flash_test_test_environment(enable_bls: bool, expect_success: bool) { // Arrange - let test_env_builder = TestEnvironmentBuilder::new(); - - let mut test_env = if enable_bls { - test_env_builder.protocol_updates( - ProtocolUpdates::none().and(ProtocolUpdateEntry::Bls12381AndKeccak256), + let mut test_env = TestEnvironmentBuilder::new() + .with_protocol(|builder| builder + .with_anemone( + AnemoneSettings::all_disabled() + .set(|s| { + s.vm_boot_to_enable_bls128_and_keccak256 = UpdateSetting::new(enable_bls) + }) + ) + .until(ProtocolVersion::Anemone) ) - } else { - test_env_builder.protocol_updates(ProtocolUpdates::none()) - } - .build(); + .build(); // Act let result = diff --git a/radix-engine-tests/tests/flash/pools.rs b/radix-engine-tests/tests/flash/pools.rs index 2b5865aa0de..f87e8eef7e3 100644 --- a/radix-engine-tests/tests/flash/pools.rs +++ b/radix-engine-tests/tests/flash/pools.rs @@ -3,15 +3,14 @@ use radix_engine::blueprints::pool::v1::errors::{ multi_resource_pool::Error as MultiResourcePoolError, two_resource_pool::Error as TwoResourcePoolError, }; -use radix_engine::updates::state_updates::generate_pool_math_precision_fix_state_updates; -use radix_engine::updates::ProtocolUpdates; +use radix_engine::updates::*; use scrypto_test::prelude::*; #[test] fn database_is_consistent_before_and_after_protocol_update() { // Arrange let mut ledger = LedgerSimulatorBuilder::new() - .with_custom_protocol_updates(ProtocolUpdates::none()) + .with_protocol_version(ProtocolVersion::Genesis) .without_kernel_trace() .build(); @@ -81,7 +80,7 @@ fn database_is_consistent_before_and_after_protocol_update() { fn single_sided_contributions_to_two_resource_pool_are_only_allowed_after_protocol_update() { // Arrange let mut ledger = LedgerSimulatorBuilder::new() - .with_custom_protocol_updates(ProtocolUpdates::none()) + .with_protocol_version(ProtocolVersion::Genesis) .without_kernel_trace() .build(); @@ -235,7 +234,7 @@ fn single_sided_contributions_to_two_resource_pool_are_only_allowed_after_protoc fn single_sided_contributions_to_multi_resource_pool_are_only_allowed_after_protocol_update() { // Arrange let mut ledger = LedgerSimulatorBuilder::new() - .with_custom_protocol_updates(ProtocolUpdates::none()) + .with_protocol_version(ProtocolVersion::Genesis) .without_kernel_trace() .build(); diff --git a/radix-engine-tests/tests/flash/role_assignment.rs b/radix-engine-tests/tests/flash/role_assignment.rs index 33ed8debb38..be4f5a3556f 100644 --- a/radix-engine-tests/tests/flash/role_assignment.rs +++ b/radix-engine-tests/tests/flash/role_assignment.rs @@ -1,4 +1,4 @@ -use radix_engine::{system::system_type_checker::TypeCheckError, updates::ProtocolUpdates}; +use radix_engine::{system::system_type_checker::TypeCheckError, updates::*}; use scrypto_test::prelude::*; #[test] @@ -6,7 +6,7 @@ fn get_owner_role_method_call_fails_without_the_protocol_update() { // Arrange let mut ledger = LedgerSimulatorBuilder::new() .without_kernel_trace() - .with_custom_protocol_updates(ProtocolUpdates::none()) + .with_protocol_version(ProtocolVersion::Genesis) .build(); // Act diff --git a/radix-engine-tests/tests/flash/seconds_precision.rs b/radix-engine-tests/tests/flash/seconds_precision.rs index d432f412a89..88f356014ec 100644 --- a/radix-engine-tests/tests/flash/seconds_precision.rs +++ b/radix-engine-tests/tests/flash/seconds_precision.rs @@ -5,8 +5,7 @@ use radix_common::prelude::{manifest_args, Round}; use radix_common::types::Epoch; use radix_engine::errors::{RuntimeError, SystemError}; use radix_engine::system::system_type_checker::TypeCheckError; -use radix_engine::updates::state_updates::generate_seconds_precision_timestamp_state_updates; -use radix_engine::updates::ProtocolUpdates; +use radix_engine::updates::*; use radix_engine_interface::blueprints::consensus_manager::{ ConsensusManagerNextRoundInput, CONSENSUS_MANAGER_NEXT_ROUND_IDENT, }; @@ -29,11 +28,11 @@ fn get_current_time_rounded_to_seconds_with_state_flash_should_succeed() { fn run_flash_test(flash_substates: bool, expect_success: bool) { // Arrange let mut ledger = LedgerSimulatorBuilder::new() - .with_custom_protocol_updates(ProtocolUpdates::none()) .with_custom_genesis(CustomGenesis::default( Epoch::of(1), CustomGenesis::default_consensus_manager_config(), )) + .with_protocol_version(ProtocolVersion::Genesis) .build(); let package_address = ledger.publish_package_simple(PackageLoader::get("clock")); diff --git a/radix-engine-tests/tests/system/typed_substate_layout.rs b/radix-engine-tests/tests/system/typed_substate_layout.rs index 78dbf6dd8b6..b92d7a39329 100644 --- a/radix-engine-tests/tests/system/typed_substate_layout.rs +++ b/radix-engine-tests/tests/system/typed_substate_layout.rs @@ -160,14 +160,14 @@ fn test_bootstrap_receipt_should_have_events_that_can_be_typed() { fn test_all_scenario_commit_receipts_should_have_substate_changes_which_can_be_typed() { DefaultTransactionScenarioExecutor::new( InMemorySubstateDatabase::standard(), - NetworkDefinition::simulator(), + &NetworkDefinition::simulator(), ) .on_transaction_executed(|_, _, receipt, _| { if let TransactionResult::Commit(ref commit_result) = receipt.result { assert_receipt_substate_changes_can_be_typed(commit_result); }; }) - .execute_all() + .execute_every_protocol_update_and_scenario() .expect("Must succeed!"); } @@ -175,14 +175,14 @@ fn test_all_scenario_commit_receipts_should_have_substate_changes_which_can_be_t fn test_all_scenario_commit_receipts_should_have_events_that_can_be_typed() { DefaultTransactionScenarioExecutor::new( InMemorySubstateDatabase::standard(), - NetworkDefinition::simulator(), + &NetworkDefinition::simulator(), ) .on_transaction_executed(|_, _, receipt, _| { if let TransactionResult::Commit(ref commit_result) = receipt.result { assert_receipt_events_can_be_typed(commit_result); }; }) - .execute_all() + .execute_every_protocol_update_and_scenario() .expect("Must succeed!"); } diff --git a/radix-engine/src/lib.rs b/radix-engine/src/lib.rs index 232513cb114..2aa98746f11 100644 --- a/radix-engine/src/lib.rs +++ b/radix-engine/src/lib.rs @@ -46,6 +46,7 @@ pub(crate) mod internal_prelude { pub use radix_common::prelude::*; pub use radix_engine_interface::blueprints::component::*; pub use radix_engine_interface::prelude::*; + pub use radix_substate_store_interface::interface::*; pub use sbor::rust::ops::AddAssign; pub use sbor::rust::ops::SubAssign; } diff --git a/radix-engine/src/system/system_db_reader.rs b/radix-engine/src/system/system_db_reader.rs index 8e125b2e4bc..4e787f190b6 100644 --- a/radix-engine/src/system/system_db_reader.rs +++ b/radix-engine/src/system/system_db_reader.rs @@ -102,7 +102,7 @@ pub enum SystemReaderError { } /// A System Layer (Layer 2) abstraction over an underlying substate database -pub struct SystemDatabaseReader<'a, S: SubstateDatabase> { +pub struct SystemDatabaseReader<'a, S: SubstateDatabase + ?Sized> { substate_db: &'a S, state_updates: Option<&'a StateUpdates>, @@ -110,7 +110,7 @@ pub struct SystemDatabaseReader<'a, S: SubstateDatabase> { schema_cache: RefCell>>, } -impl<'a, S: SubstateDatabase> SystemDatabaseReader<'a, S> { +impl<'a, S: SubstateDatabase + ?Sized> SystemDatabaseReader<'a, S> { pub fn new_with_overlay(substate_db: &'a S, state_updates: &'a StateUpdates) -> Self { Self { substate_db, @@ -1124,14 +1124,16 @@ impl<'a, S: SubstateDatabase> SystemDatabaseReader<'a, S> { } } -struct ValidationPayloadCheckerContext<'a, S: SubstateDatabase> { +struct ValidationPayloadCheckerContext<'a, S: SubstateDatabase + ?Sized> { reader: &'a SystemDatabaseReader<'a, S>, schema_origin: SchemaOrigin, allow_non_global_ref: bool, allow_ownership: bool, } -impl<'a, S: SubstateDatabase> ValidationContext for ValidationPayloadCheckerContext<'a, S> { +impl<'a, S: SubstateDatabase + ?Sized> ValidationContext + for ValidationPayloadCheckerContext<'a, S> +{ type Error = String; fn get_node_type_info(&self, node_id: &NodeId) -> Result { diff --git a/radix-engine/src/updates/state_updates.rs b/radix-engine/src/updates/anemone.rs similarity index 51% rename from radix-engine/src/updates/state_updates.rs rename to radix-engine/src/updates/anemone.rs index f308d9af70d..b6fefe7eae5 100644 --- a/radix-engine/src/updates/state_updates.rs +++ b/radix-engine/src/updates/anemone.rs @@ -1,54 +1,137 @@ +use super::*; use crate::blueprints::consensus_manager::*; -use crate::blueprints::locker::LockerNativePackage; use crate::blueprints::models::KeyValueEntryContentSource; use crate::blueprints::package::*; use crate::blueprints::pool::v1::constants::*; -use crate::internal_prelude::*; -use crate::object_modules::role_assignment::*; -use crate::system::system_callback::{ - SystemBoot, SystemParameters, BOOT_LOADER_SYSTEM_SUBSTATE_FIELD_KEY, -}; -use crate::system::system_db_reader::{ObjectCollectionKey, SystemDatabaseReader}; -use crate::track::{NodeStateUpdates, PartitionStateUpdates, StateUpdates}; -use crate::transaction::{CostingParameters, LimitParameters}; +use crate::system::system_db_reader::*; +use crate::track::*; use crate::vm::*; -use radix_common::constants::*; -use radix_common::crypto::hash; -use radix_common::math::Decimal; -use radix_common::prelude::ScopedTypeId; -use radix_common::prelude::{scrypto_encode, ScryptoCustomTypeKind}; -use radix_common::types::SubstateKey; -use radix_engine_interface::api::ObjectModuleId; -use radix_engine_interface::blueprints::account::*; -use radix_engine_interface::blueprints::consensus_manager::*; -use radix_engine_interface::prelude::*; -use radix_engine_interface::types::CollectionDescriptor; -use radix_rust::indexmap; -use radix_substate_store_interface::interface::*; use sbor::{generate_full_schema, TypeAggregator}; -/// A quick macro for encoding and unwrapping. -macro_rules! scrypto_encode { - ( - $expr: expr - ) => { - ::radix_common::prelude::scrypto_encode($expr).unwrap() - }; +#[derive(Clone)] +pub struct AnemoneSettings { + /// Changes the cost associated with validator creation. + pub validator_fee_fix: UpdateSetting<()>, + + /// Exposes second-precision timestamp. + pub seconds_precision: UpdateSetting<()>, + + /// Introduces BLS12-381 and Keccak-256 features. + pub vm_boot_to_enable_bls128_and_keccak256: UpdateSetting<()>, + + /// Increases the math precision with native pool implementations. + pub pools_update: UpdateSetting<()>, } -pub fn generate_bls128_and_keccak256_state_updates() -> StateUpdates { - let substate = scrypto_encode(&VmBoot::V1 { - scrypto_version: ScryptoVmVersion::crypto_utils_added().into(), - }) - .unwrap(); +impl UpdateSettings for AnemoneSettings { + type BatchGenerator = AnemoneBatchGenerator; + + fn all_enabled_as_default_for_network(network: &NetworkDefinition) -> Self { + Self { + validator_fee_fix: UpdateSetting::enabled_as_default_for_network(network), + seconds_precision: UpdateSetting::enabled_as_default_for_network(network), + vm_boot_to_enable_bls128_and_keccak256: UpdateSetting::enabled_as_default_for_network( + network, + ), + pools_update: UpdateSetting::enabled_as_default_for_network(network), + } + } + + fn all_disabled() -> Self { + Self { + validator_fee_fix: UpdateSetting::Disabled, + seconds_precision: UpdateSetting::Disabled, + vm_boot_to_enable_bls128_and_keccak256: UpdateSetting::Disabled, + pools_update: UpdateSetting::Disabled, + } + } + + fn create_batch_generator(&self) -> Self::BatchGenerator { + AnemoneBatchGenerator { + settings: self.clone(), + } + } +} + +#[derive(Clone)] +pub struct AnemoneBatchGenerator { + settings: AnemoneSettings, +} + +impl ProtocolUpdateBatchGenerator for AnemoneBatchGenerator { + fn generate_batch( + &self, + store: &dyn SubstateDatabase, + batch_index: u32, + ) -> Option { + match batch_index { + // Just a single batch for Anemone, perhaps in future updates we should have separate batches for each update? + 0 => Some(generate_principal_batch(store, &self.settings)), + _ => None, + } + } +} + +fn generate_principal_batch( + store: &dyn SubstateDatabase, + settings: &AnemoneSettings, +) -> ProtocolUpdateBatch { + let mut transactions = vec![]; + if let UpdateSetting::Enabled(_) = &settings.validator_fee_fix { + transactions.push(ProtocolUpdateTransactionDetails::flash( + "anemone-validator-fee-fix", + generate_validator_creation_fee_fix_state_updates(store), + )); + } + if let UpdateSetting::Enabled(_) = &settings.seconds_precision { + transactions.push(ProtocolUpdateTransactionDetails::flash( + "anemone-seconds-precision", + generate_seconds_precision_timestamp_state_updates(store), + )); + } + if let UpdateSetting::Enabled(_) = &settings.vm_boot_to_enable_bls128_and_keccak256 { + transactions.push(ProtocolUpdateTransactionDetails::flash( + "anemone-vm-boot", + generate_vm_boot_for_bls128_and_keccak256_state_updates(), + )); + } + if let UpdateSetting::Enabled(_) = &settings.pools_update { + transactions.push(ProtocolUpdateTransactionDetails::flash( + "anemone-pools", + generate_pool_math_precision_fix_state_updates(store), + )); + } + ProtocolUpdateBatch { transactions } +} + +fn generate_validator_creation_fee_fix_state_updates( + db: &S, +) -> StateUpdates { + let reader = SystemDatabaseReader::new(db); + let consensus_mgr_node_id = CONSENSUS_MANAGER.into_node_id(); + + let versioned_config: VersionedConsensusManagerConfiguration = reader + .read_typed_object_field( + &consensus_mgr_node_id, + ModuleId::Main, + ConsensusManagerField::Configuration.field_index(), + ) + .unwrap(); + + let mut config = versioned_config.fully_update_and_into_latest_version(); + config.config.validator_creation_usd_cost = Decimal::from(100); + + let updated_substate = config.into_locked_substate(); StateUpdates { by_node: indexmap!( - TRANSACTION_TRACKER.into_node_id() => NodeStateUpdates::Delta { + consensus_mgr_node_id => NodeStateUpdates::Delta { by_partition: indexmap! { - BOOT_LOADER_PARTITION => PartitionStateUpdates::Delta { + MAIN_BASE_PARTITION => PartitionStateUpdates::Delta { by_substate: indexmap! { - SubstateKey::Field(BOOT_LOADER_VM_SUBSTATE_FIELD_KEY) => DatabaseUpdate::Set(substate) + SubstateKey::Field(ConsensusManagerField::Configuration.field_index()) => DatabaseUpdate::Set( + scrypto_encode(&updated_substate).unwrap() + ) } }, } @@ -59,7 +142,7 @@ pub fn generate_bls128_and_keccak256_state_updates() -> StateUpdates { /// Generates the state updates required for updating the Consensus Manager blueprint /// to use seconds precision -pub fn generate_seconds_precision_timestamp_state_updates( +pub fn generate_seconds_precision_timestamp_state_updates( db: &S, ) -> StateUpdates { let reader = SystemDatabaseReader::new(db); @@ -234,6 +317,27 @@ pub fn generate_seconds_precision_timestamp_state_updates( } } +pub fn generate_vm_boot_for_bls128_and_keccak256_state_updates() -> StateUpdates { + let substate = scrypto_encode(&VmBoot::V1 { + scrypto_version: ScryptoVmVersion::crypto_utils_added().into(), + }) + .unwrap(); + + StateUpdates { + by_node: indexmap!( + TRANSACTION_TRACKER.into_node_id() => NodeStateUpdates::Delta { + by_partition: indexmap! { + BOOT_LOADER_PARTITION => PartitionStateUpdates::Delta { + by_substate: indexmap! { + SubstateKey::Field(BOOT_LOADER_VM_SUBSTATE_FIELD_KEY) => DatabaseUpdate::Set(substate) + } + }, + } + } + ), + } +} + /// Generates the state updates required to update the pool package from the v1.0 to the v1.1 /// logic. No schema changes took place, just a change of logic. It produces the following /// updates: @@ -243,7 +347,9 @@ pub fn generate_seconds_precision_timestamp_state_updates( /// * Removes the old code_hash => original_code substate. /// * Adds a new code_hash => original_code substate. /// * Updates the function exports in the blueprint definitions to point to the new code hash. -pub fn generate_pool_math_precision_fix_state_updates(db: &S) -> StateUpdates { +pub fn generate_pool_math_precision_fix_state_updates( + db: &S, +) -> StateUpdates { let reader = SystemDatabaseReader::new(db); let pool_package_node_id = POOL_PACKAGE.into_node_id(); @@ -371,353 +477,3 @@ pub fn generate_pool_math_precision_fix_state_updates(db: & }, } } - -pub fn generate_validator_creation_fee_fix_state_updates( - db: &S, -) -> StateUpdates { - let reader = SystemDatabaseReader::new(db); - let consensus_mgr_node_id = CONSENSUS_MANAGER.into_node_id(); - - let versioned_config: VersionedConsensusManagerConfiguration = reader - .read_typed_object_field( - &consensus_mgr_node_id, - ModuleId::Main, - ConsensusManagerField::Configuration.field_index(), - ) - .unwrap(); - - let mut config = versioned_config.fully_update_and_into_latest_version(); - config.config.validator_creation_usd_cost = Decimal::from(100); - - let updated_substate = config.into_locked_substate(); - - StateUpdates { - by_node: indexmap!( - consensus_mgr_node_id => NodeStateUpdates::Delta { - by_partition: indexmap! { - MAIN_BASE_PARTITION => PartitionStateUpdates::Delta { - by_substate: indexmap! { - SubstateKey::Field(ConsensusManagerField::Configuration.field_index()) => DatabaseUpdate::Set( - scrypto_encode(&updated_substate).unwrap() - ) - } - }, - } - } - ), - } -} - -pub fn generate_owner_role_getter_state_updates(db: &S) -> StateUpdates { - let reader = SystemDatabaseReader::new(db); - let node_id = ROLE_ASSIGNMENT_MODULE_PACKAGE.into_node_id(); - let blueprint_version_key = BlueprintVersionKey { - blueprint: ROLE_ASSIGNMENT_BLUEPRINT.to_string(), - version: Default::default(), - }; - - // Creating the original code substates for extension. - let (code_hash, (code_substate, vm_type_substate)) = { - let original_code = (NativeCodeId::RoleAssignmentCode2 as u64) - .to_be_bytes() - .to_vec(); - - let code_hash = CodeHash::from_hash(hash(&original_code)); - let code_substate = PackageCodeOriginalCodeV1 { - code: original_code, - } - .into_versioned() - .into_locked_substate(); - let vm_type_substate = PackageCodeVmTypeV1 { - vm_type: VmType::Native, - } - .into_locked_substate(); - - (code_hash, (code_substate, vm_type_substate)) - }; - - // Creating the new schema substate with the methods added by the extension - let (added_functions, schema) = RoleAssignmentBottlenoseExtension::added_functions_schema(); - let (schema_hash, schema_substate) = - (schema.generate_schema_hash(), schema.into_locked_substate()); - - // Updating the blueprint definition of the existing blueprint with the added functions. - let blueprint_definition_substate = { - let mut blueprint_definition = reader - .read_object_collection_entry::<_, VersionedPackageBlueprintVersionDefinition>( - &node_id, - ObjectModuleId::Main, - ObjectCollectionKey::KeyValue( - PackageCollection::BlueprintVersionDefinitionKeyValue.collection_index(), - &blueprint_version_key, - ), - ) - .unwrap() - .unwrap() - .fully_update_and_into_latest_version(); - - for (function_name, added_function) in added_functions.into_iter() { - let TypeRef::Static(input_local_id) = added_function.input else { - unreachable!() - }; - let TypeRef::Static(output_local_id) = added_function.output else { - unreachable!() - }; - - blueprint_definition.function_exports.insert( - function_name.clone(), - PackageExport { - code_hash, - export_name: function_name.clone(), - }, - ); - blueprint_definition.interface.functions.insert( - function_name, - FunctionSchema { - receiver: added_function.receiver, - input: BlueprintPayloadDef::Static(ScopedTypeId(schema_hash, input_local_id)), - output: BlueprintPayloadDef::Static(ScopedTypeId(schema_hash, output_local_id)), - }, - ); - } - - blueprint_definition.into_locked_substate() - }; - - // Getting the partition number of the various collections that we're updating - let [blueprint_version_definition_partition_number, code_vm_type_partition_number, code_original_code_partition_number, schema_partition_number] = - [ - PackageCollection::BlueprintVersionDefinitionKeyValue, - PackageCollection::CodeVmTypeKeyValue, - PackageCollection::CodeOriginalCodeKeyValue, - PackageCollection::SchemaKeyValue, - ] - .map(|package_collection| { - reader - .get_partition_of_collection( - &node_id, - ObjectModuleId::Main, - package_collection.collection_index(), - ) - .unwrap() - }); - - // Generating the state updates - StateUpdates { - by_node: indexmap!( - node_id => NodeStateUpdates::Delta { - by_partition: indexmap! { - blueprint_version_definition_partition_number => PartitionStateUpdates::Delta { - by_substate: indexmap! { - SubstateKey::Map(scrypto_encode!(&blueprint_version_key)) => DatabaseUpdate::Set( - scrypto_encode!(&blueprint_definition_substate) - ) - } - }, - code_vm_type_partition_number => PartitionStateUpdates::Delta { - by_substate: indexmap! { - SubstateKey::Map(scrypto_encode!(&code_hash)) => DatabaseUpdate::Set( - scrypto_encode!(&vm_type_substate) - ) - } - }, - code_original_code_partition_number => PartitionStateUpdates::Delta { - by_substate: indexmap! { - SubstateKey::Map(scrypto_encode!(&code_hash)) => DatabaseUpdate::Set( - scrypto_encode!(&code_substate) - ) - } - }, - schema_partition_number => PartitionStateUpdates::Delta { - by_substate: indexmap! { - SubstateKey::Map(scrypto_encode!(&schema_hash)) => DatabaseUpdate::Set( - scrypto_encode!(&schema_substate) - ) - } - } - } - } - ), - } -} - -pub fn generate_locker_package_state_updates() -> StateUpdates { - let package_definition = LockerNativePackage::definition(); - let package_structure = PackageNativePackage::validate_and_build_package_structure( - package_definition, - VmType::Native, - (NativeCodeId::LockerCode1 as u64).to_be_bytes().to_vec(), - Default::default(), - &VmVersion::latest(), - ) - .unwrap_or_else(|err| { - panic!( - "Invalid flashed Package definition with native_code_id {}: {:?}", - NativeCodeId::LockerCode1 as u64, - err - ) - }); - - let partitions = create_package_partition_substates( - package_structure, - metadata_init! { - "name" => "Locker Package", locked; - "description" => "A native package that defines the logic for dApp-owned lockers to send resources to specified account addresses.", locked; - }, - None, - ); - - StateUpdates { - by_node: indexmap! { - LOCKER_PACKAGE.into_node_id() => NodeStateUpdates::Delta { - by_partition: partitions - .into_iter() - .map(|(partition_num, substates)| { - ( - partition_num, - PartitionStateUpdates::Delta { - by_substate: substates - .into_iter() - .map(|(key, value)| { - (key, DatabaseUpdate::Set(value.as_vec_ref().clone())) - }) - .collect(), - }, - ) - }) - .collect(), - } - }, - } -} - -pub fn generate_account_bottlenose_extension_state_updates( - db: &S, -) -> StateUpdates { - let reader = SystemDatabaseReader::new(db); - let node_id = ACCOUNT_PACKAGE.into_node_id(); - let blueprint_version_key = BlueprintVersionKey { - blueprint: ACCOUNT_BLUEPRINT.to_string(), - version: Default::default(), - }; - - // Creating the original code substates for extension. - let (code_hash, (code_substate, vm_type_substate)) = { - let original_code = (NativeCodeId::AccountCode2 as u64).to_be_bytes().to_vec(); - - let code_hash = CodeHash::from_hash(hash(&original_code)); - let code_substate = PackageCodeOriginalCodeV1 { - code: original_code, - } - .into_locked_substate(); - let vm_type_substate = PackageCodeVmTypeV1 { - vm_type: VmType::Native, - } - .into_locked_substate(); - - (code_hash, (code_substate, vm_type_substate)) - }; - - // Updating the blueprint definition of the existing blueprint so that the code used is the new - // one. - let blueprint_definition_substate = { - let mut blueprint_definition = reader - .read_object_collection_entry::<_, VersionedPackageBlueprintVersionDefinition>( - &node_id, - ObjectModuleId::Main, - ObjectCollectionKey::KeyValue( - PackageCollection::BlueprintVersionDefinitionKeyValue.collection_index(), - &blueprint_version_key, - ), - ) - .unwrap() - .unwrap() - .fully_update_and_into_latest_version(); - - for function_name in [ - ACCOUNT_TRY_DEPOSIT_OR_REFUND_IDENT, - ACCOUNT_TRY_DEPOSIT_BATCH_OR_REFUND_IDENT, - ] { - blueprint_definition - .function_exports - .get_mut(function_name) - .expect("This function must exist") - .code_hash = code_hash; - } - - blueprint_definition.into_locked_substate() - }; - - // Getting the partition number of the various collections that we're updating - let [blueprint_version_definition_partition_number, code_vm_type_partition_number, code_original_code_partition_number] = - [ - PackageCollection::BlueprintVersionDefinitionKeyValue, - PackageCollection::CodeVmTypeKeyValue, - PackageCollection::CodeOriginalCodeKeyValue, - ] - .map(|package_collection| { - reader - .get_partition_of_collection( - &node_id, - ObjectModuleId::Main, - package_collection.collection_index(), - ) - .unwrap() - }); - - // Generating the state updates - StateUpdates { - by_node: indexmap!( - node_id => NodeStateUpdates::Delta { - by_partition: indexmap! { - blueprint_version_definition_partition_number => PartitionStateUpdates::Delta { - by_substate: indexmap! { - SubstateKey::Map(scrypto_encode!(&blueprint_version_key)) => DatabaseUpdate::Set( - scrypto_encode!(&blueprint_definition_substate) - ) - } - }, - code_vm_type_partition_number => PartitionStateUpdates::Delta { - by_substate: indexmap! { - SubstateKey::Map(scrypto_encode!(&code_hash)) => DatabaseUpdate::Set( - scrypto_encode!(&vm_type_substate) - ) - } - }, - code_original_code_partition_number => PartitionStateUpdates::Delta { - by_substate: indexmap! { - SubstateKey::Map(scrypto_encode!(&code_hash)) => DatabaseUpdate::Set( - scrypto_encode!(&code_substate) - ) - } - }, - } - } - ), - } -} - -pub fn generate_protocol_params_to_state_state_updates( - network_definition: NetworkDefinition, -) -> StateUpdates { - StateUpdates { - by_node: indexmap!( - TRANSACTION_TRACKER.into_node_id() => NodeStateUpdates::Delta { - by_partition: indexmap! { - BOOT_LOADER_PARTITION => PartitionStateUpdates::Delta { - by_substate: indexmap! { - SubstateKey::Field(BOOT_LOADER_SYSTEM_SUBSTATE_FIELD_KEY) => DatabaseUpdate::Set( - scrypto_encode(&SystemBoot::V1(SystemParameters { - network_definition, - costing_parameters: CostingParameters::babylon_genesis(), - limit_parameters: LimitParameters::babylon_genesis(), - max_per_function_royalty_in_xrd: Decimal::try_from(MAX_PER_FUNCTION_ROYALTY_IN_XRD).unwrap(), - })).unwrap() - ) - } - }, - } - } - ), - } -} diff --git a/radix-engine/src/updates/bottlenose.rs b/radix-engine/src/updates/bottlenose.rs new file mode 100644 index 00000000000..f318810c812 --- /dev/null +++ b/radix-engine/src/updates/bottlenose.rs @@ -0,0 +1,461 @@ +use super::*; +use crate::blueprints::locker::LockerNativePackage; +use crate::blueprints::models::KeyValueEntryContentSource; +use crate::blueprints::package::*; +use crate::object_modules::role_assignment::*; +use crate::system::system_callback::*; +use crate::system::system_db_reader::*; +use crate::track::*; +use crate::transaction::*; +use crate::vm::*; +use radix_engine_interface::blueprints::account::*; + +#[derive(Clone)] +pub struct BottlenoseSettings { + /// Exposes a getter method for reading owner role rule. + pub add_owner_role_getter: UpdateSetting<()>, + + /// Various system patches. + pub add_system_patches: UpdateSetting<()>, + + /// Introduces the account locker blueprint. + pub add_locker_package: UpdateSetting<()>, + + /// Makes some behavioral changes to the try_deposit_or_refund (and batch variants too) method + /// on the account blueprint. + pub fix_account_try_deposit_or_refund_behaviour: UpdateSetting<()>, + + /// Moves various protocol parameters to state. + pub move_protocol_params_to_state: UpdateSetting, +} + +#[derive(Clone)] +pub struct ProtocolParamsSettings { + pub network_definition: NetworkDefinition, +} + +impl DefaultForNetwork for ProtocolParamsSettings { + fn default_for_network(network_definition: &NetworkDefinition) -> Self { + Self { + network_definition: network_definition.clone(), + } + } +} + +impl UpdateSettings for BottlenoseSettings { + type BatchGenerator = BottlenoseBatchGenerator; + + fn all_enabled_as_default_for_network(network: &NetworkDefinition) -> Self { + Self { + add_owner_role_getter: UpdateSetting::enabled_as_default_for_network(network), + add_system_patches: UpdateSetting::enabled_as_default_for_network(network), + add_locker_package: UpdateSetting::enabled_as_default_for_network(network), + move_protocol_params_to_state: UpdateSetting::enabled_as_default_for_network(network), + fix_account_try_deposit_or_refund_behaviour: + UpdateSetting::enabled_as_default_for_network(network), + } + } + + fn all_disabled() -> Self { + Self { + add_owner_role_getter: UpdateSetting::Disabled, + add_system_patches: UpdateSetting::Disabled, + add_locker_package: UpdateSetting::Disabled, + move_protocol_params_to_state: UpdateSetting::Disabled, + fix_account_try_deposit_or_refund_behaviour: UpdateSetting::Disabled, + } + } + + fn create_batch_generator(&self) -> Self::BatchGenerator { + BottlenoseBatchGenerator { + settings: self.clone(), + } + } +} + +#[derive(Clone)] +pub struct BottlenoseBatchGenerator { + settings: BottlenoseSettings, +} + +impl ProtocolUpdateBatchGenerator for BottlenoseBatchGenerator { + fn generate_batch( + &self, + store: &dyn SubstateDatabase, + batch_index: u32, + ) -> Option { + match batch_index { + // Just a single batch for Bottlenose, perhaps in future updates we should have separate batches for each update? + 0 => Some(generate_principal_batch(store, &self.settings)), + _ => None, + } + } +} + +fn generate_principal_batch( + store: &dyn SubstateDatabase, + settings: &BottlenoseSettings, +) -> ProtocolUpdateBatch { + let mut transactions = vec![]; + if let UpdateSetting::Enabled(_) = &settings.add_owner_role_getter { + transactions.push(ProtocolUpdateTransactionDetails::flash( + "bottlenose-owner-role-getter", + generate_owner_role_getter_state_updates(store), + )); + } + if let UpdateSetting::Enabled(_) = &settings.add_system_patches { + transactions.push(ProtocolUpdateTransactionDetails::flash( + "bottlenose-system-patches", + generate_system_patches(), + )); + } + if let UpdateSetting::Enabled(_) = &settings.add_locker_package { + transactions.push(ProtocolUpdateTransactionDetails::flash( + "bottlenose-locker-package", + generate_locker_package_state_updates(), + )); + } + if let UpdateSetting::Enabled(_) = &settings.fix_account_try_deposit_or_refund_behaviour { + transactions.push(ProtocolUpdateTransactionDetails::flash( + "bottlenose-account-try-deposit-or-refund", + generate_account_bottlenose_extension_state_updates(store), + )); + } + if let UpdateSetting::Enabled(settings) = &settings.move_protocol_params_to_state { + transactions.push(ProtocolUpdateTransactionDetails::flash( + "bottlenose-protocol-params-to-state", + generate_protocol_params_to_state_updates(settings.network_definition.clone()), + )); + } + ProtocolUpdateBatch { transactions } +} + +/// A quick macro for encoding and unwrapping. +macro_rules! scrypto_encode { + ( + $expr: expr + ) => { + ::radix_common::prelude::scrypto_encode($expr).unwrap() + }; +} + +pub fn generate_owner_role_getter_state_updates( + db: &S, +) -> StateUpdates { + let reader = SystemDatabaseReader::new(db); + let node_id = ROLE_ASSIGNMENT_MODULE_PACKAGE.into_node_id(); + let blueprint_version_key = BlueprintVersionKey { + blueprint: ROLE_ASSIGNMENT_BLUEPRINT.to_string(), + version: Default::default(), + }; + + // Creating the original code substates for extension. + let (code_hash, (code_substate, vm_type_substate)) = { + let original_code = (NativeCodeId::RoleAssignmentCode2 as u64) + .to_be_bytes() + .to_vec(); + + let code_hash = CodeHash::from_hash(hash(&original_code)); + let code_substate = PackageCodeOriginalCodeV1 { + code: original_code, + } + .into_versioned() + .into_locked_substate(); + let vm_type_substate = PackageCodeVmTypeV1 { + vm_type: VmType::Native, + } + .into_locked_substate(); + + (code_hash, (code_substate, vm_type_substate)) + }; + + // Creating the new schema substate with the methods added by the extension + let (added_functions, schema) = RoleAssignmentBottlenoseExtension::added_functions_schema(); + let (schema_hash, schema_substate) = + (schema.generate_schema_hash(), schema.into_locked_substate()); + + // Updating the blueprint definition of the existing blueprint with the added functions. + let blueprint_definition_substate = { + let mut blueprint_definition = reader + .read_object_collection_entry::<_, VersionedPackageBlueprintVersionDefinition>( + &node_id, + ObjectModuleId::Main, + ObjectCollectionKey::KeyValue( + PackageCollection::BlueprintVersionDefinitionKeyValue.collection_index(), + &blueprint_version_key, + ), + ) + .unwrap() + .unwrap() + .fully_update_and_into_latest_version(); + + for (function_name, added_function) in added_functions.into_iter() { + let TypeRef::Static(input_local_id) = added_function.input else { + unreachable!() + }; + let TypeRef::Static(output_local_id) = added_function.output else { + unreachable!() + }; + + blueprint_definition.function_exports.insert( + function_name.clone(), + PackageExport { + code_hash, + export_name: function_name.clone(), + }, + ); + blueprint_definition.interface.functions.insert( + function_name, + FunctionSchema { + receiver: added_function.receiver, + input: BlueprintPayloadDef::Static(ScopedTypeId(schema_hash, input_local_id)), + output: BlueprintPayloadDef::Static(ScopedTypeId(schema_hash, output_local_id)), + }, + ); + } + + blueprint_definition.into_locked_substate() + }; + + // Getting the partition number of the various collections that we're updating + let [blueprint_version_definition_partition_number, code_vm_type_partition_number, code_original_code_partition_number, schema_partition_number] = + [ + PackageCollection::BlueprintVersionDefinitionKeyValue, + PackageCollection::CodeVmTypeKeyValue, + PackageCollection::CodeOriginalCodeKeyValue, + PackageCollection::SchemaKeyValue, + ] + .map(|package_collection| { + reader + .get_partition_of_collection( + &node_id, + ObjectModuleId::Main, + package_collection.collection_index(), + ) + .unwrap() + }); + + // Generating the state updates + StateUpdates { + by_node: indexmap!( + node_id => NodeStateUpdates::Delta { + by_partition: indexmap! { + blueprint_version_definition_partition_number => PartitionStateUpdates::Delta { + by_substate: indexmap! { + SubstateKey::Map(scrypto_encode!(&blueprint_version_key)) => DatabaseUpdate::Set( + scrypto_encode!(&blueprint_definition_substate) + ) + } + }, + code_vm_type_partition_number => PartitionStateUpdates::Delta { + by_substate: indexmap! { + SubstateKey::Map(scrypto_encode!(&code_hash)) => DatabaseUpdate::Set( + scrypto_encode!(&vm_type_substate) + ) + } + }, + code_original_code_partition_number => PartitionStateUpdates::Delta { + by_substate: indexmap! { + SubstateKey::Map(scrypto_encode!(&code_hash)) => DatabaseUpdate::Set( + scrypto_encode!(&code_substate) + ) + } + }, + schema_partition_number => PartitionStateUpdates::Delta { + by_substate: indexmap! { + SubstateKey::Map(scrypto_encode!(&schema_hash)) => DatabaseUpdate::Set( + scrypto_encode!(&schema_substate) + ) + } + } + } + } + ), + } +} + +pub fn generate_system_patches() -> StateUpdates { + // TODO + StateUpdates::default() +} + +pub fn generate_locker_package_state_updates() -> StateUpdates { + let package_definition = LockerNativePackage::definition(); + let package_structure = PackageNativePackage::validate_and_build_package_structure( + package_definition, + VmType::Native, + (NativeCodeId::LockerCode1 as u64).to_be_bytes().to_vec(), + Default::default(), + &VmVersion::latest(), + ) + .unwrap_or_else(|err| { + panic!( + "Invalid flashed Package definition with native_code_id {}: {:?}", + NativeCodeId::LockerCode1 as u64, + err + ) + }); + + let partitions = create_package_partition_substates( + package_structure, + metadata_init! { + "name" => "Locker Package", locked; + "description" => "A native package that defines the logic for dApp-owned lockers to send resources to specified account addresses.", locked; + }, + None, + ); + + StateUpdates { + by_node: indexmap! { + LOCKER_PACKAGE.into_node_id() => NodeStateUpdates::Delta { + by_partition: partitions + .into_iter() + .map(|(partition_num, substates)| { + ( + partition_num, + PartitionStateUpdates::Delta { + by_substate: substates + .into_iter() + .map(|(key, value)| { + (key, DatabaseUpdate::Set(value.as_vec_ref().clone())) + }) + .collect(), + }, + ) + }) + .collect(), + } + }, + } +} + +pub fn generate_account_bottlenose_extension_state_updates( + db: &S, +) -> StateUpdates { + let reader = SystemDatabaseReader::new(db); + let node_id = ACCOUNT_PACKAGE.into_node_id(); + let blueprint_version_key = BlueprintVersionKey { + blueprint: ACCOUNT_BLUEPRINT.to_string(), + version: Default::default(), + }; + + // Creating the original code substates for extension. + let (code_hash, (code_substate, vm_type_substate)) = { + let original_code = (NativeCodeId::AccountCode2 as u64).to_be_bytes().to_vec(); + + let code_hash = CodeHash::from_hash(hash(&original_code)); + let code_substate = PackageCodeOriginalCodeV1 { + code: original_code, + } + .into_locked_substate(); + let vm_type_substate = PackageCodeVmTypeV1 { + vm_type: VmType::Native, + } + .into_locked_substate(); + + (code_hash, (code_substate, vm_type_substate)) + }; + + // Updating the blueprint definition of the existing blueprint so that the code used is the new + // one. + let blueprint_definition_substate = { + let mut blueprint_definition = reader + .read_object_collection_entry::<_, VersionedPackageBlueprintVersionDefinition>( + &node_id, + ObjectModuleId::Main, + ObjectCollectionKey::KeyValue( + PackageCollection::BlueprintVersionDefinitionKeyValue.collection_index(), + &blueprint_version_key, + ), + ) + .unwrap() + .unwrap() + .fully_update_and_into_latest_version(); + + for function_name in [ + ACCOUNT_TRY_DEPOSIT_OR_REFUND_IDENT, + ACCOUNT_TRY_DEPOSIT_BATCH_OR_REFUND_IDENT, + ] { + blueprint_definition + .function_exports + .get_mut(function_name) + .expect("This function must exist") + .code_hash = code_hash; + } + + blueprint_definition.into_locked_substate() + }; + + // Getting the partition number of the various collections that we're updating + let [blueprint_version_definition_partition_number, code_vm_type_partition_number, code_original_code_partition_number] = + [ + PackageCollection::BlueprintVersionDefinitionKeyValue, + PackageCollection::CodeVmTypeKeyValue, + PackageCollection::CodeOriginalCodeKeyValue, + ] + .map(|package_collection| { + reader + .get_partition_of_collection( + &node_id, + ObjectModuleId::Main, + package_collection.collection_index(), + ) + .unwrap() + }); + + // Generating the state updates + StateUpdates { + by_node: indexmap!( + node_id => NodeStateUpdates::Delta { + by_partition: indexmap! { + blueprint_version_definition_partition_number => PartitionStateUpdates::Delta { + by_substate: indexmap! { + SubstateKey::Map(scrypto_encode!(&blueprint_version_key)) => DatabaseUpdate::Set( + scrypto_encode!(&blueprint_definition_substate) + ) + } + }, + code_vm_type_partition_number => PartitionStateUpdates::Delta { + by_substate: indexmap! { + SubstateKey::Map(scrypto_encode!(&code_hash)) => DatabaseUpdate::Set( + scrypto_encode!(&vm_type_substate) + ) + } + }, + code_original_code_partition_number => PartitionStateUpdates::Delta { + by_substate: indexmap! { + SubstateKey::Map(scrypto_encode!(&code_hash)) => DatabaseUpdate::Set( + scrypto_encode!(&code_substate) + ) + } + }, + } + } + ), + } +} + +pub fn generate_protocol_params_to_state_updates( + network_definition: NetworkDefinition, +) -> StateUpdates { + StateUpdates { + by_node: indexmap!( + TRANSACTION_TRACKER.into_node_id() => NodeStateUpdates::Delta { + by_partition: indexmap! { + BOOT_LOADER_PARTITION => PartitionStateUpdates::Delta { + by_substate: indexmap! { + SubstateKey::Field(BOOT_LOADER_SYSTEM_SUBSTATE_FIELD_KEY) => DatabaseUpdate::Set( + scrypto_encode(&SystemBoot::V1(SystemParameters { + network_definition, + costing_parameters: CostingParameters::babylon_genesis(), + limit_parameters: LimitParameters::babylon_genesis(), + max_per_function_royalty_in_xrd: Decimal::try_from(MAX_PER_FUNCTION_ROYALTY_IN_XRD).unwrap(), + })).unwrap() + ) + } + }, + } + } + ), + } +} diff --git a/radix-engine/src/updates/mod.rs b/radix-engine/src/updates/mod.rs index 03036a133d3..22f15ac8142 100644 --- a/radix-engine/src/updates/mod.rs +++ b/radix-engine/src/updates/mod.rs @@ -1,4 +1,139 @@ -pub mod state_updates; - +use crate::{internal_prelude::*, track::StateUpdates}; +mod anemone; +mod bottlenose; +mod protocol_builder; mod protocol_updates; + +pub use anemone::*; +pub use bottlenose::*; +pub use protocol_builder::*; pub use protocol_updates::*; + +// TODO AFTER MERGE WITH NODE: Replace with node's UpdateTransaction +pub enum ProtocolUpdateTransactionDetails { + FlashV1Transaction(FlashProtocolUpdateTransactionDetails), +} + +impl ProtocolUpdateTransactionDetails { + pub fn flash(name: &str, state_updates: StateUpdates) -> Self { + Self::FlashV1Transaction(FlashProtocolUpdateTransactionDetails { + name: name.to_string(), + state_updates, + }) + } +} + +// TODO AFTER MERGE WITH NODE: Merge replace with node's FlashTransactionV1 +pub struct FlashProtocolUpdateTransactionDetails { + pub name: String, + pub state_updates: StateUpdates, +} + +/// A set of transactions which all get committed together with the same proof. +/// To avoid memory overflows, this should be kept small (e.g. one transaction each). +pub struct ProtocolUpdateBatch { + pub transactions: Vec, +} + +pub trait UpdateSettings: Sized { + type BatchGenerator: ProtocolUpdateBatchGenerator; + + fn all_enabled_as_default_for_network(network: &NetworkDefinition) -> Self; + + fn all_disabled() -> Self; + + fn create_batch_generator(&self) -> Self::BatchGenerator; + + fn enable(mut self, prop: impl FnOnce(&mut Self) -> &mut UpdateSetting<()>) -> Self { + *prop(&mut self) = UpdateSetting::Enabled(()); + self + } + + fn enable_with( + mut self, + prop: impl FnOnce(&mut Self) -> &mut UpdateSetting, + setting: T, + ) -> Self { + *prop(&mut self) = UpdateSetting::Enabled(setting); + self + } + + fn disable(mut self, prop: impl FnOnce(&mut Self) -> &mut UpdateSetting) -> Self { + *prop(&mut self) = UpdateSetting::Disabled; + self + } + + fn set(mut self, updater: impl FnOnce(&mut Self)) -> Self { + updater(&mut self); + self + } +} + +pub trait DefaultForNetwork { + fn default_for_network(network_definition: &NetworkDefinition) -> Self; +} + +impl DefaultForNetwork for T { + fn default_for_network(_: &NetworkDefinition) -> Self { + Self::default() + } +} + +#[derive(Clone)] +pub enum UpdateSetting { + Enabled(T), + Disabled, +} + +impl UpdateSetting<()> { + pub fn new(is_enabled: bool) -> Self { + if is_enabled { + Self::Enabled(()) + } else { + Self::Disabled + } + } +} + +impl UpdateSetting { + pub fn enabled_as_default_for_network(network_definition: &NetworkDefinition) -> Self { + Self::Enabled(T::default_for_network(network_definition)) + } +} + +// TODO AFTER MERGE WITH NODE: Merge with UpdateBatchGenerator +/// This must be stateless, to allow the update to be resumed. +pub trait ProtocolUpdateBatchGenerator: ProtocolUpdateBatchGeneratorDynClone { + /// Generate a batch of transactions to be committed atomically with a proof. + /// Return None if it's the last batch. + /// + /// It should be assumed that the SubstateDatabase has *committed all previous batches*, this + /// ensures that the update is deterministically continuable if the node shuts down mid update. + /// + /// This is the interface currently needed by the node, to allow the update to be resumed. + /// This update isn't great, we could/should probably improve this in future. + fn generate_batch( + &self, + store: &dyn SubstateDatabase, + batch_index: u32, + ) -> Option; +} + +pub trait ProtocolUpdateBatchGeneratorDynClone { + fn clone_box(&self) -> Box; +} + +impl ProtocolUpdateBatchGeneratorDynClone for T +where + T: 'static + ProtocolUpdateBatchGenerator + Clone, +{ + fn clone_box(&self) -> Box { + Box::new(self.clone()) + } +} + +impl Clone for Box { + fn clone(&self) -> Box { + self.clone_box() + } +} diff --git a/radix-engine/src/updates/protocol_builder.rs b/radix-engine/src/updates/protocol_builder.rs new file mode 100644 index 00000000000..0f1f097cfb9 --- /dev/null +++ b/radix-engine/src/updates/protocol_builder.rs @@ -0,0 +1,131 @@ +use radix_substate_store_interface::db_key_mapper::SpreadPrefixKeyMapper; + +use super::*; + +#[derive(Clone)] +pub struct ProtocolUpdateExecutor { + pub protocol_update: ProtocolUpdate, + pub batch_generator: Box, +} + +impl ProtocolUpdateExecutor { + pub fn run_and_commit(self, store: &mut S) { + let mut current_batch_index = 0; + let batch_generator = self.batch_generator; + loop { + let Some(batch) = batch_generator.generate_batch(store, current_batch_index) else { + break; + }; + for transaction in batch.transactions { + let state_updates = match transaction { + ProtocolUpdateTransactionDetails::FlashV1Transaction(flash) => { + flash.state_updates + } + }; + let db_updates = state_updates.create_database_updates::(); + store.commit(&db_updates); + } + current_batch_index += 1; + } + } +} + +#[derive(Clone)] +pub struct ProtocolBuilder { + settings: ProtocolSettings, +} + +#[derive(Clone)] +pub struct ProtocolSettings { + // TODO: It would be nice to move bootstrap / Genesis into this formulation + anemone: AnemoneSettings, + bottlenose: BottlenoseSettings, +} + +impl ProtocolBuilder { + pub fn for_simulator() -> Self { + Self::for_network(&NetworkDefinition::simulator()) + } + + pub fn for_network(network_definition: &NetworkDefinition) -> Self { + Self { + settings: ProtocolSettings { + anemone: AnemoneSettings::all_enabled_as_default_for_network(network_definition), + bottlenose: BottlenoseSettings::all_enabled_as_default_for_network( + network_definition, + ), + }, + } + } + + pub fn with_anemone(mut self, settings: AnemoneSettings) -> Self { + self.settings.anemone = settings; + self + } + + pub fn with_bottlenose(mut self, settings: BottlenoseSettings) -> Self { + self.settings.bottlenose = settings; + self + } + + pub fn until_genesis(self) -> ProtocolExecutor { + self.until(ProtocolVersion::Genesis) + } + + pub fn until_latest_protocol_version(self) -> ProtocolExecutor { + self.until(ProtocolVersion::latest()) + } + + pub fn until(self, protocol_version: ProtocolVersion) -> ProtocolExecutor { + ProtocolExecutor::new(protocol_version, self.settings) + } +} + +pub struct ProtocolExecutor { + update_until: ProtocolVersion, + settings: ProtocolSettings, +} + +impl ProtocolExecutor { + pub fn new(update_until: ProtocolVersion, settings: ProtocolSettings) -> Self { + Self { + update_until, + settings, + } + } + + pub fn commit_each_protocol_update( + &self, + store: &mut S, + ) { + for update_execution in self.each_protocol_update_executor() { + update_execution.run_and_commit(store); + } + } + + pub fn each_protocol_update_executor( + &self, + ) -> impl Iterator + '_ { + let until_protocol_version = self.update_until; + ProtocolUpdate::VARIANTS + .into_iter() + .take_while(move |update| ProtocolVersion::from(*update) <= until_protocol_version) + .map(move |protocol_update| self.create_executor_for_update(protocol_update)) + } + + pub fn create_executor_for_update( + &self, + protocol_update: ProtocolUpdate, + ) -> ProtocolUpdateExecutor { + let generator: Box = match protocol_update { + ProtocolUpdate::Anemone => Box::new(self.settings.anemone.create_batch_generator()), + ProtocolUpdate::Bottlenose => { + Box::new(self.settings.bottlenose.create_batch_generator()) + } + }; + ProtocolUpdateExecutor { + protocol_update, + batch_generator: generator, + } + } +} diff --git a/radix-engine/src/updates/protocol_updates.rs b/radix-engine/src/updates/protocol_updates.rs index 16b7b4ad16e..400f5084615 100644 --- a/radix-engine/src/updates/protocol_updates.rs +++ b/radix-engine/src/updates/protocol_updates.rs @@ -1,69 +1,40 @@ -use super::state_updates::*; -use crate::{internal_prelude::*, track::StateUpdates}; -use radix_substate_store_interface::interface::SubstateDatabase; +use super::*; -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum ProtocolUpdateEntry { - /// Introduces BLS12-381 and Keccak-256 features. - Bls12381AndKeccak256, - - /// Exposes second-precision timestamp. - SecondPrecisionTimestamp, - - /// Increases the math precision with native pool implementations. - PoolMathPrecisionFix, - - /// Changes the cost associated with validator creation. - ValidatorCreationFeeFix, - - /// Exposes a getter method for reading owner role rule. - OwnerRoleGetter, - - /// Various system patches. - SystemPatches, - - /// Introduces the account locker blueprint. - LockerPackage, - - /// Moves various protocol parameters to state. - ProtocolParamsToState, +macro_rules! derive_protocol_updates { + ( + $( + $variant_ident: ident + ),* $(,)? + ) => { + #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] + pub enum ProtocolUpdate { + $( + $variant_ident + ),* + } - /// Makes some behavioral changes to the try_deposit_or_refund (and batch variants too) method - /// on the account blueprint. - AccountTryDepositOrRefundBehaviorChanges, -} + impl ProtocolUpdate { + pub const VARIANTS: [Self; count!( $($variant_ident),* )] = [ + $( + Self::$variant_ident + ),* + ]; -impl ProtocolUpdateEntry { - pub fn generate_state_updates( - &self, - db: &S, - network: &NetworkDefinition, - ) -> StateUpdates { - match self { - ProtocolUpdateEntry::Bls12381AndKeccak256 => { - generate_bls128_and_keccak256_state_updates() - } - ProtocolUpdateEntry::SecondPrecisionTimestamp => { - generate_seconds_precision_timestamp_state_updates(db) - } - ProtocolUpdateEntry::PoolMathPrecisionFix => { - generate_pool_math_precision_fix_state_updates(db) - } - ProtocolUpdateEntry::ValidatorCreationFeeFix => { - generate_validator_creation_fee_fix_state_updates(db) - } - ProtocolUpdateEntry::OwnerRoleGetter => generate_owner_role_getter_state_updates(db), - ProtocolUpdateEntry::LockerPackage => generate_locker_package_state_updates(), - ProtocolUpdateEntry::AccountTryDepositOrRefundBehaviorChanges => { - generate_account_bottlenose_extension_state_updates(db) + pub fn latest() -> Self { + Self::VARIANTS[count!( $($variant_ident),* ) - 1] } - // TODO implement the following - ProtocolUpdateEntry::SystemPatches => StateUpdates::default(), - ProtocolUpdateEntry::ProtocolParamsToState => { - generate_protocol_params_to_state_state_updates(network.clone()) + } + + impl From for ProtocolVersion { + fn from(value: ProtocolUpdate) -> ProtocolVersion { + match value { + $( + ProtocolUpdate::$variant_ident => ProtocolVersion::$variant_ident + ),* + } } } - } + }; } macro_rules! count { @@ -79,128 +50,40 @@ macro_rules! count { } } -macro_rules! enum_const_array { - ( - $(#[$meta:meta])* - $vis: vis enum $ident: ident { - $( - $variant_ident: ident - ),* $(,)? - } - ) => { - $(#[$meta])* - $vis enum $ident { - $( - $variant_ident - ),* - } - - impl $ident { - pub const VARIANTS: [Self; count!( $($variant_ident),* )] = [ - $( - Self::$variant_ident - ),* - ]; - } - }; -} - -enum_const_array! { - #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] - pub enum ProtocolUpdate { - Anemone, - Bottlenose, - } -} - -impl ProtocolUpdate { - pub fn generate_state_updates( - &self, - db: &S, - network: &NetworkDefinition, - ) -> Vec { - match self { - ProtocolUpdate::Anemone => vec![ - ProtocolUpdateEntry::Bls12381AndKeccak256, - ProtocolUpdateEntry::SecondPrecisionTimestamp, - ProtocolUpdateEntry::PoolMathPrecisionFix, - ProtocolUpdateEntry::ValidatorCreationFeeFix, - ], - ProtocolUpdate::Bottlenose => vec![ - ProtocolUpdateEntry::OwnerRoleGetter, - ProtocolUpdateEntry::SystemPatches, - ProtocolUpdateEntry::LockerPackage, - ProtocolUpdateEntry::AccountTryDepositOrRefundBehaviorChanges, - ProtocolUpdateEntry::ProtocolParamsToState, - ], - } - .iter() - .map(|update| update.generate_state_updates(db, network)) - .collect() - } +derive_protocol_updates! { + Anemone, + Bottlenose, } -#[derive(Debug, Clone)] -pub struct ProtocolUpdates { - protocol_updates: Vec, - additional_updates: Vec, +#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)] +pub enum ProtocolVersion { + Genesis, + Anemone, + Bottlenose, } -impl ProtocolUpdates { - pub fn none() -> Self { - Self { - protocol_updates: vec![], - additional_updates: vec![], - } +impl ProtocolVersion { + pub fn all_iterator() -> impl Iterator { + core::iter::once(Self::Genesis).chain(ProtocolUpdate::VARIANTS.map(From::from)) } - pub fn up_to_anemone() -> Self { - Self { - protocol_updates: vec![ProtocolUpdate::Anemone], - additional_updates: vec![], - } + pub fn latest() -> Self { + ProtocolUpdate::latest().into() } - pub fn up_to_bottlenose() -> Self { - Self { - protocol_updates: vec![ProtocolUpdate::Anemone, ProtocolUpdate::Bottlenose], - additional_updates: vec![], + pub fn logical_name(&self) -> &'static str { + match self { + ProtocolVersion::Genesis => "babylon", + ProtocolVersion::Anemone => "anemone", + ProtocolVersion::Bottlenose => "bottlenose", } } - pub fn all() -> Self { - Self::up_to_bottlenose() - } - - pub fn and(mut self, protocol_update: ProtocolUpdateEntry) -> Self { - self.additional_updates.push(protocol_update); - self - } - - pub fn generate_state_updates( - &self, - db: &S, - network: &NetworkDefinition, - ) -> Vec { - let mut results = Vec::new(); - for protocol_update in &self.protocol_updates { - results.extend(protocol_update.generate_state_updates(db, network)); - } - for protocol_update in &self.additional_updates { - results.push(protocol_update.generate_state_updates(db, network)); + pub fn display_name(&self) -> &'static str { + match self { + ProtocolVersion::Genesis => "Babylon", + ProtocolVersion::Anemone => "Anemone", + ProtocolVersion::Bottlenose => "Bottlenose", } - results - } -} - -#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)] -pub enum ProtocolVersion { - Genesis, - ProtocolUpdate(ProtocolUpdate), -} - -impl ProtocolVersion { - pub fn all_iterator() -> impl Iterator { - core::iter::once(Self::Genesis).chain(ProtocolUpdate::VARIANTS.map(Self::ProtocolUpdate)) } } diff --git a/radix-substate-store-interface/src/db_key_mapper.rs b/radix-substate-store-interface/src/db_key_mapper.rs index 5bb09916b00..bf2b159b79c 100644 --- a/radix-substate-store-interface/src/db_key_mapper.rs +++ b/radix-substate-store-interface/src/db_key_mapper.rs @@ -186,7 +186,7 @@ pub trait MappedSubstateDatabase { ) -> Box + '_>; } -impl MappedSubstateDatabase for S { +impl MappedSubstateDatabase for S { fn get_mapped( &self, node_id: &NodeId, diff --git a/radix-substate-store-interface/src/interface.rs b/radix-substate-store-interface/src/interface.rs index 64c54714d99..b58590249a7 100644 --- a/radix-substate-store-interface/src/interface.rs +++ b/radix-substate-store-interface/src/interface.rs @@ -1,8 +1,4 @@ -use radix_common::Sbor; -use radix_rust::prelude::index_map_new; -use radix_rust::rust::boxed::Box; -use radix_rust::rust::collections::IndexMap; -use radix_rust::rust::vec::Vec; +use radix_common::prelude::*; pub type DbNodeKey = Vec; diff --git a/radix-transaction-scenarios/Cargo.toml b/radix-transaction-scenarios/Cargo.toml index bbbbb4afd1d..de2e050b6f9 100644 --- a/radix-transaction-scenarios/Cargo.toml +++ b/radix-transaction-scenarios/Cargo.toml @@ -20,6 +20,7 @@ radix-rust = { workspace = true } itertools = { workspace = true } hex = { workspace = true } walkdir = { workspace = true } +lazy_static = { workspace = true } [features] # You should enable either `std` or `alloc` diff --git a/radix-transaction-scenarios/src/executor.rs b/radix-transaction-scenarios/src/executor.rs index 29be7e7118f..7de4492a7ad 100644 --- a/radix-transaction-scenarios/src/executor.rs +++ b/radix-transaction-scenarios/src/executor.rs @@ -2,8 +2,6 @@ use core::ops::*; -use crate::scenarios::maya_router::MayaRouterScenarioCreator; - use self::internal_prelude::*; use super::*; use radix_engine::blueprints::consensus_manager::*; @@ -18,18 +16,21 @@ use radix_transactions::errors::*; use radix_transactions::validation::*; use sbor::prelude::*; -use scenarios::account_authorized_depositors::AccountAuthorizedDepositorsScenarioCreator; -use scenarios::account_locker::AccountLockerScenarioCreator; -use scenarios::fungible_resource::FungibleResourceScenarioCreator; -use scenarios::global_n_owned::GlobalNOwnedScenarioCreator; -use scenarios::kv_store_with_remote_type::KVStoreScenarioCreator; -use scenarios::max_transaction::MaxTransactionScenarioCreator; -use scenarios::metadata::MetadataScenario; -use scenarios::non_fungible_resource::NonFungibleResourceScenarioCreator; -use scenarios::non_fungible_resource_with_remote_type::NonFungibleResourceWithRemoteTypeScenarioCreator; -use scenarios::radiswap::RadiswapScenarioCreator; -use scenarios::royalties::RoyaltiesScenarioCreator; -use scenarios::transfer_xrd::TransferXrdScenarioCreator; +use scenarios::ALL_SCENARIOS; + +#[derive(Clone, Debug)] +pub enum ScenarioTrigger { + AtStartOfProtocolVersions(BTreeSet), + AtStartOfEveryProtocolVersion, + AfterCompletionOfAllProtocolUpdates, +} + +#[derive(Clone, Debug)] +pub enum ScenarioFilter { + ValidScenariosFrom(BTreeSet), + AllScenariosValidAtProtocolVersion, + AllScenariosFirstValidAtProtocolVersion, +} pub struct TransactionScenarioExecutor<'a, D, W, E> where @@ -46,13 +47,6 @@ where native_vm_extension: E, /* Execution */ - /// A map of the scenarios registered on the executor. Not all registered scenarios will be - /// executed, it merely informs the executor of the existence of these scenarios. Execution of a - /// scenario requires that is passes the filter specified by the client. - registered_scenarios: - BTreeMap Box>>>, - /// Controls whether the bootstrap process should be performed or not. - bootstrap: bool, /// The first nonce to use in the execution of the scenarios. starting_nonce: u32, /// How the executor should handle nonces and how it should get the next nonce. @@ -61,17 +55,12 @@ where network_definition: NetworkDefinition, /* Callbacks */ - /// A callback that is called when a new protocol requirement is encountered. This can be useful - /// for clients who wish to apply protocol updates they wish. - on_new_protocol_requirement_encountered: - Box, /// A callback that is called when a scenario transaction is executed. on_transaction_executed: Box, /// A callback that is called when a new scenario is started. on_scenario_start: Box, - /// A callback that is called after bootstrapping if bootstrapping is enabled. - after_bootstrap: Box, + on_before_execute_protocol_update: Box, /* Phantom */ /// The lifetime of the callbacks used in the executor. @@ -89,7 +78,7 @@ where { pub fn new( database: D, - network_definition: NetworkDefinition, + network_definition: &NetworkDefinition, ) -> DefaultTransactionScenarioExecutor<'a, D> { DefaultTransactionScenarioExecutor::<'a, D> { /* Environment */ @@ -97,44 +86,14 @@ where scrypto_vm: ScryptoVm::default(), native_vm_extension: NoExtension, /* Execution */ - registered_scenarios: { - let vector = scenarios_vector(); - let mut map = BTreeMap::< - ProtocolVersion, - Vec Box>>, - >::new(); - for (version, func) in vector.into_iter() { - map.entry(version).or_default().push(func); - } - for version in ProtocolVersion::all_iterator() { - map.entry(version).or_default(); - } - map - }, - bootstrap: true, starting_nonce: 0, next_scenario_nonce_handling: ScenarioStartNonceHandling::PreviousScenarioEndNoncePlusOne, - network_definition, + network_definition: network_definition.clone(), /* Callbacks */ - on_new_protocol_requirement_encountered: Box::new( - |network_definition, protocol_update, db| { - if let ProtocolVersion::ProtocolUpdate(update) = protocol_update { - update - .generate_state_updates(db, network_definition) - .into_iter() - .for_each(|state_updates| { - db.commit( - &state_updates - .create_database_updates::(), - ) - }) - } - }, - ), on_transaction_executed: Box::new(|_, _, _, _| {}), on_scenario_start: Box::new(|_| {}), - after_bootstrap: Box::new(|_, _| {}), + on_before_execute_protocol_update: Box::new(|_| {}), /* Phantom */ callback_lifetime: Default::default(), } @@ -151,16 +110,13 @@ where scrypto_vm, native_vm_extension: self.native_vm_extension, /* Execution */ - registered_scenarios: self.registered_scenarios, - bootstrap: self.bootstrap, starting_nonce: self.starting_nonce, next_scenario_nonce_handling: self.next_scenario_nonce_handling, network_definition: self.network_definition, /* Callbacks */ - on_new_protocol_requirement_encountered: self.on_new_protocol_requirement_encountered, on_transaction_executed: self.on_transaction_executed, on_scenario_start: self.on_scenario_start, - after_bootstrap: self.after_bootstrap, + on_before_execute_protocol_update: self.on_before_execute_protocol_update, /* Phantom */ callback_lifetime: self.callback_lifetime, } @@ -177,27 +133,18 @@ where scrypto_vm: self.scrypto_vm, native_vm_extension, /* Execution */ - registered_scenarios: self.registered_scenarios, - bootstrap: self.bootstrap, starting_nonce: self.starting_nonce, next_scenario_nonce_handling: self.next_scenario_nonce_handling, network_definition: self.network_definition, /* Callbacks */ - on_new_protocol_requirement_encountered: self.on_new_protocol_requirement_encountered, on_transaction_executed: self.on_transaction_executed, on_scenario_start: self.on_scenario_start, - after_bootstrap: self.after_bootstrap, + on_before_execute_protocol_update: self.on_before_execute_protocol_update, /* Phantom */ callback_lifetime: self.callback_lifetime, } } - /// Controls whether the bootstrap process should be performed or not. - pub fn bootstrap(mut self, bootstrap: bool) -> Self { - self.bootstrap = bootstrap; - self - } - /// Sets the starting nonce for executing scenarios. pub fn starting_nonce(mut self, starting_nonce: u32) -> Self { self.starting_nonce = starting_nonce; @@ -210,17 +157,6 @@ where self } - /// Sets the callback to call when a new protocol requirement is encountered. - pub fn on_new_protocol_requirement_encountered< - F: FnMut(&NetworkDefinition, ProtocolVersion, &mut D) + 'a, - >( - mut self, - callback: F, - ) -> Self { - self.on_new_protocol_requirement_encountered = Box::new(callback); - self - } - /// Sets the callback to call after executing a scenario transaction. pub fn on_transaction_executed< F: FnMut(&ScenarioMetadata, &NextTransaction, &TransactionReceiptV1, &D) + 'a, @@ -238,163 +174,161 @@ where self } - pub fn without_protocol_updates(self) -> Self { - self.on_new_protocol_requirement_encountered(|_, _, _| {}) - } - - /// A callback that is called after bootstrapping if bootstrap is enabled. - pub fn after_bootstrap( + /// A callback that is called before a protocol update is executed. + pub fn on_before_execute_protocol_update( mut self, callback: F, ) -> Self { - self.after_bootstrap = Box::new(callback); + self.on_before_execute_protocol_update = Box::new(callback); self } - pub fn execute_all_matching( - self, - filter: ScenarioFilter, - ) -> Result, ScenarioExecutorError> { - self.internal_execute(Some(filter)) + pub fn into_database(self) -> D { + self.database } - pub fn execute_all(self) -> Result, ScenarioExecutorError> { - self.internal_execute(None) + /// Each scenario is executed once, when it first becomes valid. + pub fn execute_every_protocol_update_and_scenario( + &mut self, + ) -> Result<(), ScenarioExecutorError> { + self.execute_protocol_updates_and_scenarios( + ProtocolBuilder::for_network(&self.network_definition).until_latest_protocol_version(), + ScenarioTrigger::AtStartOfEveryProtocolVersion, + ScenarioFilter::AllScenariosFirstValidAtProtocolVersion, + ) } - fn internal_execute( - mut self, - filter: Option, - ) -> Result, ScenarioExecutorError> { - // Bootstrapping if needed - if self.bootstrap { - Bootstrapper::new( - self.network_definition.clone(), - &mut self.database, - VmInit::new(&self.scrypto_vm, self.native_vm_extension.clone()), + pub fn execute_protocol_updates_and_scenarios( + &mut self, + protocol_executor: ProtocolExecutor, + trigger: ScenarioTrigger, + filter: ScenarioFilter, + ) -> Result<(), ScenarioExecutorError> { + Bootstrapper::new( + self.network_definition.clone(), + &mut self.database, + VmInit::new(&self.scrypto_vm, self.native_vm_extension.clone()), + false, + ) + .bootstrap_test_default() + .ok_or(ScenarioExecutorError::BootstrapFailed)?; + + let mut current_protocol_version = ProtocolVersion::Genesis; + + for protocol_update_executor in protocol_executor.each_protocol_update_executor() { + self.execute_scenarios_at_new_protocol_version( + current_protocol_version, + &trigger, + &filter, false, - ) - .bootstrap_test_default() - .ok_or(ScenarioExecutorError::BootstrapFailed)?; - (self.after_bootstrap)(&self.network_definition, &mut self.database); + )?; + current_protocol_version = protocol_update_executor.protocol_update.into(); + (self.on_before_execute_protocol_update)(&protocol_update_executor); + protocol_update_executor.run_and_commit(&mut self.database); + } + + self.execute_scenarios_at_new_protocol_version( + current_protocol_version, + &trigger, + &filter, + true, + )?; + + Ok(()) + } + + fn execute_scenarios_at_new_protocol_version( + &mut self, + at_version: ProtocolVersion, + trigger: &ScenarioTrigger, + filter: &ScenarioFilter, + is_last: bool, + ) -> Result<(), ScenarioExecutorError> { + let trigger_applies = match trigger { + ScenarioTrigger::AtStartOfProtocolVersions(set) => set.contains(&at_version), + ScenarioTrigger::AtStartOfEveryProtocolVersion => true, + ScenarioTrigger::AfterCompletionOfAllProtocolUpdates => is_last, }; - // Getting the scenario builder functions of the scenarios that we will execute. There is a - // canonical order to these function batches which is that the genesis functions come first, - // then anemone, bottlenose, and so on. This order is enforced by this function and by the - // ordering of the `ProtocolUpdate` enum variants. Within a protocol update (or requirement) - // the canonical order is as seen in the [`new`] function. - for protocol_requirement in ProtocolVersion::all_iterator() { - // When a new protocol requirement is encountered the appropriate callback is called to - // inform the client of this event. - (self.on_new_protocol_requirement_encountered)( - &self.network_definition, - protocol_requirement, - &mut self.database, - ); - - // Build each scenario and execute it. - let mut next_nonce = self.starting_nonce; - for scenario_builder in self - .registered_scenarios - .remove(&protocol_requirement) - .unwrap_or_default() - .into_iter() - { - let epoch = SystemDatabaseReader::new(&self.database) - .read_object_field( - CONSENSUS_MANAGER.as_node_id(), - ModuleId::Main, - ConsensusManagerField::State.field_index(), - ) - .map_err(|_| ScenarioExecutorError::FailedToGetEpoch)? - .as_typed::() - .unwrap() - .fully_update_and_into_latest_version() - .epoch; - let mut scenario = scenario_builder(ScenarioCore::new( - self.network_definition.clone(), - epoch, - next_nonce, - )); - let metadata = scenario.metadata().clone(); - - // Before executing the scenario determine if it's valid for the current filter that - // the client specified. - let passes_filter = match filter { - // Ensure that the scenario name from the metadata is in the list of exact - // scenarios. Otherwise continue to the next. - Some(ScenarioFilter::ExactScenarios(ref exact_scenarios)) => { - exact_scenarios.contains(metadata.logical_name) - } - // Execute only ones from a particular protocol update. - Some(ScenarioFilter::SpecificProtocolVersion(protocol_version)) => { - protocol_requirement == protocol_version - } - // Execute only scenarios that are valid before a particular protocol update. - Some(ScenarioFilter::AllValidBeforeProtocolVersion(protocol_version)) => { - match protocol_version { - Boundary::Inclusive(protocol_version) => RangeToInclusive { - end: protocol_version, - } - .contains(&protocol_requirement), - Boundary::Exclusive(protocol_version) => RangeTo { - end: protocol_version, - } - .contains(&protocol_requirement), - } - } - // Execute only scenarios that are valid after a particular protocol update. - Some(ScenarioFilter::AllValidAfterProtocolVersion(protocol_version)) => { - RangeFrom { - start: protocol_version, - } - .contains(&protocol_requirement) - } - // No filter is specified, the scenario is valid. - None => true, - }; - if !passes_filter { - continue; + if !trigger_applies { + return Ok(()); + } + + let matching_scenarios = ALL_SCENARIOS.iter().filter(|(logical_name, creator)| { + let metadata = creator.metadata(); + let is_valid = metadata.protocol_min_requirement >= at_version; + if !is_valid { + return false; + } + match filter { + ScenarioFilter::ValidScenariosFrom(scenario_names) => { + scenario_names.contains(&**logical_name) + } + ScenarioFilter::AllScenariosValidAtProtocolVersion => true, + ScenarioFilter::AllScenariosFirstValidAtProtocolVersion => { + metadata.protocol_min_requirement == at_version } + } + }); - (self.on_scenario_start)(&metadata); - let mut previous = None; - loop { - let next = scenario - .next(previous.as_ref()) - .map_err(|err| err.into_full(&scenario)) - .unwrap(); - match next { - NextAction::Transaction(next) => { - let receipt = self.execute_transaction(&next.raw_transaction)?; - (self.on_transaction_executed)( - &metadata, - &next, - &receipt, - &self.database, - ); - previous = Some(receipt); + for (_, scenario_creator) in matching_scenarios { + self.execute_scenario(scenario_creator.as_ref())?; + } + + Ok(()) + } + + pub fn execute_scenario( + &mut self, + scenario_creator: &dyn ScenarioCreatorObjectSafe, + ) -> Result<(), ScenarioExecutorError> { + let epoch = SystemDatabaseReader::new(&self.database) + .read_object_field( + CONSENSUS_MANAGER.as_node_id(), + ModuleId::Main, + ConsensusManagerField::State.field_index(), + ) + .map_err(|_| ScenarioExecutorError::FailedToGetEpoch)? + .as_typed::() + .unwrap() + .fully_update_and_into_latest_version() + .epoch; + + let mut scenario = scenario_creator.create(ScenarioCore::new( + self.network_definition.clone(), + epoch, + self.starting_nonce, + )); + let metadata = scenario.metadata().clone(); + + (self.on_scenario_start)(&metadata); + let mut previous = None; + loop { + let next = scenario + .next(previous.as_ref()) + .map_err(|err| err.into_full(&scenario)) + .unwrap(); + match next { + NextAction::Transaction(next) => { + let receipt = self.execute_transaction(&next.raw_transaction)?; + (self.on_transaction_executed)(&metadata, &next, &receipt, &self.database); + previous = Some(receipt); + } + NextAction::Completed(end_state) => { + match self.next_scenario_nonce_handling { + ScenarioStartNonceHandling::PreviousScenarioStartNoncePlus(increment) => { + self.starting_nonce += increment } - NextAction::Completed(end_state) => { - match self.next_scenario_nonce_handling { - ScenarioStartNonceHandling::PreviousScenarioStartNoncePlus( - increment, - ) => next_nonce += increment, - ScenarioStartNonceHandling::PreviousScenarioEndNoncePlusOne => { - next_nonce = end_state.next_unused_nonce - } - } - break; + ScenarioStartNonceHandling::PreviousScenarioEndNoncePlusOne => { + self.starting_nonce = end_state.next_unused_nonce } } + break; } } } - Ok(ScenarioExecutionReceipt { - database: self.database, - }) + Ok(()) } fn execute_transaction( @@ -438,90 +372,3 @@ pub enum ScenarioStartNonceHandling { PreviousScenarioStartNoncePlus(u32), PreviousScenarioEndNoncePlusOne, } - -pub struct ScenarioExecutionReceipt { - pub database: D, -} - -#[derive(Clone, Debug)] -pub enum ScenarioFilter { - /// An exact set of scenarios to execute, specified by their scenario name. Before a scenario is - /// executed its name is checked against this set. It is executed if it's name is a member of - /// this set and ignored otherwise. Note that there is no check to ensure that the names in this - /// filter are valid. If an incorrect scenario name is provided in the set then it will simply - /// be ignored and wont match against anything. - ExactScenarios(BTreeSet), - /// Only executes the scenarios of a particular protocol update. - SpecificProtocolVersion(ProtocolVersion), - /// Filters scenarios based on their protocol version requirements executing all scenarios up - /// until the ones that require the specified protocol update. As an example, to execute all - /// scenarios from Genesis to Anemone this variant could be used and specified a protocol - /// update of [`ProtocolVersion::ProtocolUpdate(ProtocolUpdate::Anemone)`]. - AllValidBeforeProtocolVersion(Boundary), - /// Filters scenarios based on their protocol version requirements executing all scenarios from - /// the specified protocol update and up until the end. As an example, to execute all scenarios - /// from Anemone to the end then this variant could be used specified a protocol update of - /// [`ProtocolVersion::ProtocolUpdate(ProtocolUpdate::Anemone)`]. The specified protocol update - /// is included. - AllValidAfterProtocolVersion(ProtocolVersion), -} - -#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub enum Boundary { - Inclusive(T), - Exclusive(T), -} - -macro_rules! scenarios_vector { - ( - $( - $ty: ty - ),* $(,)? - ) => { - { - let mut vec = Vec::<( - ProtocolVersion, - Box Box> - )>::new(); - - $( - vec.push( - ( - <$ty as crate::scenario::ScenarioCreator>::SCENARIO_PROTOCOL_REQUIREMENT, - Box::new(|core| { - Box::new(<$ty as crate::scenario::ScenarioCreator>::create(core)) - as Box - }) - as Box Box> - ) - ); - )* - - vec - } - }; -} - -pub fn scenarios_vector() -> Vec<( - ProtocolVersion, - Box Box>, -)> { - scenarios_vector![ - // Genesis Scenarios - TransferXrdScenarioCreator, - RadiswapScenarioCreator, - MetadataScenario, - FungibleResourceScenarioCreator, - NonFungibleResourceScenarioCreator, - AccountAuthorizedDepositorsScenarioCreator, - GlobalNOwnedScenarioCreator, - NonFungibleResourceWithRemoteTypeScenarioCreator, - KVStoreScenarioCreator, - MaxTransactionScenarioCreator, - RoyaltiesScenarioCreator, - // Anemone Scenarios - None. - // Bottlenose Scenarios. - AccountLockerScenarioCreator, - MayaRouterScenarioCreator, - ] -} diff --git a/radix-transaction-scenarios/src/runners/dumper.rs b/radix-transaction-scenarios/src/runners/dumper.rs index d411ca4ae92..d596bca1846 100644 --- a/radix-transaction-scenarios/src/runners/dumper.rs +++ b/radix-transaction-scenarios/src/runners/dumper.rs @@ -29,41 +29,46 @@ pub fn run_all_in_memory_and_dump_examples( ) -> Result<(), FullScenarioError> { let mut event_hasher = HashAccumulator::new(); let mut substate_db = StateTreeUpdatingDatabase::new(InMemorySubstateDatabase::standard()); + let network_definition = NetworkDefinition::simulator(); + + let mut scenario_executor = + DefaultTransactionScenarioExecutor::new(substate_db, &network_definition) + .on_scenario_start(|scenario_metadata| { + let sub_folder = root_path.join(scenario_metadata.logical_name); + if sub_folder.exists() { + std::fs::remove_dir_all(&sub_folder).unwrap(); + } + }) + .on_transaction_executed(|scenario_metadata, transaction, receipt, _| { + transaction.dump_manifest( + &Some(root_path.join(scenario_metadata.logical_name)), + &network_definition, + ); - let ScenarioExecutionReceipt { - database: mut substate_db, - } = DefaultTransactionScenarioExecutor::new(substate_db, NetworkDefinition::simulator()) - .on_scenario_start(|scenario_metadata| { - let sub_folder = root_path.join(scenario_metadata.logical_name); - if sub_folder.exists() { - std::fs::remove_dir_all(&sub_folder).unwrap(); - } - }) - .on_transaction_executed(|scenario_metadata, transaction, receipt, _| { - transaction.dump_manifest( - &Some(root_path.join(scenario_metadata.logical_name)), - &NetworkDefinition::simulator(), - ); - - let intent_hash = - PreparedNotarizedTransactionV1::prepare_from_raw(&transaction.raw_transaction) - .unwrap() - .intent_hash(); + let intent_hash = + PreparedNotarizedTransactionV1::prepare_from_raw(&transaction.raw_transaction) + .unwrap() + .intent_hash(); - match &receipt.result { - TransactionResult::Commit(c) => { - event_hasher.update_no_chain(intent_hash.as_hash().as_bytes()); - event_hasher.update_no_chain(scrypto_encode(&c.application_events).unwrap()); + match &receipt.result { + TransactionResult::Commit(c) => { + event_hasher.update_no_chain(intent_hash.as_hash().as_bytes()); + event_hasher + .update_no_chain(scrypto_encode(&c.application_events).unwrap()); + } + TransactionResult::Reject(_) | TransactionResult::Abort(_) => {} } - TransactionResult::Reject(_) | TransactionResult::Abort(_) => {} - } - }) - .nonce_handling(ScenarioStartNonceHandling::PreviousScenarioStartNoncePlus( - 1000, - )) - .execute_all() + }) + .nonce_handling(ScenarioStartNonceHandling::PreviousScenarioStartNoncePlus( + 1000, + )); + + scenario_executor + .execute_every_protocol_update_and_scenario() .expect("Must succeed"); + substate_db = scenario_executor.into_database(); + assert_eq!( substate_db.get_current_root_hash().to_string(), "e0424eb560f6c7fbb671a7e7e4a273e3ec682b42e8ea2c5afb55be624ada4716", @@ -108,119 +113,4 @@ mod test { .unwrap(); } } - - #[test] - pub fn check_state_and_event_hashes_for_up_to_genesis_scenarios() { - assert_event_and_state_hashes( - "43be4cce2d4f2ed2eb519d77dfa770697244e843b2a0f7fd86bdf773d9b6f278", - "1be7a3d32b165f77a2126e706ed1d79b9198a09a1f08fa8b0f168ed54e8a19cc", - ScenarioFilter::AllValidBeforeProtocolVersion(Boundary::Exclusive( - ProtocolVersion::ProtocolUpdate(ProtocolUpdate::Anemone), - )), - |_, _, _| {}, - |_, _| {}, - ); - } - - #[test] - pub fn check_state_and_event_hashes_for_up_to_anemone_scenarios() { - assert_event_and_state_hashes( - "17567dbaf89a77a20e837e8d48187585b0547374fac9e19b9acc9d04d630a774", - "1be7a3d32b165f77a2126e706ed1d79b9198a09a1f08fa8b0f168ed54e8a19cc", - ScenarioFilter::AllValidBeforeProtocolVersion(Boundary::Inclusive( - ProtocolVersion::ProtocolUpdate(ProtocolUpdate::Anemone), - )), - |network, protocol_update, db| { - if let ProtocolVersion::ProtocolUpdate(protocol_update @ ProtocolUpdate::Anemone) = - protocol_update - { - protocol_update - .generate_state_updates(db, network) - .into_iter() - .for_each(|update| { - db.commit(&update.create_database_updates::()) - }); - } - }, - |_, _| {}, - ); - } - - #[test] - pub fn check_state_and_event_hashes_for_up_to_bottlenose_scenarios() { - assert_event_and_state_hashes( - "e0424eb560f6c7fbb671a7e7e4a273e3ec682b42e8ea2c5afb55be624ada4716", - "a1b834e8699d16a03f495f6e98ba603b7157ddf9c71f743c5965a9012d334ad8", - ScenarioFilter::AllValidBeforeProtocolVersion(Boundary::Inclusive( - ProtocolVersion::ProtocolUpdate(ProtocolUpdate::Bottlenose), - )), - |network, protocol_update, db| { - if let ProtocolVersion::ProtocolUpdate( - protocol_update @ (ProtocolUpdate::Anemone | ProtocolUpdate::Bottlenose), - ) = protocol_update - { - protocol_update - .generate_state_updates(db, network) - .into_iter() - .for_each(|update| { - db.commit(&update.create_database_updates::()) - }); - } - }, - |_, _| {}, - ); - } - - fn assert_event_and_state_hashes( - expected_state_root_hash: &str, - expected_event_hash: &str, - filter: ScenarioFilter, - protocol_update_handling: P, - after_bootstrap: B, - ) where - P: FnMut( - &NetworkDefinition, - ProtocolVersion, - &mut StateTreeUpdatingDatabase, - ), - B: FnMut(&NetworkDefinition, &mut StateTreeUpdatingDatabase), - { - // Arrange - let mut event_hasher = HashAccumulator::new(); - let mut substate_db = StateTreeUpdatingDatabase::new(InMemorySubstateDatabase::standard()); - - // Act - let ScenarioExecutionReceipt { - database: mut substate_db, - } = DefaultTransactionScenarioExecutor::new(substate_db, NetworkDefinition::simulator()) - .on_transaction_executed(|metadata, transaction, receipt, _| { - let intent_hash = - PreparedNotarizedTransactionV1::prepare_from_raw(&transaction.raw_transaction) - .unwrap() - .intent_hash(); - - match &receipt.result { - TransactionResult::Commit(c) => { - event_hasher.update_no_chain(intent_hash.as_hash().as_bytes()); - event_hasher - .update_no_chain(scrypto_encode(&c.application_events).unwrap()); - } - TransactionResult::Reject(_) | TransactionResult::Abort(_) => {} - } - }) - .on_new_protocol_requirement_encountered(protocol_update_handling) - .after_bootstrap(after_bootstrap) - .nonce_handling(ScenarioStartNonceHandling::PreviousScenarioStartNoncePlus( - 1000, - )) - .execute_all_matching(filter) - .expect("Must succeed"); - - // Assert - assert_eq!( - substate_db.get_current_root_hash().to_string(), - expected_state_root_hash - ); - assert_eq!(event_hasher.finalize().to_string(), expected_event_hash); - } } diff --git a/radix-transaction-scenarios/src/scenario.rs b/radix-transaction-scenarios/src/scenario.rs index 54d7bc7f847..4e957281505 100644 --- a/radix-transaction-scenarios/src/scenario.rs +++ b/radix-transaction-scenarios/src/scenario.rs @@ -375,25 +375,20 @@ pub struct ScenarioMetadata { /// - This is used in Node genesis to specify which scenarios should be run /// - This should be spaceless as it will be used for a file path pub logical_name: &'static str, + /// The minimal protocol version required to successfully run this scenario. + pub protocol_min_requirement: ProtocolVersion, + /// If set, this will run immediately after this protocol update on a testnet. + /// Note that setting this will change the definition of the given protocol update, + /// so shouldn't be changed once the protocol update is locked in. + pub testnet_run_at: Option, } -pub trait ScenarioCreator: Sized { +pub trait ScenarioCreator: Sized + 'static + Send + Sync { type Config: Default; type State: Default; type Instance: ScenarioInstance + 'static; - /// The protocol requirement of the scenario. If set to [`None`] then the scenario does not have - /// any requirements and can be run against all protocol versions including genesis and all that - /// comes after. If [`Some`] protocol update then this is the protocol update required for this - /// scenario to be executed, meaning that the scenario could be executed on this protocol update - /// and everything that comes after. This is a property of the [`ScenarioCreator`] rather than - /// the individual instance since the creator can be thought of the as "class" and the instance - /// is the object. - const SCENARIO_PROTOCOL_REQUIREMENT: ProtocolVersion; - - fn create(core: ScenarioCore) -> Self::Instance { - Self::create_with_config_and_state(core, Default::default(), Default::default()) - } + const METADATA: ScenarioMetadata; fn create_with_config_and_state( core: ScenarioCore, @@ -402,6 +397,26 @@ pub trait ScenarioCreator: Sized { ) -> Self::Instance; } +pub trait ScenarioCreatorObjectSafe: Send + Sync + 'static { + fn metadata(&self) -> ScenarioMetadata; + + fn create(&self, core: ScenarioCore) -> Box; +} + +impl ScenarioCreatorObjectSafe for T { + fn metadata(&self) -> ScenarioMetadata { + Self::METADATA + } + + fn create(&self, core: ScenarioCore) -> Box { + Box::new(Self::create_with_config_and_state( + core, + Default::default(), + Default::default(), + )) + } +} + pub trait ScenarioInstance { fn metadata(&self) -> &ScenarioMetadata; diff --git a/radix-transaction-scenarios/src/scenarios/account_authorized_depositors.rs b/radix-transaction-scenarios/src/scenarios/account_authorized_depositors.rs index 32f86295507..3ea525b0bf2 100644 --- a/radix-transaction-scenarios/src/scenarios/account_authorized_depositors.rs +++ b/radix-transaction-scenarios/src/scenarios/account_authorized_depositors.rs @@ -28,20 +28,20 @@ impl ScenarioCreator for AccountAuthorizedDepositorsScenarioCreator { type Config = AccountAuthorizedDepositorsScenarioConfig; type State = AccountAuthorizedDepositorsScenarioState; type Instance = Scenario; - const SCENARIO_PROTOCOL_REQUIREMENT: ProtocolVersion = ProtocolVersion::Genesis; - #[allow(deprecated)] + const METADATA: ScenarioMetadata = ScenarioMetadata { + logical_name: "account_authorized_depositors", + protocol_min_requirement: ProtocolVersion::Genesis, + testnet_run_at: Some(ProtocolVersion::Genesis), + }; + fn create_with_config_and_state( core: ScenarioCore, config: Self::Config, start_state: Self::State, ) -> Self::Instance { - let metadata = ScenarioMetadata { - logical_name: "account_authorized_depositors", - }; - #[allow(unused_variables)] - ScenarioBuilder::new(core, metadata, config, start_state) + ScenarioBuilder::new(core, Self::METADATA, config, start_state) .successful_transaction_with_result_handler( |core, config, _| { let AccountAuthorizedDepositorsScenarioConfig { diff --git a/radix-transaction-scenarios/src/scenarios/account_locker.rs b/radix-transaction-scenarios/src/scenarios/account_locker.rs index de14375b446..2c575c574c7 100644 --- a/radix-transaction-scenarios/src/scenarios/account_locker.rs +++ b/radix-transaction-scenarios/src/scenarios/account_locker.rs @@ -1,7 +1,7 @@ use crate::internal_prelude::*; use crate::utils::*; use radix_engine::blueprints::account::DepositEvent; -use radix_engine::updates::{ProtocolUpdate, ProtocolVersion}; +use radix_engine::updates::*; use radix_engine_interface::blueprints::account::*; use radix_engine_interface::*; @@ -46,19 +46,19 @@ impl ScenarioCreator for AccountLockerScenarioCreator { type Config = AccountLockerScenarioConfig; type State = AccountLockerScenarioState; type Instance = Scenario; - const SCENARIO_PROTOCOL_REQUIREMENT: ProtocolVersion = - ProtocolVersion::ProtocolUpdate(ProtocolUpdate::Bottlenose); + + const METADATA: ScenarioMetadata = ScenarioMetadata { + logical_name: "account_locker", + protocol_min_requirement: ProtocolVersion::Bottlenose, + testnet_run_at: Some(ProtocolVersion::Bottlenose), + }; fn create_with_config_and_state( core: ScenarioCore, config: Self::Config, start_state: Self::State, ) -> Self::Instance { - let metadata = ScenarioMetadata { - logical_name: "account_locker", - }; - - ScenarioBuilder::new(core, metadata, config, start_state) + ScenarioBuilder::new(core, Self::METADATA, config, start_state) .successful_transaction_with_result_handler( |core, config, _| { core.next_transaction_with_faucet_lock_fee( diff --git a/radix-transaction-scenarios/src/scenarios/fungible_resource.rs b/radix-transaction-scenarios/src/scenarios/fungible_resource.rs index a076e170019..0768ee38647 100644 --- a/radix-transaction-scenarios/src/scenarios/fungible_resource.rs +++ b/radix-transaction-scenarios/src/scenarios/fungible_resource.rs @@ -32,20 +32,20 @@ impl ScenarioCreator for FungibleResourceScenarioCreator { type Config = FungibleResourceScenarioConfig; type State = FungibleResourceScenarioState; type Instance = Scenario; - const SCENARIO_PROTOCOL_REQUIREMENT: ProtocolVersion = ProtocolVersion::Genesis; - #[allow(deprecated)] + const METADATA: ScenarioMetadata = ScenarioMetadata { + logical_name: "fungible_resource", + protocol_min_requirement: ProtocolVersion::Genesis, + testnet_run_at: Some(ProtocolVersion::Genesis), + }; + fn create_with_config_and_state( core: ScenarioCore, config: Self::Config, start_state: Self::State, ) -> Self::Instance { - let metadata = ScenarioMetadata { - logical_name: "fungible_resource", - }; - #[allow(unused_variables)] - ScenarioBuilder::new(core, metadata, config, start_state) + ScenarioBuilder::new(core, Self::METADATA, config, start_state) .successful_transaction_with_result_handler( |core, config, state| { core.next_transaction_with_faucet_lock_fee( diff --git a/radix-transaction-scenarios/src/scenarios/global_n_owned.rs b/radix-transaction-scenarios/src/scenarios/global_n_owned.rs index a17ba4c1a75..6ef89485ea7 100644 --- a/radix-transaction-scenarios/src/scenarios/global_n_owned.rs +++ b/radix-transaction-scenarios/src/scenarios/global_n_owned.rs @@ -11,19 +11,20 @@ impl ScenarioCreator for GlobalNOwnedScenarioCreator { type Config = (); type State = GlobalNOwnedScenarioState; type Instance = Scenario; - const SCENARIO_PROTOCOL_REQUIREMENT: ProtocolVersion = ProtocolVersion::Genesis; + + const METADATA: ScenarioMetadata = ScenarioMetadata { + logical_name: "global_n_owned", + protocol_min_requirement: ProtocolVersion::Genesis, + testnet_run_at: Some(ProtocolVersion::Genesis), + }; fn create_with_config_and_state( core: ScenarioCore, config: Self::Config, start_state: Self::State, ) -> Self::Instance { - let metadata = ScenarioMetadata { - logical_name: "global_n_owned", - }; - #[allow(unused_variables)] - ScenarioBuilder::new(core, metadata, config, start_state) + ScenarioBuilder::new(core, Self::METADATA, config, start_state) .successful_transaction_with_result_handler( |core, state, _| { let code = include_bytes!("../../assets/global_n_owned.wasm"); diff --git a/radix-transaction-scenarios/src/scenarios/kv_store_with_remote_type.rs b/radix-transaction-scenarios/src/scenarios/kv_store_with_remote_type.rs index 4a0d2be0a15..726f1050f41 100644 --- a/radix-transaction-scenarios/src/scenarios/kv_store_with_remote_type.rs +++ b/radix-transaction-scenarios/src/scenarios/kv_store_with_remote_type.rs @@ -11,19 +11,20 @@ impl ScenarioCreator for KVStoreScenarioCreator { type Config = (); type State = KVStoreScenarioState; type Instance = Scenario; - const SCENARIO_PROTOCOL_REQUIREMENT: ProtocolVersion = ProtocolVersion::Genesis; + + const METADATA: ScenarioMetadata = ScenarioMetadata { + logical_name: "kv_store_with_remote_type", + protocol_min_requirement: ProtocolVersion::Genesis, + testnet_run_at: Some(ProtocolVersion::Genesis), + }; fn create_with_config_and_state( core: ScenarioCore, config: Self::Config, start_state: Self::State, ) -> Self::Instance { - let metadata = ScenarioMetadata { - logical_name: "kv_store_with_remote_type", - }; - #[allow(unused_variables)] - ScenarioBuilder::new(core, metadata, config, start_state) + ScenarioBuilder::new(core, Self::METADATA, config, start_state) .successful_transaction_with_result_handler( |core, state, _| { let code = include_bytes!("../../assets/kv_store.wasm"); diff --git a/radix-transaction-scenarios/src/scenarios/max_transaction.rs b/radix-transaction-scenarios/src/scenarios/max_transaction.rs index 9a78ea80fdf..6f559beff6c 100644 --- a/radix-transaction-scenarios/src/scenarios/max_transaction.rs +++ b/radix-transaction-scenarios/src/scenarios/max_transaction.rs @@ -11,19 +11,20 @@ impl ScenarioCreator for MaxTransactionScenarioCreator { type Config = MaxTransactionScenarioState; type State = MaxTransactionScenarioState; type Instance = Scenario; - const SCENARIO_PROTOCOL_REQUIREMENT: ProtocolVersion = ProtocolVersion::Genesis; + + const METADATA: ScenarioMetadata = ScenarioMetadata { + logical_name: "max_transaction", + protocol_min_requirement: ProtocolVersion::Genesis, + testnet_run_at: Some(ProtocolVersion::Genesis), + }; fn create_with_config_and_state( core: ScenarioCore, config: Self::Config, start_state: Self::State, ) -> Self::Instance { - let metadata = ScenarioMetadata { - logical_name: "max_transaction", - }; - #[allow(unused_variables)] - ScenarioBuilder::new(core, metadata, config, start_state) + ScenarioBuilder::new(core, Self::METADATA, config, start_state) .successful_transaction_with_result_handler( |core, state, _| { let code = include_bytes!("../../assets/max_transaction.wasm").to_vec(); diff --git a/radix-transaction-scenarios/src/scenarios/maya_router.rs b/radix-transaction-scenarios/src/scenarios/maya_router.rs index f183de4d834..6b28e70bba5 100644 --- a/radix-transaction-scenarios/src/scenarios/maya_router.rs +++ b/radix-transaction-scenarios/src/scenarios/maya_router.rs @@ -1,6 +1,6 @@ use crate::internal_prelude::*; use crate::utils::{new_ed25519_private_key, new_secp256k1_private_key}; -use radix_engine::updates::{ProtocolUpdate, ProtocolVersion}; +use radix_engine::updates::ProtocolVersion; use radix_engine_interface::blueprints::account::*; use radix_engine_interface::blueprints::package::*; use radix_engine_interface::object_modules::ModuleConfig; @@ -54,20 +54,20 @@ impl ScenarioCreator for MayaRouterScenarioCreator { type Config = MayaRouterScenarioConfig; type State = MayaRouterScenarioState; type Instance = Scenario; - const SCENARIO_PROTOCOL_REQUIREMENT: ProtocolVersion = - ProtocolVersion::ProtocolUpdate(ProtocolUpdate::Bottlenose); + + const METADATA: ScenarioMetadata = ScenarioMetadata { + logical_name: "maya_router", + protocol_min_requirement: ProtocolVersion::Bottlenose, + testnet_run_at: Some(ProtocolVersion::Bottlenose), + }; fn create_with_config_and_state( core: ScenarioCore, config: Self::Config, start_state: Self::State, ) -> Self::Instance { - let metadata = ScenarioMetadata { - logical_name: "maya_router", - }; - #[allow(unused_variables)] - ScenarioBuilder::new(core, metadata, config, start_state) + ScenarioBuilder::new(core, Self::METADATA, config, start_state) .successful_transaction_with_result_handler( |core, config, _| { core.next_transaction_with_faucet_lock_fee( diff --git a/radix-transaction-scenarios/src/scenarios/metadata.rs b/radix-transaction-scenarios/src/scenarios/metadata.rs index 42cb9559733..93f92f952b6 100644 --- a/radix-transaction-scenarios/src/scenarios/metadata.rs +++ b/radix-transaction-scenarios/src/scenarios/metadata.rs @@ -8,13 +8,6 @@ use radix_engine_interface::*; use crate::internal_prelude::*; -pub struct MetadataScenario { - core: ScenarioCore, - metadata: ScenarioMetadata, - config: MetadataScenarioConfig, - state: MetadataScenarioState, -} - #[allow(deprecated)] pub struct MetadataScenarioConfig { pub user_account_1: VirtualAccount, @@ -40,29 +33,40 @@ pub struct MetadataScenarioState { pub resource_with_metadata2: Option, } -impl ScenarioCreator for MetadataScenario { +pub struct MetadataScenarioCreator; + +impl ScenarioCreator for MetadataScenarioCreator { type Config = MetadataScenarioConfig; type State = MetadataScenarioState; - type Instance = Self; - const SCENARIO_PROTOCOL_REQUIREMENT: ProtocolVersion = ProtocolVersion::Genesis; + type Instance = MetadataScenario; + + const METADATA: ScenarioMetadata = ScenarioMetadata { + logical_name: "metadata", + protocol_min_requirement: ProtocolVersion::Genesis, + testnet_run_at: Some(ProtocolVersion::Genesis), + }; fn create_with_config_and_state( core: ScenarioCore, config: Self::Config, start_state: Self::State, ) -> Self::Instance { - let metadata = ScenarioMetadata { - logical_name: "metadata", - }; - Self { + Self::Instance { core, - metadata, + metadata: Self::METADATA, config, state: start_state, } } } +pub struct MetadataScenario { + core: ScenarioCore, + metadata: ScenarioMetadata, + config: MetadataScenarioConfig, + state: MetadataScenarioState, +} + impl ScenarioInstance for MetadataScenario { fn metadata(&self) -> &ScenarioMetadata { &self.metadata diff --git a/radix-transaction-scenarios/src/scenarios/mod.rs b/radix-transaction-scenarios/src/scenarios/mod.rs index 3bc583070fd..346b13fcadf 100644 --- a/radix-transaction-scenarios/src/scenarios/mod.rs +++ b/radix-transaction-scenarios/src/scenarios/mod.rs @@ -1,13 +1,53 @@ -pub mod account_authorized_depositors; -pub mod account_locker; -pub mod fungible_resource; -pub mod global_n_owned; -pub mod kv_store_with_remote_type; -pub mod max_transaction; -pub mod maya_router; -pub mod metadata; -pub mod non_fungible_resource; -pub mod non_fungible_resource_with_remote_type; -pub mod radiswap; -pub mod royalties; -pub mod transfer_xrd; +use super::*; +use internal_prelude::*; + +mod account_authorized_depositors; +mod account_locker; +mod fungible_resource; +mod global_n_owned; +mod kv_store_with_remote_type; +mod max_transaction; +mod maya_router; +mod metadata; +mod non_fungible_resource; +mod non_fungible_resource_with_remote_type; +mod radiswap; +mod royalties; +mod transfer_xrd; + +// Add new scenarios TO THE BOTTOM OF THE LIST below. +lazy_static::lazy_static! { + pub static ref ALL_SCENARIOS: IndexMap> = { + fn add(map: &mut IndexMap>, creator: C) { + map.insert( + creator.metadata().logical_name.to_string(), + Box::new(creator), + ); + } + + let mut map = Default::default(); + + // Add new scenarios here TO THE BOTTOM OF THE LIST to register them + // with the outside world. + // + // NOTE: ORDER MATTERS, as it affects the canonical order in which + // scenarios get run, if multiple scenarios can get run at a given time. + // This order therefore shouldn't be changed, to avoid affecting historic + // execution on testnets. + add(&mut map, transfer_xrd::TransferXrdScenarioCreator); + add(&mut map, radiswap::RadiswapScenarioCreator); + add(&mut map, metadata::MetadataScenarioCreator); + add(&mut map, fungible_resource::FungibleResourceScenarioCreator); + add(&mut map, non_fungible_resource::NonFungibleResourceScenarioCreator); + add(&mut map, account_authorized_depositors::AccountAuthorizedDepositorsScenarioCreator); + add(&mut map, global_n_owned::GlobalNOwnedScenarioCreator); + add(&mut map, non_fungible_resource_with_remote_type::NonFungibleResourceWithRemoteTypeScenarioCreator); + add(&mut map, kv_store_with_remote_type::KVStoreScenarioCreator); + add(&mut map, max_transaction::MaxTransactionScenarioCreator); + add(&mut map, royalties::RoyaltiesScenarioCreator); + add(&mut map, account_locker::AccountLockerScenarioCreator); + add(&mut map, maya_router::MayaRouterScenarioCreator); + + map + }; +} diff --git a/radix-transaction-scenarios/src/scenarios/non_fungible_resource.rs b/radix-transaction-scenarios/src/scenarios/non_fungible_resource.rs index ec1684e34a3..c091cc1dd73 100644 --- a/radix-transaction-scenarios/src/scenarios/non_fungible_resource.rs +++ b/radix-transaction-scenarios/src/scenarios/non_fungible_resource.rs @@ -36,20 +36,20 @@ impl ScenarioCreator for NonFungibleResourceScenarioCreator { type Config = NonFungibleResourceScenarioConfig; type State = NonFungibleResourceScenarioState; type Instance = Scenario; - const SCENARIO_PROTOCOL_REQUIREMENT: ProtocolVersion = ProtocolVersion::Genesis; - #[allow(deprecated)] + const METADATA: ScenarioMetadata = ScenarioMetadata { + logical_name: "non_fungible_resource", + protocol_min_requirement: ProtocolVersion::Genesis, + testnet_run_at: Some(ProtocolVersion::Genesis), + }; + fn create_with_config_and_state( core: ScenarioCore, config: Self::Config, start_state: Self::State, ) -> Self::Instance { - let metadata = ScenarioMetadata { - logical_name: "non_fungible_resource", - }; - #[allow(unused_variables)] - ScenarioBuilder::new(core, metadata, config, start_state) + ScenarioBuilder::new(core, Self::METADATA, config, start_state) .successful_transaction_with_result_handler( |core, config, state| { core.next_transaction_with_faucet_lock_fee( @@ -642,5 +642,3 @@ pub enum InnerEnum { InnerEnum(Box), InnerStruct(Box), } - -pub type NoNonFungibleData = (); diff --git a/radix-transaction-scenarios/src/scenarios/non_fungible_resource_with_remote_type.rs b/radix-transaction-scenarios/src/scenarios/non_fungible_resource_with_remote_type.rs index adaba865e9c..5fc5841326d 100644 --- a/radix-transaction-scenarios/src/scenarios/non_fungible_resource_with_remote_type.rs +++ b/radix-transaction-scenarios/src/scenarios/non_fungible_resource_with_remote_type.rs @@ -33,20 +33,20 @@ impl ScenarioCreator for NonFungibleResourceWithRemoteTypeScenarioCreator { type Config = NonFungibleResourceWithRemoteTypeScenarioConfig; type State = NonFungibleResourceWithRemoteTypeScenarioState; type Instance = Scenario; - const SCENARIO_PROTOCOL_REQUIREMENT: ProtocolVersion = ProtocolVersion::Genesis; - #[allow(deprecated)] + const METADATA: ScenarioMetadata = ScenarioMetadata { + logical_name: "non_fungible_resource_with_remote_type", + protocol_min_requirement: ProtocolVersion::Genesis, + testnet_run_at: Some(ProtocolVersion::Genesis), + }; + fn create_with_config_and_state( core: ScenarioCore, config: Self::Config, start_state: Self::State, ) -> Self::Instance { - let metadata = ScenarioMetadata { - logical_name: "non_fungible_resource_with_remote_type", - }; - #[allow(unused_variables)] - ScenarioBuilder::new(core, metadata, config, start_state) + ScenarioBuilder::new(core, Self::METADATA, config, start_state) .successful_transaction_with_result_handler( |core, config, state| { core.next_transaction_with_faucet_lock_fee( diff --git a/radix-transaction-scenarios/src/scenarios/radiswap.rs b/radix-transaction-scenarios/src/scenarios/radiswap.rs index a74d347cd06..bbaec4b21a9 100644 --- a/radix-transaction-scenarios/src/scenarios/radiswap.rs +++ b/radix-transaction-scenarios/src/scenarios/radiswap.rs @@ -49,20 +49,20 @@ impl ScenarioCreator for RadiswapScenarioCreator { type Config = RadiswapScenarioConfig; type State = RadiswapScenarioState; type Instance = Scenario; - const SCENARIO_PROTOCOL_REQUIREMENT: ProtocolVersion = ProtocolVersion::Genesis; - #[allow(deprecated)] + const METADATA: ScenarioMetadata = ScenarioMetadata { + logical_name: "radiswap", + protocol_min_requirement: ProtocolVersion::Genesis, + testnet_run_at: Some(ProtocolVersion::Genesis), + }; + fn create_with_config_and_state( core: ScenarioCore, config: Self::Config, start_state: Self::State, ) -> Self::Instance { - let metadata = ScenarioMetadata { - logical_name: "radiswap", - }; - #[allow(unused_variables)] - ScenarioBuilder::new(core, metadata, config, start_state) + ScenarioBuilder::new(core, Self::METADATA, config, start_state) .successful_transaction_with_result_handler( |core, config, state| { core.next_transaction_with_faucet_lock_fee_fallible( diff --git a/radix-transaction-scenarios/src/scenarios/royalties.rs b/radix-transaction-scenarios/src/scenarios/royalties.rs index 4ea4b688b5d..7626d132b21 100644 --- a/radix-transaction-scenarios/src/scenarios/royalties.rs +++ b/radix-transaction-scenarios/src/scenarios/royalties.rs @@ -10,25 +10,26 @@ pub struct RoyaltiesState { pub usd_royalty_component_address: Option, } -pub enum RoyaltiesScenarioCreator {} +pub struct RoyaltiesScenarioCreator; impl ScenarioCreator for RoyaltiesScenarioCreator { type Config = (); type State = RoyaltiesState; type Instance = Scenario; - const SCENARIO_PROTOCOL_REQUIREMENT: ProtocolVersion = ProtocolVersion::Genesis; + + const METADATA: ScenarioMetadata = ScenarioMetadata { + logical_name: "royalties", + protocol_min_requirement: ProtocolVersion::Genesis, + testnet_run_at: Some(ProtocolVersion::Genesis), + }; fn create_with_config_and_state( core: ScenarioCore, config: Self::Config, start_state: Self::State, ) -> Self::Instance { - let metadata = ScenarioMetadata { - logical_name: "royalties", - }; - #[allow(unused_variables)] - ScenarioBuilder::new(core, metadata, config, start_state) + ScenarioBuilder::new(core, Self::METADATA, config, start_state) .successful_transaction_with_result_handler( |core, state, _| { let code = include_bytes!("../../assets/royalties.wasm").to_vec(); diff --git a/radix-transaction-scenarios/src/scenarios/transfer_xrd.rs b/radix-transaction-scenarios/src/scenarios/transfer_xrd.rs index e30b7cca312..818e2ef9829 100644 --- a/radix-transaction-scenarios/src/scenarios/transfer_xrd.rs +++ b/radix-transaction-scenarios/src/scenarios/transfer_xrd.rs @@ -19,26 +19,26 @@ impl Default for TransferXrdConfig { } } -pub enum TransferXrdScenarioCreator {} +pub struct TransferXrdScenarioCreator; impl ScenarioCreator for TransferXrdScenarioCreator { type Config = TransferXrdConfig; type State = (); type Instance = Scenario; - const SCENARIO_PROTOCOL_REQUIREMENT: ProtocolVersion = ProtocolVersion::Genesis; - #[allow(deprecated)] + const METADATA: ScenarioMetadata = ScenarioMetadata { + logical_name: "transfer_xrd", + protocol_min_requirement: ProtocolVersion::Genesis, + testnet_run_at: Some(ProtocolVersion::Genesis), + }; + fn create_with_config_and_state( core: ScenarioCore, config: Self::Config, start_state: Self::State, ) -> Self::Instance { - let metadata = ScenarioMetadata { - logical_name: "transfer_xrd", - }; - #[allow(unused_variables)] - ScenarioBuilder::new(core, metadata, config, start_state) + ScenarioBuilder::new(core, Self::METADATA, config, start_state) .successful_transaction(|core, config, state| { core.next_transaction_free_xrd_from_faucet(config.from_account.address) }) diff --git a/scrypto-test/src/environment/builder.rs b/scrypto-test/src/environment/builder.rs index ef09dad68ff..c42fa7d67dd 100644 --- a/scrypto-test/src/environment/builder.rs +++ b/scrypto-test/src/environment/builder.rs @@ -18,7 +18,7 @@ use radix_engine::system::system_modules::transaction_runtime::TransactionRuntim use radix_engine::system::system_modules::*; use radix_engine::track::*; use radix_engine::transaction::*; -use radix_engine::updates::ProtocolUpdates; +use radix_engine::updates::*; use radix_engine::vm::wasm::*; use radix_engine::vm::*; use radix_engine_interface::blueprints::package::*; @@ -53,8 +53,8 @@ where /// Specifies whether the builder should run genesis and bootstrap the environment or not. bootstrap: bool, - /// The protocol updates the the user has opt in to get. This defaults to all true. - protocol_updates: ProtocolUpdates, + /// The protocol updates the the user wishes to execute. This defaults to all. + protocol_executor: ProtocolExecutor, } impl Default for TestEnvironmentBuilder { @@ -70,7 +70,7 @@ impl TestEnvironmentBuilder { flash_database: FlashSubstateDatabase::standard(), added_global_references: Default::default(), bootstrap: true, - protocol_updates: ProtocolUpdates::all(), + protocol_executor: ProtocolBuilder::for_simulator().until_latest_protocol_version(), } } } @@ -178,12 +178,15 @@ where added_global_references: self.added_global_references, flash_database: self.flash_database, bootstrap: self.bootstrap, - protocol_updates: self.protocol_updates, + protocol_executor: self.protocol_executor, } } - pub fn protocol_updates(mut self, protocol_updates: ProtocolUpdates) -> Self { - self.protocol_updates = protocol_updates; + pub fn with_protocol( + mut self, + executor: impl FnOnce(ProtocolBuilder) -> ProtocolExecutor, + ) -> Self { + self.protocol_executor = executor(ProtocolBuilder::for_simulator()); self } @@ -213,13 +216,8 @@ where let id_allocator = IdAllocator::new(Self::DEFAULT_INTENT_HASH); // Determine if any protocol updates need to be run against the database. - for state_updates in self - .protocol_updates - .generate_state_updates(&self.database, &NetworkDefinition::simulator()) - { - let db_updates = state_updates.create_database_updates::(); - self.database.commit(&db_updates); - } + self.protocol_executor + .commit_each_protocol_update(&mut self.database); // If a flash is specified execute it. let database_updates = self.flash_database.database_updates(); diff --git a/scrypto-test/src/ledger_simulator/ledger_simulator.rs b/scrypto-test/src/ledger_simulator/ledger_simulator.rs index 5913d48f8ca..86087fd8518 100644 --- a/scrypto-test/src/ledger_simulator/ledger_simulator.rs +++ b/scrypto-test/src/ledger_simulator/ledger_simulator.rs @@ -14,7 +14,7 @@ use radix_engine::transaction::{ execute_preview, execute_transaction_with_configuration, BalanceChange, CommitResult, CostingParameters, ExecutionConfig, PreviewError, TransactionReceipt, TransactionResult, }; -use radix_engine::updates::{ProtocolUpdate, ProtocolUpdates}; +use radix_engine::updates::*; use radix_engine::vm::wasm::{DefaultWasmEngine, WasmValidatorConfigV1}; use radix_engine::vm::{NativeVmExtension, NoExtension, ScryptoVm, Vm}; use radix_engine_interface::api::ModuleId; @@ -197,7 +197,7 @@ pub struct LedgerSimulatorBuilder { custom_genesis: Option, custom_extension: E, custom_database: D, - custom_protocol_updates: ProtocolUpdates, + protocol_executor: ProtocolExecutor, // General options with_kernel_trace: bool, @@ -210,7 +210,8 @@ impl LedgerSimulatorBuilder { custom_genesis: None, custom_extension: NoExtension, custom_database: InMemorySubstateDatabase::standard(), - custom_protocol_updates: ProtocolUpdates::all(), + protocol_executor: ProtocolBuilder::for_network(&NetworkDefinition::simulator()) + .until_latest_protocol_version(), with_kernel_trace: true, with_receipt_substate_check: true, } @@ -218,12 +219,16 @@ impl LedgerSimulatorBuilder { } impl LedgerSimulatorBuilder { + pub fn network_definition() -> NetworkDefinition { + NetworkDefinition::simulator() + } + pub fn with_state_hashing(self) -> LedgerSimulatorBuilder> { LedgerSimulatorBuilder { custom_genesis: self.custom_genesis, custom_extension: self.custom_extension, custom_database: StateTreeUpdatingDatabase::new(self.custom_database), - custom_protocol_updates: self.custom_protocol_updates, + protocol_executor: self.protocol_executor, with_kernel_trace: self.with_kernel_trace, with_receipt_substate_check: self.with_receipt_substate_check, } @@ -262,7 +267,7 @@ impl LedgerSimulatorBuilder { custom_genesis: self.custom_genesis, custom_extension: extension, custom_database: self.custom_database, - custom_protocol_updates: self.custom_protocol_updates, + protocol_executor: self.protocol_executor, with_kernel_trace: self.with_kernel_trace, with_receipt_substate_check: self.with_receipt_substate_check, } @@ -276,17 +281,25 @@ impl LedgerSimulatorBuilder { custom_genesis: self.custom_genesis, custom_extension: self.custom_extension, custom_database: database, - custom_protocol_updates: self.custom_protocol_updates, + protocol_executor: self.protocol_executor, with_kernel_trace: self.with_kernel_trace, with_receipt_substate_check: self.with_receipt_substate_check, } } - pub fn with_custom_protocol_updates(mut self, protocol_updates: ProtocolUpdates) -> Self { - self.custom_protocol_updates = protocol_updates; + pub fn with_custom_protocol( + mut self, + executor: impl FnOnce(ProtocolBuilder) -> ProtocolExecutor, + ) -> Self { + self.protocol_executor = + executor(ProtocolBuilder::for_network(&Self::network_definition())); self } + pub fn with_protocol_version(self, protocol_version: ProtocolVersion) -> Self { + self.with_custom_protocol(|builder| builder.until(protocol_version)) + } + pub fn build_from_snapshot( self, snapshot: LedgerSimulatorSnapshot, @@ -369,13 +382,8 @@ impl LedgerSimulatorBuilder { let next_transaction_nonce = 100; // Protocol Updates - for state_updates in self - .custom_protocol_updates - .generate_state_updates(&substate_db, &NetworkDefinition::simulator()) - { - let db_updates = state_updates.create_database_updates::(); - substate_db.commit(&db_updates); - } + self.protocol_executor + .commit_each_protocol_update(&mut substate_db); let runner = LedgerSimulator { scrypto_vm, @@ -2336,18 +2344,6 @@ impl LedgerSimulator { .expect("Resource reconciliation failed"); } } - - pub fn apply_protocol_updates(&mut self, protocol_updates: &[ProtocolUpdate]) { - protocol_updates.iter().for_each(|protocol_update| { - protocol_update - .generate_state_updates(&self.database, &NetworkDefinition::simulator()) - .into_iter() - .for_each(|update| { - self.database - .commit(&update.create_database_updates::()) - }) - }) - } } impl LedgerSimulator> {