From dfceb3deb3a4bb7bac2f9bd957d6d9dc94f8af67 Mon Sep 17 00:00:00 2001 From: Jay Geng Date: Thu, 9 Nov 2023 19:12:49 -0500 Subject: [PATCH] Adapt to rs-stellar-xdr `Limits` change (#1139) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ### What - Adapts to https://github.com/stellar/rs-stellar-xdr/pull/317 and runs perf analysis. - Make `DepthLimiter` internal (the depth limiting mechanism for the host stays the same) - Defines the default XDR read/write limits (depth, length). ### Why [TODO: Why this change is being made. Include any context required to understand the why.] ### Known limitations [TODO or N/A] --------- Co-authored-by: Graydon Hoare --- Cargo.lock | 9 +- Cargo.toml | 7 +- soroban-env-common/src/error.rs | 2 +- soroban-env-host/Cargo.toml | 2 +- .../benches/common/cost_types/host_mem_cmp.rs | 10 +- .../benches/common/cost_types/val_deser.rs | 8 +- .../benches/common/cost_types/val_ser.rs | 1 + .../benches/common/experimental/mod.rs | 2 + .../benches/common/experimental/read_xdr.rs | 24 +++++ soroban-env-host/benches/common/mod.rs | 9 +- soroban-env-host/benches/common/util.rs | 7 ++ .../benches/worst_case_linear_models.rs | 7 +- soroban-env-host/src/budget.rs | 52 ++-------- soroban-env-host/src/budget/limits.rs | 99 +++++++++++++++++++ .../test_stellar_asset_contract.rs | 15 +-- .../src/cost_runner/experimental/mod.rs | 4 + .../src/cost_runner/experimental/read_xdr.rs | 39 ++++++++ soroban-env-host/src/host.rs | 14 --- soroban-env-host/src/host/comparison.rs | 11 +-- soroban-env-host/src/host/conversion.rs | 7 +- soroban-env-host/src/host/metered_clone.rs | 4 +- soroban-env-host/src/host/metered_xdr.rs | 14 +-- soroban-env-host/src/lib.rs | 3 +- soroban-env-host/src/test/bytes.rs | 4 +- soroban-env-host/src/test/depth_limit.rs | 44 ++++++--- soroban-env-host/src/test/lifecycle.rs | 8 +- soroban-env-host/src/vm.rs | 18 +--- soroban-env-host/tests/fees.rs | 18 ++-- soroban-env-macros/src/lib.rs | 5 +- 29 files changed, 292 insertions(+), 155 deletions(-) create mode 100644 soroban-env-host/benches/common/experimental/read_xdr.rs create mode 100644 soroban-env-host/src/budget/limits.rs create mode 100644 soroban-env-host/src/cost_runner/experimental/read_xdr.rs diff --git a/Cargo.lock b/Cargo.lock index 973edc9ae..2e704476b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1484,17 +1484,19 @@ checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" [[package]] name = "stellar-strkey" -version = "0.0.7" -source = "git+https://github.com/stellar/rs-stellar-strkey?rev=e6ba45c60c16de28c7522586b80ed0150157df73#e6ba45c60c16de28c7522586b80ed0150157df73" +version = "0.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12d2bf45e114117ea91d820a846fd1afbe3ba7d717988fee094ce8227a3bf8bd" dependencies = [ "base32", + "crate-git-revision", "thiserror", ] [[package]] name = "stellar-xdr" version = "20.0.0-rc1" -source = "git+https://github.com/stellar/rs-stellar-xdr?rev=9c97e4fa909a0b6455547a4f4a95800696b2a69a#9c97e4fa909a0b6455547a4f4a95800696b2a69a" +source = "git+https://github.com/stellar/rs-stellar-xdr?rev=957273d7b8092888849219a3ad2aa054059ca89d#957273d7b8092888849219a3ad2aa054059ca89d" dependencies = [ "arbitrary", "base64 0.13.1", @@ -1502,6 +1504,7 @@ dependencies = [ "hex", "serde", "serde_with", + "stellar-strkey", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index c418b9193..5cf258c17 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -28,7 +28,7 @@ soroban-builtin-sdk-macros = { version = "20.0.0-rc2", path = "soroban-builtin-s [workspace.dependencies.stellar-xdr] version = "20.0.0-rc1" git = "https://github.com/stellar/rs-stellar-xdr" -rev = "9c97e4fa909a0b6455547a4f4a95800696b2a69a" +rev = "957273d7b8092888849219a3ad2aa054059ca89d" default-features = false [workspace.dependencies.wasmi] @@ -37,11 +37,6 @@ version = "0.31.0-soroban1" git = "https://github.com/stellar/wasmi" rev = "7e63b4c9e08c4163f417d118d81f7ea34789d0be" -[workspace.dependencies.stellar-strkey] -version = "0.0.7" -git = "https://github.com/stellar/rs-stellar-strkey" -rev = "e6ba45c60c16de28c7522586b80ed0150157df73" - # [patch."https://github.com/stellar/rs-stellar-xdr"] # stellar-xdr = { path = "../rs-stellar-xdr/" } # [patch."https://github.com/stellar/wasmi"] diff --git a/soroban-env-common/src/error.rs b/soroban-env-common/src/error.rs index 3dcdebb61..a9705c995 100644 --- a/soroban-env-common/src/error.rs +++ b/soroban-env-common/src/error.rs @@ -155,7 +155,7 @@ impl From for Error { impl From for Error { fn from(e: crate::xdr::Error) -> Self { match e { - crate::xdr::Error::DepthLimitExceeded => { + crate::xdr::Error::DepthLimitExceeded | crate::xdr::Error::LengthLimitExceeded => { Error::from_type_and_code(ScErrorType::Context, ScErrorCode::ExceededLimit) } _ => Error::from_type_and_code(ScErrorType::Value, ScErrorCode::InvalidInput), diff --git a/soroban-env-host/Cargo.toml b/soroban-env-host/Cargo.toml index 9f8e54a48..e4fc27d6e 100644 --- a/soroban-env-host/Cargo.toml +++ b/soroban-env-host/Cargo.toml @@ -14,8 +14,8 @@ build = "build.rs" [dependencies] soroban-builtin-sdk-macros = { workspace = true } soroban-env-common = { workspace = true, features = ["std", "wasmi"] } -stellar-strkey = { workspace = true } wasmi = { workspace = true } +stellar-strkey = "0.0.8" static_assertions = "1.1.0" sha2 = "0.10.0" ed25519-dalek = {version = "2.0.0", features = ["rand_core"] } diff --git a/soroban-env-host/benches/common/cost_types/host_mem_cmp.rs b/soroban-env-host/benches/common/cost_types/host_mem_cmp.rs index 1e853a849..5d5c91267 100644 --- a/soroban-env-host/benches/common/cost_types/host_mem_cmp.rs +++ b/soroban-env-host/benches/common/cost_types/host_mem_cmp.rs @@ -1,13 +1,7 @@ -use crate::HostCostMeasurement; -use rand::{rngs::StdRng, RngCore}; +use crate::{common::util::randvec, HostCostMeasurement}; +use rand::rngs::StdRng; use soroban_env_host::{cost_runner::*, Host}; -fn randvec(rng: &mut StdRng, len: u64) -> Vec { - let mut res: Vec = vec![0; len as usize]; - rng.fill_bytes(res.as_mut_slice()); - res -} - pub struct MemCmpMeasure; impl HostCostMeasurement for MemCmpMeasure { type Runner = MemCmpRun; diff --git a/soroban-env-host/benches/common/cost_types/val_deser.rs b/soroban-env-host/benches/common/cost_types/val_deser.rs index 0c775e8df..88e1feb08 100644 --- a/soroban-env-host/benches/common/cost_types/val_deser.rs +++ b/soroban-env-host/benches/common/cost_types/val_deser.rs @@ -1,4 +1,6 @@ -use soroban_env_host::{cost_runner::ValDeserRun, xdr::ScVal, xdr::WriteXdr}; +use soroban_env_host::{ + cost_runner::ValDeserRun, xdr::ScVal, xdr::WriteXdr, DEFAULT_XDR_RW_LIMITS, +}; use super::{ValSerMeasure, MAX_DEPTH}; use crate::common::HostCostMeasurement; @@ -14,7 +16,7 @@ impl HostCostMeasurement for ValDeserMeasure { input: u64, ) -> Vec { let scval = ValSerMeasure::new_random_case(host, rng, input); - scval.0.to_xdr().unwrap() + scval.0.to_xdr(DEFAULT_XDR_RW_LIMITS).unwrap() } // The worst case is a deeply nested structure, each level containing minimal @@ -37,6 +39,6 @@ impl HostCostMeasurement for ValDeserMeasure { v = ScVal::Vec(Some(inner.try_into().unwrap())); rem -= elem_per_level; } - v.to_xdr().unwrap() + v.to_xdr(DEFAULT_XDR_RW_LIMITS).unwrap() } } diff --git a/soroban-env-host/benches/common/cost_types/val_ser.rs b/soroban-env-host/benches/common/cost_types/val_ser.rs index 43c8c3dd5..20fdce7af 100644 --- a/soroban-env-host/benches/common/cost_types/val_ser.rs +++ b/soroban-env-host/benches/common/cost_types/val_ser.rs @@ -10,6 +10,7 @@ pub(crate) struct ValSerMeasure; // This measures the costs of converting an ScVal into bytes. impl HostCostMeasurement for ValSerMeasure { type Runner = ValSerRun; + const STEP_SIZE: u64 = 512; fn new_random_case(_host: &Host, rng: &mut StdRng, input: u64) -> (ScVal, Vec) { let len = 1 + input * Self::STEP_SIZE; diff --git a/soroban-env-host/benches/common/experimental/mod.rs b/soroban-env-host/benches/common/experimental/mod.rs index 0811ba6ef..aa11efb15 100644 --- a/soroban-env-host/benches/common/experimental/mod.rs +++ b/soroban-env-host/benches/common/experimental/mod.rs @@ -1,3 +1,5 @@ mod ed25519_scalar_mul; +mod read_xdr; pub(crate) use ed25519_scalar_mul::*; +pub(crate) use read_xdr::*; diff --git a/soroban-env-host/benches/common/experimental/read_xdr.rs b/soroban-env-host/benches/common/experimental/read_xdr.rs new file mode 100644 index 000000000..c6e6be679 --- /dev/null +++ b/soroban-env-host/benches/common/experimental/read_xdr.rs @@ -0,0 +1,24 @@ +use soroban_env_host::{ + cost_runner::ReadXdrByteArrayRun, + xdr::{ScBytes, ScVal, WriteXdr}, + DEFAULT_XDR_RW_LIMITS, +}; + +use crate::common::{util::randvec, HostCostMeasurement}; + +pub(crate) struct ReadXdrByteArrayMeasure; + +impl HostCostMeasurement for ReadXdrByteArrayMeasure { + type Runner = ReadXdrByteArrayRun; + + fn new_random_case( + _host: &soroban_env_host::Host, + rng: &mut rand::prelude::StdRng, + input: u64, + ) -> Vec { + let len = 1 + input * Self::STEP_SIZE; + let vec = randvec(rng, len); + let scval = ScVal::Bytes(ScBytes(vec.try_into().unwrap())); + scval.to_xdr(DEFAULT_XDR_RW_LIMITS).unwrap() + } +} diff --git a/soroban-env-host/benches/common/mod.rs b/soroban-env-host/benches/common/mod.rs index fd0694887..713f9086b 100644 --- a/soroban-env-host/benches/common/mod.rs +++ b/soroban-env-host/benches/common/mod.rs @@ -49,9 +49,12 @@ fn call_bench( } pub(crate) fn for_each_experimental_cost_measurement( -) -> std::io::Result<(FPCostModel, FPCostModel)> { - B::bench::()?; - B::bench::() +) -> std::io::Result> { + let mut params: BTreeMap = BTreeMap::new(); + call_bench::(&mut params)?; + call_bench::(&mut params)?; + call_bench::(&mut params)?; + Ok(params) } pub(crate) fn for_each_host_cost_measurement( diff --git a/soroban-env-host/benches/common/util.rs b/soroban-env-host/benches/common/util.rs index ae4634943..a41c222f7 100644 --- a/soroban-env-host/benches/common/util.rs +++ b/soroban-env-host/benches/common/util.rs @@ -1,3 +1,4 @@ +use rand::{rngs::StdRng, RngCore}; use soroban_env_host::{budget::AsBudget, meta, Host, LedgerInfo, Val, I256}; pub(crate) fn test_host() -> Host { @@ -35,3 +36,9 @@ pub(crate) fn repeating_byte_i256(byte: u8, input: u64) -> I256 { res.extend_from_slice(buf.as_slice()); I256::from_be_bytes(res.try_into().unwrap()) } + +pub(crate) fn randvec(rng: &mut StdRng, len: u64) -> Vec { + let mut res: Vec = vec![0; len as usize]; + rng.fill_bytes(res.as_mut_slice()); + res +} diff --git a/soroban-env-host/benches/worst_case_linear_models.rs b/soroban-env-host/benches/worst_case_linear_models.rs index b66d0c606..3de6fea8a 100644 --- a/soroban-env-host/benches/worst_case_linear_models.rs +++ b/soroban-env-host/benches/worst_case_linear_models.rs @@ -2,6 +2,7 @@ // $ cargo bench --features testutils --bench worst_case_linear_models -- --nocapture // You can optionally pass in args listing the {`ContractCostType`, `WasmInsnType`} combination to run with, e.g. // $ cargo bench --features testutils --bench worst_case_linear_models -- MemCpy I64Rotr --nocapture +// To run the experimental cost types: $ RUN_EXPERIMENT=1 cargo bench ... mod common; use common::*; use soroban_env_host::{ @@ -176,7 +177,11 @@ fn extract_wasmi_fuel_costs( #[cfg(all(test, any(target_os = "linux", target_os = "macos")))] fn main() -> std::io::Result<()> { - let params = for_each_host_cost_measurement::()?; + let params = if std::env::var("RUN_EXPERIMENT").is_err() { + for_each_host_cost_measurement::()? + } else { + for_each_experimental_cost_measurement::()? + }; let params_wasm = for_each_wasm_insn_measurement::()?; let mut tw = TabWriter::new(vec![]) diff --git a/soroban-env-host/src/budget.rs b/soroban-env-host/src/budget.rs index cf9ba7b45..1091dac94 100644 --- a/soroban-env-host/src/budget.rs +++ b/soroban-env-host/src/budget.rs @@ -1,8 +1,11 @@ mod dimension; +mod limits; mod model; mod util; mod wasmi_helper; +pub(crate) use limits::DepthLimiter; +pub use limits::{DEFAULT_HOST_DEPTH_LIMIT, DEFAULT_XDR_RW_LIMITS}; pub use model::COST_MODEL_LIN_TERM_SCALE_BITS; use std::{ @@ -13,19 +16,14 @@ use std::{ use crate::{ host::error::TryBorrowOrErr, - xdr::{ContractCostParams, ContractCostType, DepthLimiter, ScErrorCode, ScErrorType}, - Error, Host, HostError, DEFAULT_HOST_DEPTH_LIMIT, + xdr::{ContractCostParams, ContractCostType, ScErrorCode, ScErrorType}, + Host, HostError, }; use dimension::{BudgetDimension, IsCpu, IsShadowMode}; use model::ScaledU64; use wasmi_helper::FuelConfig; -// These are some sane values, however the embedder should typically customize -// these to match the network config. -const DEFAULT_CPU_INSN_LIMIT: u64 = 100_000_000; -const DEFAULT_MEM_BYTES_LIMIT: u64 = 40 * 1024 * 1024; // 40MB - #[derive(Clone, Default)] struct MeterTracker { // Tracks the `(sum_of_iterations, total_input)` for each `CostType` @@ -423,8 +421,8 @@ impl Default for BudgetImpl { } // define the limits - b.cpu_insns.reset(DEFAULT_CPU_INSN_LIMIT); - b.mem_bytes.reset(DEFAULT_MEM_BYTES_LIMIT); + b.cpu_insns.reset(limits::DEFAULT_CPU_INSN_LIMIT); + b.mem_bytes.reset(limits::DEFAULT_MEM_BYTES_LIMIT); b } } @@ -527,30 +525,6 @@ impl Display for BudgetImpl { } } -impl DepthLimiter for BudgetImpl { - type DepthLimiterError = HostError; - - fn enter(&mut self) -> Result<(), HostError> { - if let Some(depth) = self.depth_limit.checked_sub(1) { - self.depth_limit = depth; - } else { - return Err(Error::from_type_and_code( - ScErrorType::Context, - ScErrorCode::ExceededLimit, - ) - .into()); - } - Ok(()) - } - - // `leave` should be called in tandem with `enter` such that the depth - // doesn't exceed the initial depth limit. - fn leave(&mut self) -> Result<(), HostError> { - self.depth_limit = self.depth_limit.saturating_add(1); - Ok(()) - } -} - #[derive(Clone)] pub struct Budget(pub(crate) Rc>); @@ -603,18 +577,6 @@ impl AsBudget for &Host { } } -impl DepthLimiter for Budget { - type DepthLimiterError = HostError; - - fn enter(&mut self) -> Result<(), HostError> { - self.0.try_borrow_mut_or_err()?.enter() - } - - fn leave(&mut self) -> Result<(), HostError> { - self.0.try_borrow_mut_or_err()?.leave() - } -} - impl Budget { /// Initializes the budget from network configuration settings. pub fn try_from_configs( diff --git a/soroban-env-host/src/budget/limits.rs b/soroban-env-host/src/budget/limits.rs new file mode 100644 index 000000000..0d27dfa3b --- /dev/null +++ b/soroban-env-host/src/budget/limits.rs @@ -0,0 +1,99 @@ +use crate::{ + budget::{Budget, BudgetImpl}, + host::error::TryBorrowOrErr, + xdr::{Limits, ScErrorCode, ScErrorType}, + Error, HostError, +}; + +/// These constants are used to set limits on recursion and data length in the +/// context of XDR (de)serialization. They serve as safeguards agaist both +/// exccessive stack allocation, which could cause an unrecoverable `SIGABRT`, +/// and exccessive heap memory allocation. +pub const DEFAULT_XDR_RW_LIMITS: Limits = Limits { + /// recursion limit for reading and writing XDR structures. + depth: 500, + /// maximum byte length for a data structure during serialization and + /// deserialization to and from the XDR format. The limit of 16MiB + /// corresponds to the overlay maximum message size. + len: 0x1000000, +}; + +/// - `DEFAULT_HOST_DEPTH_LIMIT`: This limit applies to the host environment. It +/// guards recursion paths involving the `Env` and `Budget`, particularly +/// during operations like conversion, comparison, and deep cloning. The limit +/// is strategically checked at critical recursion points, such as when +/// encountering a `Val`. As the actual stack usage can be higher, +/// `DEFAULT_HOST_DEPTH_LIMIT` is conservatively set to a lower value than the +/// XDR limit. +pub const DEFAULT_HOST_DEPTH_LIMIT: u32 = 100; + +// These are some sane values, however the embedder should typically customize +// these to match the network config. +pub(crate) const DEFAULT_CPU_INSN_LIMIT: u64 = 100_000_000; +pub(crate) const DEFAULT_MEM_BYTES_LIMIT: u64 = 40 * 1024 * 1024; // 40MB + +/// `DepthLimiter` is a trait designed for managing the depth of recursive operations. +/// It provides a mechanism to limit recursion depth, and defines the behavior upon +/// entering and leaving a recursion level. +pub(crate) trait DepthLimiter { + /// Defines the behavior for entering a new recursion level. + /// An `ExceededLimit` is returned if the new level exceeds the depth limit. + fn enter(&mut self) -> Result<(), HostError>; + + /// Defines the behavior for leaving a recursion level. + fn leave(&mut self) -> Result<(), HostError>; + + /// Wraps a given function `f` with depth limiting guards. + /// It triggers an `enter` before, and a `leave` after the execution of `f`. + /// + /// # Parameters + /// + /// - `f`: The function to be executed under depth limit constraints. + /// + /// # Returns + /// + /// - `Err` if 1. the depth limit has been exceeded upon `enter` 2. `f` executes + /// with an error 3. if error occurs on `leave`. + /// `Ok` otherwise. + fn with_limited_depth(&mut self, f: F) -> Result + where + F: FnOnce(&mut Self) -> Result, + { + self.enter()?; + let res = f(self); + self.leave()?; + res + } +} + +impl DepthLimiter for BudgetImpl { + fn enter(&mut self) -> Result<(), HostError> { + if let Some(depth) = self.depth_limit.checked_sub(1) { + self.depth_limit = depth; + } else { + return Err(Error::from_type_and_code( + ScErrorType::Context, + ScErrorCode::ExceededLimit, + ) + .into()); + } + Ok(()) + } + + // `leave` should be called in tandem with `enter` such that the depth + // doesn't exceed the initial depth limit. + fn leave(&mut self) -> Result<(), HostError> { + self.depth_limit = self.depth_limit.saturating_add(1); + Ok(()) + } +} + +impl DepthLimiter for Budget { + fn enter(&mut self) -> Result<(), HostError> { + self.0.try_borrow_mut_or_err()?.enter() + } + + fn leave(&mut self) -> Result<(), HostError> { + self.0.try_borrow_mut_or_err()?.leave() + } +} diff --git a/soroban-env-host/src/builtin_contracts/stellar_asset_contract/test_stellar_asset_contract.rs b/soroban-env-host/src/builtin_contracts/stellar_asset_contract/test_stellar_asset_contract.rs index cde4da1c4..3d14f98e8 100644 --- a/soroban-env-host/src/builtin_contracts/stellar_asset_contract/test_stellar_asset_contract.rs +++ b/soroban-env-host/src/builtin_contracts/stellar_asset_contract/test_stellar_asset_contract.rs @@ -1,17 +1,12 @@ use crate::{ builtin_contracts::{ - base_types::Address, + base_types::{Address, Bytes, String}, testutils::{authorize_single_invocation, ContractTypeVec, TestSigner}, }, - host_vec, Host, HostError, + host_vec, + xdr::{Asset, Limited, WriteXdr}, + Env, Host, HostError, Symbol, TryFromVal, TryIntoVal, DEFAULT_XDR_RW_LIMITS, }; -use soroban_env_common::{ - xdr::{Asset, DepthLimitedWrite, WriteXdr, DEFAULT_XDR_RW_DEPTH_LIMIT}, - Env, -}; -use soroban_env_common::{Symbol, TryFromVal, TryIntoVal}; - -use crate::builtin_contracts::base_types::{Bytes, String}; pub(crate) struct TestStellarAssetContract<'a> { pub(crate) address: Address, @@ -20,7 +15,7 @@ pub(crate) struct TestStellarAssetContract<'a> { impl<'a> TestStellarAssetContract<'a> { pub(crate) fn new_from_asset(host: &'a Host, asset: Asset) -> Self { - let mut asset_bytes_vec = DepthLimitedWrite::new(vec![], DEFAULT_XDR_RW_DEPTH_LIMIT); + let mut asset_bytes_vec = Limited::new(vec![], DEFAULT_XDR_RW_LIMITS); asset.write_xdr(&mut asset_bytes_vec).unwrap(); let address_obj = host .create_asset_contract( diff --git a/soroban-env-host/src/cost_runner/experimental/mod.rs b/soroban-env-host/src/cost_runner/experimental/mod.rs index c43a2b428..2f0ef2413 100644 --- a/soroban-env-host/src/cost_runner/experimental/mod.rs +++ b/soroban-env-host/src/cost_runner/experimental/mod.rs @@ -1,6 +1,8 @@ mod ed25519_scalar_mut; +mod read_xdr; pub use ed25519_scalar_mut::*; +pub use read_xdr::*; use crate::xdr::Name; use core::fmt; @@ -8,6 +10,7 @@ use core::fmt; #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] pub enum ExperimentalCostType { EdwardsPointCurve25519ScalarMul, + ReadXdrByteArray, } impl Name for ExperimentalCostType { @@ -16,6 +19,7 @@ impl Name for ExperimentalCostType { ExperimentalCostType::EdwardsPointCurve25519ScalarMul => { "EdwardsPointCurve25519ScalarMul" } + ExperimentalCostType::ReadXdrByteArray => "ReadXdrByteArray", } } } diff --git a/soroban-env-host/src/cost_runner/experimental/read_xdr.rs b/soroban-env-host/src/cost_runner/experimental/read_xdr.rs new file mode 100644 index 000000000..a8bbfc9ba --- /dev/null +++ b/soroban-env-host/src/cost_runner/experimental/read_xdr.rs @@ -0,0 +1,39 @@ +use std::hint::black_box; + +use crate::{ + budget::AsBudget, + cost_runner::{experimental::ExperimentalCostType::ReadXdrByteArray, CostRunner, CostType}, + xdr::{ContractCostType::ValDeser, ScVal}, +}; + +pub struct ReadXdrByteArrayRun; + +impl CostRunner for ReadXdrByteArrayRun { + // experimental cost type that identifies this component. Used purely for result aggregation + // purpose. Internally there is no budget component associated with this type, and you'll + // have to override the get_tracker method to return the correct inputs. + const COST_TYPE: CostType = CostType::Experimental(ReadXdrByteArray); + + type SampleType = Vec; + + type RecycledType = (Option, Vec); + + fn run_iter(host: &crate::Host, _iter: u64, sample: Self::SampleType) -> Self::RecycledType { + let sv = black_box(host.metered_from_xdr::(&sample).unwrap()); + (Some(sv), sample) + } + + fn run_baseline_iter( + host: &crate::Host, + _iter: u64, + sample: Self::SampleType, + ) -> Self::RecycledType { + black_box(host.charge_budget(ValDeser, Some(0)).unwrap()); + black_box((None, sample)) + } + + fn get_tracker(host: &crate::Host) -> (u64, Option) { + // internally this is still charged under `ValDeser` + host.as_budget().get_tracker(ValDeser).unwrap() + } +} diff --git a/soroban-env-host/src/host.rs b/soroban-env-host/src/host.rs index c639166c4..05cb34506 100644 --- a/soroban-env-host/src/host.rs +++ b/soroban-env-host/src/host.rs @@ -56,20 +56,6 @@ pub(crate) use frame::Frame; #[cfg(any(test, feature = "recording_auth"))] use rand_chacha::ChaCha20Rng; -/// Defines the maximum depth for recursive calls in the host, i.e. `Val` conversion, comparison, -/// and deep clone, to prevent stack overflow. -/// -/// Similar to the `xdr::DEFAULT_XDR_RW_DEPTH_LIMIT`, `DEFAULT_HOST_DEPTH_LIMIT` is also a proxy -/// to the stack depth limit, and its purpose is to prevent the program from -/// hitting the maximum stack size allowed by Rust, which would result in an unrecoverable `SIGABRT`. -/// -/// The difference is the `DEFAULT_HOST_DEPTH_LIMIT`guards the recursion paths via the `Env` and -/// the `Budget`, i.e., conversion, comparison and deep clone. The limit is checked at specific -/// points of the recursion path, e.g. when `Val` is encountered, to minimize noise. So the -/// "actual stack depth"/"host depth" factor will typically be larger, and thus the -/// `DEFAULT_HOST_DEPTH_LIMIT` here is set to a smaller value. -pub const DEFAULT_HOST_DEPTH_LIMIT: u32 = 100; - /// Temporary helper for denoting a slice of guest memory, as formed by /// various bytes operations. pub(crate) struct VmSlice { diff --git a/soroban-env-host/src/host/comparison.rs b/soroban-env-host/src/host/comparison.rs index fe22e5b81..4eded4908 100644 --- a/soroban-env-host/src/host/comparison.rs +++ b/soroban-env-host/src/host/comparison.rs @@ -1,16 +1,15 @@ use core::cmp::{min, Ordering}; use crate::{ - budget::{AsBudget, Budget}, + budget::{AsBudget, Budget, DepthLimiter}, host_object::HostObject, storage::Storage, xdr::{ AccountId, ContractCostType, ContractDataDurability, ContractExecutable, - CreateContractArgs, DepthLimiter, Duration, Hash, Int128Parts, Int256Parts, LedgerKey, - LedgerKeyAccount, LedgerKeyContractCode, LedgerKeyContractData, LedgerKeyTrustLine, - PublicKey, ScAddress, ScContractInstance, ScError, ScErrorCode, ScErrorType, ScMap, - ScMapEntry, ScNonceKey, ScVal, ScVec, TimePoint, TrustLineAsset, UInt128Parts, - UInt256Parts, Uint256, + CreateContractArgs, Duration, Hash, Int128Parts, Int256Parts, LedgerKey, LedgerKeyAccount, + LedgerKeyContractCode, LedgerKeyContractData, LedgerKeyTrustLine, PublicKey, ScAddress, + ScContractInstance, ScError, ScErrorCode, ScErrorType, ScMap, ScMapEntry, ScNonceKey, + ScVal, ScVec, TimePoint, TrustLineAsset, UInt128Parts, UInt256Parts, Uint256, }, Compare, Host, HostError, SymbolStr, I256, U256, }; diff --git a/soroban-env-host/src/host/conversion.rs b/soroban-env-host/src/host/conversion.rs index b39cc3013..6dc3dedc4 100644 --- a/soroban-env-host/src/host/conversion.rs +++ b/soroban-env-host/src/host/conversion.rs @@ -3,7 +3,7 @@ use std::rc::Rc; use super::metered_clone::{ charge_shallow_copy, MeteredAlloc, MeteredClone, MeteredContainer, MeteredIterator, }; -use crate::budget::AsBudget; +use crate::budget::{AsBudget, DepthLimiter}; use crate::err; use crate::host_object::{HostMap, HostObject, HostVec}; use crate::xdr::{Hash, LedgerKey, LedgerKeyContractData, ScVal, ScVec, Uint256}; @@ -12,9 +12,8 @@ use soroban_env_common::num::{ i256_from_pieces, i256_into_pieces, u256_from_pieces, u256_into_pieces, }; use soroban_env_common::xdr::{ - self, int128_helpers, AccountId, ContractDataDurability, DepthLimiter, Int128Parts, - Int256Parts, ScAddress, ScBytes, ScErrorCode, ScErrorType, ScMap, ScMapEntry, UInt128Parts, - UInt256Parts, VecM, + self, int128_helpers, AccountId, ContractDataDurability, Int128Parts, Int256Parts, ScAddress, + ScBytes, ScErrorCode, ScErrorType, ScMap, ScMapEntry, UInt128Parts, UInt256Parts, VecM, }; use soroban_env_common::{ AddressObject, BytesObject, Convert, Object, ScValObjRef, ScValObject, TryFromVal, TryIntoVal, diff --git a/soroban-env-host/src/host/metered_clone.rs b/soroban-env-host/src/host/metered_clone.rs index 3cd5981dc..6d7009153 100644 --- a/soroban-env-host/src/host/metered_clone.rs +++ b/soroban-env-host/src/host/metered_clone.rs @@ -19,12 +19,12 @@ use std::{iter::FromIterator, mem, rc::Rc}; use crate::{ - budget::AsBudget, + budget::{AsBudget, DepthLimiter}, builtin_contracts::base_types::Address, storage::AccessType, xdr::{ AccountEntry, AccountId, Asset, BytesM, ContractCodeEntry, ContractCostType, - ContractExecutable, ContractIdPreimage, CreateContractArgs, DepthLimiter, Duration, Hash, + ContractExecutable, ContractIdPreimage, CreateContractArgs, Duration, Hash, InvokeContractArgs, LedgerEntry, LedgerEntryData, LedgerEntryExt, LedgerKey, LedgerKeyAccount, LedgerKeyContractCode, LedgerKeyTrustLine, PublicKey, ScAddress, ScBytes, ScContractInstance, ScErrorCode, ScErrorType, ScMap, ScMapEntry, ScNonceKey, ScString, diff --git a/soroban-env-host/src/host/metered_xdr.rs b/soroban-env-host/src/host/metered_xdr.rs index eab29fd85..8567498ea 100644 --- a/soroban-env-host/src/host/metered_xdr.rs +++ b/soroban-env-host/src/host/metered_xdr.rs @@ -1,10 +1,7 @@ use crate::{ budget::Budget, - xdr::{ - ContractCostType, DepthLimitedWrite, ReadXdr, ScBytes, ScErrorCode, ScErrorType, WriteXdr, - DEFAULT_XDR_RW_DEPTH_LIMIT, - }, - BytesObject, Host, HostError, + xdr::{ContractCostType, Limited, ReadXdr, ScBytes, ScErrorCode, ScErrorType, WriteXdr}, + BytesObject, Host, HostError, DEFAULT_XDR_RW_LIMITS, }; use std::io::Write; @@ -43,7 +40,7 @@ impl Host { pub fn metered_from_xdr(&self, bytes: &[u8]) -> Result { let _span = tracy_span!("read xdr"); self.charge_budget(ContractCostType::ValDeser, Some(bytes.len() as u64))?; - self.map_err(T::from_xdr(bytes)) + self.map_err(T::from_xdr(bytes, DEFAULT_XDR_RW_LIMITS)) } pub(crate) fn metered_from_xdr_obj( @@ -60,8 +57,7 @@ pub fn metered_write_xdr( w: &mut Vec, ) -> Result<(), HostError> { let _span = tracy_span!("write xdr"); - let mw = MeteredWrite { budget, w }; - let mut w = DepthLimitedWrite::new(mw, DEFAULT_XDR_RW_DEPTH_LIMIT); + let mut w = Limited::new(MeteredWrite { budget, w }, DEFAULT_XDR_RW_LIMITS); // MeteredWrite above turned any budget failure into an IO error; we turn it // back to a budget failure here, since there's really no "IO error" that can // occur when writing to a Vec. @@ -78,5 +74,5 @@ pub fn metered_from_xdr_with_budget( ) -> Result { let _span = tracy_span!("read xdr with budget"); budget.charge(ContractCostType::ValDeser, Some(bytes.len() as u64))?; - T::from_xdr(bytes).map_err(|e| e.into()) + T::from_xdr(bytes, DEFAULT_XDR_RW_LIMITS).map_err(|e| e.into()) } diff --git a/soroban-env-host/src/lib.rs b/soroban-env-host/src/lib.rs index 0ade700db..1441a424a 100644 --- a/soroban-env-host/src/lib.rs +++ b/soroban-env-host/src/lib.rs @@ -57,6 +57,7 @@ pub mod storage; #[cfg(test)] mod test; +pub use budget::{DEFAULT_HOST_DEPTH_LIMIT, DEFAULT_XDR_RW_LIMITS}; #[cfg(any(test, feature = "testutils"))] #[doc(hidden)] pub use host::testutils::call_with_suppressed_panic_hook; @@ -64,7 +65,7 @@ pub use host::testutils::call_with_suppressed_panic_hook; pub use host::ContractFunctionSet; pub use host::{ metered_map::MeteredOrdMap, metered_vector::MeteredVector, Host, HostError, LedgerInfo, Seed, - DEFAULT_HOST_DEPTH_LIMIT, SEED_BYTES, + SEED_BYTES, }; pub use soroban_env_common::*; diff --git a/soroban-env-host/src/test/bytes.rs b/soroban-env-host/src/test/bytes.rs index 9e4fc9c62..f5bfea4f4 100644 --- a/soroban-env-host/src/test/bytes.rs +++ b/soroban-env-host/src/test/bytes.rs @@ -1,6 +1,6 @@ use crate::{ xdr::{ScError, ScVal}, - Env, Host, HostError, Val, + Env, Host, HostError, Val, DEFAULT_XDR_RW_LIMITS, }; use soroban_env_common::{ xdr::{ScBytes, ScErrorCode, ScErrorType, ScVec, WriteXdr}, @@ -119,7 +119,7 @@ fn bytes_xdr_roundtrip() -> Result<(), HostError> { Ok(()) }; let deser_fails_scv = |v: ScVal| -> Result<(), HostError> { - let bytes: Vec = v.to_xdr()?; + let bytes: Vec = v.to_xdr(DEFAULT_XDR_RW_LIMITS)?; let bo = host.add_host_object(ScBytes(bytes.try_into()?))?; let res = host.deserialize_from_bytes(bo); assert!(res.is_err()); diff --git a/soroban-env-host/src/test/depth_limit.rs b/soroban-env-host/src/test/depth_limit.rs index 90e4652a2..6aea65380 100644 --- a/soroban-env-host/src/test/depth_limit.rs +++ b/soroban-env-host/src/test/depth_limit.rs @@ -1,10 +1,10 @@ -use soroban_env_common::xdr::{ReadXdr, WriteXdr}; +use soroban_env_common::xdr::{Limits, ReadXdr, WriteXdr}; use crate::{ budget::AsBudget, host::metered_clone::MeteredClone, xdr::{ScErrorCode, ScErrorType, ScVal, ScVec}, - Env, Host, HostError, + Env, Host, HostError, DEFAULT_XDR_RW_LIMITS, }; #[test] @@ -91,29 +91,51 @@ fn deep_host_obj_cmp() -> Result<(), HostError> { } #[test] -fn deep_scval_xdr_serialization() -> Result<(), HostError> { +fn depth_limited_scval_xdr_serialization() -> Result<(), HostError> { let mut v = ScVal::from(ScVec::default()); for _ in 0..200 { let vv = ScVec::try_from(vec![v])?; v = ScVal::from(vv); } + // default depth limit will cause serialization to fail let res = v - .to_xdr_with_depth_limit(500) + .to_xdr(DEFAULT_XDR_RW_LIMITS) .map_err(|e| HostError::from(e)); let code = (ScErrorType::Context, ScErrorCode::ExceededLimit); assert!(HostError::result_matches_err(res, code)); + + // remove the Limits to make serialization pass + let bytes = v.to_xdr(Limits::none())?; + // deserialization from the input with limits will fail + let res = ScVal::from_xdr(bytes, DEFAULT_XDR_RW_LIMITS).map_err(|e| HostError::from(e)); + assert!(HostError::result_matches_err(res, code)); Ok(()) } #[test] -fn deep_scval_xdr_deserialization() -> Result<(), HostError> { - let mut v = ScVal::from(ScVec::default()); - for _ in 0..200 { - let vv = ScVec::try_from(vec![v])?; - v = ScVal::from(vv); +fn length_limited_scval_xdr_conversion() -> Result<(), HostError> { + let buf = vec![0; 200000]; + let scv_bytes = ScVal::Bytes(buf.try_into().unwrap()); + let mut scv_vec = ScVal::Vec(Some(ScVec(vec![scv_bytes.clone(); 1].try_into().unwrap()))); + // roughly consumes 20MiB (> 16 MiB the limit) + for _i in 1..100 { + let mut v = vec![scv_vec; 1]; + v.push(scv_bytes.clone()); + scv_vec = ScVal::Vec(Some(v.try_into().unwrap())); } - let bytes = v.to_xdr_with_depth_limit(10000)?; - let res = ScVal::from_xdr_with_depth_limit(bytes, 500).map_err(|e| HostError::from(e)); + // default length limit will cause serialization to fail + let res = scv_vec + .to_xdr(DEFAULT_XDR_RW_LIMITS) + .map_err(|e| HostError::from(e)); + let code = (ScErrorType::Context, ScErrorCode::ExceededLimit); + assert!(HostError::result_matches_err(res, code)); + + // remove the Limits to make serialization pass + let bytes = scv_vec + .to_xdr(Limits::none()) + .map_err(|e| HostError::from(e))?; + // deserialization from the input with limits will fail + let res = ScVal::from_xdr(bytes, DEFAULT_XDR_RW_LIMITS).map_err(|e| HostError::from(e)); let code = (ScErrorType::Context, ScErrorCode::ExceededLimit); assert!(HostError::result_matches_err(res, code)); Ok(()) diff --git a/soroban-env-host/src/test/lifecycle.rs b/soroban-env-host/src/test/lifecycle.rs index 1368e4104..4a46885f6 100644 --- a/soroban-env-host/src/test/lifecycle.rs +++ b/soroban-env-host/src/test/lifecycle.rs @@ -9,13 +9,13 @@ use crate::{ ContractExecutable, CreateContractArgs, ExtensionPoint, Hash, HashIdPreimage, HashIdPreimageContractId, LedgerEntryData, ScSymbol, ScVal, ScVec, Uint256, }, - Env, Host, LedgerInfo, Symbol, + Env, Host, LedgerInfo, Symbol, DEFAULT_XDR_RW_LIMITS, }; use sha2::{Digest, Sha256}; use soroban_env_common::xdr::{ - ContractIdPreimage, ContractIdPreimageFromAddress, DepthLimitedWrite, HostFunction, ScAddress, + ContractIdPreimage, ContractIdPreimageFromAddress, HostFunction, Limited, ScAddress, ScErrorCode, ScErrorType, SorobanAuthorizationEntry, SorobanAuthorizedFunction, - SorobanAuthorizedInvocation, SorobanCredentials, VecM, DEFAULT_XDR_RW_DEPTH_LIMIT, + SorobanAuthorizedInvocation, SorobanCredentials, VecM, }; use soroban_env_common::{xdr::ScBytes, TryIntoVal, Val}; use soroban_env_common::{StorageType, VecObject}; @@ -241,7 +241,7 @@ fn create_contract_from_source_account() { } pub(crate) fn sha256_hash_id_preimage(pre_image: T) -> xdr::Hash { - let mut buf = DepthLimitedWrite::new(Vec::new(), DEFAULT_XDR_RW_DEPTH_LIMIT); + let mut buf = Limited::new(Vec::new(), DEFAULT_XDR_RW_LIMITS); pre_image .write_xdr(&mut buf) .expect("preimage write failed"); diff --git a/soroban-env-host/src/vm.rs b/soroban-env-host/src/vm.rs index b6aae5e2b..0bb857e70 100644 --- a/soroban-env-host/src/vm.rs +++ b/soroban-env-host/src/vm.rs @@ -19,22 +19,15 @@ use crate::{ budget::AsBudget, err, host::{error::TryBorrowOrErr, metered_clone::MeteredContainer}, - xdr::ContractCostType, - HostError, + meta::{self, get_ledger_protocol_version}, + xdr::{ContractCostType, Hash, Limited, ReadXdr, ScEnvMetaEntry, ScErrorCode, ScErrorType}, + ConversionError, Host, HostError, Symbol, SymbolStr, TryIntoVal, Val, WasmiMarshal, + DEFAULT_XDR_RW_LIMITS, }; use std::{cell::RefCell, io::Cursor, rc::Rc}; -use super::{xdr::Hash, Host, Symbol, Val}; use fuel_refillable::FuelRefillable; use func_info::HOST_FUNCTIONS; -use soroban_env_common::{ - meta::{self, get_ledger_protocol_version}, - xdr::{ - DepthLimitedRead, ReadXdr, ScEnvMetaEntry, ScErrorCode, ScErrorType, - DEFAULT_XDR_RW_DEPTH_LIMIT, - }, - ConversionError, SymbolStr, TryIntoVal, WasmiMarshal, -}; use wasmi::{Engine, FuelConsumptionMode, Instance, Linker, Memory, Module, Store, Value}; @@ -161,8 +154,7 @@ impl Vm { // us as well as a protocol that's less than or equal to our protocol. if let Some(env_meta) = Self::module_custom_section(m, meta::ENV_META_V0_SECTION_NAME) { - let mut cursor = - DepthLimitedRead::new(Cursor::new(env_meta), DEFAULT_XDR_RW_DEPTH_LIMIT); + let mut cursor = Limited::new(Cursor::new(env_meta), DEFAULT_XDR_RW_LIMITS); if let Some(env_meta_entry) = ScEnvMetaEntry::read_xdr_iter(&mut cursor).next() { let ScEnvMetaEntry::ScEnvMetaKindInterfaceVersion(v) = host.map_err(env_meta_entry)?; diff --git a/soroban-env-host/tests/fees.rs b/soroban-env-host/tests/fees.rs index 92f7730ec..cf1af1d14 100644 --- a/soroban-env-host/tests/fees.rs +++ b/soroban-env-host/tests/fees.rs @@ -1,10 +1,13 @@ use soroban_env_common::xdr::{Hash, LedgerEntry, LedgerEntryData, LedgerEntryExt, WriteXdr}; -use soroban_env_host::fees::{ - compute_rent_fee, compute_transaction_resource_fee, compute_write_fee_per_1kb, - FeeConfiguration, LedgerEntryRentChange, RentFeeConfiguration, TransactionResources, - WriteFeeConfiguration, TTL_ENTRY_SIZE, +use soroban_env_host::{ + fees::{ + compute_rent_fee, compute_transaction_resource_fee, compute_write_fee_per_1kb, + FeeConfiguration, LedgerEntryRentChange, RentFeeConfiguration, TransactionResources, + WriteFeeConfiguration, TTL_ENTRY_SIZE, + }, + xdr::TtlEntry, + DEFAULT_XDR_RW_LIMITS, }; -use soroban_env_host::xdr::TtlEntry; #[test] fn ttl_entry_size() { @@ -18,7 +21,10 @@ fn ttl_entry_size() { }; assert_eq!( TTL_ENTRY_SIZE, - expiration_entry.to_xdr().unwrap().len() as u32 + expiration_entry + .to_xdr(DEFAULT_XDR_RW_LIMITS) + .unwrap() + .len() as u32 ); } diff --git a/soroban-env-macros/src/lib.rs b/soroban-env-macros/src/lib.rs index 2e2b7fb9e..e6a0970b2 100644 --- a/soroban-env-macros/src/lib.rs +++ b/soroban-env-macros/src/lib.rs @@ -15,7 +15,7 @@ use stellar_xdr::curr as xdr; #[cfg(feature = "next")] use stellar_xdr::next as xdr; -use crate::xdr::{ScEnvMetaEntry, WriteXdr}; +use crate::xdr::{Limits, ScEnvMetaEntry, WriteXdr}; struct MetaInput { pub interface_version: u64, @@ -62,7 +62,8 @@ impl ToTokens for MetaConstsOutput { let meta_xdr = self .to_meta_entries() .into_iter() - .map(|entry| entry.to_xdr()) + // Limits::none here is okay since `MetaConstsOutput` is controled by us + .map(|entry| entry.to_xdr(Limits::none())) .collect::>, crate::xdr::Error>>() .unwrap() .concat();