From 2920d575a75146005b3efbb7caf3733653bc6123 Mon Sep 17 00:00:00 2001 From: Dmytro Kozhevin Date: Thu, 29 Feb 2024 21:05:08 -0500 Subject: [PATCH] Draft of Rust changes for soroban-simulation refactoring --- Cargo.lock | 127 ++++++- Cargo.toml | 8 +- cmd/soroban-rpc/lib/preflight/Cargo.toml | 4 +- cmd/soroban-rpc/lib/preflight/src/lib.rs | 431 +++++++++++++++-------- 4 files changed, 398 insertions(+), 172 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a730a8d5..46c86038 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1189,10 +1189,12 @@ checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" name = "preflight" version = "20.3.3" dependencies = [ + "anyhow", "base64 0.21.7", "libc", + "rand", "sha2", - "soroban-env-host", + "soroban-env-host 20.2.2", "soroban-simulation", ] @@ -1557,20 +1559,44 @@ dependencies = [ "syn", ] +[[package]] +name = "soroban-builtin-sdk-macros" +version = "20.2.2" +dependencies = [ + "itertools 0.11.0", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "soroban-env-common" version = "20.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9d609330abbcc2d7fe185304de0c10ef1a95e64eb8effb6ee4faeea97668e0a" dependencies = [ - "arbitrary", "crate-git-revision", "ethnum", "num-derive", "num-traits", "serde", - "soroban-env-macros", - "soroban-wasmi", + "soroban-env-macros 20.2.1", + "soroban-wasmi 0.31.1-soroban.20.0.1 (registry+https://github.com/rust-lang/crates.io-index)", + "static_assertions", + "stellar-xdr", +] + +[[package]] +name = "soroban-env-common" +version = "20.2.2" +dependencies = [ + "arbitrary", + "crate-git-revision", + "ethnum", + "num-derive", + "num-traits", + "soroban-env-macros 20.2.2", + "soroban-wasmi 0.31.1-soroban.20.0.1 (git+https://github.com/stellar/wasmi?rev=0ed3f3dee30dc41ebe21972399e0a73a41944aa0)", "static_assertions", "stellar-xdr", ] @@ -1581,7 +1607,7 @@ version = "20.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "280d73550935d482534abf3f897e89b40461b3401c3209163b3d0038f0b8b201" dependencies = [ - "soroban-env-common", + "soroban-env-common 20.2.1", "static_assertions", ] @@ -1590,6 +1616,30 @@ name = "soroban-env-host" version = "20.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd99f4e075f62e0faec118c568fbc70373793fb921148115d5f3f2563945c02d" +dependencies = [ + "curve25519-dalek", + "ed25519-dalek", + "getrandom", + "hex-literal", + "hmac", + "k256", + "num-derive", + "num-integer", + "num-traits", + "rand", + "rand_chacha", + "sha2", + "sha3", + "soroban-builtin-sdk-macros 20.2.1", + "soroban-env-common 20.2.1", + "soroban-wasmi 0.31.1-soroban.20.0.1 (registry+https://github.com/rust-lang/crates.io-index)", + "static_assertions", + "stellar-strkey 0.0.8", +] + +[[package]] +name = "soroban-env-host" +version = "20.2.2" dependencies = [ "backtrace", "curve25519-dalek", @@ -1605,9 +1655,9 @@ dependencies = [ "rand_chacha", "sha2", "sha3", - "soroban-builtin-sdk-macros", - "soroban-env-common", - "soroban-wasmi", + "soroban-builtin-sdk-macros 20.2.2", + "soroban-env-common 20.2.2", + "soroban-wasmi 0.31.1-soroban.20.0.1 (git+https://github.com/stellar/wasmi?rev=0ed3f3dee30dc41ebe21972399e0a73a41944aa0)", "static_assertions", "stellar-strkey 0.0.8", ] @@ -1627,6 +1677,19 @@ dependencies = [ "syn", ] +[[package]] +name = "soroban-env-macros" +version = "20.2.2" +dependencies = [ + "itertools 0.11.0", + "proc-macro2", + "quote", + "serde", + "serde_json", + "stellar-xdr", + "syn", +] + [[package]] name = "soroban-ledger-snapshot" version = "20.3.1" @@ -1636,8 +1699,8 @@ dependencies = [ "serde", "serde_json", "serde_with", - "soroban-env-common", - "soroban-env-host", + "soroban-env-common 20.2.1", + "soroban-env-host 20.2.1", "thiserror", ] @@ -1658,7 +1721,7 @@ dependencies = [ "serde-aux", "serde_json", "sha2", - "soroban-env-host", + "soroban-env-host 20.2.2", "soroban-sdk", "soroban-spec", "stellar-strkey 0.0.7", @@ -1683,7 +1746,7 @@ dependencies = [ "serde", "serde_json", "soroban-env-guest", - "soroban-env-host", + "soroban-env-host 20.2.1", "soroban-ledger-snapshot", "soroban-sdk-macros", "stellar-strkey 0.0.8", @@ -1702,7 +1765,7 @@ dependencies = [ "quote", "rustc_version", "sha2", - "soroban-env-common", + "soroban-env-common 20.2.1", "soroban-spec", "soroban-spec-rust", "stellar-xdr", @@ -1711,13 +1774,11 @@ dependencies = [ [[package]] name = "soroban-simulation" -version = "20.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "210b093c6d08b8e85ef5f4e4a231d5fa25d1d2787d4fecd50e11040849f259ba" +version = "20.2.2" dependencies = [ "anyhow", "rand", - "soroban-env-host", + "soroban-env-host 20.2.2", "static_assertions", "thiserror", ] @@ -1758,8 +1819,20 @@ checksum = "710403de32d0e0c35375518cb995d4fc056d0d48966f2e56ea471b8cb8fc9719" dependencies = [ "smallvec", "spin", - "wasmi_arena", - "wasmi_core", + "wasmi_arena 0.4.1", + "wasmi_core 0.13.0 (registry+https://github.com/rust-lang/crates.io-index)", + "wasmparser-nostd", +] + +[[package]] +name = "soroban-wasmi" +version = "0.31.1-soroban.20.0.1" +source = "git+https://github.com/stellar/wasmi?rev=0ed3f3dee30dc41ebe21972399e0a73a41944aa0#0ed3f3dee30dc41ebe21972399e0a73a41944aa0" +dependencies = [ + "smallvec", + "spin", + "wasmi_arena 0.4.0", + "wasmi_core 0.13.0 (git+https://github.com/stellar/wasmi?rev=0ed3f3dee30dc41ebe21972399e0a73a41944aa0)", "wasmparser-nostd", ] @@ -2171,6 +2244,11 @@ version = "0.2.91" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4f186bd2dcf04330886ce82d6f33dd75a7bfcf69ecf5763b89fcde53b6ac9838" +[[package]] +name = "wasmi_arena" +version = "0.4.0" +source = "git+https://github.com/stellar/wasmi?rev=0ed3f3dee30dc41ebe21972399e0a73a41944aa0#0ed3f3dee30dc41ebe21972399e0a73a41944aa0" + [[package]] name = "wasmi_arena" version = "0.4.1" @@ -2189,6 +2267,17 @@ dependencies = [ "paste", ] +[[package]] +name = "wasmi_core" +version = "0.13.0" +source = "git+https://github.com/stellar/wasmi?rev=0ed3f3dee30dc41ebe21972399e0a73a41944aa0#0ed3f3dee30dc41ebe21972399e0a73a41944aa0" +dependencies = [ + "downcast-rs", + "libm", + "num-traits", + "paste", +] + [[package]] name = "wasmparser" version = "0.88.0" diff --git a/Cargo.toml b/Cargo.toml index 389794b7..5b50130a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,16 +12,16 @@ version = "20.3.3" rust-version = "1.74.0" [workspace.dependencies.soroban-env-host] -version = "=20.2.1" +version = "=20.2.2" # git = "https://github.com/stellar/rs-soroban-env" # rev = "1bfc0f2a2ee134efc1e1b0d5270281d0cba61c2e" -# path = "../rs-soroban-env/soroban-env-host" +path = "../rs-soroban-env/soroban-env-host" [workspace.dependencies.soroban-simulation] -version = "=20.2.1" +version = "=20.2.2" # git = "https://github.com/stellar/rs-soroban-env" # rev = "1bfc0f2a2ee134efc1e1b0d5270281d0cba61c2e" -# path = "../rs-soroban-env/soroban-simulation" +path = "../rs-soroban-env/soroban-simulation" [workspace.dependencies.soroban-spec] version = "=20.3.1" diff --git a/cmd/soroban-rpc/lib/preflight/Cargo.toml b/cmd/soroban-rpc/lib/preflight/Cargo.toml index 826c7cca..5c7d7648 100644 --- a/cmd/soroban-rpc/lib/preflight/Cargo.toml +++ b/cmd/soroban-rpc/lib/preflight/Cargo.toml @@ -12,5 +12,7 @@ libc = "0.2.147" sha2 = { workspace = true } # we need the testutils feature in order to get backtraces in the preflight library # when soroban rpc is configured to run with --preflight-enable-debug -soroban-env-host = { workspace = true, features = ["recording_mode", "testutils"]} +soroban-env-host = { workspace = true, features = ["recording_mode", "testutils", "unstable-next-api"]} soroban-simulation = { workspace = true } +anyhow = "1.0.75" +rand = { version = "0.8.5", features = [] } \ No newline at end of file diff --git a/cmd/soroban-rpc/lib/preflight/src/lib.rs b/cmd/soroban-rpc/lib/preflight/src/lib.rs index 25746e74..7901ebfc 100644 --- a/cmd/soroban-rpc/lib/preflight/src/lib.rs +++ b/cmd/soroban-rpc/lib/preflight/src/lib.rs @@ -1,23 +1,29 @@ +extern crate anyhow; extern crate base64; extern crate libc; extern crate sha2; extern crate soroban_env_host; extern crate soroban_simulation; +use anyhow::{anyhow, bail, Result}; use sha2::{Digest, Sha256}; +use soroban_env_host::storage::EntryWithLiveUntil; use soroban_env_host::xdr::{ - AccountId, Hash, InvokeHostFunctionOp, LedgerEntry, LedgerEntryData, LedgerFootprint, - LedgerKey, LedgerKeyTtl, Limits, OperationBody, ReadXdr, TtlEntry, WriteXdr, + AccountId, ExtendFootprintTtlOp, Hash, InvokeHostFunctionOp, LedgerEntry, LedgerEntryData, + LedgerFootprint, LedgerKey, LedgerKeyTtl, Limits, OperationBody, ReadXdr, ScErrorCode, + ScErrorType, SorobanTransactionData, TtlEntry, WriteXdr, }; -use soroban_env_host::LedgerInfo; -use soroban_simulation::{ledger_storage, ResourceConfig}; -use soroban_simulation::{ - simulate_footprint_ttl_op, simulate_invoke_hf_op, LedgerStorage, SimulationResult, +use soroban_env_host::{HostError, LedgerInfo, DEFAULT_XDR_RW_LIMITS}; +use soroban_simulation::simulation::{ + simulate_extend_ttl_op, simulate_invoke_host_function_op, simulate_restore_op, + InvokeHostFunctionSimulationResult, RestoreOpSimulationResult, SimulationAdjustmentConfig, }; -use std::error::Error; +use soroban_simulation::{AutoRestoringSnapshotSource, NetworkConfig, SnapshotSourceWithArchive}; +use std::cell::RefCell; use std::ffi::{CStr, CString}; use std::panic; use std::ptr::null_mut; +use std::rc::Rc; use std::{mem, slice}; #[repr(C)] @@ -28,25 +34,20 @@ pub struct CLedgerInfo { pub timestamp: u64, pub network_passphrase: *const libc::c_char, pub base_reserve: u32, - pub min_temp_entry_ttl: u32, - pub min_persistent_entry_ttl: u32, - pub max_entry_ttl: u32, } -impl From for LedgerInfo { - fn from(c: CLedgerInfo) -> Self { - let network_passphrase = from_c_string(c.network_passphrase); - Self { - protocol_version: c.protocol_version, - sequence_number: c.sequence_number, - timestamp: c.timestamp, - network_id: Sha256::digest(network_passphrase).into(), - base_reserve: c.base_reserve, - min_temp_entry_ttl: c.min_temp_entry_ttl, - min_persistent_entry_ttl: c.min_persistent_entry_ttl, - max_entry_ttl: c.max_entry_ttl, - } - } +fn fill_ledger_info(c_ledger_info: CLedgerInfo, network_config: &NetworkConfig) -> LedgerInfo { + let network_passphrase = from_c_string(c_ledger_info.network_passphrase); + let mut ledger_info = LedgerInfo { + protocol_version: c_ledger_info.protocol_version, + sequence_number: c_ledger_info.sequence_number, + timestamp: c_ledger_info.timestamp, + network_id: Sha256::digest(network_passphrase).into(), + base_reserve: c_ledger_info.base_reserve, + ..Default::default() + }; + network_config.fill_config_fields_in_ledger_info(&mut ledger_info); + ledger_info } #[repr(C)] @@ -85,14 +86,6 @@ pub struct CResourceConfig { pub instruction_leeway: u64, } -impl From for ResourceConfig { - fn from(r: CResourceConfig) -> Self { - return ResourceConfig { - instruction_leeway: r.instruction_leeway, - }; - } -} - #[repr(C)] #[derive(Copy, Clone)] pub struct CPreflightResult { @@ -116,22 +109,67 @@ pub struct CPreflightResult { pub pre_restore_min_fee: i64, } -impl From for CPreflightResult { - fn from(s: SimulationResult) -> Self { - let mut result = Self { - error: string_to_c(s.error), - auth: xdr_vec_to_c(s.auth), - result: option_xdr_to_c(s.result), - transaction_data: option_xdr_to_c(s.transaction_data), - min_fee: s.min_fee, - events: xdr_vec_to_c(s.events), - cpu_instructions: s.cpu_instructions, - memory_bytes: s.memory_bytes, +impl Default for CPreflightResult { + fn default() -> Self { + Self { + error: CString::new(String::new()).unwrap().into_raw(), + auth: get_default_c_xdr_vector(), + result: get_default_c_xdr(), + transaction_data: get_default_c_xdr(), + min_fee: 0, + events: get_default_c_xdr_vector(), + cpu_instructions: 0, + memory_bytes: 0, pre_restore_transaction_data: get_default_c_xdr(), pre_restore_min_fee: 0, + } + } +} + +impl CPreflightResult { + fn new_from_invoke_host_function( + invoke_hf_result: InvokeHostFunctionSimulationResult, + restore_preamble: Option, + error: String, + ) -> Self { + let mut result = Self { + error: string_to_c(error), + auth: xdr_vec_to_c(invoke_hf_result.auth), + result: option_xdr_to_c( + invoke_hf_result + .invoke_result + .map_or_else(|_| None, |v| Some(v)), + ), + min_fee: invoke_hf_result + .transaction_data + .as_ref() + .map_or_else(|| 0, |r| r.resource_fee), + transaction_data: option_xdr_to_c(invoke_hf_result.transaction_data), + // TODO: Diagnostic and contract events should be separated in the response + events: xdr_vec_to_c(invoke_hf_result.diagnostic_events), + cpu_instructions: invoke_hf_result.simulated_instructions as u64, + memory_bytes: invoke_hf_result.simulated_memory as u64, + ..Default::default() }; - if let Some(p) = s.restore_preamble { - result.pre_restore_min_fee = p.min_fee; + if let Some(p) = restore_preamble { + result.pre_restore_min_fee = p.transaction_data.resource_fee; + result.pre_restore_transaction_data = xdr_to_c(p.transaction_data); + }; + result + } + + fn new_from_transaction_data( + transaction_data: Option, + restore_preamble: Option, + error: String, + ) -> Self { + let mut result = Self { + error: string_to_c(error), + transaction_data: option_xdr_to_c(transaction_data), + ..Default::default() + }; + if let Some(p) = restore_preamble { + result.pre_restore_min_fee = p.transaction_data.resource_fee; result.pre_restore_transaction_data = xdr_to_c(p.transaction_data); }; result @@ -141,7 +179,6 @@ impl From for CPreflightResult { #[no_mangle] pub extern "C" fn preflight_invoke_hf_op( handle: libc::uintptr_t, // Go Handle to forward to SnapshotSourceGet and SnapshotSourceHas - bucket_list_size: u64, // Bucket list size for current ledger invoke_hf_op: CXDR, // InvokeHostFunctionOp XDR in base64 source_account: CXDR, // AccountId XDR in base64 ledger_info: CLedgerInfo, @@ -151,7 +188,6 @@ pub extern "C" fn preflight_invoke_hf_op( catch_preflight_panic(Box::new(move || { preflight_invoke_hf_op_or_maybe_panic( handle, - bucket_list_size, invoke_hf_op, source_account, ledger_info, @@ -163,105 +199,172 @@ pub extern "C" fn preflight_invoke_hf_op( fn preflight_invoke_hf_op_or_maybe_panic( handle: libc::uintptr_t, - bucket_list_size: u64, // Go Handle to forward to SnapshotSourceGet and SnapshotSourceHas - invoke_hf_op: CXDR, // InvokeHostFunctionOp XDR in base64 - source_account: CXDR, // AccountId XDR in base64 - ledger_info: CLedgerInfo, + invoke_hf_op: CXDR, // InvokeHostFunctionOp XDR in base64 + source_account: CXDR, // AccountId XDR in base64 + c_ledger_info: CLedgerInfo, resource_config: CResourceConfig, enable_debug: bool, -) -> Result> { +) -> Result { let invoke_hf_op = InvokeHostFunctionOp::from_xdr(from_c_xdr(invoke_hf_op), Limits::none()).unwrap(); let source_account = AccountId::from_xdr(from_c_xdr(source_account), Limits::none()).unwrap(); - let go_storage = GoLedgerStorage { - golang_handle: handle, - current_ledger_sequence: ledger_info.sequence_number, + let go_storage = Rc::new(GoLedgerStorage::new(handle)); + let network_config = NetworkConfig::load_from_snapshot(go_storage.as_ref())?; + let ledger_info = fill_ledger_info(c_ledger_info, &network_config); + let auto_restore_snapshot = Rc::new(AutoRestoringSnapshotSource::new( + go_storage.clone(), + &ledger_info, + )?); + + let mut adjustment_config = SimulationAdjustmentConfig::default_adjustment(); + // It would be reasonable to extend `resource_config` to be compatible with `adjustment_config` + // in order to let the users customize the resource/fee adjustments in a more granular fashion. + adjustment_config.instructions.additive_factor = adjustment_config + .instructions + .additive_factor + .max(resource_config.instruction_leeway.min(u32::MAX as u64) as u32); + // Here we assume that no input auth means that the user requests the recording auth. + let auth_entries = if invoke_hf_op.auth.is_empty() { + None + } else { + Some(invoke_hf_op.auth.to_vec()) }; - let ledger_storage = - LedgerStorage::with_restore_tracking(Box::new(go_storage), ledger_info.sequence_number)?; - let result = simulate_invoke_hf_op( - ledger_storage, - bucket_list_size, - invoke_hf_op, - source_account, - LedgerInfo::from(ledger_info), - resource_config.into(), + // Invoke the host function. The user errors should normally be captured in `invoke_hf_result.invoke_result` and + // this should return Err result for misconfigured ledger. + let invoke_hf_result = simulate_invoke_host_function_op( + auto_restore_snapshot.clone(), + &network_config, + &adjustment_config, + &ledger_info, + invoke_hf_op.host_function, + auth_entries, + &source_account, + rand::Rng::gen(&mut rand::thread_rng()), enable_debug, - ); - match result { - Ok(r) => Ok(r.into()), - Err(e) => Err(e), - } + )?; + let maybe_restore_result = match &invoke_hf_result.invoke_result { + Ok(_) => auto_restore_snapshot.simulate_restore_keys_op( + &network_config, + &SimulationAdjustmentConfig::default_adjustment(), + &ledger_info, + ), + Err(e) => Err(e.clone().into()), + }; + let error_str = extract_error_string(&maybe_restore_result, go_storage.as_ref()); + Ok(CPreflightResult::new_from_invoke_host_function( + invoke_hf_result, + maybe_restore_result.unwrap_or(None), + error_str, + )) } #[no_mangle] pub extern "C" fn preflight_footprint_ttl_op( handle: libc::uintptr_t, // Go Handle to forward to SnapshotSourceGet and SnapshotSourceHas - bucket_list_size: u64, // Bucket list size for current ledger op_body: CXDR, // OperationBody XDR footprint: CXDR, // LedgerFootprint XDR - current_ledger_seq: u32, + ledger_info: CLedgerInfo, ) -> *mut CPreflightResult { catch_preflight_panic(Box::new(move || { - preflight_footprint_ttl_op_or_maybe_panic( - handle, - bucket_list_size, - op_body, - footprint, - current_ledger_seq, - ) + preflight_footprint_ttl_op_or_maybe_panic(handle, op_body, footprint, ledger_info) })) } - fn preflight_footprint_ttl_op_or_maybe_panic( handle: libc::uintptr_t, - bucket_list_size: u64, op_body: CXDR, footprint: CXDR, - current_ledger_seq: u32, -) -> Result> { - let op_body = OperationBody::from_xdr(from_c_xdr(op_body), Limits::none()).unwrap(); - let footprint = LedgerFootprint::from_xdr(from_c_xdr(footprint), Limits::none()).unwrap(); - let go_storage = GoLedgerStorage { - golang_handle: handle, - current_ledger_sequence: current_ledger_seq, + c_ledger_info: CLedgerInfo, +) -> Result { + let op_body = OperationBody::from_xdr(from_c_xdr(op_body), DEFAULT_XDR_RW_LIMITS)?; + let footprint = LedgerFootprint::from_xdr(from_c_xdr(footprint), DEFAULT_XDR_RW_LIMITS)?; + let go_storage = Rc::new(GoLedgerStorage::new(handle)); + let network_config = NetworkConfig::load_from_snapshot(go_storage.as_ref())?; + let ledger_info = fill_ledger_info(c_ledger_info, &network_config); + // TODO: It would make for a better UX if the user passed only the neccesary fields for every operation. + // That would remove a possibility of providing bad operation body, or a possibility of filling wrong footprint + // field. + match op_body { + OperationBody::ExtendFootprintTtl(extend_op) => { + preflight_extend_ttl_op(extend_op, footprint.read_only.as_slice(), go_storage, &network_config, &ledger_info) + }, + OperationBody::RestoreFootprint(_) => { + preflight_restore_op(footprint.read_write.as_slice(), go_storage, &network_config, &ledger_info) + } + _ => Err(anyhow!("encountered unsupported operation type: '{:?}', instead of 'ExtendFootprintTtl' or 'RestoreFootprint' operations.", + op_body.discriminant()).into()) + } +} +fn preflight_extend_ttl_op( + extend_op: ExtendFootprintTtlOp, + keys_to_extend: &[LedgerKey], + go_storage: Rc, + network_config: &NetworkConfig, + ledger_info: &LedgerInfo, +) -> Result { + let auto_restore_snapshot = AutoRestoringSnapshotSource::new(go_storage.clone(), ledger_info)?; + let simulation_result = simulate_extend_ttl_op( + &auto_restore_snapshot, + &network_config, + &SimulationAdjustmentConfig::default_adjustment(), + &ledger_info, + keys_to_extend, + extend_op.extend_to, + ); + let (maybe_transaction_data, maybe_restore_result) = match simulation_result { + Ok(r) => ( + Some(r.transaction_data), + auto_restore_snapshot.simulate_restore_keys_op( + &network_config, + &SimulationAdjustmentConfig::default_adjustment(), + &ledger_info, + ), + ), + Err(e) => (None, Err(e)), }; - let ledger_storage = &LedgerStorage::new(Box::new(go_storage), current_ledger_seq); - let result = simulate_footprint_ttl_op( - ledger_storage, - bucket_list_size, - op_body, - footprint, - current_ledger_seq, + + let error_str = extract_error_string(&maybe_restore_result, go_storage.as_ref()); + Ok(CPreflightResult::new_from_transaction_data( + maybe_transaction_data, + maybe_restore_result.unwrap_or(None), + error_str, + )) +} + +fn preflight_restore_op( + keys_to_restore: &[LedgerKey], + go_storage: Rc, + network_config: &NetworkConfig, + ledger_info: &LedgerInfo, +) -> Result { + let simulation_result = simulate_restore_op( + go_storage.as_ref(), + &network_config, + &SimulationAdjustmentConfig::default_adjustment(), + &ledger_info, + keys_to_restore, ); - match result { - Ok(r) => Ok(r.into()), - Err(e) => Err(e), - } + let error_str = extract_error_string(&simulation_result, go_storage.as_ref()); + Ok(CPreflightResult::new_from_transaction_data( + simulation_result + .map(|r| Some(r.transaction_data)) + .unwrap_or(None), + None, + error_str, + )) } fn preflight_error(str: String) -> CPreflightResult { let c_str = CString::new(str).unwrap(); CPreflightResult { error: c_str.into_raw(), - auth: get_default_c_xdr_vector(), - result: get_default_c_xdr(), - transaction_data: get_default_c_xdr(), - min_fee: 0, - events: get_default_c_xdr_vector(), - cpu_instructions: 0, - memory_bytes: 0, - pre_restore_transaction_data: get_default_c_xdr(), - pre_restore_min_fee: 0, + ..Default::default() } } -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: std::thread::Result>> = + let res: std::thread::Result> = panic::catch_unwind(panic::AssertUnwindSafe(op)); let c_preflight_result = match res { Err(panic) => match panic.downcast::() { @@ -385,37 +488,40 @@ extern "C" { struct GoLedgerStorage { golang_handle: libc::uintptr_t, - current_ledger_sequence: u32, + internal_error: RefCell>, } impl GoLedgerStorage { + fn new(golang_handle: libc::uintptr_t) -> Self { + Self { + golang_handle, + internal_error: RefCell::new(None), + } + } + // Get the XDR, regardless of ttl - fn get_xdr_internal( - &self, - key_xdr: &mut Vec, - ) -> std::result::Result, ledger_storage::Error> { + fn get_xdr_internal(&self, key_xdr: &mut Vec) -> Option> { let key_c_xdr = CXDR { xdr: key_xdr.as_mut_ptr(), len: key_xdr.len(), }; let res = unsafe { SnapshotSourceGet(self.golang_handle, key_c_xdr) }; if res.xdr.is_null() { - return Err(ledger_storage::Error::NotFound); + return None; } let v = from_c_xdr(res); unsafe { FreeGoXDR(res) }; - Ok(v) + Some(v) } -} -impl ledger_storage::LedgerGetter for GoLedgerStorage { - fn get( - &self, - key: &LedgerKey, - include_not_live: bool, - ) -> std::result::Result<(LedgerEntry, Option), ledger_storage::Error> { - let mut key_xdr = key.to_xdr(Limits::none())?; - let xdr = self.get_xdr_internal(&mut key_xdr)?; + // Gets a ledger entry by key, including the archived/removed entries. + // The failures of this function are not recoverable and should only happen in case + // if the underlying storage is somehow corrupted. + fn get_fallible(&self, key: &LedgerKey) -> anyhow::Result> { + let mut key_xdr = key.to_xdr(DEFAULT_XDR_RW_LIMITS)?; + let Some(xdr) = self.get_xdr_internal(&mut key_xdr) else { + return Ok(None); + }; let live_until_ledger_seq = match key { // TODO: it would probably be more efficient to do all of this in the Go side @@ -425,36 +531,65 @@ impl ledger_storage::LedgerGetter for GoLedgerStorage { let ttl_key = LedgerKey::Ttl(LedgerKeyTtl { key_hash: Hash(key_hash), }); - let mut ttl_key_xdr = ttl_key.to_xdr(Limits::none())?; - let ttl_entry_xdr = self.get_xdr_internal(&mut ttl_key_xdr)?; - let ttl_entry = LedgerEntry::from_xdr(ttl_entry_xdr, Limits::none())?; - if let LedgerEntryData::Ttl(TtlEntry { + let mut ttl_key_xdr = ttl_key.to_xdr(DEFAULT_XDR_RW_LIMITS)?; + let ttl_entry_xdr = self.get_xdr_internal(&mut ttl_key_xdr).ok_or_else(|| { + anyhow!( + "TTL entry is missing for an entry that should have TTL with key: '{key:?}'" + ) + })?; + let ttl_entry = LedgerEntry::from_xdr(ttl_entry_xdr, DEFAULT_XDR_RW_LIMITS)?; + let LedgerEntryData::Ttl(TtlEntry { live_until_ledger_seq, .. }) = ttl_entry.data - { - Some(live_until_ledger_seq) - } else { - return Err(ledger_storage::Error::UnexpectedLedgerEntryTypeForTtlKey { - ledger_entry_type: ttl_entry.data.name().to_string(), - }); - } + else { + bail!( + "unexpected non-TTL entry '{:?}' has been fetched for TTL key '{:?}'", + ttl_entry, + ttl_key + ); + }; + Some(live_until_ledger_seq) } _ => None, }; - if !include_not_live - && live_until_ledger_seq.is_some() - && !is_live(live_until_ledger_seq.unwrap(), self.current_ledger_sequence) - { - return Err(ledger_storage::Error::NotLive); - } + let entry = LedgerEntry::from_xdr(xdr, DEFAULT_XDR_RW_LIMITS)?; + Ok(Some((Rc::new(entry), live_until_ledger_seq))) + } +} - let entry = LedgerEntry::from_xdr(xdr, Limits::none())?; - Ok((entry, live_until_ledger_seq)) +impl SnapshotSourceWithArchive for GoLedgerStorage { + fn get_including_archived( + &self, + key: &Rc, + ) -> std::result::Result, HostError> { + let res = self.get_fallible(key.as_ref()); + match res { + Ok(res) => Ok(res), + Err(e) => { + // Store the internal error in the storage as the info won't be propagated from simulation. + if let Ok(mut err) = self.internal_error.try_borrow_mut() { + *err = Some(e.into()); + } + // Errors that occur in storage are not recoverable, so we force host to halt by passing + // it an internal error. + return Err((ScErrorType::Storage, ScErrorCode::InternalError).into()); + } + } } } -pub(crate) fn is_live(live_until_ledger_seq: u32, current_ledger_seq: u32) -> bool { - live_until_ledger_seq >= current_ledger_seq +fn extract_error_string(simulation_result: &Result, go_storage: &GoLedgerStorage) -> String { + match simulation_result { + Ok(_) => String::new(), + Err(e) => { + // Override any simulation result with a storage error (if any). Simulation does not propagate the storage + // errors, but these provide more exact information on the root cause. + match go_storage.internal_error.borrow().as_ref() { + Some(e) => format!("{e:?}"), + None => format!("{e:?}"), + } + } + } }