diff --git a/Cargo.lock b/Cargo.lock index a75fb0630..fb4b69712 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1623,6 +1623,7 @@ dependencies = [ "soroban-env-host", "soroban-test-wasms", "static_assertions", + "stellar-xdr", "tap", "thiserror", ] diff --git a/soroban-simulation/Cargo.toml b/soroban-simulation/Cargo.toml index e032c50a1..a8eaa5879 100644 --- a/soroban-simulation/Cargo.toml +++ b/soroban-simulation/Cargo.toml @@ -19,12 +19,15 @@ testutils = ["soroban-env-host/testutils"] [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", "unstable-next-api", "next"]} +stellar-xdr = { workspace = true, features = ["next"]} 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", "unstable-next-api", "next"]} +stellar-xdr = { workspace = true, features = ["next"]} 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/network_config.rs b/soroban-simulation/src/network_config.rs index 94eb4ef49..acc5c72a6 100644 --- a/soroban-simulation/src/network_config.rs +++ b/soroban-simulation/src/network_config.rs @@ -35,8 +35,9 @@ fn load_configuration_setting( let key = Rc::new(LedgerKey::ConfigSetting(LedgerKeyConfigSetting { config_setting_id: setting_id, })); - let (entry, _) = snapshot + let entry = snapshot .get_including_archived(&key)? + .entry .ok_or_else(|| anyhow!("setting {setting_id:?} is not present in the snapshot"))?; if let LedgerEntry { data: LedgerEntryData::ConfigSetting(cs), diff --git a/soroban-simulation/src/resources.rs b/soroban-simulation/src/resources.rs index 77bd3b078..0de19fd81 100644 --- a/soroban-simulation/src/resources.rs +++ b/soroban-simulation/src/resources.rs @@ -1,8 +1,10 @@ use super::snapshot_source::SnapshotSourceWithArchive; use crate::network_config::NetworkConfig; use crate::simulation::{SimulationAdjustmentConfig, SimulationAdjustmentFactor}; -use anyhow::{anyhow, ensure, Context, Result}; +use crate::snapshot_source::LedgerEntryArchivalState; +use anyhow::{anyhow, bail, ensure, Context, Result}; +use soroban_env_host::xdr::ArchivalProof; use soroban_env_host::{ e2e_invoke::{extract_rent_changes, LedgerEntryChange}, fees::{ @@ -12,12 +14,12 @@ use soroban_env_host::{ ledger_info::get_key_durability, storage::SnapshotSource, xdr::{ - BytesM, ContractDataDurability, DecoratedSignature, Duration, ExtensionPoint, Hash, - LedgerBounds, LedgerFootprint, LedgerKey, Limits, Memo, MuxedAccount, MuxedAccountMed25519, - Operation, OperationBody, Preconditions, PreconditionsV2, ReadXdr, SequenceNumber, - Signature, SignatureHint, SignerKey, SignerKeyEd25519SignedPayload, SorobanResources, - SorobanTransactionData, TimeBounds, TimePoint, Transaction, TransactionExt, - TransactionV1Envelope, Uint256, WriteXdr, + BytesM, ContractDataDurability, DecoratedSignature, Duration, Hash, LedgerBounds, + LedgerFootprint, LedgerKey, Limits, Memo, MuxedAccount, MuxedAccountMed25519, Operation, + OperationBody, Preconditions, PreconditionsV2, ReadXdr, SequenceNumber, Signature, + SignatureHint, SignerKey, SignerKeyEd25519SignedPayload, SorobanResources, + SorobanTransactionData, SorobanTransactionDataExt, TimeBounds, TimePoint, Transaction, + TransactionExt, TransactionV1Envelope, Uint256, WriteXdr, }, LedgerInfo, DEFAULT_XDR_RW_LIMITS, }; @@ -193,7 +195,11 @@ pub(crate) fn simulate_restore_op_resources( keys_to_restore: &[LedgerKey], snapshot_source: &impl SnapshotSourceWithArchive, ledger_info: &LedgerInfo, -) -> Result<(SorobanResources, Vec)> { +) -> Result<( + SorobanResources, + Vec, + Option, +)> { let restored_live_until_ledger = ledger_info .min_live_until_ledger_checked(ContractDataDurability::Persistent) .ok_or_else(|| { @@ -202,6 +208,8 @@ pub(crate) fn simulate_restore_op_resources( let mut restored_bytes = 0_u32; let mut rent_changes: Vec = Vec::with_capacity(keys_to_restore.len()); let mut restored_keys = Vec::::with_capacity(keys_to_restore.len()); + let mut restored_keys_needing_proof = + Vec::>::with_capacity(keys_to_restore.len()); for key in keys_to_restore { let durability = get_key_durability(key); @@ -209,29 +217,33 @@ pub(crate) fn simulate_restore_op_resources( durability == Some(ContractDataDurability::Persistent), "Can't restore a ledger entry with key: {key:?}. Only persistent ledger entries with TTL can be restored." ); - let entry_with_live_until = snapshot_source - .get_including_archived(&Rc::new(key.clone()))? - .ok_or_else(|| anyhow!("Missing entry to restore for key {key:?}"))?; - let (entry, live_until) = entry_with_live_until; - - let current_live_until_ledger = live_until.ok_or_else(|| { - anyhow!("Internal error: missing TTL for ledger key that must have TTL: `{key:?}`") - })?; - - if current_live_until_ledger >= ledger_info.sequence_number { - continue; + let rc_key = Rc::new(key.clone()); + let entry_state = snapshot_source.get_including_archived(&rc_key)?; + match &entry_state.state { + LedgerEntryArchivalState::Archived(need_proof) => { + if *need_proof { + restored_keys_needing_proof.push(rc_key.clone()); + } + restored_keys.push(key.clone()); + } + _ => { + continue; + } } - restored_keys.push(key.clone()); - let entry_size: u32 = entry.to_xdr(DEFAULT_XDR_RW_LIMITS)?.len().try_into()?; - restored_bytes = restored_bytes.saturating_add(entry_size); - rent_changes.push(LedgerEntryRentChange { - is_persistent: true, - old_size_bytes: 0, - new_size_bytes: entry_size, - old_live_until_ledger: 0, - new_live_until_ledger: restored_live_until_ledger, - }); + if let Some(entry) = &entry_state.entry { + let entry_size: u32 = entry.to_xdr(DEFAULT_XDR_RW_LIMITS)?.len().try_into()?; + restored_bytes = restored_bytes.saturating_add(entry_size); + rent_changes.push(LedgerEntryRentChange { + is_persistent: true, + old_size_bytes: 0, + new_size_bytes: entry_size, + old_live_until_ledger: 0, + new_live_until_ledger: restored_live_until_ledger, + }); + } else { + bail!("missing entry to be restored for key '{key:?}', is the archive incomplete?"); + } } restored_keys.sort(); let resources = SorobanResources { @@ -243,7 +255,12 @@ pub(crate) fn simulate_restore_op_resources( read_bytes: restored_bytes, write_bytes: restored_bytes, }; - Ok((resources, rent_changes)) + let archival_proof = if !restored_keys_needing_proof.is_empty() { + Some(snapshot_source.generate_restoration_proof(restored_keys_needing_proof.as_slice())?) + } else { + None + }; + Ok((resources, rent_changes, archival_proof)) } fn estimate_max_transaction_size_for_operation( @@ -299,7 +316,7 @@ fn estimate_max_transaction_size_for_operation( write_bytes: 0, }, resource_fee: 0, - ext: ExtensionPoint::V0, + ext: SorobanTransactionDataExt::V0, }), }, signatures: signatures.try_into()?, diff --git a/soroban-simulation/src/simulation.rs b/soroban-simulation/src/simulation.rs index b8cebb002..20600d469 100644 --- a/soroban-simulation/src/simulation.rs +++ b/soroban-simulation/src/simulation.rs @@ -12,10 +12,11 @@ use soroban_env_host::{ e2e_invoke::LedgerEntryChange, storage::SnapshotSource, xdr::{ - AccountId, ContractEvent, DiagnosticEvent, HostFunction, InvokeHostFunctionOp, LedgerKey, - OperationBody, ScVal, SorobanAuthorizationEntry, SorobanResources, SorobanTransactionData, + AccountId, ArchivalProof, ContractEvent, DiagnosticEvent, ExtendFootprintTtlOp, + ExtensionPoint, HostFunction, InvokeHostFunctionOp, LedgerEntry, LedgerKey, OperationBody, + ReadXdr, RestoreFootprintOp, ScVal, SorobanAuthorizationEntry, SorobanResources, + SorobanTransactionData, SorobanTransactionDataExt, }, - xdr::{ExtendFootprintTtlOp, ExtensionPoint, LedgerEntry, ReadXdr, RestoreFootprintOp}, HostError, LedgerInfo, DEFAULT_XDR_RW_LIMITS, }; use std::rc::Rc; @@ -116,7 +117,7 @@ pub struct RestoreOpSimulationResult { /// for failed invocations. It should only fail if ledger is /// mis-configured (e.g. when computed fees cause overflows). #[allow(clippy::too_many_arguments)] -pub fn simulate_invoke_host_function_op( +pub fn simulate_invoke_host_function_op( snapshot_source: Rc, network_config: &NetworkConfig, adjustment_config: &SimulationAdjustmentConfig, @@ -126,7 +127,11 @@ pub fn simulate_invoke_host_function_op( source_account: &AccountId, base_prng_seed: [u8; 32], enable_diagnostics: bool, -) -> Result { + generate_new_entry_proof: F, +) -> Result +where + F: FnOnce() -> Result>, +{ let snapshot_source = Rc::new(SimulationSnapshotSource::new_from_rc(snapshot_source)); let budget = network_config.create_budget()?; let mut diagnostic_events = vec![]; @@ -194,7 +199,11 @@ pub fn simulate_invoke_host_function_op( &rent_changes, adjustment_config, ); - simulation_result.transaction_data = Some(create_transaction_data(resources, resource_fee)); + simulation_result.transaction_data = Some(create_transaction_data( + resources, + resource_fee, + generate_new_entry_proof()?, + )); Ok(simulation_result) } @@ -243,7 +252,7 @@ pub fn simulate_extend_ttl_op( adjustment_config, ); Ok(ExtendTtlOpSimulationResult { - transaction_data: create_transaction_data(resources, resource_fee), + transaction_data: create_transaction_data(resources, resource_fee, None), }) } @@ -271,7 +280,7 @@ pub fn simulate_restore_op( keys_to_restore: &[LedgerKey], ) -> Result { let snapshot_source = SimulationSnapshotSourceWithArchive::new(snapshot_source); - let (mut resources, rent_changes) = + let (mut resources, rent_changes, archival_proof) = simulate_restore_op_resources(keys_to_restore, &snapshot_source, ledger_info)?; let operation = OperationBody::RestoreFootprint(RestoreFootprintOp { ext: ExtensionPoint::V0, @@ -286,7 +295,7 @@ pub fn simulate_restore_op( adjustment_config, ); Ok(RestoreOpSimulationResult { - transaction_data: create_transaction_data(resources, resource_fee), + transaction_data: create_transaction_data(resources, resource_fee, archival_proof), }) } @@ -333,11 +342,16 @@ impl SimulationAdjustmentConfig { fn create_transaction_data( resources: SorobanResources, resource_fee: i64, + new_entries_proof: Option, ) -> SorobanTransactionData { SorobanTransactionData { resources, resource_fee, - ext: ExtensionPoint::V0, + ext: if let Some(proof) = new_entries_proof { + SorobanTransactionDataExt::V1(vec![proof].try_into().unwrap()) + } else { + SorobanTransactionDataExt::V0 + }, } } diff --git a/soroban-simulation/src/snapshot_source.rs b/soroban-simulation/src/snapshot_source.rs index f2eb15537..010d12e65 100644 --- a/soroban-simulation/src/snapshot_source.rs +++ b/soroban-simulation/src/snapshot_source.rs @@ -5,8 +5,8 @@ use crate::simulation::{ use anyhow::{anyhow, Result}; use soroban_env_host::xdr::{ AccountEntry, AccountEntryExt, AccountEntryExtensionV1, AccountEntryExtensionV1Ext, - AccountEntryExtensionV2, AccountEntryExtensionV2Ext, AccountEntryExtensionV3, ExtensionPoint, - LedgerEntryData, Liabilities, SponsorshipDescriptor, TimePoint, + AccountEntryExtensionV2, AccountEntryExtensionV2Ext, AccountEntryExtensionV3, ArchivalProof, + ExtensionPoint, LedgerEntry, LedgerEntryData, Liabilities, SponsorshipDescriptor, TimePoint, }; use soroban_env_host::{ ledger_info::get_key_durability, @@ -18,6 +18,20 @@ use std::cell::RefCell; use std::collections::{BTreeMap, BTreeSet}; use std::rc::Rc; +#[derive(Copy, Clone)] +pub enum LedgerEntryArchivalState { + Live, + New(bool), + Archived(bool), +} + +#[derive(Clone)] +pub struct LedgerEntryWithArchivalState { + pub entry: Option>, + pub live_until_ledger: Option, + pub state: LedgerEntryArchivalState, +} + /// Read-only ledger snapshot accessor that also has access /// to archived entries. /// @@ -27,7 +41,11 @@ pub trait SnapshotSourceWithArchive { fn get_including_archived( &self, key: &Rc, - ) -> std::result::Result, HostError>; + ) -> std::result::Result; + + fn generate_new_entries_proof(&self, keys: &[Rc]) -> Result; + + fn generate_restoration_proof(&self, keys: &[Rc]) -> Result; } /// The `SnapshotSource` implementation that automatically restores @@ -43,24 +61,41 @@ pub struct AutoRestoringSnapshotSource { snapshot_source: Rc, min_persistent_live_until_ledger: u32, current_ledger_sequence: u32, + new_keys_requiring_proof: RefCell>>, restored_ledger_keys: RefCell>>, } impl AutoRestoringSnapshotSource { - pub fn new(snapshot_source: Rc, ledger_info: &LedgerInfo) -> Result { + pub fn new(snapshot_source: Rc, ledger_info: &LedgerInfo) -> Result { Ok(Self { snapshot_source, - min_persistent_live_until_ledger: ledger_info.min_live_until_ledger_checked(ContractDataDurability::Persistent).ok_or_else(|| anyhow!("minimum persistent live until ledger overflows - ledger info is misconfigured"))?, + min_persistent_live_until_ledger: ledger_info.min_live_until_ledger_checked( + ContractDataDurability::Persistent).ok_or_else(|| + anyhow!("minimum persistent live until ledger overflows - ledger info is misconfigured"))?, current_ledger_sequence: ledger_info.sequence_number, + new_keys_requiring_proof: RefCell::new(Default::default()), restored_ledger_keys: RefCell::new(Default::default()), }) } - /// Resets all the restored keys recorded so far. - pub fn reset_restored_keys(&self) { + /// Resets all the tracked keys recorded so far. + /// + /// Key tracking includes new keys requiring proofs and restored keys. + pub fn reset_tracked_keys(&self) { + self.new_keys_requiring_proof.borrow_mut().clear(); self.restored_ledger_keys.borrow_mut().clear(); } + pub fn get_new_keys_proof(&self) -> Result> { + let keys = self.new_keys_requiring_proof.borrow(); + if keys.is_empty() { + return Ok(None); + } + Ok(Some(self.snapshot_source.generate_new_entries_proof( + keys.iter().cloned().collect::>().as_slice(), + )?)) + } + /// Simulates a `RestoreFootprintOp` for all the keys that have been /// restored so far. pub fn simulate_restore_keys_op( @@ -90,70 +125,105 @@ impl AutoRestoringSnapshotSource { impl SnapshotSource for AutoRestoringSnapshotSource { fn get(&self, key: &Rc) -> Result, HostError> { - let entry_with_live_until = self.snapshot_source.get_including_archived(key)?; - if let Some((entry, live_until)) = entry_with_live_until { - if let Some(durability) = get_key_durability(key.as_ref()) { - let live_until = live_until.ok_or_else(|| { - // Entries with durability must have TTL. - HostError::from((ScErrorType::Storage, ScErrorCode::InternalError)) - })?; - if live_until < self.current_ledger_sequence { - return match durability { - ContractDataDurability::Temporary => Ok(None), - ContractDataDurability::Persistent => { - let mut restored_ledger_keys = - self.restored_ledger_keys.try_borrow_mut().map_err(|_| { - HostError::from(( - ScErrorType::Context, - ScErrorCode::InternalError, - )) - })?; - restored_ledger_keys.insert(key.clone()); - Ok(Some((entry, Some(self.min_persistent_live_until_ledger)))) - } - }; + let entry_state = self.snapshot_source.get_including_archived(key)?; + match entry_state.state { + LedgerEntryArchivalState::Live => (), + LedgerEntryArchivalState::New(need_proof) => { + if need_proof { + let mut new_keys_requiring_proof = self + .new_keys_requiring_proof + .try_borrow_mut() + .map_err(|_| { + HostError::from((ScErrorType::Context, ScErrorCode::InternalError)) + })?; + new_keys_requiring_proof.insert(key.clone()); + } + } + LedgerEntryArchivalState::Archived(_) => { + if entry_state.entry.is_some() { + let mut restored_ledger_keys = + self.restored_ledger_keys.try_borrow_mut().map_err(|_| { + HostError::from((ScErrorType::Context, ScErrorCode::InternalError)) + })?; + restored_ledger_keys.insert(key.clone()); + } + } + } + if let Some(entry) = entry_state.entry { + if let Some(_durability) = get_key_durability(key.as_ref()) { + if let Some(live_until) = entry_state.live_until_ledger { + if live_until < self.current_ledger_sequence { + return Err(HostError::from(( + ScErrorType::Storage, + ScErrorCode::InternalError, + ))); + } + } else { + if !matches!(entry_state.state, LedgerEntryArchivalState::Archived(_)) { + return Err(HostError::from(( + ScErrorType::Storage, + ScErrorCode::InternalError, + ))); + } + return Ok(Some((entry, Some(self.min_persistent_live_until_ledger)))); + } + } else { + if !matches!(entry_state.state, LedgerEntryArchivalState::Live) + && !matches!(entry_state.state, LedgerEntryArchivalState::New(false)) + { + // Something went wrong, entries without durability can't be archived or + // require a proof. + return Err(HostError::from(( + ScErrorType::Storage, + ScErrorCode::InternalError, + ))); } } - Ok(Some((entry, live_until))) + Ok(Some((entry, entry_state.live_until_ledger))) } else { - Ok(None) + if matches!(entry_state.state, LedgerEntryArchivalState::Archived(_)) { + Err(HostError::from(( + ScErrorType::Storage, + ScErrorCode::InternalError, + ))) + } else { + Ok(None) + } } } } #[derive(Default)] struct LedgerEntryUpdater { - updated_entries_cache: BTreeMap, Option>, + updated_entries_cache: BTreeMap, Rc>, } impl LedgerEntryUpdater { fn maybe_update_entry( &mut self, key: &Rc, - entry: Option, - ) -> Option { + entry: &Rc, + ) -> Option> { if let Some(e) = self.updated_entries_cache.get(key) { - return e.clone(); + return Some(e.clone()); } - if let Some((entry, live_until)) = &entry { - match &entry.data { - LedgerEntryData::Account(_) => { - let mut updated_entry = (**entry).clone(); - match &mut updated_entry.data { - LedgerEntryData::Account(acc) => { - update_account_entry(acc); - } - _ => (), + + match entry.data { + LedgerEntryData::Account(_) => { + let mut updated_entry = (**entry).clone(); + match &mut updated_entry.data { + LedgerEntryData::Account(acc) => { + update_account_entry(acc); } - let entry_with_live_until = Some((Rc::new(updated_entry), *live_until)); - self.updated_entries_cache - .insert(key.clone(), entry_with_live_until.clone()); - return entry_with_live_until; + _ => (), } - _ => (), + let updated_entry = Rc::new(updated_entry); + self.updated_entries_cache + .insert(key.clone(), updated_entry.clone()); + Some(updated_entry.clone()) } + _ => None, } - entry } } @@ -218,10 +288,18 @@ impl<'a, T: SnapshotSourceWithArchive> SimulationSnapshotSourceWithArchive<'a, T impl<'a> SnapshotSource for SimulationSnapshotSource<'a> { fn get(&self, key: &Rc) -> Result, HostError> { - Ok(self - .entry_updater - .borrow_mut() - .maybe_update_entry(key, self.inner_snapshot.get(key)?)) + let entry_with_live_until = self.inner_snapshot.get(key)?; + + if let Some((entry, live_until)) = &entry_with_live_until { + if let Some(updated_entry) = self + .entry_updater + .borrow_mut() + .maybe_update_entry(key, entry) + { + return Ok(Some((updated_entry, *live_until))); + } + } + Ok(entry_with_live_until) } } @@ -231,11 +309,31 @@ impl<'a, T: SnapshotSourceWithArchive> SnapshotSourceWithArchive fn get_including_archived( &self, key: &Rc, - ) -> Result, HostError> { - Ok(self - .entry_updater - .borrow_mut() - .maybe_update_entry(key, self.inner_snapshot.get_including_archived(key)?)) + ) -> Result { + let entry_state = self.inner_snapshot.get_including_archived(key)?; + + if let Some(entry) = &entry_state.entry { + if let Some(updated_entry) = self + .entry_updater + .borrow_mut() + .maybe_update_entry(key, entry) + { + return Ok(LedgerEntryWithArchivalState { + entry: Some(updated_entry), + live_until_ledger: entry_state.live_until_ledger, + state: entry_state.state, + }); + } + } + Ok(entry_state) + } + + fn generate_new_entries_proof(&self, keys: &[Rc]) -> Result { + self.inner_snapshot.generate_new_entries_proof(keys) + } + + fn generate_restoration_proof(&self, keys: &[Rc]) -> Result { + self.inner_snapshot.generate_restoration_proof(keys) } } diff --git a/soroban-simulation/src/test/simulation.rs b/soroban-simulation/src/test/simulation.rs index 58e4f7ac5..ffb56a441 100644 --- a/soroban-simulation/src/test/simulation.rs +++ b/soroban-simulation/src/test/simulation.rs @@ -3,8 +3,9 @@ use crate::simulation::{ ExtendTtlOpSimulationResult, LedgerEntryDiff, RestoreOpSimulationResult, SimulationAdjustmentConfig, SimulationAdjustmentFactor, }; +use crate::snapshot_source::LedgerEntryArchivalState; use crate::testutils::{ledger_entry_to_ledger_key, temp_entry, MockSnapshotSource}; -use crate::NetworkConfig; +use crate::{AutoRestoringSnapshotSource, NetworkConfig}; use pretty_assertions::assert_eq; use soroban_env_host::e2e_testutils::{ account_entry, auth_contract_invocation, bytes_sc_val, create_contract_auth, @@ -14,14 +15,16 @@ use soroban_env_host::e2e_testutils::{ }; use soroban_env_host::fees::{FeeConfiguration, RentFeeConfiguration}; use soroban_env_host::xdr::{ - AccountId, AlphaNum4, AssetCode4, ContractCostParamEntry, ContractCostParams, ContractCostType, - ContractDataDurability, ContractDataEntry, ContractExecutable, ExtensionPoint, Hash, - HostFunction, Int128Parts, InvokeContractArgs, LedgerEntry, LedgerEntryData, LedgerFootprint, - LedgerKey, LedgerKeyContractData, LedgerKeyTrustLine, PublicKey, ScAddress, ScBytes, - ScContractInstance, ScErrorCode, ScErrorType, ScMap, ScNonceKey, ScString, ScSymbol, ScVal, + AccountId, AlphaNum4, ArchivalProof, ArchivalProofBody, AssetCode4, ContractCostParamEntry, + ContractCostParams, ContractCostType, ContractDataDurability, ContractDataEntry, + ContractExecutable, ExtensionPoint, Hash, HostFunction, Int128Parts, InvokeContractArgs, + LedgerEntry, LedgerEntryData, LedgerFootprint, LedgerKey, LedgerKeyContractData, + LedgerKeyTrustLine, NonexistenceProofBody, PublicKey, ScAddress, ScBytes, ScContractInstance, + ScErrorCode, ScErrorType, ScMap, ScNonceKey, ScString, ScSymbol, ScVal, SorobanAddressCredentials, SorobanAuthorizationEntry, SorobanAuthorizedFunction, SorobanAuthorizedInvocation, SorobanCredentials, SorobanResources, SorobanTransactionData, - TrustLineAsset, TrustLineEntry, TrustLineEntryExt, TrustLineFlags, Uint256, VecM, + SorobanTransactionDataExt, TrustLineAsset, TrustLineEntry, TrustLineEntryExt, TrustLineFlags, + Uint256, VecM, }; use soroban_env_host::HostError; use soroban_test_wasms::{ADD_I32, AUTH_TEST_CONTRACT, TRY_CALL_SAC}; @@ -107,7 +110,121 @@ fn test_simulate_upload_wasm() { let network_config = default_network_config(); let snapshot_source = Rc::new(MockSnapshotSource::from_entries(vec![], ledger_info.sequence_number).unwrap()); + let snapshot_source = + Rc::new(AutoRestoringSnapshotSource::new(snapshot_source, &ledger_info).unwrap()); + let res = simulate_invoke_host_function_op( + snapshot_source.clone(), + &network_config, + &SimulationAdjustmentConfig::no_adjustments(), + &ledger_info, + upload_wasm_host_fn(ADD_I32), + None, + &source_account, + [1; 32], + true, + || snapshot_source.get_new_keys_proof(), + ) + .unwrap(); + assert_eq!( + res.invoke_result.unwrap(), + bytes_sc_val(&get_wasm_hash(ADD_I32)) + ); + + assert_eq!(res.auth, vec![]); + assert!(res.contract_events.is_empty()); + assert!(res.diagnostic_events.is_empty()); + + let expected_instructions = 1644789; + let expected_write_bytes = 684; + assert_eq!( + res.transaction_data, + Some(SorobanTransactionData { + ext: SorobanTransactionDataExt::V0, + resources: SorobanResources { + footprint: LedgerFootprint { + read_only: Default::default(), + read_write: vec![get_wasm_key(ADD_I32)].try_into().unwrap() + }, + instructions: expected_instructions, + read_bytes: 0, + write_bytes: expected_write_bytes, + }, + resource_fee: 35548, + }) + ); + assert_eq!(res.simulated_instructions, expected_instructions); + assert_eq!(res.simulated_memory, 822393); + assert_eq!( + res.modified_entries, + vec![LedgerEntryDiff { + state_before: None, + state_after: Some(wasm_entry(ADD_I32)) + }] + ); + + let res_with_adjustments = simulate_invoke_host_function_op( + snapshot_source.clone(), + &network_config, + &test_adjustment_config(), + &ledger_info, + upload_wasm_host_fn(ADD_I32), + None, + &source_account, + [1; 32], + true, + || snapshot_source.get_new_keys_proof(), + ) + .unwrap(); + assert_eq!( + res_with_adjustments.invoke_result.unwrap(), + bytes_sc_val(&get_wasm_hash(ADD_I32)) + ); + + assert_eq!(res_with_adjustments.auth, res.auth); + assert_eq!(res_with_adjustments.contract_events, res.contract_events); + assert_eq!( + res_with_adjustments.diagnostic_events, + res.diagnostic_events + ); + assert_eq!( + res_with_adjustments.simulated_instructions, + res.simulated_instructions + ); + assert_eq!(res_with_adjustments.simulated_memory, res.simulated_memory); + assert_eq!( + res_with_adjustments.transaction_data, + Some(SorobanTransactionData { + ext: SorobanTransactionDataExt::V0, + resources: SorobanResources { + footprint: LedgerFootprint { + read_only: Default::default(), + read_write: vec![get_wasm_key(ADD_I32)].try_into().unwrap() + }, + instructions: (expected_instructions as f64 * 1.1) as u32, + read_bytes: 0, + write_bytes: expected_write_bytes + 300, + }, + resource_fee: 135867, + }) + ); +} +#[test] +fn test_simulate_upload_wasm_with_new_entry_proof() { + let source_account = get_account_id([123; 32]); + let ledger_info = default_ledger_info(); + let network_config = default_network_config(); + let snapshot_source = Rc::new( + MockSnapshotSource::from_entries_with_archival_state(vec![( + Some(get_wasm_key(ADD_I32)), + None, + None, + LedgerEntryArchivalState::New(true), + )]) + .unwrap(), + ); + let snapshot_source = + Rc::new(AutoRestoringSnapshotSource::new(snapshot_source, &ledger_info).unwrap()); let res = simulate_invoke_host_function_op( snapshot_source.clone(), &network_config, @@ -118,6 +235,7 @@ fn test_simulate_upload_wasm() { &source_account, [1; 32], true, + || snapshot_source.get_new_keys_proof(), ) .unwrap(); assert_eq!( @@ -131,10 +249,25 @@ fn test_simulate_upload_wasm() { let expected_instructions = 1644789; let expected_write_bytes = 684; + + let proof_ext = SorobanTransactionDataExt::V1( + vec![ArchivalProof { + epoch: 12345, + body: ArchivalProofBody::Nonexistence(NonexistenceProofBody { + keys_to_prove: vec![get_wasm_key(ADD_I32)].try_into().unwrap(), + low_bound_entries: Default::default(), + high_bound_entries: Default::default(), + proof_levels: Default::default(), + }), + }] + .try_into() + .unwrap(), + ); + assert_eq!( res.transaction_data, Some(SorobanTransactionData { - ext: ExtensionPoint::V0, + ext: proof_ext.clone(), resources: SorobanResources { footprint: LedgerFootprint { read_only: Default::default(), @@ -158,7 +291,7 @@ fn test_simulate_upload_wasm() { ); let res_with_adjustments = simulate_invoke_host_function_op( - snapshot_source, + snapshot_source.clone(), &network_config, &test_adjustment_config(), &ledger_info, @@ -167,6 +300,7 @@ fn test_simulate_upload_wasm() { &source_account, [1; 32], true, + || snapshot_source.get_new_keys_proof(), ) .unwrap(); assert_eq!( @@ -188,7 +322,7 @@ fn test_simulate_upload_wasm() { assert_eq!( res_with_adjustments.transaction_data, Some(SorobanTransactionData { - ext: ExtensionPoint::V0, + ext: proof_ext, resources: SorobanResources { footprint: LedgerFootprint { read_only: Default::default(), @@ -211,7 +345,8 @@ fn test_simulation_returns_insufficient_budget_error() { network_config.tx_max_instructions = 100_000; let snapshot_source = Rc::new(MockSnapshotSource::from_entries(vec![], ledger_info.sequence_number).unwrap()); - + let snapshot_source = + Rc::new(AutoRestoringSnapshotSource::new(snapshot_source, &ledger_info).unwrap()); let res = simulate_invoke_host_function_op( snapshot_source.clone(), &network_config, @@ -222,6 +357,7 @@ fn test_simulation_returns_insufficient_budget_error() { &source_account, [1; 32], true, + || snapshot_source.get_new_keys_proof(), ) .unwrap(); assert!(HostError::result_matches_err( @@ -245,6 +381,8 @@ fn test_simulation_returns_logic_error() { let network_config = default_network_config(); let snapshot_source = Rc::new(MockSnapshotSource::from_entries(vec![], ledger_info.sequence_number).unwrap()); + let snapshot_source = + Rc::new(AutoRestoringSnapshotSource::new(snapshot_source, &ledger_info).unwrap()); let bad_wasm = [0; 1000]; let res = simulate_invoke_host_function_op( @@ -257,6 +395,7 @@ fn test_simulation_returns_logic_error() { &source_account, [1; 32], true, + || snapshot_source.get_new_keys_proof(), ) .unwrap(); assert!(HostError::result_matches_err( @@ -290,6 +429,8 @@ fn test_simulate_create_contract() { ) .unwrap(), ); + let snapshot_source = + Rc::new(AutoRestoringSnapshotSource::new(snapshot_source, &ledger_info).unwrap()); let res = simulate_invoke_host_function_op( snapshot_source.clone(), @@ -301,6 +442,7 @@ fn test_simulate_create_contract() { &source_account, [1; 32], true, + || snapshot_source.get_new_keys_proof(), ) .unwrap(); assert_eq!( @@ -321,7 +463,7 @@ fn test_simulate_create_contract() { assert_eq!( res.transaction_data, Some(SorobanTransactionData { - ext: ExtensionPoint::V0, + ext: SorobanTransactionDataExt::V0, resources: SorobanResources { footprint: LedgerFootprint { read_only: vec![contract.wasm_key.clone()].try_into().unwrap(), @@ -423,9 +565,11 @@ fn test_simulate_invoke_contract_with_auth() { ) .unwrap(), ); + let snapshot_source = + Rc::new(AutoRestoringSnapshotSource::new(snapshot_source, &ledger_info).unwrap()); let res = simulate_invoke_host_function_op( - snapshot_source, + snapshot_source.clone(), &network_config, &SimulationAdjustmentConfig::no_adjustments(), &ledger_info, @@ -434,6 +578,7 @@ fn test_simulate_invoke_contract_with_auth() { &source_account, [1; 32], true, + || snapshot_source.get_new_keys_proof(), ) .unwrap(); assert_eq!(res.invoke_result.unwrap(), ScVal::Void); @@ -467,7 +612,7 @@ fn test_simulate_invoke_contract_with_auth() { assert_eq!( res.transaction_data, Some(SorobanTransactionData { - ext: ExtensionPoint::V0, + ext: SorobanTransactionDataExt::V0, resources: SorobanResources { footprint: LedgerFootprint { read_only: vec![ @@ -555,7 +700,7 @@ fn test_simulate_extend_ttl_op() { no_op_extension, ExtendTtlOpSimulationResult { transaction_data: SorobanTransactionData { - ext: ExtensionPoint::V0, + ext: SorobanTransactionDataExt::V0, resources: SorobanResources { footprint: LedgerFootprint { read_only: Default::default(), @@ -584,7 +729,7 @@ fn test_simulate_extend_ttl_op() { extension_for_some_entries, ExtendTtlOpSimulationResult { transaction_data: SorobanTransactionData { - ext: ExtensionPoint::V0, + ext: SorobanTransactionDataExt::V0, resources: SorobanResources { footprint: LedgerFootprint { read_only: vec![ @@ -621,7 +766,7 @@ fn test_simulate_extend_ttl_op() { extension_for_all_entries, ExtendTtlOpSimulationResult { transaction_data: SorobanTransactionData { - ext: ExtensionPoint::V0, + ext: SorobanTransactionDataExt::V0, resources: SorobanResources { footprint: LedgerFootprint { read_only: keys.clone().tap_mut(|v| v.sort()).try_into().unwrap(), @@ -666,7 +811,7 @@ fn test_simulate_extend_ttl_op() { extension_for_all_entries_with_adjustment, ExtendTtlOpSimulationResult { transaction_data: SorobanTransactionData { - ext: ExtensionPoint::V0, + ext: SorobanTransactionDataExt::V0, resources: SorobanResources { footprint: extension_for_all_entries .transaction_data @@ -703,14 +848,13 @@ fn test_simulate_restore_op() { ), ]; let keys: Vec = entries + .clone() .iter() .map(|e| ledger_entry_to_ledger_key(&e.0).unwrap()) .collect(); - let snapshot_source = - MockSnapshotSource::from_entries(entries, ledger_info.sequence_number).unwrap(); let no_op_restoration = simulate_restore_op( - &snapshot_source, + &MockSnapshotSource::from_entries(entries.clone(), ledger_info.sequence_number).unwrap(), &network_config, &SimulationAdjustmentConfig::no_adjustments(), &ledger_info, @@ -722,7 +866,7 @@ fn test_simulate_restore_op() { no_op_restoration, RestoreOpSimulationResult { transaction_data: SorobanTransactionData { - ext: ExtensionPoint::V0, + ext: SorobanTransactionDataExt::V0, resources: SorobanResources { footprint: LedgerFootprint { read_only: Default::default(), @@ -740,7 +884,7 @@ fn test_simulate_restore_op() { let init_seq_num = ledger_info.sequence_number; ledger_info.sequence_number = init_seq_num + 100_001; let restoration_for_some_entries = simulate_restore_op( - &snapshot_source, + &MockSnapshotSource::from_entries(entries.clone(), ledger_info.sequence_number).unwrap(), &network_config, &SimulationAdjustmentConfig::no_adjustments(), &ledger_info, @@ -752,7 +896,7 @@ fn test_simulate_restore_op() { restoration_for_some_entries, RestoreOpSimulationResult { transaction_data: SorobanTransactionData { - ext: ExtensionPoint::V0, + ext: SorobanTransactionDataExt::V0, resources: SorobanResources { footprint: LedgerFootprint { read_only: Default::default(), @@ -772,7 +916,7 @@ fn test_simulate_restore_op() { ledger_info.sequence_number = init_seq_num + 1_000_001; let extension_for_all_entries = simulate_restore_op( - &snapshot_source, + &MockSnapshotSource::from_entries(entries.clone(), ledger_info.sequence_number).unwrap(), &network_config, &SimulationAdjustmentConfig::no_adjustments(), &ledger_info, @@ -784,7 +928,7 @@ fn test_simulate_restore_op() { extension_for_all_entries, RestoreOpSimulationResult { transaction_data: SorobanTransactionData { - ext: ExtensionPoint::V0, + ext: SorobanTransactionDataExt::V0, resources: SorobanResources { footprint: LedgerFootprint { read_only: Default::default(), @@ -800,7 +944,7 @@ fn test_simulate_restore_op() { ); let extension_for_all_entries_with_adjustment = simulate_restore_op( - &snapshot_source, + &MockSnapshotSource::from_entries(entries.clone(), ledger_info.sequence_number).unwrap(), &network_config, &test_adjustment_config(), &ledger_info, @@ -812,7 +956,7 @@ fn test_simulate_restore_op() { extension_for_all_entries_with_adjustment, RestoreOpSimulationResult { transaction_data: SorobanTransactionData { - ext: ExtensionPoint::V0, + ext: SorobanTransactionDataExt::V0, resources: SorobanResources { footprint: LedgerFootprint { read_only: Default::default(), @@ -850,12 +994,17 @@ fn test_simulate_restore_op_returns_error_for_temp_entries() { } #[test] -fn test_simulate_restore_op_returns_error_for_non_existent_entry() { +fn test_simulate_restore_op_returns_error_for_non_existent_archived_entry() { let ledger_info = default_ledger_info(); let network_config = default_network_config(); - let snapshot_source = - MockSnapshotSource::from_entries(vec![], ledger_info.sequence_number).unwrap(); + let snapshot_source = MockSnapshotSource::from_entries_with_archival_state(vec![( + Some(get_wasm_key(b"123")), + None, + None, + LedgerEntryArchivalState::Archived(false), + )]) + .unwrap(); let res = simulate_restore_op( &snapshot_source, @@ -997,8 +1146,10 @@ fn test_simulate_successful_sac_call() { ) .unwrap(), ); + let snapshot_source = + Rc::new(AutoRestoringSnapshotSource::new(snapshot_source, &ledger_info).unwrap()); let res = simulate_invoke_host_function_op( - snapshot_source, + snapshot_source.clone(), &network_config, &SimulationAdjustmentConfig::no_adjustments(), &ledger_info, @@ -1007,6 +1158,7 @@ fn test_simulate_successful_sac_call() { &source_account, [1; 32], true, + || snapshot_source.get_new_keys_proof(), ) .unwrap(); assert_eq!(res.invoke_result.unwrap(), ScVal::Void); @@ -1028,7 +1180,7 @@ fn test_simulate_successful_sac_call() { assert_eq!( res.transaction_data, Some(SorobanTransactionData { - ext: ExtensionPoint::V0, + ext: SorobanTransactionDataExt::V0, resources: SorobanResources { footprint: LedgerFootprint { read_only: vec![ledger_entry_to_ledger_key(&contract_instance_le).unwrap(),] @@ -1096,9 +1248,11 @@ fn test_simulate_unsuccessful_sac_call_with_try_call() { ) .unwrap(), ); + let snapshot_source = + Rc::new(AutoRestoringSnapshotSource::new(snapshot_source, &ledger_info).unwrap()); let res = simulate_invoke_host_function_op( - snapshot_source, + snapshot_source.clone(), &network_config, &SimulationAdjustmentConfig::no_adjustments(), &ledger_info, @@ -1107,6 +1261,7 @@ fn test_simulate_unsuccessful_sac_call_with_try_call() { &source_account, [1; 32], true, + || snapshot_source.get_new_keys_proof(), ) .unwrap(); // The return value indicates the whether the internal `mint` call has @@ -1124,7 +1279,7 @@ fn test_simulate_unsuccessful_sac_call_with_try_call() { assert_eq!( res.transaction_data, Some(SorobanTransactionData { - ext: ExtensionPoint::V0, + ext: SorobanTransactionDataExt::V0, resources: SorobanResources { footprint: LedgerFootprint { read_only: vec![ diff --git a/soroban-simulation/src/test/snapshot_source.rs b/soroban-simulation/src/test/snapshot_source.rs index 4279786b3..9dc2be1fe 100644 --- a/soroban-simulation/src/test/snapshot_source.rs +++ b/soroban-simulation/src/test/snapshot_source.rs @@ -1,41 +1,114 @@ use crate::network_config::NetworkConfig; use crate::simulation::{RestoreOpSimulationResult, SimulationAdjustmentConfig}; -use crate::snapshot_source::{AutoRestoringSnapshotSource, SimulationSnapshotSource}; -use crate::testutils::{ledger_entry_to_ledger_key, temp_entry, MockSnapshotSource}; +use crate::snapshot_source::{ + AutoRestoringSnapshotSource, LedgerEntryArchivalState, SimulationSnapshotSource, +}; +use crate::testutils::{ + ledger_entry_to_ledger_key, temp_entry, temp_entry_key, MockSnapshotSource, +}; use pretty_assertions::assert_eq; use soroban_env_host::e2e_testutils::{ - account_entry, get_account_id, ledger_entry, wasm_entry_non_validated, + account_entry, get_account_id, get_wasm_key, ledger_entry, wasm_entry_non_validated, }; use soroban_env_host::fees::{FeeConfiguration, RentFeeConfiguration}; use soroban_env_host::storage::SnapshotSource; use soroban_env_host::xdr::{ AccountEntry, AccountEntryExt, AccountEntryExtensionV1, AccountEntryExtensionV1Ext, - AccountEntryExtensionV2, AccountEntryExtensionV2Ext, AccountEntryExtensionV3, ExtensionPoint, - LedgerEntryData, LedgerFootprint, Liabilities, SequenceNumber, Signer, SignerKey, - SorobanResources, SorobanTransactionData, SponsorshipDescriptor, Thresholds, TimePoint, - Uint256, + AccountEntryExtensionV2, AccountEntryExtensionV2Ext, AccountEntryExtensionV3, ArchivalProof, + ArchivalProofBody, ColdArchiveArchivedLeaf, ColdArchiveBucketEntry, ExistenceProofBody, + ExtensionPoint, LedgerEntryData, LedgerFootprint, Liabilities, NonexistenceProofBody, + SequenceNumber, Signer, SignerKey, SorobanResources, SorobanTransactionData, + SorobanTransactionDataExt, SponsorshipDescriptor, Thresholds, TimePoint, Uint256, }; use soroban_env_host::LedgerInfo; use std::rc::Rc; #[test] -fn test_automatic_restoration() { +fn test_automatic_restoration_with_archival_state() { let ledger_seq = 300; let snapshot = Rc::new( - MockSnapshotSource::from_entries( - vec![ - (wasm_entry_non_validated(b"1"), Some(100)), // persistent, expired - (wasm_entry_non_validated(b"2"), Some(299)), // persistent, expired - (wasm_entry_non_validated(b"3"), Some(300)), // persistent, live - (wasm_entry_non_validated(b"4"), Some(400)), // persistent, live - (temp_entry(b"5"), Some(299)), // temp, removed - (temp_entry(b"6"), Some(300)), // temp, live - (temp_entry(b"7"), Some(400)), // temp, live - ], - ledger_seq, - ) + MockSnapshotSource::from_entries_with_archival_state(vec![ + // persistent, archived, no proof + ( + None, + Some(wasm_entry_non_validated(b"persistent_archived_no_proof")), + None, + LedgerEntryArchivalState::Archived(false), + ), + // persistent, archived, need proof + ( + None, + Some(wasm_entry_non_validated(b"persistent_archived_need_proof")), + None, + LedgerEntryArchivalState::Archived(true), + ), + // persistent, live + ( + None, + Some(wasm_entry_non_validated(b"persistent_live_1")), + Some(300), + LedgerEntryArchivalState::Live, + ), + // persistent, live + ( + None, + Some(wasm_entry_non_validated(b"persistent_live_2")), + Some(400), + LedgerEntryArchivalState::Live, + ), + // persistent, archived, no proof, no entry + ( + Some(get_wasm_key(b"persistent_archived_no_proof_no_entry")), + None, + None, + LedgerEntryArchivalState::Archived(false), + ), + // persistent, archived, need proof, no entry + ( + Some(get_wasm_key(b"persistent_archived_need_proof_no_entry")), + None, + None, + LedgerEntryArchivalState::Archived(true), + ), + // persistent, new, no proof + ( + Some(get_wasm_key(b"persistent_new_no_proof")), + None, + None, + LedgerEntryArchivalState::New(false), + ), + // persistent, new, need proof + ( + Some(get_wasm_key(b"persistent_new_need_proof")), + None, + None, + LedgerEntryArchivalState::New(true), + ), + // temp, new + ( + Some(temp_entry_key(b"temp_new")), + None, + None, + LedgerEntryArchivalState::Live, + ), + // temp, live + ( + None, + Some(temp_entry(b"temp_live_1")), + Some(300), + LedgerEntryArchivalState::Live, + ), + // temp, live + ( + None, + Some(temp_entry(b"temp_live_2")), + Some(400), + LedgerEntryArchivalState::New(false), + ), + ]) .unwrap(), ); + let ledger_info = LedgerInfo { sequence_number: ledger_seq, min_persistent_entry_ttl: 1000, @@ -71,79 +144,82 @@ fn test_automatic_restoration() { .unwrap(), None ); + assert_eq!(auto_restoring_snapshot.get_new_keys_proof().unwrap(), None); let restored_entry_expiration = ledger_seq + 1000 - 1; assert_eq!( auto_restoring_snapshot - .get(&Rc::new( - ledger_entry_to_ledger_key(&wasm_entry_non_validated(b"1111")).unwrap() - )) + .get(&Rc::new(get_wasm_key(b"1111"))) .unwrap(), None ); assert_eq!( auto_restoring_snapshot - .get(&Rc::new( - ledger_entry_to_ledger_key(&wasm_entry_non_validated(b"1")).unwrap() - )) + .get(&Rc::new(get_wasm_key(b"persistent_archived_no_proof"))) .unwrap(), Some(( - Rc::new(wasm_entry_non_validated(b"1")), + Rc::new(wasm_entry_non_validated(b"persistent_archived_no_proof")), Some(restored_entry_expiration) )) ); assert_eq!( auto_restoring_snapshot - .get(&Rc::new( - ledger_entry_to_ledger_key(&wasm_entry_non_validated(b"2")).unwrap() - )) + .get(&Rc::new(get_wasm_key(b"persistent_archived_need_proof"))) .unwrap(), Some(( - Rc::new(wasm_entry_non_validated(b"2")), + Rc::new(wasm_entry_non_validated(b"persistent_archived_need_proof")), Some(restored_entry_expiration) )) ); assert_eq!( auto_restoring_snapshot - .get(&Rc::new( - ledger_entry_to_ledger_key(&wasm_entry_non_validated(b"3")).unwrap() - )) + .get(&Rc::new(get_wasm_key(b"persistent_live_1"))) + .unwrap(), + Some(( + Rc::new(wasm_entry_non_validated(b"persistent_live_1")), + Some(300) + )) + ); + assert_eq!( + auto_restoring_snapshot + .get(&Rc::new(get_wasm_key(b"persistent_live_2"))) .unwrap(), - Some((Rc::new(wasm_entry_non_validated(b"3")), Some(300))) + Some(( + Rc::new(wasm_entry_non_validated(b"persistent_live_2")), + Some(400) + )) ); assert_eq!( auto_restoring_snapshot - .get(&Rc::new( - ledger_entry_to_ledger_key(&wasm_entry_non_validated(b"4")).unwrap() - )) + .get(&Rc::new(get_wasm_key(b"persistent_new_no_proof"))) .unwrap(), - Some((Rc::new(wasm_entry_non_validated(b"4")), Some(400))) + None + ); + assert_eq!( + auto_restoring_snapshot + .get(&Rc::new(get_wasm_key(b"persistent_new_need_proof"))) + .unwrap(), + None ); assert_eq!( auto_restoring_snapshot - .get(&Rc::new( - ledger_entry_to_ledger_key(&temp_entry(b"5")).unwrap() - )) + .get(&Rc::new(temp_entry_key(b"temp_new"))) .unwrap(), None ); assert_eq!( auto_restoring_snapshot - .get(&Rc::new( - ledger_entry_to_ledger_key(&temp_entry(b"6")).unwrap() - )) + .get(&Rc::new(temp_entry_key(b"temp_live_1"))) .unwrap(), - Some((Rc::new(temp_entry(b"6")), Some(300))) + Some((Rc::new(temp_entry(b"temp_live_1")), Some(300))) ); assert_eq!( auto_restoring_snapshot - .get(&Rc::new( - ledger_entry_to_ledger_key(&temp_entry(b"7")).unwrap() - )) + .get(&Rc::new(temp_entry_key(b"temp_live_2"))) .unwrap(), - Some((Rc::new(temp_entry(b"7")), Some(400))) + Some((Rc::new(temp_entry(b"temp_live_2")), Some(400))) ); assert_eq!( @@ -156,27 +232,85 @@ fn test_automatic_restoration() { .unwrap(), Some(RestoreOpSimulationResult { transaction_data: SorobanTransactionData { - ext: ExtensionPoint::V0, + ext: SorobanTransactionDataExt::V1( + vec![ArchivalProof { + epoch: 12345, + body: ArchivalProofBody::Existence(ExistenceProofBody { + entries_to_prove: vec![ColdArchiveBucketEntry::ArchivedLeaf( + ColdArchiveArchivedLeaf { + index: 111, + archived_entry: wasm_entry_non_validated( + b"persistent_archived_need_proof" + ), + } + )] + .try_into() + .unwrap(), + proof_levels: Default::default(), + }), + }] + .try_into() + .unwrap() + ), resources: SorobanResources { footprint: LedgerFootprint { read_only: Default::default(), read_write: vec![ - ledger_entry_to_ledger_key(&wasm_entry_non_validated(b"1")).unwrap(), - ledger_entry_to_ledger_key(&wasm_entry_non_validated(b"2")).unwrap(), + get_wasm_key(b"persistent_archived_no_proof"), + get_wasm_key(b"persistent_archived_need_proof"), ] .try_into() .unwrap() }, instructions: 0, - read_bytes: 112, - write_bytes: 112, + read_bytes: 164, + write_bytes: 164, }, - resource_fee: 62192, + resource_fee: 87587, } }) ); - auto_restoring_snapshot.reset_restored_keys(); + assert_eq!( + auto_restoring_snapshot.get_new_keys_proof().unwrap(), + Some(ArchivalProof { + epoch: 12345, + body: ArchivalProofBody::Nonexistence(NonexistenceProofBody { + keys_to_prove: vec![get_wasm_key(b"persistent_new_need_proof")] + .try_into() + .unwrap(), + low_bound_entries: Default::default(), + high_bound_entries: Default::default(), + proof_levels: Default::default(), + }), + }) + ); + + auto_restoring_snapshot.reset_tracked_keys(); + assert_eq!( + auto_restoring_snapshot + .simulate_restore_keys_op( + &network_config, + &SimulationAdjustmentConfig::no_adjustments(), + &ledger_info + ) + .unwrap(), + None + ); + assert_eq!(auto_restoring_snapshot.get_new_keys_proof().unwrap(), None); + + assert!(auto_restoring_snapshot + .get(&Rc::new(get_wasm_key( + b"persistent_archived_no_proof_no_entry" + ))) + .is_err()); + assert!(auto_restoring_snapshot + .get(&Rc::new(get_wasm_key( + b"persistent_archived_need_proof_no_entry" + ))) + .is_err()); + // We don't try to restore the non-existent entires (this shouldn't + // normally be called at all though in such case). assert_eq!( auto_restoring_snapshot .simulate_restore_keys_op( diff --git a/soroban-simulation/src/testutils.rs b/soroban-simulation/src/testutils.rs index d189ec754..283e1d16e 100644 --- a/soroban-simulation/src/testutils.rs +++ b/soroban-simulation/src/testutils.rs @@ -1,13 +1,17 @@ -use crate::snapshot_source::SnapshotSourceWithArchive; +use crate::snapshot_source::{ + LedgerEntryArchivalState, LedgerEntryWithArchivalState, SnapshotSourceWithArchive, +}; use anyhow::{bail, Result}; use soroban_env_host::{ e2e_testutils::ledger_entry, + ledger_info::get_key_durability, storage::{EntryWithLiveUntil, SnapshotSource}, xdr::{ - ContractDataDurability, ContractDataEntry, ExtensionPoint, Hash, LedgerEntry, - LedgerEntryData, LedgerKey, LedgerKeyAccount, LedgerKeyConfigSetting, - LedgerKeyContractCode, LedgerKeyContractData, LedgerKeyTrustLine, ScAddress, ScBytes, - ScErrorCode, ScErrorType, ScVal, + ArchivalProof, ArchivalProofBody, ColdArchiveArchivedLeaf, ColdArchiveBucketEntry, + ContractDataDurability, ContractDataEntry, ExistenceProofBody, ExtensionPoint, Hash, + LedgerEntry, LedgerEntryData, LedgerKey, LedgerKeyAccount, LedgerKeyConfigSetting, + LedgerKeyContractCode, LedgerKeyContractData, LedgerKeyTrustLine, NonexistenceProofBody, + ScAddress, ScBytes, ScErrorCode, ScErrorType, ScVal, }, HostError, }; @@ -15,24 +19,86 @@ use std::collections::BTreeMap; use std::rc::Rc; pub struct MockSnapshotSource { - map: BTreeMap, EntryWithLiveUntil>, - current_ledger_seq: u32, + map: BTreeMap, LedgerEntryWithArchivalState>, } +pub type EntriesWithArchivalState = Vec<( + Option, + Option, + Option, + LedgerEntryArchivalState, +)>; + impl MockSnapshotSource { pub fn from_entries( entries: Vec<(LedgerEntry, Option)>, current_ledger_seq: u32, ) -> Result { - let mut map = BTreeMap::, (Rc, Option)>::new(); - for (e, maybe_ttl) in entries { + let mut map = BTreeMap::, LedgerEntryWithArchivalState>::new(); + for (e, live_until_ledger) in entries { let key = Rc::new(ledger_entry_to_ledger_key(&e)?); - map.insert(key, (Rc::new(e), maybe_ttl)); + let state = if let Some(live_until_ledger) = &live_until_ledger { + if *live_until_ledger < current_ledger_seq { + if matches!( + get_key_durability(&key), + Some(ContractDataDurability::Persistent) + ) { + LedgerEntryArchivalState::Archived(false) + } else { + LedgerEntryArchivalState::New(false) + } + } else { + LedgerEntryArchivalState::Live + } + } else { + LedgerEntryArchivalState::Live + }; + let adjusted_live_until_ledger = if matches!(state, LedgerEntryArchivalState::Live) { + live_until_ledger + } else { + None + }; + let adjusted_entry = if matches!(state, LedgerEntryArchivalState::New(_)) { + None + } else { + Some(Rc::new(e)) + }; + map.insert( + key, + LedgerEntryWithArchivalState { + entry: adjusted_entry, + live_until_ledger: adjusted_live_until_ledger, + state, + }, + ); } - Ok(Self { - map, - current_ledger_seq, - }) + Ok(Self { map }) + } + + pub fn from_entries_with_archival_state(entries: EntriesWithArchivalState) -> Result { + let mut map = BTreeMap::, LedgerEntryWithArchivalState>::new(); + for (maybe_key, maybe_entry, live_until_ledger, state) in entries { + let key = if let Some(k) = maybe_key { + k + } else { + ledger_entry_to_ledger_key(maybe_entry.as_ref().unwrap())? + }; + let key = Rc::new(key); + let entry = if let Some(e) = maybe_entry { + Some(Rc::new(e)) + } else { + None + }; + map.insert( + key, + LedgerEntryWithArchivalState { + entry, + live_until_ledger, + state, + }, + ); + } + Ok(Self { map }) } } @@ -66,13 +132,60 @@ impl SnapshotSourceWithArchive for MockSnapshotSource { fn get_including_archived( &self, key: &Rc, - ) -> std::result::Result, HostError> { - if let Some((entry, live_until)) = self.map.get(key) { - Ok(Some((entry.clone(), *live_until))) + ) -> std::result::Result { + if let Some(state) = self.map.get(key) { + Ok(state.clone()) } else { - Ok(None) + Ok(LedgerEntryWithArchivalState { + entry: None, + live_until_ledger: None, + state: LedgerEntryArchivalState::New(false), + }) } } + + fn generate_new_entries_proof(&self, keys: &[Rc]) -> Result { + Ok(ArchivalProof { + epoch: 12345, + body: ArchivalProofBody::Nonexistence(NonexistenceProofBody { + keys_to_prove: keys + .iter() + .map(|k| (**k).clone()) + .collect::>() + .try_into() + .unwrap(), + low_bound_entries: Default::default(), + high_bound_entries: Default::default(), + proof_levels: Default::default(), + }), + }) + } + + fn generate_restoration_proof(&self, keys: &[Rc]) -> Result { + let entries_to_prove = keys + .iter() + .map(|k| { + ColdArchiveBucketEntry::ArchivedLeaf(ColdArchiveArchivedLeaf { + index: 111, + archived_entry: self + .map + .get(k) + .unwrap() + .entry + .as_ref() + .map(|e| (**e).clone()) + .unwrap(), + }) + }) + .collect::>(); + Ok(ArchivalProof { + epoch: 12345, + body: ArchivalProofBody::Existence(ExistenceProofBody { + entries_to_prove: entries_to_prove.try_into().unwrap(), + proof_levels: Default::default(), + }), + }) + } } impl SnapshotSource for MockSnapshotSource { @@ -80,14 +193,19 @@ impl SnapshotSource for MockSnapshotSource { &self, key: &Rc, ) -> std::result::Result, HostError> { - if let Some((entry, live_until)) = self.map.get(key) { - if let Some(live_until) = live_until { - if *live_until < self.current_ledger_seq { - return Err((ScErrorType::Storage, ScErrorCode::InternalError).into()); + if let Some(entry_state) = self.map.get(key) { + if let Some(entry) = &entry_state.entry { + Ok(Some((entry.clone(), entry_state.live_until_ledger))) + } else { + if matches!(entry_state.state, LedgerEntryArchivalState::Archived(_)) { + Err(HostError::from(( + ScErrorType::Storage, + ScErrorCode::InternalError, + ))) + } else { + Ok(None) } } - - Ok(Some((entry.clone(), *live_until))) } else { Ok(None) } @@ -103,3 +221,8 @@ pub fn temp_entry(key: &[u8]) -> LedgerEntry { val: ScVal::Void, })) } + +pub fn temp_entry_key(key: &[u8]) -> LedgerKey { + let entry = temp_entry(key); + ledger_entry_to_ledger_key(&entry).unwrap() +} diff --git a/soroban-test-wasms/wasm-workspace/Cargo.lock b/soroban-test-wasms/wasm-workspace/Cargo.lock index 19327437c..22863e295 100644 --- a/soroban-test-wasms/wasm-workspace/Cargo.lock +++ b/soroban-test-wasms/wasm-workspace/Cargo.lock @@ -288,7 +288,6 @@ dependencies = [ "elliptic-curve", "rfc6979", "signature", - "spki", ] [[package]] @@ -333,7 +332,6 @@ dependencies = [ "ff", "generic-array", "group", - "pkcs8", "rand_core", "sec1", "subtle", @@ -667,9 +665,7 @@ dependencies = [ "cfg-if", "ecdsa", "elliptic-curve", - "once_cell", "sha2", - "signature", ] [[package]] @@ -753,6 +749,18 @@ version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" +[[package]] +name = "p256" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9863ad85fa8f4460f9c48cb909d38a0d689dba1f6f6988a5e3e0d31071bcd4b" +dependencies = [ + "ecdsa", + "elliptic-curve", + "primeorder", + "sha2", +] + [[package]] name = "paste" version = "1.0.14" @@ -797,6 +805,15 @@ dependencies = [ "syn", ] +[[package]] +name = "primeorder" +version = "0.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7dbe9ed3b56368bd99483eb32fe9c17fdd3730aebadc906918ce78d54c7eeb4" +dependencies = [ + "elliptic-curve", +] + [[package]] name = "proc-macro2" version = "1.0.69" @@ -893,7 +910,6 @@ dependencies = [ "base16ct", "der", "generic-array", - "pkcs8", "subtle", "zeroize", ] @@ -1003,9 +1019,9 @@ checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" [[package]] name = "soroban-builtin-sdk-macros" -version = "20.3.0" +version = "21.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cc32c6e817f3ca269764ec0d7d14da6210b74a5bf14d4e745aa3ee860558900" +checksum = "44877373b3dc6c662377cb1600e3a62706d75e484b6064f9cd22e467c676b159" dependencies = [ "itertools", "proc-macro2", @@ -1015,9 +1031,9 @@ dependencies = [ [[package]] name = "soroban-env-common" -version = "20.3.0" +version = "21.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c14e18d879c520ff82612eaae0590acaf6a7f3b977407e1abb1c9e31f94c7814" +checksum = "590add16843a61b01844e19e89bccaaee6aa21dc76809017b0662c17dc139ee9" dependencies = [ "crate-git-revision", "ethnum", @@ -1028,13 +1044,14 @@ dependencies = [ "soroban-wasmi", "static_assertions", "stellar-xdr", + "wasmparser", ] [[package]] name = "soroban-env-guest" -version = "20.3.0" +version = "21.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5122ca2abd5ebcc1e876a96b9b44f87ce0a0e06df8f7c09772ddb58b159b7454" +checksum = "05ec8dc43acdd6c7e7b371acf44fc1a7dac24934ae3b2f05fafd618818548176" dependencies = [ "soroban-env-common", "static_assertions", @@ -1042,12 +1059,15 @@ dependencies = [ [[package]] name = "soroban-env-host" -version = "20.3.0" +version = "21.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "114a0fa0d0cc39d0be16b1ee35b6e5f4ee0592ddcf459bde69391c02b03cf520" +checksum = "4e25aaffe0c62eb65e0e349f725b4b8b13ad0764d78a15aab5bbccb5c4797726" dependencies = [ "curve25519-dalek", + "ecdsa", "ed25519-dalek", + "elliptic-curve", + "generic-array", "getrandom", "hex-literal", "hmac", @@ -1055,8 +1075,10 @@ dependencies = [ "num-derive", "num-integer", "num-traits", + "p256", "rand", "rand_chacha", + "sec1", "sha2", "sha3", "soroban-builtin-sdk-macros", @@ -1064,13 +1086,14 @@ dependencies = [ "soroban-wasmi", "static_assertions", "stellar-strkey", + "wasmparser", ] [[package]] name = "soroban-env-macros" -version = "20.3.0" +version = "21.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b13e3f8c86f812e0669e78fcb3eae40c385c6a9dd1a4886a1de733230b4fcf27" +checksum = "3e16b761459fdf3c4b62b24df3941498d14e5246e6fadfb4774ed8114d243aa4" dependencies = [ "itertools", "proc-macro2", @@ -1083,9 +1106,9 @@ dependencies = [ [[package]] name = "soroban-ledger-snapshot" -version = "20.5.0" +version = "21.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61a54708f44890e0546180db6b4f530e2a88d83b05a9b38a131caa21d005e25a" +checksum = "a46f8b134b1e0b517f70d24dd72399cd263fd18c3c7cfcb5e56ffc6de9dad0c3" dependencies = [ "serde", "serde_json", @@ -1097,9 +1120,9 @@ dependencies = [ [[package]] name = "soroban-sdk" -version = "20.5.0" +version = "21.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84fc8be9068dd4e0212d8b13ad61089ea87e69ac212c262914503a961c8dc3a3" +checksum = "60cd55eb88cbe1d9e7fe3ab1845c7c10c26b27e2d226e973150e5b55580aa359" dependencies = [ "bytes-lit", "rand", @@ -1114,9 +1137,9 @@ dependencies = [ [[package]] name = "soroban-sdk-macros" -version = "20.5.0" +version = "21.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db20def4ead836663633f58d817d0ed8e1af052c9650a04adf730525af85b964" +checksum = "6f24c6b0c46e41852a8603bb32d43cf6a4046ce65483f6383903ec653118cd90" dependencies = [ "crate-git-revision", "darling", @@ -1134,9 +1157,9 @@ dependencies = [ [[package]] name = "soroban-spec" -version = "20.5.0" +version = "21.7.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3eefeb5d373b43f6828145d00f0c5cc35e96db56a6671ae9614f84beb2711cab" +checksum = "c2c70b20e68cae3ef700b8fa3ae29db1c6a294b311fba66918f90cb8f9fd0a1a" dependencies = [ "base64 0.13.1", "stellar-xdr", @@ -1146,9 +1169,9 @@ dependencies = [ [[package]] name = "soroban-spec-rust" -version = "20.5.0" +version = "21.7.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3152bca4737ef734ac37fe47b225ee58765c9095970c481a18516a2b287c7a33" +checksum = "a2dafbde981b141b191c6c036abc86097070ddd6eaaa33b273701449501e43d3" dependencies = [ "prettyplease", "proc-macro2", @@ -1215,9 +1238,9 @@ dependencies = [ [[package]] name = "stellar-xdr" -version = "20.1.0" +version = "21.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e59cdf3eb4467fb5a4b00b52e7de6dca72f67fac6f9b700f55c95a5d86f09c9d" +checksum = "2675a71212ed39a806e415b0dbf4702879ff288ec7f5ee996dda42a135512b50" dependencies = [ "base64 0.13.1", "crate-git-revision", @@ -1468,11 +1491,12 @@ dependencies = [ [[package]] name = "wasmparser" -version = "0.88.0" +version = "0.116.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb8cf7dd82407fe68161bedcd57fde15596f32ebf6e9b3bdbf3ae1da20e38e5e" +checksum = "a58e28b80dd8340cb07b8242ae654756161f6fc8d0038123d679b7b99964fa50" dependencies = [ - "indexmap 1.9.3", + "indexmap 2.1.0", + "semver", ] [[package]] @@ -1555,3 +1579,23 @@ name = "zeroize" version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2a0956f1ba7c7909bfb66c2e9e4124ab6f6482560f6628b5aaeba39207c9aad9" + +[[patch.unused]] +name = "soroban-env-common" +version = "22.0.0" + +[[patch.unused]] +name = "soroban-env-guest" +version = "22.0.0" + +[[patch.unused]] +name = "soroban-env-host" +version = "22.0.0" + +[[patch.unused]] +name = "soroban-sdk" +version = "20.0.0-rc2" + +[[patch.unused]] +name = "stellar-xdr" +version = "22.0.0"