Skip to content

Commit

Permalink
Add a helper that invokes a host function 'end-to-end' (#964)
Browse files Browse the repository at this point in the history
* Move all the metered code to invoke host functions into a single host-side helper.

This helper can be invoked from any environment to get the metering data and results consistent with what Core will do.

Also provide utilities to build input data for computing the rent fees.

* !fixup clippy fixes

---------

Co-authored-by: Graydon Hoare <graydon@pobox.com>
  • Loading branch information
dmkozh and graydon authored Jul 29, 2023
1 parent 14f4856 commit 75c0439
Show file tree
Hide file tree
Showing 10 changed files with 552 additions and 40 deletions.
7 changes: 5 additions & 2 deletions soroban-env-host/src/cost_runner/cost_types/val_ser.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
use std::hint::black_box;

use crate::{cost_runner::CostRunner, xdr::ContractCostType, xdr::ScVal};
use crate::{
cost_runner::CostRunner, host::metered_xdr::metered_write_xdr, xdr::ContractCostType,
xdr::ScVal,
};

pub struct ValSerRun;

Expand All @@ -18,7 +21,7 @@ impl CostRunner for ValSerRun {
) -> Self::RecycledType {
// Note the sample.1 is an empty vector, so metered_write_xdr includes allocation
// cost. This is how it's typically used so we are setting it up this way.
black_box(host.metered_write_xdr(&sample.0, &mut sample.1).unwrap());
black_box(metered_write_xdr(host.budget_ref(), &sample.0, &mut sample.1).unwrap());
sample
}

Expand Down
487 changes: 487 additions & 0 deletions soroban-env-host/src/e2e_invoke.rs

Large diffs are not rendered by default.

15 changes: 7 additions & 8 deletions soroban-env-host/src/host.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,12 +52,12 @@ use soroban_env_common::xdr::{
ScErrorCode, MASK_CONTRACT_DATA_FLAGS_V20,
};

use self::metered_clone::MeteredClone;
use self::{
frame::{Context, ContractReentryMode},
metered_vector::MeteredVector,
prng::Prng,
};
use self::{metered_clone::MeteredClone, metered_xdr::metered_write_xdr};
use crate::impl_bignum_host_fns;
use crate::Compare;
#[cfg(any(test, feature = "testutils"))]
Expand Down Expand Up @@ -427,15 +427,14 @@ impl Host {
}

/// Accept a _unique_ (refcount = 1) host reference and destroy the
/// underlying [`HostImpl`], returning its constituent components to the
/// caller as a tuple wrapped in `Ok(...)`.
pub fn try_finish(self) -> Result<(Storage, Budget, Events), HostError> {
/// underlying [`HostImpl`], returning its finalized components containing
/// processing side effects to the caller as a tuple wrapped in `Ok(...)`.
pub fn try_finish(self) -> Result<(Storage, Events), HostError> {
let events = self.try_borrow_events()?.externalize(&self)?;
Rc::try_unwrap(self.0)
.map(|host_impl| {
let storage = host_impl.storage.into_inner();
let budget = host_impl.budget;
(storage, budget, events)
(storage, events)
})
.map_err(|_| {
Error::from_type_and_code(ScErrorType::Context, ScErrorCode::InternalError).into()
Expand Down Expand Up @@ -552,7 +551,7 @@ impl Host {
) -> Result<(), HostError> {
if let ContractIdPreimage::Asset(asset) = id_preimage {
let mut asset_bytes: Vec<u8> = Default::default();
self.metered_write_xdr(asset, &mut asset_bytes)?;
metered_write_xdr(self.budget_ref(), asset, &mut asset_bytes)?;
self.call_n_internal(
contract_id,
Symbol::try_from_val(self, &"init_asset")?,
Expand Down Expand Up @@ -2389,7 +2388,7 @@ impl VmCallerEnv for Host {
) -> Result<BytesObject, HostError> {
let scv = self.from_host_val(v)?;
let mut buf = Vec::<u8>::new();
self.metered_write_xdr(&scv, &mut buf)?;
metered_write_xdr(self.budget_ref(), &scv, &mut buf)?;
self.add_host_object(self.scbytes_from_vec(buf)?)
}

Expand Down
10 changes: 9 additions & 1 deletion soroban-env-host/src/host/declared_size.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
use std::rc::Rc;

use soroban_env_common::xdr::SorobanAuthorizationEntry;

use crate::{
events::{EventError, HostEvent, InternalContractEvent, InternalEvent},
host::Events,
Expand Down Expand Up @@ -147,7 +149,7 @@ impl_declared_size_type!(ContractEntryBodyType, 4);
impl_declared_size_type!(ExtensionPoint, 0);
impl_declared_size_type!(SorobanAuthorizedInvocation, 128);
impl_declared_size_type!(ScContractInstance, 64);

impl_declared_size_type!(SorobanAuthorizationEntry, 240);
// composite types

// Rc is an exception, nothing is being cloned. We approximate ref counter bump with the cost of
Expand Down Expand Up @@ -338,6 +340,11 @@ mod test {
expect!["8"].assert_eq(size_of::<Rc<ScVal>>().to_string().as_str());
expect!["64"].assert_eq(size_of::<Option<ScVal>>().to_string().as_str());
expect!["64"].assert_eq(size_of::<ScContractInstance>().to_string().as_str());
expect!["240"].assert_eq(
size_of::<SorobanAuthorizedInvocation>()
.to_string()
.as_str(),
);
}

// This is the actual test.
Expand Down Expand Up @@ -460,6 +467,7 @@ mod test {
assert_mem_size_le_declared_size!(ContractEntryBodyType);
assert_mem_size_le_declared_size!(ExtensionPoint);
assert_mem_size_le_declared_size!(SorobanAuthorizedInvocation);
assert_mem_size_le_declared_size!(SorobanAuthorizationEntry);
// composite types
assert_mem_size_le_declared_size!(&[ScVal]);
assert_mem_size_le_declared_size!((Val, ScVal));
Expand Down
3 changes: 2 additions & 1 deletion soroban-env-host/src/host/metered_clone.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use std::{mem, rc::Rc};

use soroban_env_common::xdr::DepthLimiter;
use soroban_env_common::xdr::{DepthLimiter, SorobanAuthorizationEntry};

use crate::{
budget::Budget,
Expand Down Expand Up @@ -227,6 +227,7 @@ impl MeteredClone for EventError {}
impl MeteredClone for CreateContractArgs {}
impl MeteredClone for ContractIdPreimage {}
impl MeteredClone for SorobanAuthorizedInvocation {}
impl MeteredClone for SorobanAuthorizationEntry {}
// composite types
impl<T> MeteredClone for Rc<T> {}
impl<T> MeteredClone for &[T] {}
Expand Down
55 changes: 34 additions & 21 deletions soroban-env-host/src/host/metered_xdr.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use crate::{
budget::Budget,
xdr::ContractCostType,
xdr::{ReadXdr, ScBytes, WriteXdr},
BytesObject, Host, HostError,
Expand All @@ -11,7 +12,7 @@ use soroban_env_common::xdr::{
};

struct MeteredWrite<'a, W: Write> {
host: &'a Host,
budget: &'a Budget,
w: &'a mut W,
}

Expand All @@ -20,8 +21,8 @@ where
W: Write,
{
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
self.host
.charge_budget(ContractCostType::ValSer, Some(buf.len() as u64))
self.budget
.charge(ContractCostType::ValSer, Some(buf.len() as u64))
.map_err(Into::<std::io::Error>::into)?;
self.w.write(buf)
}
Expand All @@ -32,30 +33,15 @@ where
}

impl Host {
pub(crate) fn metered_write_xdr(
&self,
obj: &impl WriteXdr,
w: &mut Vec<u8>,
) -> Result<(), HostError> {
let _span = tracy_span!("write xdr");
let mw = MeteredWrite { host: self, w };
let mut w = DepthLimitedWrite::new(mw, DEFAULT_XDR_RW_DEPTH_LIMIT);
// 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<u8>.
obj.write_xdr(&mut w)
.map_err(|_| (ScErrorType::Budget, ScErrorCode::ExceededLimit).into())
}

pub(crate) fn metered_hash_xdr(&self, obj: &impl WriteXdr) -> Result<[u8; 32], HostError> {
pub fn metered_hash_xdr(&self, obj: &impl WriteXdr) -> Result<[u8; 32], HostError> {
let _span = tracy_span!("hash xdr");
let mut buf = vec![];
self.metered_write_xdr(obj, &mut buf)?;
metered_write_xdr(self.budget_ref(), obj, &mut buf)?;
self.charge_budget(ContractCostType::ComputeSha256Hash, Some(buf.len() as u64))?;
Ok(Sha256::digest(&buf).try_into()?)
}

pub(crate) fn metered_from_xdr<T: ReadXdr>(&self, bytes: &[u8]) -> Result<T, HostError> {
pub fn metered_from_xdr<T: ReadXdr>(&self, bytes: &[u8]) -> Result<T, HostError> {
let _span = tracy_span!("read xdr");
self.charge_budget(ContractCostType::ValDeser, Some(bytes.len() as u64))?;
self.map_err(T::from_xdr(bytes))
Expand All @@ -68,3 +54,30 @@ impl Host {
self.visit_obj(bytes, |hv: &ScBytes| self.metered_from_xdr(hv.as_slice()))
}
}

pub fn metered_write_xdr(
budget: &Budget,
obj: &impl WriteXdr,
w: &mut Vec<u8>,
) -> Result<(), HostError> {
let _span = tracy_span!("write xdr");
let mw = MeteredWrite { budget, w };
let mut w = DepthLimitedWrite::new(mw, DEFAULT_XDR_RW_DEPTH_LIMIT);
// 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<u8>.
obj.write_xdr(&mut w)
.map_err(|_| (ScErrorType::Budget, ScErrorCode::ExceededLimit).into())
}

// Host-less metered XDR decoding.
// Prefer using `metered_from_xdr` when host is available for better error
// reporting.
pub fn metered_from_xdr_with_budget<T: ReadXdr>(
bytes: &[u8],
budget: &Budget,
) -> Result<T, HostError> {
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())
}
3 changes: 2 additions & 1 deletion soroban-env-host/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
//! - The [storage] module which is responsible for providing an interface
//! between contracts and their durable storage.
//!

#![recursion_limit = "256"]
#[cfg(all(not(target_family = "wasm"), feature = "tracy"))]
macro_rules! tracy_span {
() => {
Expand Down Expand Up @@ -71,4 +71,5 @@ pub use host::{
};
pub use soroban_env_common::*;

pub mod e2e_invoke;
pub mod fees;
6 changes: 3 additions & 3 deletions soroban-env-host/src/test/budget_metering.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use crate::{
budget::AsBudget,
host::metered_clone::MeteredClone,
host::{metered_clone::MeteredClone, metered_xdr::metered_write_xdr},
xdr::{ContractCostType, ScMap, ScMapEntry, ScVal},
Env, Host, HostError, Symbol, Val,
};
Expand Down Expand Up @@ -93,7 +93,7 @@ fn metered_xdr() -> Result<(), HostError> {
.try_into(),
)?;
let mut w = Vec::<u8>::new();
host.metered_write_xdr(&scmap, &mut w)?;
metered_write_xdr(host.budget_ref(), &scmap, &mut w)?;
host.with_budget(|budget| {
assert_eq!(
budget.get_tracker(ContractCostType::ValSer)?.1,
Expand Down Expand Up @@ -133,7 +133,7 @@ fn metered_xdr_out_of_budget() -> Result<(), HostError> {
.try_into(),
)?;
let mut w = Vec::<u8>::new();
let res = host.metered_write_xdr(&scmap, &mut w);
let res = metered_write_xdr(host.budget_ref(), &scmap, &mut w);
let code = (ScErrorType::Budget, ScErrorCode::ExceededLimit);
assert!(HostError::result_matches_err(res, code));
Ok(())
Expand Down
2 changes: 1 addition & 1 deletion soroban-env-host/src/test/complex.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ fn run_complex() -> Result<(), HostError> {
Symbol::try_from_small_str("go")?,
host.add_host_object(HostVec::new())?,
)?;
let (store, _, _) = host.try_finish().unwrap();
let (store, _) = host.try_finish().unwrap();
store.footprint
};

Expand Down
4 changes: 2 additions & 2 deletions soroban-env-host/src/test/metering_benchmark.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ fn run_add_i32() -> Result<(), HostError> {
Symbol::try_from_small_str("add")?,
host.test_vec_obj(&[a, b])?,
)?;
let (store, _, _) = host.try_finish().unwrap();
let (store, _) = host.try_finish().unwrap();
store.footprint
};
// Run 2: enforce preflight footprint
Expand Down Expand Up @@ -91,7 +91,7 @@ fn run_complex() -> Result<(), HostError> {
Symbol::try_from_small_str("go")?,
host.add_host_object(HostVec::new())?,
)?;
let (store, _, _) = host.try_finish().unwrap();
let (store, _) = host.try_finish().unwrap();
store.footprint
};

Expand Down

0 comments on commit 75c0439

Please sign in to comment.