From 26cc3504f3dd8c76ffd2403c76ae10f5943c83bf Mon Sep 17 00:00:00 2001 From: Alfonso Acosta Date: Wed, 13 Dec 2023 10:32:26 -0400 Subject: [PATCH] rpc: Use external soroban-simulation library for preflight computations --- Cargo.lock | 149 ++++-- Cargo.toml | 31 +- cmd/soroban-rpc/lib/preflight/Cargo.toml | 4 +- cmd/soroban-rpc/lib/preflight/src/fees.rs | 498 ------------------ .../lib/preflight/src/ledger_storage.rs | 262 --------- cmd/soroban-rpc/lib/preflight/src/lib.rs | 176 +++++-- .../lib/preflight/src/preflight.rs | 315 ----------- .../lib/preflight/src/state_ttl.rs | 67 --- 8 files changed, 274 insertions(+), 1228 deletions(-) delete mode 100644 cmd/soroban-rpc/lib/preflight/src/fees.rs delete mode 100644 cmd/soroban-rpc/lib/preflight/src/ledger_storage.rs delete mode 100644 cmd/soroban-rpc/lib/preflight/src/preflight.rs delete mode 100644 cmd/soroban-rpc/lib/preflight/src/state_ttl.rs diff --git a/Cargo.lock b/Cargo.lock index b82c6d9d13..828fd560c4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1909,13 +1909,11 @@ dependencies = [ name = "preflight" version = "20.1.1" dependencies = [ - "anyhow", "base64 0.21.5", "libc", - "rand", "sha2 0.10.8", - "soroban-env-host", - "thiserror", + "soroban-env-host 20.1.0 (git+https://github.com/stellar/rs-soroban-env?rev=36d33cb6c986c9a8a9200b7eb04cf02e2c3f0ef4)", + "soroban-simulation", ] [[package]] @@ -2481,8 +2479,19 @@ dependencies = [ [[package]] name = "soroban-builtin-sdk-macros" -version = "20.0.2" -source = "git+https://github.com/stellar/rs-soroban-env?rev=81f6eb1eefd299af3e03fa0db7b20eb355b2b55f#81f6eb1eefd299af3e03fa0db7b20eb355b2b55f" +version = "20.1.0" +source = "git+https://github.com/stellar/rs-soroban-env?rev=36d33cb6c986c9a8a9200b7eb04cf02e2c3f0ef4#36d33cb6c986c9a8a9200b7eb04cf02e2c3f0ef4" +dependencies = [ + "itertools 0.11.0", + "proc-macro2", + "quote", + "syn 2.0.39", +] + +[[package]] +name = "soroban-builtin-sdk-macros" +version = "20.1.0" +source = "git+https://github.com/stellar/rs-soroban-env?rev=c1b238b65bfd13666be4ac14e0e390c31b549caf#c1b238b65bfd13666be4ac14e0e390c31b549caf" dependencies = [ "itertools 0.11.0", "proc-macro2", @@ -2530,7 +2539,7 @@ dependencies = [ "serde_json", "sha2 0.10.8", "shlex", - "soroban-env-host", + "soroban-env-host 20.1.0 (git+https://github.com/stellar/rs-soroban-env?rev=36d33cb6c986c9a8a9200b7eb04cf02e2c3f0ef4)", "soroban-ledger-snapshot", "soroban-sdk", "soroban-spec", @@ -2556,8 +2565,24 @@ dependencies = [ [[package]] name = "soroban-env-common" -version = "20.0.2" -source = "git+https://github.com/stellar/rs-soroban-env?rev=81f6eb1eefd299af3e03fa0db7b20eb355b2b55f#81f6eb1eefd299af3e03fa0db7b20eb355b2b55f" +version = "20.1.0" +source = "git+https://github.com/stellar/rs-soroban-env?rev=36d33cb6c986c9a8a9200b7eb04cf02e2c3f0ef4#36d33cb6c986c9a8a9200b7eb04cf02e2c3f0ef4" +dependencies = [ + "arbitrary", + "crate-git-revision 0.0.6", + "ethnum", + "num-derive", + "num-traits", + "soroban-env-macros 20.1.0 (git+https://github.com/stellar/rs-soroban-env?rev=36d33cb6c986c9a8a9200b7eb04cf02e2c3f0ef4)", + "soroban-wasmi", + "static_assertions", + "stellar-xdr", +] + +[[package]] +name = "soroban-env-common" +version = "20.1.0" +source = "git+https://github.com/stellar/rs-soroban-env?rev=c1b238b65bfd13666be4ac14e0e390c31b549caf#c1b238b65bfd13666be4ac14e0e390c31b549caf" dependencies = [ "arbitrary", "crate-git-revision 0.0.6", @@ -2565,7 +2590,7 @@ dependencies = [ "num-derive", "num-traits", "serde", - "soroban-env-macros", + "soroban-env-macros 20.1.0 (git+https://github.com/stellar/rs-soroban-env?rev=c1b238b65bfd13666be4ac14e0e390c31b549caf)", "soroban-wasmi", "static_assertions", "stellar-xdr", @@ -2573,17 +2598,43 @@ dependencies = [ [[package]] name = "soroban-env-guest" -version = "20.0.2" -source = "git+https://github.com/stellar/rs-soroban-env?rev=81f6eb1eefd299af3e03fa0db7b20eb355b2b55f#81f6eb1eefd299af3e03fa0db7b20eb355b2b55f" +version = "20.1.0" +source = "git+https://github.com/stellar/rs-soroban-env?rev=c1b238b65bfd13666be4ac14e0e390c31b549caf#c1b238b65bfd13666be4ac14e0e390c31b549caf" dependencies = [ - "soroban-env-common", + "soroban-env-common 20.1.0 (git+https://github.com/stellar/rs-soroban-env?rev=c1b238b65bfd13666be4ac14e0e390c31b549caf)", "static_assertions", ] [[package]] name = "soroban-env-host" -version = "20.0.2" -source = "git+https://github.com/stellar/rs-soroban-env?rev=81f6eb1eefd299af3e03fa0db7b20eb355b2b55f#81f6eb1eefd299af3e03fa0db7b20eb355b2b55f" +version = "20.1.0" +source = "git+https://github.com/stellar/rs-soroban-env?rev=36d33cb6c986c9a8a9200b7eb04cf02e2c3f0ef4#36d33cb6c986c9a8a9200b7eb04cf02e2c3f0ef4" +dependencies = [ + "backtrace", + "curve25519-dalek 4.1.1", + "ed25519-dalek 2.0.0", + "getrandom", + "hex-literal", + "hmac 0.12.1", + "k256", + "num-derive", + "num-integer", + "num-traits", + "rand", + "rand_chacha", + "sha2 0.10.8", + "sha3", + "soroban-builtin-sdk-macros 20.1.0 (git+https://github.com/stellar/rs-soroban-env?rev=36d33cb6c986c9a8a9200b7eb04cf02e2c3f0ef4)", + "soroban-env-common 20.1.0 (git+https://github.com/stellar/rs-soroban-env?rev=36d33cb6c986c9a8a9200b7eb04cf02e2c3f0ef4)", + "soroban-wasmi", + "static_assertions", + "stellar-strkey 0.0.8", +] + +[[package]] +name = "soroban-env-host" +version = "20.1.0" +source = "git+https://github.com/stellar/rs-soroban-env?rev=c1b238b65bfd13666be4ac14e0e390c31b549caf#c1b238b65bfd13666be4ac14e0e390c31b549caf" dependencies = [ "backtrace", "curve25519-dalek 4.1.1", @@ -2599,8 +2650,8 @@ dependencies = [ "rand_chacha", "sha2 0.10.8", "sha3", - "soroban-builtin-sdk-macros", - "soroban-env-common", + "soroban-builtin-sdk-macros 20.1.0 (git+https://github.com/stellar/rs-soroban-env?rev=c1b238b65bfd13666be4ac14e0e390c31b549caf)", + "soroban-env-common 20.1.0 (git+https://github.com/stellar/rs-soroban-env?rev=c1b238b65bfd13666be4ac14e0e390c31b549caf)", "soroban-wasmi", "static_assertions", "stellar-strkey 0.0.8", @@ -2608,8 +2659,22 @@ dependencies = [ [[package]] name = "soroban-env-macros" -version = "20.0.2" -source = "git+https://github.com/stellar/rs-soroban-env?rev=81f6eb1eefd299af3e03fa0db7b20eb355b2b55f#81f6eb1eefd299af3e03fa0db7b20eb355b2b55f" +version = "20.1.0" +source = "git+https://github.com/stellar/rs-soroban-env?rev=36d33cb6c986c9a8a9200b7eb04cf02e2c3f0ef4#36d33cb6c986c9a8a9200b7eb04cf02e2c3f0ef4" +dependencies = [ + "itertools 0.11.0", + "proc-macro2", + "quote", + "serde", + "serde_json", + "stellar-xdr", + "syn 2.0.39", +] + +[[package]] +name = "soroban-env-macros" +version = "20.1.0" +source = "git+https://github.com/stellar/rs-soroban-env?rev=c1b238b65bfd13666be4ac14e0e390c31b549caf#c1b238b65bfd13666be4ac14e0e390c31b549caf" dependencies = [ "itertools 0.11.0", "proc-macro2", @@ -2626,21 +2691,21 @@ version = "20.1.1" [[package]] name = "soroban-ledger-snapshot" -version = "20.0.3" -source = "git+https://github.com/stellar/rs-soroban-sdk?rev=93b09e42e4efa841cbd034c0bff0dc362765086c#93b09e42e4efa841cbd034c0bff0dc362765086c" +version = "20.1.0" +source = "git+https://github.com/stellar/rs-soroban-sdk?rev=811ce3da801c03a21d5fa80fda187c0f1012240f#811ce3da801c03a21d5fa80fda187c0f1012240f" dependencies = [ "serde", "serde_json", "serde_with", - "soroban-env-common", - "soroban-env-host", + "soroban-env-common 20.1.0 (git+https://github.com/stellar/rs-soroban-env?rev=c1b238b65bfd13666be4ac14e0e390c31b549caf)", + "soroban-env-host 20.1.0 (git+https://github.com/stellar/rs-soroban-env?rev=c1b238b65bfd13666be4ac14e0e390c31b549caf)", "thiserror", ] [[package]] name = "soroban-sdk" -version = "20.0.3" -source = "git+https://github.com/stellar/rs-soroban-sdk?rev=93b09e42e4efa841cbd034c0bff0dc362765086c#93b09e42e4efa841cbd034c0bff0dc362765086c" +version = "20.1.0" +source = "git+https://github.com/stellar/rs-soroban-sdk?rev=811ce3da801c03a21d5fa80fda187c0f1012240f#811ce3da801c03a21d5fa80fda187c0f1012240f" dependencies = [ "arbitrary", "bytes-lit", @@ -2650,7 +2715,7 @@ dependencies = [ "serde", "serde_json", "soroban-env-guest", - "soroban-env-host", + "soroban-env-host 20.1.0 (git+https://github.com/stellar/rs-soroban-env?rev=c1b238b65bfd13666be4ac14e0e390c31b549caf)", "soroban-ledger-snapshot", "soroban-sdk-macros", "stellar-strkey 0.0.8", @@ -2658,8 +2723,8 @@ dependencies = [ [[package]] name = "soroban-sdk-macros" -version = "20.0.3" -source = "git+https://github.com/stellar/rs-soroban-sdk?rev=93b09e42e4efa841cbd034c0bff0dc362765086c#93b09e42e4efa841cbd034c0bff0dc362765086c" +version = "20.1.0" +source = "git+https://github.com/stellar/rs-soroban-sdk?rev=811ce3da801c03a21d5fa80fda187c0f1012240f#811ce3da801c03a21d5fa80fda187c0f1012240f" dependencies = [ "crate-git-revision 0.0.6", "darling", @@ -2668,17 +2733,29 @@ dependencies = [ "quote", "rustc_version", "sha2 0.10.8", - "soroban-env-common", + "soroban-env-common 20.1.0 (git+https://github.com/stellar/rs-soroban-env?rev=c1b238b65bfd13666be4ac14e0e390c31b549caf)", "soroban-spec", "soroban-spec-rust", "stellar-xdr", "syn 2.0.39", ] +[[package]] +name = "soroban-simulation" +version = "20.1.0" +source = "git+https://github.com/stellar/rs-soroban-env?rev=36d33cb6c986c9a8a9200b7eb04cf02e2c3f0ef4#36d33cb6c986c9a8a9200b7eb04cf02e2c3f0ef4" +dependencies = [ + "anyhow", + "rand", + "soroban-env-host 20.1.0 (git+https://github.com/stellar/rs-soroban-env?rev=36d33cb6c986c9a8a9200b7eb04cf02e2c3f0ef4)", + "static_assertions", + "thiserror", +] + [[package]] name = "soroban-spec" -version = "20.0.3" -source = "git+https://github.com/stellar/rs-soroban-sdk?rev=93b09e42e4efa841cbd034c0bff0dc362765086c#93b09e42e4efa841cbd034c0bff0dc362765086c" +version = "20.1.0" +source = "git+https://github.com/stellar/rs-soroban-sdk?rev=811ce3da801c03a21d5fa80fda187c0f1012240f#811ce3da801c03a21d5fa80fda187c0f1012240f" dependencies = [ "base64 0.13.1", "stellar-xdr", @@ -2702,8 +2779,8 @@ dependencies = [ [[package]] name = "soroban-spec-rust" -version = "20.0.3" -source = "git+https://github.com/stellar/rs-soroban-sdk?rev=93b09e42e4efa841cbd034c0bff0dc362765086c#93b09e42e4efa841cbd034c0bff0dc362765086c" +version = "20.1.0" +source = "git+https://github.com/stellar/rs-soroban-sdk?rev=811ce3da801c03a21d5fa80fda187c0f1012240f#811ce3da801c03a21d5fa80fda187c0f1012240f" dependencies = [ "prettyplease", "proc-macro2", @@ -2766,7 +2843,7 @@ dependencies = [ "serde_json", "sha2 0.10.8", "soroban-cli", - "soroban-env-host", + "soroban-env-host 20.1.0 (git+https://github.com/stellar/rs-soroban-env?rev=36d33cb6c986c9a8a9200b7eb04cf02e2c3f0ef4)", "soroban-ledger-snapshot", "soroban-sdk", "soroban-spec", @@ -2779,8 +2856,8 @@ dependencies = [ [[package]] name = "soroban-token-sdk" -version = "20.0.3" -source = "git+https://github.com/stellar/rs-soroban-sdk?rev=93b09e42e4efa841cbd034c0bff0dc362765086c#93b09e42e4efa841cbd034c0bff0dc362765086c" +version = "20.1.0" +source = "git+https://github.com/stellar/rs-soroban-sdk?rev=811ce3da801c03a21d5fa80fda187c0f1012240f#811ce3da801c03a21d5fa80fda187c0f1012240f" dependencies = [ "soroban-sdk", ] diff --git a/Cargo.toml b/Cargo.toml index e0a4ee2a20..6e7f9da3ee 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,20 +14,27 @@ exclude = ["cmd/crates/soroban-test/tests/fixtures/hello"] version = "20.1.1" [workspace.dependencies.soroban-env-host] -version = "=20.0.2" +version = "=20.1.0" +git = "https://github.com/stellar/rs-soroban-env" +rev = "36d33cb6c986c9a8a9200b7eb04cf02e2c3f0ef4" +# path = "../rs-soroban-env/soroban-env-host" + +[workspace.dependencies.soroban-simulation] +version = "=20.1.0" git = "https://github.com/stellar/rs-soroban-env" -rev = "81f6eb1eefd299af3e03fa0db7b20eb355b2b55f" +rev = "36d33cb6c986c9a8a9200b7eb04cf02e2c3f0ef4" +# path = "../rs-soroban-env/soroban-simulation" [workspace.dependencies.soroban-spec] -version = "=20.0.3" +version = "=20.1.0" git = "https://github.com/stellar/rs-soroban-sdk" -rev = "93b09e42e4efa841cbd034c0bff0dc362765086c" +rev = "811ce3da801c03a21d5fa80fda187c0f1012240f" # path = "../rs-soroban-sdk/soroban-spec" [workspace.dependencies.soroban-spec-rust] -version = "=20.0.3" +version = "=20.1.0" git = "https://github.com/stellar/rs-soroban-sdk" -rev = "93b09e42e4efa841cbd034c0bff0dc362765086c" +rev = "811ce3da801c03a21d5fa80fda187c0f1012240f" # path = "../rs-soroban-sdk/soroban-spec-rust" [workspace.dependencies.soroban-spec-json] @@ -43,19 +50,19 @@ version = "20.1.1" path = "./cmd/crates/soroban-spec-tools" [workspace.dependencies.soroban-sdk] -version = "=20.0.3" +version = "=20.1.0" git = "https://github.com/stellar/rs-soroban-sdk" -rev = "93b09e42e4efa841cbd034c0bff0dc362765086c" +rev = "811ce3da801c03a21d5fa80fda187c0f1012240f" [workspace.dependencies.soroban-token-sdk] -version = "=20.0.3" +version = "=20.1.0" git = "https://github.com/stellar/rs-soroban-sdk" -rev = "93b09e42e4efa841cbd034c0bff0dc362765086c" +rev = "811ce3da801c03a21d5fa80fda187c0f1012240f" [workspace.dependencies.soroban-ledger-snapshot] -version = "=20.0.3" +version = "=20.1.0" git = "https://github.com/stellar/rs-soroban-sdk" -rev = "93b09e42e4efa841cbd034c0bff0dc362765086c" +rev = "811ce3da801c03a21d5fa80fda187c0f1012240f" [workspace.dependencies.soroban-cli] version = "20.1.1" diff --git a/cmd/soroban-rpc/lib/preflight/Cargo.toml b/cmd/soroban-rpc/lib/preflight/Cargo.toml index e78939588f..d84122b60b 100644 --- a/cmd/soroban-rpc/lib/preflight/Cargo.toml +++ b/cmd/soroban-rpc/lib/preflight/Cargo.toml @@ -7,12 +7,10 @@ publish = false crate-type = ["staticlib"] [dependencies] -anyhow = "1.0.75" base64 = { workspace = true } -thiserror = { workspace = true } 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_auth", "testutils"]} -rand = "0.8.5" +soroban-simulation = { workspace = true } diff --git a/cmd/soroban-rpc/lib/preflight/src/fees.rs b/cmd/soroban-rpc/lib/preflight/src/fees.rs deleted file mode 100644 index 7bb392cbce..0000000000 --- a/cmd/soroban-rpc/lib/preflight/src/fees.rs +++ /dev/null @@ -1,498 +0,0 @@ -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, TtlEntryMap, -}; -use soroban_env_host::fees::{ - compute_rent_fee, compute_transaction_resource_fee, compute_write_fee_per_1kb, - FeeConfiguration, LedgerEntryRentChange, RentFeeConfiguration, TransactionResources, - WriteFeeConfiguration, -}; -use soroban_env_host::storage::{AccessType, Footprint, Storage}; -use soroban_env_host::xdr; -use soroban_env_host::xdr::ContractDataDurability::Persistent; -use soroban_env_host::xdr::{ - ConfigSettingEntry, ConfigSettingId, ContractEventType, DecoratedSignature, DiagnosticEvent, - ExtendFootprintTtlOp, ExtensionPoint, InvokeHostFunctionOp, LedgerFootprint, LedgerKey, Limits, - Memo, MuxedAccount, MuxedAccountMed25519, Operation, OperationBody, Preconditions, - RestoreFootprintOp, ScVal, SequenceNumber, Signature, SignatureHint, SorobanResources, - SorobanTransactionData, Transaction, TransactionExt, TransactionV1Envelope, Uint256, WriteXdr, -}; -use state_ttl::{get_restored_ledger_sequence, TTLLedgerEntry}; -use std::cmp::max; -use std::convert::{TryFrom, TryInto}; - -use crate::CResourceConfig; - -#[allow(clippy::too_many_arguments)] -pub(crate) fn compute_host_function_transaction_data_and_min_fee( - op: &InvokeHostFunctionOp, - pre_storage: &LedgerStorage, - post_storage: &Storage, - budget: &Budget, - resource_config: CResourceConfig, - events: &[DiagnosticEvent], - invocation_result: &ScVal, - bucket_list_size: u64, - current_ledger_seq: u32, -) -> Result<(SorobanTransactionData, i64)> { - let ledger_changes = get_ledger_changes(budget, post_storage, pre_storage, TtlEntryMap::new())?; - let soroban_resources = calculate_host_function_soroban_resources( - &ledger_changes, - &post_storage.footprint, - budget, - resource_config, - ) - .context("cannot compute host function resources")?; - - let contract_events_size = - calculate_contract_events_size_bytes(events).context("cannot calculate events size")?; - let invocation_return_size = u32::try_from(invocation_result.to_xdr(Limits::none())?.len())?; - // This is totally unintuitive, but it's what's expected by the library - let final_contract_events_size = contract_events_size + invocation_return_size; - - let transaction_resources = TransactionResources { - instructions: soroban_resources.instructions, - read_entries: u32::try_from(soroban_resources.footprint.read_only.as_vec().len())?, - write_entries: u32::try_from(soroban_resources.footprint.read_write.as_vec().len())?, - read_bytes: soroban_resources.read_bytes, - write_bytes: soroban_resources.write_bytes, - // Note: we could get a better transaction size if the full transaction was passed down to libpreflight - 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: final_contract_events_size, - }; - let rent_changes = extract_rent_changes(&ledger_changes); - - finalize_transaction_data_and_min_fee( - pre_storage, - &transaction_resources, - soroban_resources, - &rent_changes, - current_ledger_seq, - bucket_list_size, - ) -} - -fn estimate_max_transaction_size_for_operation( - op: &OperationBody, - fp: &LedgerFootprint, -) -> Result { - let source = MuxedAccount::MuxedEd25519(MuxedAccountMed25519 { - id: 0, - ed25519: Uint256([0; 32]), - }); - // generate the maximum memo size and signature size - // TODO: is this being too conservative? - let memo_text: Vec = [0; 28].into(); - let signatures: Vec = vec![ - DecoratedSignature { - hint: SignatureHint([0; 4]), - signature: Signature::default(), - }; - 20 - ]; - let envelope = TransactionV1Envelope { - tx: Transaction { - source_account: source.clone(), - fee: 0, - seq_num: SequenceNumber(0), - cond: Preconditions::None, - memo: Memo::Text(memo_text.try_into()?), - operations: vec![Operation { - source_account: Some(source), - body: op.clone(), - }] - .try_into()?, - ext: TransactionExt::V1(SorobanTransactionData { - resources: SorobanResources { - footprint: fp.clone(), - instructions: 0, - read_bytes: 0, - write_bytes: 0, - }, - resource_fee: 0, - ext: ExtensionPoint::V0, - }), - }, - signatures: signatures.try_into()?, - }; - - let envelope_xdr = envelope.to_xdr(Limits::none())?; - let envelope_size = envelope_xdr.len(); - - // Add a 15% leeway - let envelope_size = envelope_size * 115 / 100; - Ok(u32::try_from(envelope_size)?) -} - -#[allow(clippy::cast_possible_truncation)] -fn calculate_host_function_soroban_resources( - ledger_changes: &[LedgerEntryChange], - footprint: &Footprint, - budget: &Budget, - resource_config: CResourceConfig, -) -> 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.old_entry_size_bytes).sum(); - - let write_bytes: u32 = ledger_changes - .iter() - .map(|c| c.encoded_new_value.as_ref().map_or(0, Vec::len) as u32) - .sum(); - - // Add a 20% leeway with a minimum of 3 million instructions - let budget_instructions = budget - .get_cpu_insns_consumed() - .context("cannot get instructions consumed")?; - let instructions = max( - budget_instructions + resource_config.instruction_leeway, - budget_instructions * 120 / 100, - ); - Ok(SorobanResources { - footprint: ledger_footprint, - instructions: u32::try_from(instructions)?, - read_bytes, - write_bytes, - }) -} - -#[allow(clippy::cast_possible_wrap)] -fn get_fee_configurations( - ledger_storage: &LedgerStorage, - bucket_list_size: u64, -) -> Result<(FeeConfiguration, RentFeeConfiguration)> { - let ConfigSettingEntry::ContractComputeV0(compute) = - ledger_storage.get_configuration_setting(ConfigSettingId::ContractComputeV0)? - else { - bail!("unexpected config setting entry for ComputeV0 key"); - }; - - let ConfigSettingEntry::ContractLedgerCostV0(ledger_cost) = - ledger_storage.get_configuration_setting(ConfigSettingId::ContractLedgerCostV0)? - else { - bail!("unexpected config setting entry for LedgerCostV0 key"); - }; - - let ConfigSettingEntry::ContractHistoricalDataV0(historical_data) = - ledger_storage.get_configuration_setting(ConfigSettingId::ContractHistoricalDataV0)? - else { - bail!("unexpected config setting entry for HistoricalDataV0 key"); - }; - - let ConfigSettingEntry::ContractEventsV0(events) = - ledger_storage.get_configuration_setting(ConfigSettingId::ContractEventsV0)? - else { - bail!("unexpected config setting entry for EventsV0 key"); - }; - - let ConfigSettingEntry::ContractBandwidthV0(bandwidth) = - ledger_storage.get_configuration_setting(ConfigSettingId::ContractBandwidthV0)? - else { - bail!("unexpected config setting entry for BandwidthV0 key"); - }; - - let ConfigSettingEntry::StateArchival(state_archival) = - ledger_storage.get_configuration_setting(ConfigSettingId::StateArchival)? - else { - bail!("unexpected config setting entry for StateArchival key"); - }; - - let write_fee_configuration = WriteFeeConfiguration { - bucket_list_target_size_bytes: ledger_cost.bucket_list_target_size_bytes, - write_fee_1kb_bucket_list_low: ledger_cost.write_fee1_kb_bucket_list_low, - write_fee_1kb_bucket_list_high: ledger_cost.write_fee1_kb_bucket_list_high, - bucket_list_write_fee_growth_factor: ledger_cost.bucket_list_write_fee_growth_factor, - }; - - let write_fee_per_1kb = - compute_write_fee_per_1kb(bucket_list_size as i64, &write_fee_configuration); - - let fee_configuration = FeeConfiguration { - fee_per_instruction_increment: compute.fee_rate_per_instructions_increment, - fee_per_read_entry: ledger_cost.fee_read_ledger_entry, - fee_per_write_entry: ledger_cost.fee_write_ledger_entry, - fee_per_read_1kb: ledger_cost.fee_read1_kb, - fee_per_write_1kb: write_fee_per_1kb, - fee_per_historical_1kb: historical_data.fee_historical1_kb, - fee_per_contract_event_1kb: events.fee_contract_events1_kb, - fee_per_transaction_size_1kb: bandwidth.fee_tx_size1_kb, - }; - let rent_fee_configuration = RentFeeConfiguration { - fee_per_write_1kb: write_fee_per_1kb, - fee_per_write_entry: ledger_cost.fee_write_ledger_entry, - persistent_rent_rate_denominator: state_archival.persistent_rent_rate_denominator, - temporary_rent_rate_denominator: state_archival.temp_rent_rate_denominator, - }; - Ok((fee_configuration, rent_fee_configuration)) -} - -#[allow(clippy::cast_possible_truncation)] -fn calculate_unmodified_ledger_entry_bytes( - ledger_entries: &[LedgerKey], - pre_storage: &LedgerStorage, - include_not_live: bool, -) -> Result { - let mut res: usize = 0; - for lk in ledger_entries { - let entry_xdr = pre_storage - .get_xdr(lk, include_not_live) - .with_context(|| format!("cannot get xdr of ledger entry with key {lk:?}"))?; - let entry_size = entry_xdr.len(); - res += entry_size; - } - Ok(res as u32) -} - -fn calculate_contract_events_size_bytes(events: &[DiagnosticEvent]) -> Result { - let mut res: u32 = 0; - for e in events { - if e.event.type_ != ContractEventType::Contract - && e.event.type_ != ContractEventType::System - { - continue; - } - let event_xdr = e - .to_xdr(Limits::none()) - .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::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()), - AccessType::ReadWrite => read_write.push((**k).clone()), - } - } - Ok(LedgerFootprint { - read_only: read_only.try_into()?, - read_write: read_write.try_into()?, - }) -} - -fn finalize_transaction_data_and_min_fee( - pre_storage: &LedgerStorage, - transaction_resources: &TransactionResources, - soroban_resources: SorobanResources, - rent_changes: &Vec, - current_ledger_seq: u32, - bucket_list_size: u64, -) -> Result<(SorobanTransactionData, i64)> { - let (fee_configuration, rent_fee_configuration) = - 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); - let resource_fee = refundable_fee + non_refundable_fee + rent_fee; - let transaction_data = SorobanTransactionData { - resources: soroban_resources, - resource_fee, - ext: ExtensionPoint::V0, - }; - let res = (transaction_data, resource_fee); - Ok(res) -} - -pub(crate) fn compute_extend_footprint_ttl_transaction_data_and_min_fee( - footprint: LedgerFootprint, - extend_to: u32, - ledger_storage: &LedgerStorage, - bucket_list_size: u64, - current_ledger_seq: u32, -) -> Result<(SorobanTransactionData, i64)> { - let rent_changes = compute_extend_footprint_rent_changes( - &footprint, - ledger_storage, - extend_to, - current_ledger_seq, - ) - .context("cannot compute extend rent changes")?; - - let unmodified_entry_bytes = calculate_unmodified_ledger_entry_bytes( - footprint.read_only.as_slice(), - ledger_storage, - false, - ) - .context("cannot calculate read_bytes resource")?; - - let soroban_resources = SorobanResources { - footprint, - instructions: 0, - read_bytes: unmodified_entry_bytes, - write_bytes: 0, - }; - let transaction_size_bytes = estimate_max_transaction_size_for_operation( - &OperationBody::ExtendFootprintTtl(ExtendFootprintTtlOp { - ext: ExtensionPoint::V0, - extend_to, - }), - &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, - contract_events_size_bytes: 0, - }; - finalize_transaction_data_and_min_fee( - ledger_storage, - &transaction_resources, - soroban_resources, - &rent_changes, - current_ledger_seq, - bucket_list_size, - ) -} - -#[allow(clippy::cast_possible_truncation)] -fn compute_extend_footprint_rent_changes( - footprint: &LedgerFootprint, - ledger_storage: &LedgerStorage, - extend_to: u32, - current_ledger_seq: u32, -) -> Result> { - let mut rent_changes: Vec = - Vec::with_capacity(footprint.read_only.len()); - for key in footprint.read_only.as_slice() { - let unmodified_entry_and_ttl = ledger_storage.get(key, false).with_context(|| { - format!("cannot find extend footprint ledger entry with key {key:?}") - })?; - let size = (key.to_xdr(Limits::none())?.len() - + unmodified_entry_and_ttl.0.to_xdr(Limits::none())?.len()) as u32; - let ttl_entry: Box = - (&unmodified_entry_and_ttl) - .try_into() - .map_err(|e: String| { - Error::msg(e.clone()).context("incorrect ledger entry type in footprint") - })?; - let new_live_until_ledger = current_ledger_seq + extend_to; - if new_live_until_ledger <= ttl_entry.live_until_ledger_seq() { - // The extend would be ineffective - continue; - } - let rent_change = LedgerEntryRentChange { - is_persistent: ttl_entry.durability() == Persistent, - old_size_bytes: size, - new_size_bytes: size, - old_live_until_ledger: ttl_entry.live_until_ledger_seq(), - new_live_until_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::StateArchival(state_archival) = - ledger_storage.get_configuration_setting(ConfigSettingId::StateArchival)? - else { - bail!("unexpected config setting entry for StateArchival key"); - }; - let rent_changes = compute_restore_footprint_rent_changes( - &footprint, - ledger_storage, - state_archival.min_persistent_ttl, - 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, - read_bytes: write_bytes, - write_bytes, - }; - 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: 0, - write_entries: u32::try_from(soroban_resources.footprint.read_write.as_vec().len())?, - read_bytes: soroban_resources.read_bytes, - write_bytes: soroban_resources.write_bytes, - transaction_size_bytes, - contract_events_size_bytes: 0, - }; - finalize_transaction_data_and_min_fee( - ledger_storage, - &transaction_resources, - soroban_resources, - &rent_changes, - current_ledger_seq, - bucket_list_size, - ) -} - -#[allow(clippy::cast_possible_truncation)] -fn compute_restore_footprint_rent_changes( - footprint: &LedgerFootprint, - ledger_storage: &LedgerStorage, - min_persistent_ttl: 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_and_ttl = ledger_storage.get(key, true).with_context(|| { - format!("cannot find restore footprint ledger entry with key {key:?}") - })?; - let size = (key.to_xdr(Limits::none())?.len() - + unmodified_entry_and_ttl.0.to_xdr(Limits::none())?.len()) as u32; - let ttl_entry: Box = - (&unmodified_entry_and_ttl) - .try_into() - .map_err(|e: String| { - Error::msg(e.clone()).context("incorrect ledger entry type in footprint") - })?; - ensure!( - ttl_entry.durability() == Persistent, - "non-persistent entry in footprint: key = {key:?}" - ); - if ttl_entry.is_live(current_ledger_seq) { - // noop (the entry is alive) - continue; - } - let new_live_until_ledger = - get_restored_ledger_sequence(current_ledger_seq, min_persistent_ttl); - let rent_change = LedgerEntryRentChange { - is_persistent: true, - old_size_bytes: 0, - new_size_bytes: size, - old_live_until_ledger: 0, - new_live_until_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 deleted file mode 100644 index 264355a140..0000000000 --- a/cmd/soroban-rpc/lib/preflight/src/ledger_storage.rs +++ /dev/null @@ -1,262 +0,0 @@ -use sha2::Digest; -use soroban_env_host::storage::SnapshotSource; -use soroban_env_host::xdr::ContractDataDurability::{Persistent, Temporary}; -use soroban_env_host::xdr::{ - ConfigSettingEntry, ConfigSettingId, Error as XdrError, Hash, LedgerEntry, LedgerEntryData, - LedgerKey, LedgerKeyConfigSetting, LedgerKeyTtl, Limits, ReadXdr, ScError, ScErrorCode, - TtlEntry, WriteXdr, -}; -use soroban_env_host::HostError; -use state_ttl::{get_restored_ledger_sequence, is_live, TTLLedgerEntry}; -use std::cell::RefCell; -use std::collections::HashSet; -use std::convert::TryInto; -use std::ffi::NulError; -use std::rc::Rc; -use std::str::Utf8Error; -use {from_c_xdr, CXDR}; - -// Functions imported from Golang -extern "C" { - // Free Strings returned from Go functions - fn FreeGoXDR(xdr: CXDR); - // LedgerKey XDR in base64 string to LedgerEntry XDR in base64 string - fn SnapshotSourceGet(handle: libc::uintptr_t, ledger_key: CXDR) -> CXDR; -} - -#[derive(thiserror::Error, Debug)] -pub(crate) enum Error { - #[error("not found")] - NotFound, - #[error("entry is not live")] - NotLive, - #[error("xdr processing error: {0}")] - Xdr(#[from] XdrError), - #[error("nul error: {0}")] - NulError(#[from] NulError), - #[error("utf8 error: {0}")] - Utf8Error(#[from] Utf8Error), - #[error("unexpected config ledger entry for setting_id {setting_id}")] - UnexpectedConfigLedgerEntry { setting_id: String }, - #[error("unexpected ledger entry type ({ledger_entry_type}) for ttl ledger key")] - UnexpectedLedgerEntryTypeForTtlKey { ledger_entry_type: String }, -} - -impl From for HostError { - fn from(value: Error) -> Self { - match value { - Error::NotFound | Error::NotLive => ScError::Storage(ScErrorCode::MissingValue).into(), - Error::Xdr(_) => ScError::Value(ScErrorCode::InvalidInput).into(), - _ => ScError::Context(ScErrorCode::InternalError).into(), - } - } -} - -struct EntryRestoreTracker { - min_persistent_ttl: u32, - // RefCell is needed to mutate the hashset inside SnapshotSource::get(), which is an immutable method - ledger_keys_requiring_restore: RefCell>, -} - -impl EntryRestoreTracker { - // Tracks ledger entries which need to be restored and returns its ttl as it was restored - pub(crate) fn track_and_restore( - &self, - current_ledger_sequence: u32, - key: &LedgerKey, - entry_and_ttl: &(LedgerEntry, Option), - ) -> Option { - let ttl_entry: Box = match entry_and_ttl.try_into() { - Ok(e) => e, - Err(_) => { - // Nothing to track, the entry does not have a ttl - return None; - } - }; - if ttl_entry.durability() != Persistent || ttl_entry.is_live(current_ledger_sequence) { - // Nothing to track, the entry isn't persistent (and thus not restorable) or - // it is alive - return Some(ttl_entry.live_until_ledger_seq()); - } - self.ledger_keys_requiring_restore - .borrow_mut() - .insert(key.clone()); - Some(get_restored_ledger_sequence( - current_ledger_sequence, - self.min_persistent_ttl, - )) - } -} - -pub(crate) struct LedgerStorage { - golang_handle: libc::uintptr_t, - current_ledger_sequence: u32, - restore_tracker: Option, -} - -impl LedgerStorage { - pub(crate) fn new(golang_handle: libc::uintptr_t, current_ledger_sequence: u32) -> Self { - LedgerStorage { - golang_handle, - current_ledger_sequence, - restore_tracker: None, - } - } - - pub(crate) fn with_restore_tracking( - golang_handle: libc::uintptr_t, - current_ledger_sequence: u32, - ) -> Result { - // First, we initialize it without the tracker, to get the minimum restore ledger from the network - let mut ledger_storage = LedgerStorage { - golang_handle, - current_ledger_sequence, - restore_tracker: None, - }; - let setting_id = ConfigSettingId::StateArchival; - let ConfigSettingEntry::StateArchival(state_archival) = - ledger_storage.get_configuration_setting(setting_id)? - else { - return Err(Error::UnexpectedConfigLedgerEntry { - setting_id: setting_id.name().to_string(), - }); - }; - // Now that we have the state archival config, we can build the tracker - ledger_storage.restore_tracker = Some(EntryRestoreTracker { - ledger_keys_requiring_restore: RefCell::new(HashSet::new()), - min_persistent_ttl: state_archival.min_persistent_ttl, - }); - Ok(ledger_storage) - } - - // Get the XDR, regardless of ttl - fn get_xdr_internal(&self, key_xdr: &mut Vec) -> Result, Error> { - 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(Error::NotFound); - } - let v = from_c_xdr(res); - unsafe { FreeGoXDR(res) }; - Ok(v) - } - - pub(crate) fn get( - &self, - key: &LedgerKey, - include_not_live: bool, - ) -> Result<(LedgerEntry, Option), Error> { - let mut key_xdr = key.to_xdr(Limits::none())?; - let xdr = self.get_xdr_internal(&mut key_xdr)?; - - let live_until_ledger_seq = match key { - // TODO: it would probably be more efficient to do all of this in the Go side - // (e.g. it would allow us to query multiple entries at once) - LedgerKey::ContractData(_) | LedgerKey::ContractCode(_) => { - let key_hash: [u8; 32] = sha2::Sha256::digest(key_xdr).into(); - 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 { - live_until_ledger_seq, - .. - }) = ttl_entry.data - { - Some(live_until_ledger_seq) - } else { - return Err(Error::UnexpectedLedgerEntryTypeForTtlKey { - ledger_entry_type: ttl_entry.data.name().to_string(), - }); - } - } - _ => None, - }; - - if !include_not_live - && live_until_ledger_seq.is_some() - && !is_live(live_until_ledger_seq.unwrap(), self.current_ledger_sequence) - { - return Err(Error::NotLive); - } - - let entry = LedgerEntry::from_xdr(xdr, Limits::none())?; - Ok((entry, live_until_ledger_seq)) - } - - pub(crate) fn get_xdr( - &self, - key: &LedgerKey, - include_not_live: bool, - ) -> Result, Error> { - // TODO: this can be optimized since for entry types other than ContractCode/ContractData, - // they don't need to be deserialized and serialized again - let (entry, _) = self.get(key, include_not_live)?; - Ok(entry.to_xdr(Limits::none())?) - } - - pub(crate) fn get_configuration_setting( - &self, - setting_id: ConfigSettingId, - ) -> Result { - let key = LedgerKey::ConfigSetting(LedgerKeyConfigSetting { - config_setting_id: setting_id, - }); - match self.get(&key, false)? { - ( - LedgerEntry { - data: LedgerEntryData::ConfigSetting(cs), - .. - }, - _, - ) => Ok(cs), - _ => Err(Error::UnexpectedConfigLedgerEntry { - setting_id: setting_id.name().to_string(), - }), - } - } - - pub(crate) fn get_ledger_keys_requiring_restore(&self) -> HashSet { - match self.restore_tracker { - Some(ref t) => t.ledger_keys_requiring_restore.borrow().clone(), - None => HashSet::new(), - } - } -} - -impl SnapshotSource for LedgerStorage { - fn get(&self, key: &Rc) -> Result<(Rc, Option), HostError> { - if let Some(ref tracker) = self.restore_tracker { - let mut entry_and_ttl = self.get(key, true)?; - // Explicitly discard temporary ttl'ed entries - if let Ok(ttl_entry) = TryInto::>::try_into(&entry_and_ttl) { - if ttl_entry.durability() == Temporary - && !ttl_entry.is_live(self.current_ledger_sequence) - { - return Err(HostError::from(Error::NotLive)); - } - } - // If the entry is not live, we modify the ttl to make it seem like it was restored - entry_and_ttl.1 = - tracker.track_and_restore(self.current_ledger_sequence, key, &entry_and_ttl); - return Ok((entry_and_ttl.0.into(), entry_and_ttl.1)); - } - let entry_and_ttl = ::get(self, key, false).map_err(HostError::from)?; - Ok((entry_and_ttl.0.into(), entry_and_ttl.1)) - } - - fn has(&self, key: &Rc) -> Result { - let result = ::get(self, key); - if let Err(ref host_error) = result { - if host_error.error.is_code(ScErrorCode::MissingValue) { - return Ok(false); - } - } - result.map(|_| true) - } -} diff --git a/cmd/soroban-rpc/lib/preflight/src/lib.rs b/cmd/soroban-rpc/lib/preflight/src/lib.rs index e2c51c2db8..25746e7450 100644 --- a/cmd/soroban-rpc/lib/preflight/src/lib.rs +++ b/cmd/soroban-rpc/lib/preflight/src/lib.rs @@ -1,22 +1,20 @@ -mod fees; -mod ledger_storage; -mod preflight; -mod state_ttl; - -extern crate anyhow; extern crate base64; extern crate libc; extern crate sha2; extern crate soroban_env_host; +extern crate soroban_simulation; -use anyhow::{Context, Result}; -use ledger_storage::LedgerStorage; -use preflight::PreflightResult; use sha2::{Digest, Sha256}; use soroban_env_host::xdr::{ - AccountId, InvokeHostFunctionOp, LedgerFootprint, Limits, OperationBody, ReadXdr, WriteXdr, + AccountId, Hash, InvokeHostFunctionOp, LedgerEntry, LedgerEntryData, LedgerFootprint, + LedgerKey, LedgerKeyTtl, Limits, OperationBody, ReadXdr, 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 std::error::Error; use std::ffi::{CStr, CString}; use std::panic; use std::ptr::null_mut; @@ -87,6 +85,14 @@ 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 { @@ -110,21 +116,21 @@ pub struct CPreflightResult { pub pre_restore_min_fee: i64, } -impl From for CPreflightResult { - fn from(p: PreflightResult) -> Self { +impl From for CPreflightResult { + fn from(s: SimulationResult) -> Self { let mut result = Self { - error: string_to_c(p.error), - auth: xdr_vec_to_c(p.auth), - result: option_xdr_to_c(p.result), - transaction_data: option_xdr_to_c(p.transaction_data), - min_fee: p.min_fee, - events: xdr_vec_to_c(p.events), - cpu_instructions: p.cpu_instructions, - memory_bytes: p.memory_bytes, + 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, pre_restore_transaction_data: get_default_c_xdr(), pre_restore_min_fee: 0, }; - if let Some(p) = p.restore_preamble { + if let Some(p) = s.restore_preamble { result.pre_restore_min_fee = p.min_fee; result.pre_restore_transaction_data = xdr_to_c(p.transaction_data); }; @@ -163,22 +169,29 @@ fn preflight_invoke_hf_op_or_maybe_panic( 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 ledger_storage = LedgerStorage::with_restore_tracking(handle, ledger_info.sequence_number) - .context("cannot create LedgerStorage")?; - let result = preflight::preflight_invoke_hf_op( + let go_storage = GoLedgerStorage { + golang_handle: handle, + current_ledger_sequence: ledger_info.sequence_number, + }; + 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, + resource_config.into(), enable_debug, - )?; - Ok(result.into()) + ); + match result { + Ok(r) => Ok(r.into()), + Err(e) => Err(e), + } } #[no_mangle] @@ -206,18 +219,25 @@ fn preflight_footprint_ttl_op_or_maybe_panic( op_body: CXDR, footprint: CXDR, current_ledger_seq: u32, -) -> Result { +) -> 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 ledger_storage = &LedgerStorage::new(handle, current_ledger_seq); - let result = preflight::preflight_footprint_ttl_op( + let go_storage = GoLedgerStorage { + golang_handle: handle, + current_ledger_sequence: current_ledger_seq, + }; + 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, - )?; - Ok(result.into()) + ); + match result { + Ok(r) => Ok(r.into()), + Err(e) => Err(e), + } } fn preflight_error(str: String) -> CPreflightResult { @@ -236,10 +256,12 @@ fn preflight_error(str: String) -> CPreflightResult { } } -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::() { @@ -352,3 +374,87 @@ fn from_c_xdr(xdr: CXDR) -> Vec { let s = unsafe { slice::from_raw_parts(xdr.xdr, xdr.len) }; s.to_vec() } + +// Functions imported from Golang +extern "C" { + // Free Strings returned from Go functions + fn FreeGoXDR(xdr: CXDR); + // LedgerKey XDR in base64 string to LedgerEntry XDR in base64 string + fn SnapshotSourceGet(handle: libc::uintptr_t, ledger_key: CXDR) -> CXDR; +} + +struct GoLedgerStorage { + golang_handle: libc::uintptr_t, + current_ledger_sequence: u32, +} + +impl GoLedgerStorage { + // Get the XDR, regardless of ttl + fn get_xdr_internal( + &self, + key_xdr: &mut Vec, + ) -> std::result::Result, ledger_storage::Error> { + 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); + } + let v = from_c_xdr(res); + unsafe { FreeGoXDR(res) }; + Ok(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)?; + + let live_until_ledger_seq = match key { + // TODO: it would probably be more efficient to do all of this in the Go side + // (e.g. it would allow us to query multiple entries at once) + LedgerKey::ContractData(_) | LedgerKey::ContractCode(_) => { + let key_hash: [u8; 32] = Sha256::digest(key_xdr).into(); + 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 { + 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(), + }); + } + } + _ => 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, Limits::none())?; + Ok((entry, live_until_ledger_seq)) + } +} + +pub(crate) fn is_live(live_until_ledger_seq: u32, current_ledger_seq: u32) -> bool { + live_until_ledger_seq >= current_ledger_seq +} diff --git a/cmd/soroban-rpc/lib/preflight/src/preflight.rs b/cmd/soroban-rpc/lib/preflight/src/preflight.rs deleted file mode 100644 index cfafe14c7a..0000000000 --- a/cmd/soroban-rpc/lib/preflight/src/preflight.rs +++ /dev/null @@ -1,315 +0,0 @@ -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::collections::HashSet; -use std::convert::{TryFrom, TryInto}; -use std::iter::FromIterator; -use std::rc::Rc; - -use crate::CResourceConfig; - -pub(crate) struct RestorePreamble { - pub(crate) transaction_data: SorobanTransactionData, - pub(crate) min_fee: i64, -} - -#[derive(Default)] -pub(crate) struct PreflightResult { - pub(crate) error: String, - pub(crate) auth: Vec, - pub(crate) result: Option, - pub(crate) transaction_data: Option, - 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, - resource_config: CResourceConfig, - enable_debug: bool, -) -> 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); - host.set_source_account(source_account.clone()) - .context("cannot set source account")?; - if enable_debug { - host.set_diagnostic_level(DiagnosticLevel::Debug) - .context("cannot set debug diagnostic level")?; - } - host.set_ledger_info(ledger_info.clone()) - .context("cannot set ledger info")?; - host.set_base_prng_seed(rand::Rng::gen(&mut rand::thread_rng())) - .context("cannot set base prng seed")?; - - // 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(true) - .context("cannot switch auth to recording mode")?; - } else { - host.set_authorization_entries(invoke_hf_op.auth.to_vec()) - .context("cannot set authorization entries")?; - } - - // Run the preflight. - let maybe_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 result = match maybe_result { - Ok(r) => r, - // If the invocation failed, try to at least add the diagnostic events - Err(e) => { - return Ok(PreflightResult { - // See https://docs.rs/anyhow/latest/anyhow/struct.Error.html#display-representations - error: format!("{e:?}"), - events: diagnostic_events, - ..Default::default() - }); - } - }; - - 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, - resource_config, - &diagnostic_events, - &result, - bucket_list_size, - ledger_info.sequence_number, - ) - .context("cannot compute resources and fees")?; - - let restore_preamble = compute_restore_preamble( - ledger_storage_rc.get_ledger_keys_requiring_restore(), - &ledger_storage_rc, - bucket_list_size, - ledger_info.sequence_number, - ) - .context("cannot compute restore preamble")?; - - Ok(PreflightResult { - auth: auths.to_vec(), - result: Some(result), - transaction_data: Some(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, - ..Default::default() - }) -} - -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 compute_restore_preamble( - entries: HashSet, - ledger_storage: &LedgerStorage, - bucket_list_size: u64, - current_ledger_seq: u32, -) -> Result> { - if entries.is_empty() { - return Ok(None); - } - 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, - current_ledger_seq, - )?; - Ok(Some(RestorePreamble { - transaction_data, - min_fee, - })) -} - -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 -} -#[allow(clippy::cast_sign_loss)] -fn get_budget_from_network_config_params(ledger_storage: &LedgerStorage) -> Result { - let ConfigSettingEntry::ContractComputeV0(compute) = - ledger_storage.get_configuration_setting(ConfigSettingId::ContractComputeV0)? - else { - bail!("unexpected config setting entry for ComputeV0 key"); - }; - - let ConfigSettingEntry::ContractCostParamsCpuInstructions(cost_params_cpu) = ledger_storage - .get_configuration_setting(ConfigSettingId::ContractCostParamsCpuInstructions)? - else { - bail!("unexpected config setting entry for CostParamsCpuInstructions key"); - }; - - let ConfigSettingEntry::ContractCostParamsMemoryBytes(cost_params_memory) = - ledger_storage.get_configuration_setting(ConfigSettingId::ContractCostParamsMemoryBytes)? - else { - bail!("unexpected config setting entry for CostParamsMemoryBytes key"); - }; - let budget = Budget::try_from_configs( - compute.tx_max_instructions as u64, - u64::from(compute.tx_memory_limit), - cost_params_cpu, - cost_params_memory, - ) - .context("cannot create budget from network configuration")?; - Ok(budget) -} - -pub(crate) fn preflight_footprint_ttl_op( - ledger_storage: &LedgerStorage, - bucket_list_size: u64, - op_body: OperationBody, - footprint: LedgerFootprint, - current_ledger_seq: u32, -) -> Result { - match op_body { - OperationBody::ExtendFootprintTtl(op) => preflight_extend_footprint_ttl( - footprint, - op.extend_to, - 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_ttl_op(): unsupported operation type {}", - op.name() - )), - } -} - -fn preflight_extend_footprint_ttl( - footprint: LedgerFootprint, - extend_to: u32, - ledger_storage: &LedgerStorage, - bucket_list_size: u64, - current_ledger_seq: u32, -) -> Result { - let (transaction_data, min_fee) = - fees::compute_extend_footprint_ttl_transaction_data_and_min_fee( - footprint, - extend_to, - ledger_storage, - bucket_list_size, - current_ledger_seq, - )?; - Ok(PreflightResult { - transaction_data: Some(transaction_data), - min_fee, - ..Default::default() - }) -} - -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 { - transaction_data: Some(transaction_data), - min_fee, - ..Default::default() - }) -} diff --git a/cmd/soroban-rpc/lib/preflight/src/state_ttl.rs b/cmd/soroban-rpc/lib/preflight/src/state_ttl.rs deleted file mode 100644 index 5373177c91..0000000000 --- a/cmd/soroban-rpc/lib/preflight/src/state_ttl.rs +++ /dev/null @@ -1,67 +0,0 @@ -use soroban_env_host::xdr::ContractDataDurability::Persistent; -use soroban_env_host::xdr::{ - ContractCodeEntry, ContractDataDurability, ContractDataEntry, LedgerEntry, LedgerEntryData, -}; -use std::convert::TryInto; - -pub(crate) trait TTLLedgerEntry { - fn durability(&self) -> ContractDataDurability; - fn live_until_ledger_seq(&self) -> u32; - fn is_live(&self, current_ledger_seq: u32) -> bool { - is_live(self.live_until_ledger_seq(), current_ledger_seq) - } -} - -impl TTLLedgerEntry for (&ContractCodeEntry, u32) { - fn durability(&self) -> ContractDataDurability { - Persistent - } - - fn live_until_ledger_seq(&self) -> u32 { - self.1 - } -} - -impl TTLLedgerEntry for (&ContractDataEntry, u32) { - fn durability(&self) -> ContractDataDurability { - self.0.durability - } - - fn live_until_ledger_seq(&self) -> u32 { - self.1 - } -} - -// Convert a ledger entry and its Time to live (i.e. live_until_seq) into a TTLLedgerEntry -impl<'a> TryInto> for &'a (LedgerEntry, Option) { - type Error = String; - - fn try_into(self) -> Result, Self::Error> { - match (&self.0.data, self.1) { - (LedgerEntryData::ContractData(d), Some(live_until_seq)) => { - Ok(Box::new((d, live_until_seq))) - } - (LedgerEntryData::ContractCode(c), Some(live_until_seq)) => { - Ok(Box::new((c, live_until_seq))) - } - (LedgerEntryData::ContractData(_) | LedgerEntryData::ContractCode(_), _) => Err( - format!("missing ttl for ledger entry ({})", self.0.data.name()), - ), - _ => Err(format!( - "ledger entry type ({}) cannot have a TTL", - self.0.data.name() - )), - } - } -} - -pub(crate) fn is_live(live_until_ledger_seq: u32, current_ledger_seq: u32) -> bool { - live_until_ledger_seq >= current_ledger_seq -} - -pub(crate) fn get_restored_ledger_sequence( - current_ledger_seq: u32, - min_persistent_ttl: u32, -) -> u32 { - current_ledger_seq + min_persistent_ttl - 1 -}