diff --git a/Cargo.lock b/Cargo.lock index 08c47ef40..8aa7fc7b8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -101,9 +101,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.71" +version = "1.0.75" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c7d0618f0e0b7e8ff11427422b64564d5fb0be1940354bfe2e0529b18a9d9b8" +checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6" [[package]] name = "arbitrary" @@ -1809,6 +1809,7 @@ dependencies = [ name = "preflight" version = "0.9.4" dependencies = [ + "anyhow", "base64 0.21.2", "libc", "sha2 0.10.7", diff --git a/cmd/soroban-rpc/lib/preflight/Cargo.toml b/cmd/soroban-rpc/lib/preflight/Cargo.toml index 863db1eeb..c1e19cda9 100644 --- a/cmd/soroban-rpc/lib/preflight/Cargo.toml +++ b/cmd/soroban-rpc/lib/preflight/Cargo.toml @@ -7,10 +7,10 @@ publish = false crate-type = ["staticlib"] [dependencies] -# TODO: base64 and thiserror are also used by the CLI, -# should we make them workspace dependencies? +anyhow = "1.0.75" base64 = { workspace = true } thiserror = { workspace = true } libc = "0.2.147" sha2 = { workspace = true } soroban-env-host = { workspace = true } + diff --git a/cmd/soroban-rpc/lib/preflight/src/fees.rs b/cmd/soroban-rpc/lib/preflight/src/fees.rs index 66dd71a6d..4ca4e93d2 100644 --- a/cmd/soroban-rpc/lib/preflight/src/fees.rs +++ b/cmd/soroban-rpc/lib/preflight/src/fees.rs @@ -1,4 +1,5 @@ -use ledger_storage; +use anyhow::{bail, ensure, Context, Error, Result}; +use ledger_storage::LedgerStorage; use soroban_env_host::budget::Budget; use soroban_env_host::e2e_invoke::{extract_rent_changes, get_ledger_changes, LedgerEntryChange}; use soroban_env_host::fees::{ @@ -19,24 +20,24 @@ use soroban_env_host::xdr::{ use state_expiration::{get_restored_ledger_sequence, ExpirableLedgerEntry}; use std::cmp::max; use std::convert::{TryFrom, TryInto}; -use std::error; pub(crate) fn compute_host_function_transaction_data_and_min_fee( op: &InvokeHostFunctionOp, - pre_storage: &ledger_storage::LedgerStorage, + pre_storage: &LedgerStorage, post_storage: &Storage, budget: &Budget, events: &Vec, bucket_list_size: u64, current_ledger_seq: u32, -) -> Result<(SorobanTransactionData, i64), Box> { +) -> Result<(SorobanTransactionData, i64)> { let ledger_changes = get_ledger_changes(budget, post_storage, pre_storage)?; let soroban_resources = calculate_host_function_soroban_resources( &ledger_changes, &post_storage.footprint, budget, events, - )?; + ) + .context("cannot compute host function resources")?; let read_write_entries = u32::try_from(soroban_resources.footprint.read_write.as_vec().len())?; @@ -51,13 +52,14 @@ pub(crate) fn compute_host_function_transaction_data_and_min_fee( transaction_size_bytes: estimate_max_transaction_size_for_operation( &OperationBody::InvokeHostFunction(op.clone()), &soroban_resources.footprint, - )?, + ) + .context("cannot estimate maximum transaction size")?, contract_events_size_bytes: soroban_resources.contract_events_size_bytes, }; let rent_changes = extract_rent_changes(&ledger_changes); finalize_transaction_data_and_min_fee( - &pre_storage, + pre_storage, &transaction_resources, soroban_resources, &rent_changes, @@ -69,7 +71,7 @@ pub(crate) fn compute_host_function_transaction_data_and_min_fee( fn estimate_max_transaction_size_for_operation( op: &OperationBody, fp: &LedgerFootprint, -) -> Result> { +) -> Result { let source = MuxedAccount::MuxedEd25519(MuxedAccountMed25519 { id: 0, ed25519: Uint256([0; 32]), @@ -124,8 +126,9 @@ fn calculate_host_function_soroban_resources( footprint: &Footprint, budget: &Budget, events: &Vec, -) -> Result> { - let ledger_footprint = storage_footprint_to_ledger_footprint(footprint)?; +) -> Result { + let ledger_footprint = storage_footprint_to_ledger_footprint(footprint) + .context("cannot convert storage footprint to ledger footprint")?; let read_bytes: u32 = ledger_changes .iter() .map(|c| c.encoded_key.len() as u32 + c.old_entry_size_bytes) @@ -134,17 +137,18 @@ fn calculate_host_function_soroban_resources( let write_bytes: u32 = ledger_changes .iter() .map(|c| { - c.encoded_key.len() as u32 + c.encoded_new_value.as_ref().map_or(0, |v| v.len()) as u32 + c.encoded_key.len() as u32 + c.encoded_new_value.as_ref().map_or(0, Vec::len) as u32 }) .sum(); - let contract_events_size_bytes = calculate_event_size_bytes(events)?; + let contract_events_size_bytes = + calculate_event_size_bytes(events).context("cannot calculate events size")?; // Add a 15% leeway with a minimum of 50k instructions - let instructions = max( - budget.get_cpu_insns_consumed()? + 50000, - budget.get_cpu_insns_consumed()? * 115 / 100, - ); + let budget_instructions = budget + .get_cpu_insns_consumed() + .context("cannot get instructions consumed")?; + let instructions = max(budget_instructions + 50000, budget_instructions * 115 / 100); Ok(SorobanResources { footprint: ledger_footprint, instructions: u32::try_from(instructions)?, @@ -155,56 +159,44 @@ fn calculate_host_function_soroban_resources( } fn get_fee_configurations( - ledger_storage: &ledger_storage::LedgerStorage, + ledger_storage: &LedgerStorage, bucket_list_size: u64, -) -> Result<(FeeConfiguration, RentFeeConfiguration), Box> { +) -> Result<(FeeConfiguration, RentFeeConfiguration)> { let ConfigSettingEntry::ContractComputeV0(compute) = ledger_storage.get_configuration_setting(ConfigSettingId::ContractComputeV0)? else { - return Err( - "get_fee_configuration(): unexpected config setting entry for ComputeV0 key".into(), - ); + bail!("unexpected config setting entry for ComputeV0 key"); + }; let ConfigSettingEntry::ContractLedgerCostV0(ledger_cost) = ledger_storage.get_configuration_setting(ConfigSettingId::ContractLedgerCostV0)? else { - return Err( - "get_fee_configuration(): unexpected config setting entry for LedgerCostV0 key".into(), - ); + bail!("unexpected config setting entry for LedgerCostV0 key"); }; let ConfigSettingEntry::ContractHistoricalDataV0(historical_data) = ledger_storage.get_configuration_setting(ConfigSettingId::ContractHistoricalDataV0)? else { - return Err( - "get_fee_configuration(): unexpected config setting entry for HistoricalDataV0 key" - .into(), - ); + bail!("unexpected config setting entry for HistoricalDataV0 key"); }; let ConfigSettingEntry::ContractEventsV0(events) = ledger_storage.get_configuration_setting(ConfigSettingId::ContractEventsV0)? else { - return Err( - "get_fee_configuration(): unexpected config setting entry for ContractEventsV0 key".into(), - ); + bail!("unexpected config setting entry for EventsV0 key"); }; let ConfigSettingEntry::ContractBandwidthV0(bandwidth) = ledger_storage.get_configuration_setting(ConfigSettingId::ContractBandwidthV0)? else { - return Err( - "get_fee_configuration(): unexpected config setting entry for BandwidthV0 key".into(), - ); + bail!("unexpected config setting entry for BandwidthV0 key"); }; let ConfigSettingEntry::StateExpiration(state_expiration) = ledger_storage.get_configuration_setting(ConfigSettingId::StateExpiration)? else { - return Err( - "get_fee_configuration(): unexpected config setting entry for StateExpiration key".into(), - ); + bail!("unexpected config setting entry for StateExpiration key"); }; let write_fee_configuration = WriteFeeConfiguration { @@ -237,30 +229,38 @@ fn get_fee_configurations( fn calculate_unmodified_ledger_entry_bytes( ledger_entries: &Vec, - pre_storage: &ledger_storage::LedgerStorage, + pre_storage: &LedgerStorage, include_expired: bool, -) -> Result> { +) -> Result { let mut res: usize = 0; for lk in ledger_entries { - let key_size = lk.to_xdr()?.len(); - let entry_size = pre_storage.get_xdr(lk, include_expired)?.len(); + let key_xdr = lk + .to_xdr() + .with_context(|| format!("cannot marshall ledger key {lk:?}"))?; + let key_size = key_xdr.len(); + let entry_xdr = pre_storage + .get_xdr(lk, include_expired) + .with_context(|| format!("cannot get xdr of ledger entry with key {lk:?}"))?; + let entry_size = entry_xdr.len(); res += key_size + entry_size; } Ok(res as u32) } -fn calculate_event_size_bytes(events: &Vec) -> Result> { +fn calculate_event_size_bytes(events: &Vec) -> Result { let mut res: u32 = 0; for e in events { - let event_xdr = e.to_xdr()?; + let event_xdr = e + .to_xdr() + .with_context(|| format!("cannot marshal event {e:?}"))?; res += u32::try_from(event_xdr.len())?; } Ok(res) } fn storage_footprint_to_ledger_footprint(foot: &Footprint) -> Result { - let mut read_only: Vec = Vec::new(); - let mut read_write: Vec = Vec::new(); + let mut read_only: Vec = Vec::with_capacity(foot.0.len()); + let mut read_write: Vec = Vec::with_capacity(foot.0.len()); for (k, v) in &foot.0 { match v { AccessType::ReadOnly => read_only.push((**k).clone()), @@ -274,15 +274,16 @@ fn storage_footprint_to_ledger_footprint(foot: &Footprint) -> Result, current_ledger_seq: u32, bucket_list_size: u64, -) -> Result<(SorobanTransactionData, i64), Box> { +) -> Result<(SorobanTransactionData, i64)> { let (fee_configuration, rent_fee_configuration) = - get_fee_configurations(pre_storage, bucket_list_size)?; + get_fee_configurations(pre_storage, bucket_list_size) + .context("failed to obtain configuration settings from the network")?; let (non_refundable_fee, refundable_fee) = compute_transaction_resource_fee(transaction_resources, &fee_configuration); let rent_fee = compute_rent_fee(&rent_changes, &rent_fee_configuration, current_ledger_seq); @@ -301,34 +302,23 @@ fn finalize_transaction_data_and_min_fee( pub(crate) fn compute_bump_footprint_exp_transaction_data_and_min_fee( footprint: LedgerFootprint, ledgers_to_expire: u32, - ledger_storage: &ledger_storage::LedgerStorage, + ledger_storage: &LedgerStorage, bucket_list_size: u64, current_ledger_seq: u32, -) -> Result<(SorobanTransactionData, i64), Box> { - let mut rent_changes: Vec = Vec::new(); - for key in (&footprint).read_only.as_vec() { - let unmodified_entry = ledger_storage.get(key, false)?; - let size = (key.to_xdr()?.len() + unmodified_entry.to_xdr()?.len()) as u32; - let expirable_entry: Box = (&unmodified_entry).try_into()?; - let new_expiration_ledger = current_ledger_seq + ledgers_to_expire; - if new_expiration_ledger <= expirable_entry.expiration_ledger_seq() { - // The bump would be ineffective - continue; - } - let rent_change = LedgerEntryRentChange { - is_persistent: expirable_entry.durability() == Persistent, - old_size_bytes: size, - new_size_bytes: size, - old_expiration_ledger: expirable_entry.expiration_ledger_seq(), - new_expiration_ledger, - }; - rent_changes.push(rent_change); - } +) -> Result<(SorobanTransactionData, i64)> { + let rent_changes = compute_bump_footprint_rent_changes( + &footprint, + ledger_storage, + ledgers_to_expire, + current_ledger_seq, + ) + .context("cannot compute bump rent changes")?; let read_bytes = calculate_unmodified_ledger_entry_bytes( footprint.read_only.as_vec(), ledger_storage, false, - )?; + ) + .context("cannot calculate read_bytes resource")?; let soroban_resources = SorobanResources { footprint, instructions: 0, @@ -336,19 +326,21 @@ pub(crate) fn compute_bump_footprint_exp_transaction_data_and_min_fee( write_bytes: 0, contract_events_size_bytes: 0, }; + let transaction_size_bytes = estimate_max_transaction_size_for_operation( + &OperationBody::BumpFootprintExpiration(BumpFootprintExpirationOp { + ext: ExtensionPoint::V0, + ledgers_to_expire, + }), + &soroban_resources.footprint, + ) + .context("cannot estimate maximum transaction size")?; let transaction_resources = TransactionResources { instructions: 0, read_entries: u32::try_from(soroban_resources.footprint.read_only.as_vec().len())?, write_entries: 0, read_bytes: soroban_resources.read_bytes, write_bytes: 0, - transaction_size_bytes: estimate_max_transaction_size_for_operation( - &OperationBody::BumpFootprintExpiration(BumpFootprintExpirationOp { - ext: ExtensionPoint::V0, - ledgers_to_expire: ledgers_to_expire, - }), - &soroban_resources.footprint, - )?, + transaction_size_bytes, contract_events_size_bytes: 0, }; finalize_transaction_data_and_min_fee( @@ -361,50 +353,64 @@ pub(crate) fn compute_bump_footprint_exp_transaction_data_and_min_fee( ) } -pub(crate) fn compute_restore_footprint_transaction_data_and_min_fee( - footprint: LedgerFootprint, - ledger_storage: &ledger_storage::LedgerStorage, - bucket_list_size: u64, +fn compute_bump_footprint_rent_changes( + footprint: &LedgerFootprint, + ledger_storage: &LedgerStorage, + ledgers_to_expire: u32, current_ledger_seq: u32, -) -> Result<(SorobanTransactionData, i64), Box> { - let ConfigSettingEntry::StateExpiration(state_expiration) = - ledger_storage.get_configuration_setting(ConfigSettingId::StateExpiration)? - else { - return Err( - "get_fee_configuration(): unexpected config setting entry for StateExpiration key".into(), - ); - }; - let mut rent_changes: Vec = Vec::new(); - for key in footprint.read_write.as_vec() { - let unmodified_entry = ledger_storage.get(key, true)?; +) -> Result> { + let mut rent_changes: Vec = + Vec::with_capacity(footprint.read_only.len()); + for key in (&footprint).read_only.as_vec() { + let unmodified_entry = ledger_storage + .get(key, false) + .with_context(|| format!("cannot get ledger entry with key {key:?}"))?; let size = (key.to_xdr()?.len() + unmodified_entry.to_xdr()?.len()) as u32; - let expirable_entry: Box = (&unmodified_entry).try_into()?; - if expirable_entry.durability() != Persistent { - let err = format!("Non-persistent key ({:?}) in footprint", key).into(); - return Err(err); - } - if !expirable_entry.has_expired(current_ledger_seq) { - // noop (the entry hadn't expired) + let expirable_entry: Box = + (&unmodified_entry).try_into().map_err(|e: String| { + Error::msg(e.clone()).context("incorrect ledger entry type in footprint") + })?; + let new_expiration_ledger = current_ledger_seq + ledgers_to_expire; + if new_expiration_ledger <= expirable_entry.expiration_ledger_seq() { + // The bump would be ineffective continue; } - let new_expiration_ledger = get_restored_ledger_sequence( - current_ledger_seq, - state_expiration.min_persistent_entry_expiration, - ); let rent_change = LedgerEntryRentChange { - is_persistent: true, - old_size_bytes: 0, + is_persistent: expirable_entry.durability() == Persistent, + old_size_bytes: size, new_size_bytes: size, - old_expiration_ledger: 0, + old_expiration_ledger: expirable_entry.expiration_ledger_seq(), new_expiration_ledger, }; rent_changes.push(rent_change); } + Ok(rent_changes) +} + +pub(crate) fn compute_restore_footprint_transaction_data_and_min_fee( + footprint: LedgerFootprint, + ledger_storage: &LedgerStorage, + bucket_list_size: u64, + current_ledger_seq: u32, +) -> Result<(SorobanTransactionData, i64)> { + let ConfigSettingEntry::StateExpiration(state_expiration) = + ledger_storage.get_configuration_setting(ConfigSettingId::StateExpiration)? + else { + bail!("get_fee_configuration(): unexpected config setting entry for StateExpiration key"); + }; + let rent_changes = compute_restore_footprint_rent_changes( + &footprint, + ledger_storage, + state_expiration.min_persistent_entry_expiration, + current_ledger_seq, + ) + .context("cannot compute restore rent changes")?; let write_bytes = calculate_unmodified_ledger_entry_bytes( footprint.read_write.as_vec(), ledger_storage, true, - )?; + ) + .context("cannot calculate write_bytes resource")?; let soroban_resources = SorobanResources { footprint, instructions: 0, @@ -413,22 +419,24 @@ pub(crate) fn compute_restore_footprint_transaction_data_and_min_fee( contract_events_size_bytes: 0, }; let entry_count = u32::try_from(soroban_resources.footprint.read_write.as_vec().len())?; + let transaction_size_bytes = estimate_max_transaction_size_for_operation( + &OperationBody::RestoreFootprint(RestoreFootprintOp { + ext: ExtensionPoint::V0, + }), + &soroban_resources.footprint, + ) + .context("cannot estimate maximum transaction size")?; let transaction_resources = TransactionResources { instructions: 0, read_entries: entry_count, write_entries: entry_count, read_bytes: soroban_resources.read_bytes, write_bytes: soroban_resources.write_bytes, - transaction_size_bytes: estimate_max_transaction_size_for_operation( - &OperationBody::RestoreFootprint(RestoreFootprintOp { - ext: ExtensionPoint::V0, - }), - &soroban_resources.footprint, - )?, + transaction_size_bytes, contract_events_size_bytes: 0, }; finalize_transaction_data_and_min_fee( - &ledger_storage, + ledger_storage, &transaction_resources, soroban_resources, &rent_changes, @@ -436,3 +444,42 @@ pub(crate) fn compute_restore_footprint_transaction_data_and_min_fee( bucket_list_size, ) } + +fn compute_restore_footprint_rent_changes( + footprint: &LedgerFootprint, + ledger_storage: &LedgerStorage, + min_persistent_entry_expiration: u32, + current_ledger_seq: u32, +) -> Result> { + let mut rent_changes: Vec = + Vec::with_capacity(footprint.read_write.len()); + for key in footprint.read_write.as_vec() { + let unmodified_entry = ledger_storage + .get(key, true) + .with_context(|| format!("cannot get ledger entry with key {key:?}"))?; + let size = (key.to_xdr()?.len() + unmodified_entry.to_xdr()?.len()) as u32; + let expirable_entry: Box = + (&unmodified_entry).try_into().map_err(|e: String| { + Error::msg(e.clone()).context("incorrect ledger entry type in footprint") + })?; + ensure!( + expirable_entry.durability() == Persistent, + "non-persistent entry in footprint: key = {key:?}" + ); + if !expirable_entry.has_expired(current_ledger_seq) { + // noop (the entry hadn't expired) + continue; + } + let new_expiration_ledger = + get_restored_ledger_sequence(current_ledger_seq, min_persistent_entry_expiration); + let rent_change = LedgerEntryRentChange { + is_persistent: true, + old_size_bytes: 0, + new_size_bytes: size, + old_expiration_ledger: 0, + new_expiration_ledger, + }; + rent_changes.push(rent_change); + } + Ok(rent_changes) +} diff --git a/cmd/soroban-rpc/lib/preflight/src/ledger_storage.rs b/cmd/soroban-rpc/lib/preflight/src/ledger_storage.rs index 3dac742b1..54632d0f3 100644 --- a/cmd/soroban-rpc/lib/preflight/src/ledger_storage.rs +++ b/cmd/soroban-rpc/lib/preflight/src/ledger_storage.rs @@ -42,9 +42,9 @@ pub(crate) enum Error { UnexpectedConfigLedgerEntry { setting_id: String }, } -impl Error { - fn to_host_error(&self) -> HostError { - match self { +impl From for HostError { + fn from(value: Error) -> Self { + match value { Error::NotFound => ScError::Storage(ScErrorCode::MissingValue).into(), Error::Xdr(_) => ScError::Value(ScErrorCode::InvalidInput).into(), _ => ScError::Context(ScErrorCode::InternalError).into(), @@ -88,7 +88,7 @@ impl EntryRestoreTracker { self.ledger_keys_requiring_restore .borrow_mut() .insert(key.clone()); - return true; + true } } @@ -128,7 +128,7 @@ impl LedgerStorage { ledger_keys_requiring_restore: RefCell::new(HashSet::new()), min_persistent_entry_expiration: state_expiration.min_persistent_entry_expiration, }); - return Ok(ledger_storage); + Ok(ledger_storage) } fn get_xdr_base64(&self, key: &LedgerKey, include_expired: bool) -> Result { @@ -193,8 +193,7 @@ impl LedgerStorage { impl SnapshotSource for LedgerStorage { fn get(&self, key: &Rc) -> Result, HostError> { - let mut entry = ::get(self, key, self.restore_tracker.is_some()) - .map_err(|e| Error::to_host_error(&e))?; + let mut entry = ::get(self, key, self.restore_tracker.is_some())?; if let Some(ref tracker) = self.restore_tracker { // If the entry expired, we modify it to make it seem like it was restored tracker.track_and_restore(key, &mut entry); @@ -206,7 +205,7 @@ impl SnapshotSource for LedgerStorage { let entry = match ::get(self, key, self.restore_tracker.is_some()) { Err(e) => match e { Error::NotFound => return Ok(false), - _ => return Err(Error::to_host_error(&e)), + _ => return Err(HostError::from(e)), }, Ok(le) => le, }; diff --git a/cmd/soroban-rpc/lib/preflight/src/lib.rs b/cmd/soroban-rpc/lib/preflight/src/lib.rs index 425c0e44a..b4a4e54c1 100644 --- a/cmd/soroban-rpc/lib/preflight/src/lib.rs +++ b/cmd/soroban-rpc/lib/preflight/src/lib.rs @@ -1,32 +1,26 @@ mod fees; mod ledger_storage; +mod preflight; mod state_expiration; +extern crate anyhow; extern crate base64; extern crate libc; extern crate sha2; extern crate soroban_env_host; - +use anyhow::{Context, Result}; use ledger_storage::LedgerStorage; +use preflight::PreflightResult; use sha2::{Digest, Sha256}; -use soroban_env_host::auth::RecordedAuthPayload; -use soroban_env_host::budget::Budget; -use soroban_env_host::events::Events; -use soroban_env_host::storage::Storage; use soroban_env_host::xdr::{ - AccountId, ConfigSettingEntry, ConfigSettingId, DiagnosticEvent, InvokeHostFunctionOp, - LedgerFootprint, LedgerKey, OperationBody, ReadXdr, ScVal, SorobanAddressCredentials, - SorobanAuthorizationEntry, SorobanCredentials, VecM, WriteXdr, + AccountId, InvokeHostFunctionOp, LedgerFootprint, OperationBody, ReadXdr, WriteXdr, }; -use soroban_env_host::{DiagnosticLevel, Host, LedgerInfo}; +use soroban_env_host::LedgerInfo; use std::convert::{TryFrom, TryInto}; -use std::error::Error; use std::ffi::{CStr, CString}; -use std::iter::FromIterator; +use std::mem; use std::panic; use std::ptr::null_mut; -use std::rc::Rc; -use std::{error, mem}; #[repr(C)] #[derive(Copy, Clone)] @@ -42,20 +36,22 @@ pub struct CLedgerInfo { pub autobump_ledgers: u32, } -impl From for LedgerInfo { - fn from(c: CLedgerInfo) -> Self { - let network_passphrase_cstr = unsafe { CStr::from_ptr(c.network_passphrase) }; - Self { +impl TryFrom for LedgerInfo { + type Error = anyhow::Error; + + fn try_from(c: CLedgerInfo) -> Result { + let network_passphrase = from_c_string(c.network_passphrase)?; + Ok(Self { protocol_version: c.protocol_version, sequence_number: c.sequence_number, timestamp: c.timestamp, - network_id: Sha256::digest(network_passphrase_cstr.to_str().unwrap().as_bytes()).into(), + network_id: Sha256::digest(network_passphrase).into(), base_reserve: c.base_reserve, min_temp_entry_expiration: c.min_temp_entry_expiration, min_persistent_entry_expiration: c.min_persistent_entry_expiration, max_entry_expiration: c.max_entry_expiration, autobump_ledgers: c.autobump_ledgers, - } + }) } } @@ -73,22 +69,31 @@ pub struct CPreflightResult { pub pre_restore_min_fee: i64, // Minimum recommended resource fee for a prerequired RestoreFootprint operation } -fn preflight_error(str: String) -> *mut CPreflightResult { - let c_str = CString::new(str).unwrap(); - // transfer ownership to caller - // caller needs to invoke free_preflight_result(result) when done - Box::into_raw(Box::new(CPreflightResult { - error: c_str.into_raw(), - auth: null_mut(), - result: null_mut(), - transaction_data: null_mut(), - min_fee: 0, - events: null_mut(), - cpu_instructions: 0, - memory_bytes: 0, - pre_restore_transaction_data: null_mut(), - pre_restore_min_fee: 0, - })) +impl TryFrom for CPreflightResult { + type Error = anyhow::Error; + + fn try_from(p: PreflightResult) -> Result { + let mut result = Self { + error: null_mut(), + auth: xdr_vec_to_base64_c_null_terminated_char_array(p.auth)?, + result: match p.result { + None => null_mut(), + Some(v) => xdr_to_base64_c(v)?, + }, + transaction_data: xdr_to_base64_c(p.transaction_data)?, + min_fee: p.min_fee, + events: xdr_vec_to_base64_c_null_terminated_char_array(p.events)?, + cpu_instructions: p.cpu_instructions, + memory_bytes: p.memory_bytes, + pre_restore_transaction_data: null_mut(), + pre_restore_min_fee: 0, + }; + if let Some(p) = p.restore_preamble { + result.pre_restore_min_fee = p.min_fee; + result.pre_restore_transaction_data = xdr_to_base64_c(p.transaction_data)?; + }; + Ok(result) + } } #[no_mangle] @@ -116,138 +121,19 @@ fn preflight_invoke_hf_op_or_maybe_panic( invoke_hf_op: *const libc::c_char, // InvokeHostFunctionOp XDR in base64 source_account: *const libc::c_char, // AccountId XDR in base64 ledger_info: CLedgerInfo, -) -> Result> { - let invoke_hf_op_cstr = unsafe { CStr::from_ptr(invoke_hf_op) }; - let invoke_hf_op = InvokeHostFunctionOp::from_xdr_base64(invoke_hf_op_cstr.to_str()?)?; - let source_account_cstr = unsafe { CStr::from_ptr(source_account) }; - let source_account = AccountId::from_xdr_base64(source_account_cstr.to_str()?)?; - let ledger_storage = Rc::new(LedgerStorage::with_restore_tracking( - handle, - ledger_info.sequence_number, - )?); - let budget = get_budget_from_network_config_params(&ledger_storage)?; - let storage = Storage::with_recording_footprint(ledger_storage.clone()); - let host = Host::with_storage_and_budget(storage, budget); - - // We make an assumption here: - // - if a transaction doesn't include any soroban authorization entries the client either - // doesn't know the authorization entries, or there are none. In either case it is best to - // record the authorization entries and return them to the client. - // - if a transaction *does* include soroban authorization entries, then the client *already* - // knows the needed entries, so we should try them in enforcing mode so that we can validate - // them, and return the correct fees and footprint. - let needs_auth_recording = invoke_hf_op.auth.is_empty(); - if needs_auth_recording { - host.switch_to_recording_auth()?; - } else { - host.set_authorization_entries(invoke_hf_op.auth.to_vec())?; - } - - host.set_diagnostic_level(DiagnosticLevel::Debug)?; - host.set_source_account(source_account)?; - host.set_ledger_info(ledger_info.into())?; - - // Run the preflight. - let result = host.invoke_function(invoke_hf_op.host_function.clone())?; - let auths: VecM = if needs_auth_recording { - let payloads = host.get_recorded_auth_payloads()?; - VecM::try_from( - payloads - .iter() - .map(recorded_auth_payload_to_xdr) - .collect::>(), - )? - } else { - invoke_hf_op.auth - }; - - let budget = host.budget_cloned(); - // Recover, convert and return the storage footprint and other values to C. - let (storage, events) = host.try_finish()?; - - let diagnostic_events = host_events_to_diagnostic_events(&events); - let (transaction_data, min_fee) = fees::compute_host_function_transaction_data_and_min_fee( - &InvokeHostFunctionOp { - host_function: invoke_hf_op.host_function, - auth: auths.clone(), - }, - &ledger_storage, - &storage, - &budget, - &diagnostic_events, +) -> Result { + let invoke_hf_op = InvokeHostFunctionOp::from_xdr_base64(from_c_string(invoke_hf_op)?)?; + let source_account = AccountId::from_xdr_base64(from_c_string(source_account)?)?; + let ledger_storage = LedgerStorage::with_restore_tracking(handle, ledger_info.sequence_number) + .context("cannot create LedgerStorage")?; + let result = preflight::preflight_invoke_hf_op( + ledger_storage, bucket_list_size, - ledger_info.sequence_number, - )?; - - let entries = ledger_storage.get_ledger_keys_requiring_restore(); - let (pre_restore_transaction_data, pre_restore_min_fee) = if entries.len() > 0 { - let read_write_vec: Vec = Vec::from_iter(entries); - let restore_footprint = LedgerFootprint { - read_only: VecM::default(), - read_write: read_write_vec.try_into()?, - }; - let (transaction_data, min_fee) = - fees::compute_restore_footprint_transaction_data_and_min_fee( - restore_footprint, - &ledger_storage, - bucket_list_size, - ledger_info.sequence_number, - )?; - let transaction_data_cstr = CString::new(transaction_data.to_xdr_base64()?)?; - (transaction_data_cstr.into_raw(), min_fee) - } else { - (null_mut(), 0) - }; - - let transaction_data_cstr = CString::new(transaction_data.to_xdr_base64()?)?; - Ok(CPreflightResult { - error: null_mut(), - auth: recorded_auth_payloads_to_c(auths.to_vec())?, - result: CString::new(result.to_xdr_base64()?)?.into_raw(), - transaction_data: transaction_data_cstr.into_raw(), - min_fee, - events: diagnostic_events_to_c(diagnostic_events)?, - cpu_instructions: budget.get_cpu_insns_consumed()?, - memory_bytes: budget.get_mem_bytes_consumed()?, - pre_restore_transaction_data, - pre_restore_min_fee, - }) -} - -fn get_budget_from_network_config_params( - ledger_storage: &LedgerStorage, -) -> Result> { - let ConfigSettingEntry::ContractComputeV0(compute) = - ledger_storage.get_configuration_setting(ConfigSettingId::ContractComputeV0)? - else { - return Err( - "get_budget_from_network_config_params((): unexpected config setting entry for ComputeV0 key".into(), - ); - }; - - let ConfigSettingEntry::ContractCostParamsCpuInstructions(cost_params_cpu) = ledger_storage - .get_configuration_setting(ConfigSettingId::ContractCostParamsCpuInstructions)? - else { - return Err( - "get_budget_from_network_config_params((): unexpected config setting entry for ComputeV0 key".into(), - ); - }; - - let ConfigSettingEntry::ContractCostParamsMemoryBytes(cost_params_memory) = - ledger_storage.get_configuration_setting(ConfigSettingId::ContractCostParamsMemoryBytes)? - else { - return Err( - "get_budget_from_network_config_params((): unexpected config setting entry for ComputeV0 key".into(), - ); - }; - - let budget = Budget::try_from_configs( - compute.tx_max_instructions as u64, - compute.tx_memory_limit as u64, - cost_params_cpu, - cost_params_memory, + invoke_hf_op, + source_account, + ledger_info.try_into()?, )?; - Ok(budget) + result.try_into() } #[no_mangle] @@ -275,172 +161,76 @@ fn preflight_footprint_expiration_op_or_maybe_panic( op_body: *const libc::c_char, footprint: *const libc::c_char, current_ledger_seq: u32, -) -> Result> { - let op_body_cstr = unsafe { CStr::from_ptr(op_body) }; - let op_body = OperationBody::from_xdr_base64(op_body_cstr.to_str()?)?; - let footprint_cstr = unsafe { CStr::from_ptr(footprint) }; - let ledger_footprint = LedgerFootprint::from_xdr_base64(footprint_cstr.to_str()?)?; +) -> Result { + let op_body = OperationBody::from_xdr_base64(from_c_string(op_body)?)?; + let footprint = LedgerFootprint::from_xdr_base64(from_c_string(footprint)?)?; let ledger_storage = &LedgerStorage::new(handle); - match op_body { - OperationBody::BumpFootprintExpiration(op) => preflight_bump_footprint_expiration( - ledger_footprint, - op.ledgers_to_expire, - ledger_storage, - bucket_list_size, - current_ledger_seq, - ), - OperationBody::RestoreFootprint(_) => preflight_restore_footprint( - ledger_footprint, - ledger_storage, - bucket_list_size, - current_ledger_seq, - ), - op => Err(format!( - "preflight_footprint_expiration_op(): unsupported operation type {}", - op.name() - ) - .into()), - } -} - -fn preflight_bump_footprint_expiration( - footprint: LedgerFootprint, - ledgers_to_expire: u32, - ledger_storage: &LedgerStorage, - bucket_list_size: u64, - current_ledger_seq: u32, -) -> Result> { - let (transaction_data, min_fee) = - fees::compute_bump_footprint_exp_transaction_data_and_min_fee( - footprint, - ledgers_to_expire, - ledger_storage, - bucket_list_size, - current_ledger_seq, - )?; - let transaction_data_cstr = CString::new(transaction_data.to_xdr_base64()?)?; - Ok(CPreflightResult { - error: null_mut(), - auth: null_mut(), - result: null_mut(), - transaction_data: transaction_data_cstr.into_raw(), - min_fee, - events: null_mut(), - cpu_instructions: 0, - memory_bytes: 0, - pre_restore_transaction_data: null_mut(), - pre_restore_min_fee: 0, - }) -} - -fn preflight_restore_footprint( - footprint: LedgerFootprint, - ledger_storage: &LedgerStorage, - bucket_list_size: u64, - current_ledger_seq: u32, -) -> Result> { - let (transaction_data, min_fee) = fees::compute_restore_footprint_transaction_data_and_min_fee( - footprint, + let result = preflight::preflight_footprint_expiration_op( ledger_storage, bucket_list_size, + op_body, + footprint, current_ledger_seq, )?; - let transaction_data_cstr = CString::new(transaction_data.to_xdr_base64()?)?; - Ok(CPreflightResult { - error: null_mut(), + result.try_into() +} + +fn preflight_error(str: String) -> CPreflightResult { + let c_str = CString::new(str).unwrap(); + CPreflightResult { + error: c_str.into_raw(), auth: null_mut(), result: null_mut(), - transaction_data: transaction_data_cstr.into_raw(), - min_fee, + transaction_data: null_mut(), + min_fee: 0, events: null_mut(), cpu_instructions: 0, memory_bytes: 0, pre_restore_transaction_data: null_mut(), pre_restore_min_fee: 0, - }) + } } -fn catch_preflight_panic( - op: Box Result>>, -) -> *mut CPreflightResult { +fn catch_preflight_panic(op: Box Result>) -> *mut CPreflightResult { // catch panics before they reach foreign callers (which otherwise would result in // undefined behavior) - let res = panic::catch_unwind(panic::AssertUnwindSafe(|| op())); - match res { + let res: std::thread::Result> = + panic::catch_unwind(panic::AssertUnwindSafe(op)); + let c_preflight_result = match res { Err(panic) => match panic.downcast::() { Ok(panic_msg) => preflight_error(format!("panic during preflight() call: {panic_msg}")), Err(_) => preflight_error("panic during preflight() call: unknown cause".to_string()), }, - // transfer ownership to caller - // caller needs to invoke free_preflight_result(result) when done - Ok(r) => match r { - Ok(r2) => Box::into_raw(Box::new(r2)), - Err(e) => preflight_error(format!("{e}")), - }, - } -} - -fn recorded_auth_payloads_to_c( - payloads: Vec, -) -> Result<*mut *mut libc::c_char, Box> { - let xdr_base64_vec: Vec = payloads - .iter() - .map(WriteXdr::to_xdr_base64) - .collect::, _>>()?; - string_vec_to_c_null_terminated_char_array(xdr_base64_vec) + // See https://docs.rs/anyhow/latest/anyhow/struct.Error.html#display-representations + Ok(r) => r.unwrap_or_else(|e| preflight_error(format!("{e:?}"))), + }; + // transfer ownership to caller + // caller needs to invoke free_preflight_result(result) when done + Box::into_raw(Box::new(c_preflight_result)) } -fn recorded_auth_payload_to_xdr(payload: &RecordedAuthPayload) -> SorobanAuthorizationEntry { - match (payload.address.clone(), payload.nonce) { - (Some(address), Some(nonce)) => SorobanAuthorizationEntry { - credentials: SorobanCredentials::Address(SorobanAddressCredentials { - address, - nonce, - // signature is left empty. This is where the client will put their signatures when - // submitting the transaction. - signature_expiration_ledger: 0, - signature: ScVal::Void, - }), - root_invocation: payload.invocation.clone(), - }, - (None, None) => SorobanAuthorizationEntry { - credentials: SorobanCredentials::SourceAccount, - root_invocation: payload.invocation.clone(), - }, - // the address and the nonce can't be present independently - (a,n) => - panic!("recorded_auth_payload_to_xdr: address and nonce present independently (address: {:?}, nonce: {:?})", a, n), - } +fn xdr_to_base64_c(v: impl WriteXdr) -> Result<*mut libc::c_char> { + string_to_c(v.to_xdr_base64()?) } -fn host_events_to_diagnostic_events(events: &Events) -> Vec { - let mut res: Vec = Vec::new(); - for e in &events.0 { - let diagnostic_event = DiagnosticEvent { - in_successful_contract_call: !e.failed_call, - event: e.event.clone(), - }; - res.push(diagnostic_event); - } - res +fn string_to_c(str: String) -> Result<*mut libc::c_char> { + Ok(CString::new(str)?.into_raw()) } -fn diagnostic_events_to_c( - events: Vec, -) -> Result<*mut *mut libc::c_char, Box> { - let xdr_base64_vec: Vec = events +fn xdr_vec_to_base64_c_null_terminated_char_array( + payloads: Vec, +) -> Result<*mut *mut libc::c_char> { + let xdr_base64_vec: Vec = payloads .iter() - .map(DiagnosticEvent::to_xdr_base64) + .map(WriteXdr::to_xdr_base64) .collect::, _>>()?; string_vec_to_c_null_terminated_char_array(xdr_base64_vec) } -fn string_vec_to_c_null_terminated_char_array( - v: Vec, -) -> Result<*mut *mut libc::c_char, Box> { +fn string_vec_to_c_null_terminated_char_array(v: Vec) -> Result<*mut *mut libc::c_char> { let mut out_vec: Vec<*mut libc::c_char> = Vec::new(); for s in &v { - let c_str = CString::new(s.clone())?.into_raw(); + let c_str = string_to_c(s.clone())?; out_vec.push(c_str); } @@ -474,43 +264,48 @@ pub unsafe extern "C" fn free_preflight_result(result: *mut CPreflightResult) { if result.is_null() { return; } - unsafe { - if !(*result).error.is_null() { - _ = CString::from_raw((*result).error); - } - - if !(*result).auth.is_null() { - free_c_null_terminated_char_array((*result).auth); - } - - if !(*result).result.is_null() { - _ = CString::from_raw((*result).result); - } + let boxed = Box::from_raw(result); + free_c_string(boxed.error); + free_c_null_terminated_char_array(boxed.auth); + free_c_string(boxed.result); + free_c_string(boxed.transaction_data); + free_c_null_terminated_char_array(boxed.events); + free_c_string(boxed.pre_restore_transaction_data); +} - if !(*result).transaction_data.is_null() { - _ = CString::from_raw((*result).transaction_data); - } - if !(*result).events.is_null() { - free_c_null_terminated_char_array((*result).events); - } - _ = Box::from_raw(result); +fn free_c_string(str: *mut libc::c_char) { + if str.is_null() { + return; + } + unsafe { + _ = CString::from_raw(str); } } fn free_c_null_terminated_char_array(array: *mut *mut libc::c_char) { + if array.is_null() { + return; + } unsafe { - // Iterate until we find a null value let mut i: usize = 0; loop { let c_char_ptr = *array.add(i); if c_char_ptr.is_null() { + // Iterate until we find the ending null value break; } // deallocate each string _ = CString::from_raw(c_char_ptr); i += 1; } + // convert the last (NULL) element's index to the vector's length + let len = i + 1; // deallocate the containing vector - _ = Vec::from_raw_parts(array, i + 1, i + 1); + // (for which vec_to_c_array() ensured the same length and capacity) + _ = Vec::from_raw_parts(array, len, len); } } +fn from_c_string(str: *const libc::c_char) -> Result { + let c_str = unsafe { CStr::from_ptr(str) }; + Ok(c_str.to_str()?.to_string()) +} diff --git a/cmd/soroban-rpc/lib/preflight/src/preflight.rs b/cmd/soroban-rpc/lib/preflight/src/preflight.rs new file mode 100644 index 000000000..fcd378f1e --- /dev/null +++ b/cmd/soroban-rpc/lib/preflight/src/preflight.rs @@ -0,0 +1,289 @@ +use anyhow::{anyhow, bail, Context, Result}; +use fees; +use ledger_storage::LedgerStorage; +use soroban_env_host::auth::RecordedAuthPayload; +use soroban_env_host::budget::Budget; +use soroban_env_host::events::Events; +use soroban_env_host::storage::Storage; +use soroban_env_host::xdr::{ + AccountId, ConfigSettingEntry, ConfigSettingId, DiagnosticEvent, InvokeHostFunctionOp, + LedgerFootprint, LedgerKey, OperationBody, ScVal, SorobanAddressCredentials, + SorobanAuthorizationEntry, SorobanCredentials, SorobanTransactionData, VecM, +}; +use soroban_env_host::{DiagnosticLevel, Host, LedgerInfo}; +use std::convert::{TryFrom, TryInto}; +use std::iter::FromIterator; +use std::rc::Rc; + +pub(crate) struct RestorePreamble { + pub(crate) transaction_data: SorobanTransactionData, + pub(crate) min_fee: i64, +} + +pub(crate) struct PreflightResult { + pub(crate) auth: Vec, + pub(crate) result: Option, + pub(crate) transaction_data: SorobanTransactionData, + pub(crate) min_fee: i64, + pub(crate) events: Vec, + pub(crate) cpu_instructions: u64, + pub(crate) memory_bytes: u64, + pub(crate) restore_preamble: Option, +} + +pub(crate) fn preflight_invoke_hf_op( + ledger_storage: LedgerStorage, + bucket_list_size: u64, + invoke_hf_op: InvokeHostFunctionOp, + source_account: AccountId, + ledger_info: LedgerInfo, +) -> Result { + let ledger_storage_rc = Rc::new(ledger_storage); + let budget = get_budget_from_network_config_params(&ledger_storage_rc) + .context("cannot create budget")?; + let storage = Storage::with_recording_footprint(ledger_storage_rc.clone()); + let host = Host::with_storage_and_budget(storage, budget); + + // We make an assumption here: + // - if a transaction doesn't include any soroban authorization entries the client either + // doesn't know the authorization entries, or there are none. In either case it is best to + // record the authorization entries and return them to the client. + // - if a transaction *does* include soroban authorization entries, then the client *already* + // knows the needed entries, so we should try them in enforcing mode so that we can validate + // them, and return the correct fees and footprint. + let needs_auth_recording = invoke_hf_op.auth.is_empty(); + if needs_auth_recording { + host.switch_to_recording_auth() + .context("cannot switch auth to recording mode")?; + } else { + host.set_authorization_entries(invoke_hf_op.auth.to_vec()) + .context("cannot set authorization entries")?; + } + + host.set_diagnostic_level(DiagnosticLevel::Debug) + .context("cannot set debug diagnostic level")?; + host.set_source_account(source_account.clone()) + .context("cannot set source account")?; + host.set_ledger_info(ledger_info.clone()) + .context("cannot set ledger info")?; + + // Run the preflight. + let result = host + .invoke_function(invoke_hf_op.host_function.clone()) + .context("host invocation failed")?; + let auths: VecM = if needs_auth_recording { + let payloads = host.get_recorded_auth_payloads()?; + VecM::try_from( + payloads + .iter() + .map(recorded_auth_payload_to_xdr) + .collect::>>()?, + )? + } else { + invoke_hf_op.auth + }; + + let budget = host.budget_cloned(); + // Recover, convert and return the storage footprint and other values to C. + let (storage, events) = host.try_finish().context("cannot finish host invocation")?; + + let diagnostic_events = host_events_to_diagnostic_events(&events); + let invoke_host_function_with_auth = InvokeHostFunctionOp { + host_function: invoke_hf_op.host_function, + auth: auths.clone(), + }; + let (transaction_data, min_fee) = fees::compute_host_function_transaction_data_and_min_fee( + &invoke_host_function_with_auth, + &ledger_storage_rc, + &storage, + &budget, + &diagnostic_events, + bucket_list_size, + ledger_info.sequence_number, + ) + .context("cannot compute resources and fees")?; + + let entries = ledger_storage_rc.get_ledger_keys_requiring_restore(); + let restore_preamble = if entries.is_empty() { + None + } else { + let read_write_vec: Vec = Vec::from_iter(entries); + let restore_footprint = LedgerFootprint { + read_only: VecM::default(), + read_write: read_write_vec.try_into()?, + }; + let (transaction_data, min_fee) = + fees::compute_restore_footprint_transaction_data_and_min_fee( + restore_footprint, + &ledger_storage_rc, + bucket_list_size, + ledger_info.sequence_number, + ) + .context("cannot compute restore preamble")?; + Some(RestorePreamble { + transaction_data, + min_fee, + }) + }; + + Ok(PreflightResult { + auth: auths.to_vec(), + result: Some(result), + transaction_data, + min_fee, + events: diagnostic_events, + cpu_instructions: budget + .get_cpu_insns_consumed() + .context("cannot get cpu instructions")?, + memory_bytes: budget + .get_mem_bytes_consumed() + .context("cannot get consumed memory")?, + restore_preamble, + }) +} + +fn recorded_auth_payload_to_xdr( + payload: &RecordedAuthPayload, +) -> Result { + let result = match (payload.address.clone(), payload.nonce) { + (Some(address), Some(nonce)) => SorobanAuthorizationEntry { + credentials: SorobanCredentials::Address(SorobanAddressCredentials { + address, + nonce, + // signature is left empty. This is where the client will put their signatures when + // submitting the transaction. + signature_expiration_ledger: 0, + signature: ScVal::Void, + }), + root_invocation: payload.invocation.clone(), + }, + (None, None) => SorobanAuthorizationEntry { + credentials: SorobanCredentials::SourceAccount, + root_invocation: payload.invocation.clone(), + }, + // the address and the nonce can't be present independently + (a,n) => + bail!("recorded_auth_payload_to_xdr: address and nonce present independently (address: {:?}, nonce: {:?})", a, n), + }; + Ok(result) +} + +fn host_events_to_diagnostic_events(events: &Events) -> Vec { + let mut res: Vec = Vec::with_capacity(events.0.len()); + for e in &events.0 { + let diagnostic_event = DiagnosticEvent { + in_successful_contract_call: !e.failed_call, + event: e.event.clone(), + }; + res.push(diagnostic_event); + } + res +} + +fn get_budget_from_network_config_params(ledger_storage: &LedgerStorage) -> Result { + let ConfigSettingEntry::ContractComputeV0(compute) = + ledger_storage.get_configuration_setting(ConfigSettingId::ContractComputeV0)? + else { + bail!("get_budget_from_network_config_params(): unexpected config setting entry for ComputeV0 key"); + }; + + let ConfigSettingEntry::ContractCostParamsCpuInstructions(cost_params_cpu) = ledger_storage + .get_configuration_setting(ConfigSettingId::ContractCostParamsCpuInstructions)? + else { + bail!("get_budget_from_network_config_params(): unexpected config setting entry for ComputeV0 key"); + }; + + let ConfigSettingEntry::ContractCostParamsMemoryBytes(cost_params_memory) = + ledger_storage.get_configuration_setting(ConfigSettingId::ContractCostParamsMemoryBytes)? + else { + bail!("get_budget_from_network_config_params(): unexpected config setting entry for ComputeV0 key"); + }; + + let budget = Budget::try_from_configs( + compute.tx_max_instructions as u64, + compute.tx_memory_limit as u64, + cost_params_cpu, + cost_params_memory, + ) + .context("cannot create budget from network configuration")?; + Ok(budget) +} + +pub(crate) fn preflight_footprint_expiration_op( + ledger_storage: &LedgerStorage, + bucket_list_size: u64, + op_body: OperationBody, + footprint: LedgerFootprint, + current_ledger_seq: u32, +) -> Result { + match op_body { + OperationBody::BumpFootprintExpiration(op) => preflight_bump_footprint_expiration( + footprint, + op.ledgers_to_expire, + ledger_storage, + bucket_list_size, + current_ledger_seq, + ), + OperationBody::RestoreFootprint(_) => preflight_restore_footprint( + footprint, + ledger_storage, + bucket_list_size, + current_ledger_seq, + ), + op => Err(anyhow!( + "preflight_footprint_expiration_op(): unsupported operation type {}", + op.name() + )), + } +} + +fn preflight_bump_footprint_expiration( + footprint: LedgerFootprint, + ledgers_to_expire: u32, + ledger_storage: &LedgerStorage, + bucket_list_size: u64, + current_ledger_seq: u32, +) -> Result { + let (transaction_data, min_fee) = + fees::compute_bump_footprint_exp_transaction_data_and_min_fee( + footprint, + ledgers_to_expire, + ledger_storage, + bucket_list_size, + current_ledger_seq, + )?; + Ok(PreflightResult { + auth: vec![], + result: None, + transaction_data, + min_fee, + events: vec![], + cpu_instructions: 0, + memory_bytes: 0, + restore_preamble: None, + }) +} + +fn preflight_restore_footprint( + footprint: LedgerFootprint, + ledger_storage: &LedgerStorage, + bucket_list_size: u64, + current_ledger_seq: u32, +) -> Result { + let (transaction_data, min_fee) = fees::compute_restore_footprint_transaction_data_and_min_fee( + footprint, + ledger_storage, + bucket_list_size, + current_ledger_seq, + )?; + Ok(PreflightResult { + auth: vec![], + result: None, + transaction_data, + min_fee, + events: vec![], + cpu_instructions: 0, + memory_bytes: 0, + restore_preamble: None, + }) +} diff --git a/cmd/soroban-rpc/lib/preflight/src/state_expiration.rs b/cmd/soroban-rpc/lib/preflight/src/state_expiration.rs index eed1dd31d..9efd330c8 100644 --- a/cmd/soroban-rpc/lib/preflight/src/state_expiration.rs +++ b/cmd/soroban-rpc/lib/preflight/src/state_expiration.rs @@ -40,7 +40,7 @@ impl<'a> TryInto> for &'a LedgerEntry { LedgerEntryData::ContractData(d) => Ok(Box::new(d)), LedgerEntryData::ContractCode(c) => Ok(Box::new(c)), _ => Err(format!( - "Incorrect ledger entry type ({}) in footprint", + "ledger entry type ({}) is not expirable", self.data.name() )), }