From f0af363204a46cad08640c98c3658ae8bbf9ba0d Mon Sep 17 00:00:00 2001 From: Dmytro Kozhevin Date: Mon, 23 Dec 2024 20:07:49 -0500 Subject: [PATCH] Allow passing the setting for non-root auth to the simulation. This is unfortunately a breaking change and thus it needs to be guarded by unstable-next-api feature. --- soroban-env-host/src/e2e_invoke.rs | 61 ++++++++++++++++++++--- soroban-env-host/src/test/e2e_tests.rs | 51 +++++++++++++------ soroban-simulation/Cargo.toml | 5 +- soroban-simulation/src/simulation.rs | 13 ++--- soroban-simulation/src/test/simulation.rs | 27 +++++++--- 5 files changed, 120 insertions(+), 37 deletions(-) diff --git a/soroban-env-host/src/e2e_invoke.rs b/soroban-env-host/src/e2e_invoke.rs index d97fc920c..609882fb9 100644 --- a/soroban-env-host/src/e2e_invoke.rs +++ b/soroban-env-host/src/e2e_invoke.rs @@ -494,6 +494,25 @@ fn clear_signature(auth_entry: &mut SorobanAuthorizationEntry) { } } +#[cfg(any(test, feature = "recording_mode"))] +#[cfg(not(feature = "unstable-next-api"))] +/// Defines the authorization mode for the `invoke_host_function_in_recording_mode`. +/// +/// When `None`, recording authorization with disabled non-root authorization will be used. +/// When `Some()`, enforcing auth will be used with the provided entries. +pub type RecordingInvocationAuthMode = Option>; + +#[cfg(all(any(test, feature = "recording_mode"), feature = "unstable-next-api"))] +/// Defines the authorization mode for the `invoke_host_function_in_recording_mode`. +pub enum RecordingInvocationAuthMode { + /// Use enforcing auth and pass the signed authorization entries to be used. + Enforcing(Vec), + /// Use recording auth and determine whether non-root authorization is + /// disabled (i.e. non-root auth is not allowed when `true` is passed to + /// the enum). + Recording(bool), +} + /// Invokes a host function within a fresh host instance in 'recording' mode. /// /// The purpose of recording mode is to measure the resources necessary for @@ -503,10 +522,11 @@ fn clear_signature(auth_entry: &mut SorobanAuthorizationEntry) { /// - Footprint - this is based on the ledger entries accessed. /// - Read/write bytes - this is based on the sizes of ledger entries read /// from the provided `ledger_snapshot` -/// - Authorization payloads - when the input `auth_entries` is `None`, Host +/// - Authorization mode - when the input `auth_mode` is `None`, Host /// switches to recording auth mode and fills the recorded data in the output. -/// When `auth_entries` is not `None`, the authorization is performed in -/// enforcing mode and `auth_entries` are passed through to the output. +/// When `auth_mode` is not `None`, the authorization is performed in +/// enforcing mode and entries from `auth_mode` are passed through to the +/// output. /// - Instructions - this simply measures the instructions measured by the /// provided `budget`. While this function makes the best effort to emulate /// the work performed by `invoke_host_function`, the measured value might @@ -532,7 +552,7 @@ pub fn invoke_host_function_in_recording_mode( enable_diagnostics: bool, host_fn: &HostFunction, source_account: &AccountId, - auth_entries: Option>, + auth_mode: RecordingInvocationAuthMode, ledger_info: LedgerInfo, ledger_snapshot: Rc, base_prng_seed: [u8; 32], @@ -540,18 +560,32 @@ pub fn invoke_host_function_in_recording_mode( ) -> Result { let storage = Storage::with_recording_footprint(ledger_snapshot.clone()); let host = Host::with_storage_and_budget(storage, budget.clone()); - let is_recording_auth = auth_entries.is_none(); + #[cfg(not(feature = "unstable-next-api"))] + let is_recording_auth = auth_mode.is_none(); + #[cfg(feature = "unstable-next-api")] + let is_recording_auth = matches!(auth_mode, RecordingInvocationAuthMode::Recording(_)); let ledger_seq = ledger_info.sequence_number; let host_function = host.xdr_roundtrip(host_fn)?; let source_account: AccountId = host.xdr_roundtrip(source_account)?; host.set_source_account(source_account)?; host.set_ledger_info(ledger_info)?; host.set_base_prng_seed(base_prng_seed)?; - if let Some(auth_entries) = &auth_entries { + + #[cfg(not(feature = "unstable-next-api"))] + if let Some(auth_entries) = &auth_mode { host.set_authorization_entries(auth_entries.clone())?; } else { host.switch_to_recording_auth(true)?; } + #[cfg(feature = "unstable-next-api")] + match &auth_mode { + RecordingInvocationAuthMode::Enforcing(auth_entries) => { + host.set_authorization_entries(auth_entries.clone())?; + } + RecordingInvocationAuthMode::Recording(disable_non_root_auth) => { + host.switch_to_recording_auth(*disable_non_root_auth)?; + } + } if enable_diagnostics { host.set_diagnostic_level(DiagnosticLevel::Debug)?; @@ -564,7 +598,9 @@ pub fn invoke_host_function_in_recording_mode( contract_events_and_return_value_size = contract_events_and_return_value_size .saturating_add(encoded_result_sc_val.len() as u32); } - let mut output_auth = if let Some(auth_entries) = auth_entries { + + #[cfg(not(feature = "unstable-next-api"))] + let mut output_auth = if let Some(auth_entries) = auth_mode { auth_entries } else { let recorded_auth = host.get_recorded_auth_payloads()?; @@ -573,6 +609,17 @@ pub fn invoke_host_function_in_recording_mode( .map(|a| a.into_auth_entry_with_emulated_signature()) .collect::, HostError>>()? }; + #[cfg(feature = "unstable-next-api")] + let mut output_auth = if let RecordingInvocationAuthMode::Enforcing(auth_entries) = auth_mode { + auth_entries + } else { + let recorded_auth = host.get_recorded_auth_payloads()?; + recorded_auth + .into_iter() + .map(|a| a.into_auth_entry_with_emulated_signature()) + .collect::, HostError>>()? + }; + let encoded_auth_entries = output_auth .iter() .map(|e| host.to_xdr_non_metered(e)) diff --git a/soroban-env-host/src/test/e2e_tests.rs b/soroban-env-host/src/test/e2e_tests.rs index ce7c1658f..568b2414a 100644 --- a/soroban-env-host/src/test/e2e_tests.rs +++ b/soroban-env-host/src/test/e2e_tests.rs @@ -1,4 +1,5 @@ use crate::builtin_contracts::testutils::AccountContractSigner; +use crate::e2e_invoke::RecordingInvocationAuthMode; use crate::e2e_testutils::{account_entry, bytes_sc_val, upload_wasm_host_fn}; use crate::testutils::simple_account_sign_fn; use crate::{ @@ -191,6 +192,28 @@ impl LedgerEntryChangeHelper { } } +// NB: this is a temporary helper function that we should remove and embed +// RecordingInvocationAuthMode into code during the `unstable-next-api` cleanup +// when switching to v23. +fn recording_auth_mode() -> RecordingInvocationAuthMode { + #[cfg(not(feature = "unstable-next-api"))] + return None; + #[cfg(feature = "unstable-next-api")] + return RecordingInvocationAuthMode::Recording(true); +} + +// NB: this is a temporary helper function that we should remove and embed +// RecordingInvocationAuthMode into code during the `unstable-next-api` cleanup +// when switching to v23. +fn enforcing_auth_mode( + auth_entries: Vec, +) -> RecordingInvocationAuthMode { + #[cfg(not(feature = "unstable-next-api"))] + return Some(auth_entries); + #[cfg(feature = "unstable-next-api")] + return RecordingInvocationAuthMode::Enforcing(auth_entries); +} + struct InvokeHostFunctionHelperResult { invoke_result: Result, ledger_changes: Vec, @@ -294,7 +317,7 @@ fn invoke_host_function_recording_helper( enable_diagnostics: bool, host_fn: &HostFunction, source_account: &AccountId, - auth_entries: Option>, + auth_mode: RecordingInvocationAuthMode, ledger_info: &LedgerInfo, ledger_entries_with_ttl: Vec<(LedgerEntry, Option)>, prng_seed: &[u8; 32], @@ -311,7 +334,7 @@ fn invoke_host_function_recording_helper( enable_diagnostics, host_fn, source_account, - auth_entries, + auth_mode, ledger_info.clone(), snapshot, *prng_seed, @@ -342,7 +365,7 @@ fn invoke_host_function_using_simulation_with_signers( enable_diagnostics, host_fn, source_account, - None, + recording_auth_mode(), ledger_info, ledger_entries_with_ttl.clone(), prng_seed, @@ -360,7 +383,7 @@ fn invoke_host_function_using_simulation_with_signers( enable_diagnostics, host_fn, source_account, - Some(signed_auth.clone()), + enforcing_auth_mode(signed_auth.clone()), ledger_info, ledger_entries_with_ttl.clone(), prng_seed, @@ -557,7 +580,7 @@ fn test_run_out_of_budget_before_calling_host_in_recording_mode() { true, &upload_wasm_host_fn(ADD_I32), &get_account_id([0; 32]), - None, + recording_auth_mode(), &default_ledger_info(), vec![], &prng_seed(), @@ -646,7 +669,7 @@ fn test_wasm_upload_success_in_recording_mode() { false, &upload_wasm_host_fn(ADD_I32), &get_account_id([123; 32]), - None, + recording_auth_mode(), &ledger_info, vec![], &prng_seed(), @@ -699,7 +722,7 @@ fn test_wasm_upload_failure_in_recording_mode() { true, &upload_wasm_host_fn(&[0_u8; 1000]), &get_account_id([123; 32]), - None, + recording_auth_mode(), &ledger_info, vec![], &prng_seed(), @@ -737,7 +760,7 @@ fn test_unsupported_wasm_upload_failure_in_recording_mode() { true, &upload_wasm_host_fn(ADD_F32), &get_account_id([123; 32]), - None, + recording_auth_mode(), &ledger_info, vec![], &prng_seed(), @@ -1149,7 +1172,7 @@ fn test_create_contract_success_in_recording_mode() { true, &cd.host_fn, &cd.deployer, - None, + recording_auth_mode(), &ledger_info, vec![( cd.wasm_entry.clone(), @@ -1235,7 +1258,7 @@ fn test_create_contract_success_in_recording_mode_with_custom_account() { true, &cd.host_fn, &cd.deployer, - None, + recording_auth_mode(), &ledger_info, vec![ ( @@ -1351,7 +1374,7 @@ fn test_create_contract_success_in_recording_mode_with_enforced_auth() { true, &cd.host_fn, &cd.deployer, - Some(vec![cd.auth_entry.clone()]), + enforcing_auth_mode(vec![cd.auth_entry.clone()]), &ledger_info, vec![( cd.wasm_entry.clone(), @@ -1767,7 +1790,7 @@ fn test_invoke_contract_with_storage_ops_success_in_recording_mode() { true, &host_fn, &cd.deployer, - None, + recording_auth_mode(), &ledger_info, vec![ ( @@ -1842,7 +1865,7 @@ fn test_invoke_contract_with_storage_ops_success_in_recording_mode() { true, &extend_host_fn, &cd.deployer, - None, + recording_auth_mode(), &ledger_info, vec![ ( @@ -1945,7 +1968,7 @@ fn test_invoke_contract_with_storage_ops_success_using_simulation() { true, &extend_host_fn, &cd.deployer, - None, + recording_auth_mode(), &ledger_info, vec![ ( diff --git a/soroban-simulation/Cargo.toml b/soroban-simulation/Cargo.toml index e032c50a1..148ff3029 100644 --- a/soroban-simulation/Cargo.toml +++ b/soroban-simulation/Cargo.toml @@ -15,16 +15,17 @@ publish = true [features] testutils = ["soroban-env-host/testutils"] +unstable-next-api = ["soroban-env-host/unstable-next-api"] [dependencies] anyhow = { version = "1.0.75", features = [] } thiserror = "1.0.40" -soroban-env-host = { workspace = true, features = ["recording_mode", "unstable-next-api"]} +soroban-env-host = { workspace = true, features = ["recording_mode"]} static_assertions = "1.1.0" rand = "0.8.5" [dev-dependencies] -soroban-env-host = { workspace = true, features = ["recording_mode", "testutils", "unstable-next-api"]} +soroban-env-host = { workspace = true, features = ["recording_mode", "testutils"]} soroban-test-wasms = { package = "soroban-test-wasms", path = "../soroban-test-wasms" } pretty_assertions = "1.4" tap = "1.0.1" diff --git a/soroban-simulation/src/simulation.rs b/soroban-simulation/src/simulation.rs index b8cebb002..ef3de2807 100644 --- a/soroban-simulation/src/simulation.rs +++ b/soroban-simulation/src/simulation.rs @@ -9,7 +9,7 @@ use crate::snapshot_source::{ use anyhow::Result; use soroban_env_host::{ e2e_invoke::invoke_host_function_in_recording_mode, - e2e_invoke::LedgerEntryChange, + e2e_invoke::{LedgerEntryChange, RecordingInvocationAuthMode}, storage::SnapshotSource, xdr::{ AccountId, ContractEvent, DiagnosticEvent, HostFunction, InvokeHostFunctionOp, LedgerKey, @@ -100,9 +100,10 @@ pub struct RestoreOpSimulationResult { /// relevant payload parts. /// /// The operation is defined by the host function itself (`host_fn`) -/// and optionally signed `auth_entries`. In case if `auth_entries` are -/// omitted, the simulation will use recording authorization mode and -/// return non-signed recorded authorization entries. +/// and `auth_mode`. In case if `auth_mode` is `None`, the simulation will +/// use recording authorization mode and return non-signed recorded +/// authorization entries. Otherwise, the signed entries will be used for +/// authorization and authentication enforcement. /// /// The rest of parameters define the ledger state (`snapshot_source`, /// `network_config`, `ledger_info`), simulation adjustment @@ -122,7 +123,7 @@ pub fn simulate_invoke_host_function_op( adjustment_config: &SimulationAdjustmentConfig, ledger_info: &LedgerInfo, host_fn: HostFunction, - auth_entries: Option>, + auth_mode: RecordingInvocationAuthMode, source_account: &AccountId, base_prng_seed: [u8; 32], enable_diagnostics: bool, @@ -135,7 +136,7 @@ pub fn simulate_invoke_host_function_op( enable_diagnostics, &host_fn, source_account, - auth_entries, + auth_mode, ledger_info.clone(), snapshot_source.clone(), base_prng_seed, diff --git a/soroban-simulation/src/test/simulation.rs b/soroban-simulation/src/test/simulation.rs index 58e4f7ac5..d89d584ba 100644 --- a/soroban-simulation/src/test/simulation.rs +++ b/soroban-simulation/src/test/simulation.rs @@ -6,6 +6,7 @@ use crate::simulation::{ use crate::testutils::{ledger_entry_to_ledger_key, temp_entry, MockSnapshotSource}; use crate::NetworkConfig; use pretty_assertions::assert_eq; +use soroban_env_host::e2e_invoke::RecordingInvocationAuthMode; use soroban_env_host::e2e_testutils::{ account_entry, auth_contract_invocation, bytes_sc_val, create_contract_auth, default_ledger_info, get_account_id, get_contract_id_preimage, get_wasm_hash, get_wasm_key, @@ -100,6 +101,16 @@ fn nonce_entry(address: ScAddress, nonce: i64) -> LedgerEntry { })) } +// NB: this is a temporary helper function that we should remove and embed +// RecordingInvocationAuthMode into code during the `unstable-next-api` cleanup +// when switching to v23. +fn recording_auth_mode() -> RecordingInvocationAuthMode { + #[cfg(not(feature = "unstable-next-api"))] + return None; + #[cfg(feature = "unstable-next-api")] + return RecordingInvocationAuthMode::Recording(true); +} + #[test] fn test_simulate_upload_wasm() { let source_account = get_account_id([123; 32]); @@ -114,7 +125,7 @@ fn test_simulate_upload_wasm() { &SimulationAdjustmentConfig::no_adjustments(), &ledger_info, upload_wasm_host_fn(ADD_I32), - None, + recording_auth_mode(), &source_account, [1; 32], true, @@ -163,7 +174,7 @@ fn test_simulate_upload_wasm() { &test_adjustment_config(), &ledger_info, upload_wasm_host_fn(ADD_I32), - None, + recording_auth_mode(), &source_account, [1; 32], true, @@ -218,7 +229,7 @@ fn test_simulation_returns_insufficient_budget_error() { &SimulationAdjustmentConfig::no_adjustments(), &ledger_info, upload_wasm_host_fn(ADD_I32), - None, + recording_auth_mode(), &source_account, [1; 32], true, @@ -253,7 +264,7 @@ fn test_simulation_returns_logic_error() { &SimulationAdjustmentConfig::no_adjustments(), &ledger_info, upload_wasm_host_fn(&bad_wasm), - None, + recording_auth_mode(), &source_account, [1; 32], true, @@ -297,7 +308,7 @@ fn test_simulate_create_contract() { &SimulationAdjustmentConfig::no_adjustments(), &ledger_info, contract.host_fn.clone(), - None, + recording_auth_mode(), &source_account, [1; 32], true, @@ -430,7 +441,7 @@ fn test_simulate_invoke_contract_with_auth() { &SimulationAdjustmentConfig::no_adjustments(), &ledger_info, host_fn, - None, + recording_auth_mode(), &source_account, [1; 32], true, @@ -1003,7 +1014,7 @@ fn test_simulate_successful_sac_call() { &SimulationAdjustmentConfig::no_adjustments(), &ledger_info, host_fn, - None, + recording_auth_mode(), &source_account, [1; 32], true, @@ -1103,7 +1114,7 @@ fn test_simulate_unsuccessful_sac_call_with_try_call() { &SimulationAdjustmentConfig::no_adjustments(), &ledger_info, host_fn, - None, + recording_auth_mode(), &source_account, [1; 32], true,