Skip to content

Commit

Permalink
Proposal: Allow Extending Instance and Code TTL With Separate Values …
Browse files Browse the repository at this point in the history
…on the Host Environment For More Cost-Efficient Implementations (#1368)

This is a copy of #1355
with clean history.

---------

Co-authored-by: Siddharth Suresh <[email protected]>
Co-authored-by: Jay Geng <[email protected]>
  • Loading branch information
3 people authored Mar 26, 2024
1 parent 41b4ee3 commit a54b40f
Show file tree
Hide file tree
Showing 6 changed files with 256 additions and 6 deletions.
42 changes: 42 additions & 0 deletions soroban-env-common/env.json
Original file line number Diff line number Diff line change
Expand Up @@ -1447,6 +1447,48 @@
],
"return": "AddressObject",
"docs": "Get the id of the Stellar Asset contract corresponding to the provided asset without creating the instance. `serialized_asset` is `stellar::Asset` XDR serialized to bytes format. Returns the address of the would-be asset contract."
},
{
"export": "c",
"name": "extend_contract_instance_ttl",
"args": [
{
"name": "contract",
"type": "AddressObject"
},
{
"name": "threshold",
"type": "U32Val"
},
{
"name": "extend_to",
"type": "U32Val"
}
],
"return": "Void",
"docs": "If the TTL for the provided contract instance is below `threshold` ledgers, extend `live_until_ledger_seq` such that TTL == `extend_to`, where TTL is defined as live_until_ledger_seq - current ledger.",
"min_supported_protocol": 21
},
{
"export": "d",
"name": "extend_contract_code_ttl",
"args": [
{
"name": "contract",
"type": "AddressObject"
},
{
"name": "threshold",
"type": "U32Val"
},
{
"name": "extend_to",
"type": "U32Val"
}
],
"return": "Void",
"docs": "If the TTL for the provided contract's code (if applicable) is below `threshold` ledgers, extend `live_until_ledger_seq` such that TTL == `extend_to`, where TTL is defined as live_until_ledger_seq - current ledger.",
"min_supported_protocol": 21
}
]
},
Expand Down
42 changes: 42 additions & 0 deletions soroban-env-host/src/host.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2081,6 +2081,22 @@ impl VmCallerEnv for Host {
Ok(Val::VOID)
}

fn extend_contract_instance_ttl(
&self,
_vmcaller: &mut VmCaller<Self::VmUserState>,
contract: AddressObject,
threshold: U32Val,
extend_to: U32Val,
) -> Result<Void, Self::Error> {
let contract_id = self.contract_id_from_address(contract)?;
let key = self.contract_instance_ledger_key(&contract_id)?;
self.try_borrow_storage_mut()?
.extend_ttl(self, key, threshold.into(), extend_to.into())
.map_err(|e| self.decorate_contract_instance_storage_error(e, &contract_id))?;

Ok(Val::VOID)
}

fn extend_contract_instance_and_code_ttl(
&self,
_vmcaller: &mut VmCaller<Self::VmUserState>,
Expand All @@ -2097,6 +2113,32 @@ impl VmCallerEnv for Host {
Ok(Val::VOID)
}

fn extend_contract_code_ttl(
&self,
_vmcaller: &mut VmCaller<Self::VmUserState>,
contract: AddressObject,
threshold: U32Val,
extend_to: U32Val,
) -> Result<Void, Self::Error> {
let contract_id = self.contract_id_from_address(contract)?;
let key = self.contract_instance_ledger_key(&contract_id)?;

match self
.retrieve_contract_instance_from_storage(&key)?
.executable
{
ContractExecutable::Wasm(wasm_hash) => {
let key = self.contract_code_ledger_key(&wasm_hash)?;
self.try_borrow_storage_mut()?
.extend_ttl(self, key, threshold.into(), extend_to.into())
.map_err(|e| self.decorate_contract_code_storage_error(e, &wasm_hash))?;
}
ContractExecutable::StellarAsset => {}
}

Ok(Val::VOID)
}

// Notes on metering: covered by the components.
fn create_contract(
&self,
Expand Down
1 change: 1 addition & 0 deletions soroban-env-host/src/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ mod hostile_opt;
mod invocation;
mod ledger;
mod lifecycle;
mod lifetime_extension;
mod linear_memory;
mod map;
#[cfg(feature = "testutils")]
Expand Down
140 changes: 140 additions & 0 deletions soroban-env-host/src/test/lifetime_extension.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
// Note: ignoring error handling safety in these tests.
use crate::xdr::{ContractExecutable, Hash};
use soroban_env_common::{AddressObject, Env};
use soroban_test_wasms::CONTRACT_STORAGE;

use crate::Host;

struct InstanceCodeTest {
host: Host,
contract_id: AddressObject,
contract: Hash,
code: Hash,
}

impl InstanceCodeTest {
// We can potentially add some customizability for the ledger here.
fn setup() -> Self {
let host = Host::test_host_with_recording_footprint();
let contract_id = host.register_test_contract_wasm(CONTRACT_STORAGE);
let hash = host.contract_id_from_address(contract_id).unwrap();

let code = if let ContractExecutable::Wasm(hash) = host
.retrieve_contract_instance_from_storage(
&host.contract_instance_ledger_key(&hash).unwrap(),
)
.unwrap()
.executable
{
hash
} else {
panic!("Expected Wasm executable")
};

host.set_ledger_info(crate::LedgerInfo {
protocol_version: 21,
sequence_number: 4090,
max_entry_ttl: 10000,
..Default::default()
})
.unwrap();

Self {
host,
contract_id,
contract: hash,
code,
}
}
}

mod separate_instance_code_extension {
use crate::budget::AsBudget;

use super::*;

#[cfg(feature = "next")]
#[test]
fn extend_only_instance() {
let InstanceCodeTest {
host,
contract_id,
contract,
..
} = InstanceCodeTest::setup();

assert!(host
.extend_contract_instance_ttl(contract_id, 5.into(), 5000.into())
.is_ok());
let entry_with_live_until = host
.try_borrow_storage_mut()
.unwrap()
.get_with_live_until_ledger(
&host.contract_instance_ledger_key(&contract).unwrap(),
host.as_budget(),
)
.unwrap();

assert_eq!(entry_with_live_until.1, Some(9090));
}

#[cfg(feature = "next")]
#[test]
fn extend_only_code() {
let InstanceCodeTest {
host,
contract_id,
code,
..
} = InstanceCodeTest::setup();

assert!(host
.extend_contract_code_ttl(contract_id, 5.into(), 5000.into())
.is_ok());
let entry_with_live_until = host
.try_borrow_storage_mut()
.unwrap()
.get_with_live_until_ledger(
&host.contract_code_ledger_key(&code).unwrap(),
host.as_budget(),
)
.unwrap();

assert_eq!(entry_with_live_until.1, Some(9090));
}

#[test]
fn extend_code_and_instance() {
let InstanceCodeTest {
host,
contract_id,
code,
contract,
} = InstanceCodeTest::setup();

assert!(host
.extend_contract_instance_and_code_ttl(contract_id, 5.into(), 5000.into())
.is_ok());
let code_entry_with_live_until = host
.try_borrow_storage_mut()
.unwrap()
.get_with_live_until_ledger(
&host.contract_code_ledger_key(&code).unwrap(),
host.as_budget(),
)
.unwrap();

assert_eq!(code_entry_with_live_until.1, Some(9090));

let instance_entry_with_live_until = host
.try_borrow_storage_mut()
.unwrap()
.get_with_live_until_ledger(
&host.contract_instance_ledger_key(&contract).unwrap(),
host.as_budget(),
)
.unwrap();

assert_eq!(instance_entry_with_live_until.1, Some(9090));
}
}
10 changes: 10 additions & 0 deletions soroban-env-macros/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,15 @@ use stellar_xdr::next as xdr;

use crate::xdr::{Limits, ScEnvMetaEntry, WriteXdr};

// We need the protocol version for some tests generated by this crate.
// Unfortunately it is not available at this layer and can't read from
// `meta.rs`, since this is at the lower layer (`meta.rs` is compile-time
// generated by routines here)
#[cfg(not(feature = "next"))]
pub(crate) const LEDGER_PROTOCOL_VERSION: u32 = 20;
#[cfg(feature = "next")]
pub(crate) const LEDGER_PROTOCOL_VERSION: u32 = 21;

struct MetaInput {
pub interface_version: u64,
}
Expand All @@ -37,6 +46,7 @@ impl Parse for MetaInput {
input.parse::<Token![,]>()?;
assert!(pre <= 0xffff_ffff);
assert!(proto <= 0xffff_ffff);
assert_eq!(proto, LEDGER_PROTOCOL_VERSION as u64);
proto << 32 | pre
},
})
Expand Down
27 changes: 21 additions & 6 deletions soroban-env-macros/src/synth_dispatch_host_fn_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use proc_macro2::TokenStream;
use quote::{format_ident, quote};
use syn::{Error, LitStr};

use crate::Function;
use crate::{Function, LEDGER_PROTOCOL_VERSION};

use std::collections::{BTreeMap, BTreeSet};

Expand Down Expand Up @@ -80,6 +80,16 @@ fn dfs(edges: &BTreeMap<String, BTreeSet<String>>, ty: &String, set: &mut BTreeS
}
}

fn check_function_protocol_is_in_range(func: &Function) -> bool {
let min_supported_proto_is_too_new = func
.min_supported_protocol
.is_some_and(|v| v > LEDGER_PROTOCOL_VERSION);
let max_supported_proto_is_too_old = func
.max_supported_protocol
.is_some_and(|v| v < LEDGER_PROTOCOL_VERSION);
!min_supported_proto_is_too_new && !max_supported_proto_is_too_old
}

// This requires the input to be a valid signature
const SPECIAL_CASES: [&str; 1] = ["recover_key_ecdsa_secp256k1"];

Expand Down Expand Up @@ -220,11 +230,15 @@ pub fn generate_hostfn_call_with_wrong_types(file_lit: LitStr) -> Result<TokenSt
let mut type_to_fn_arg: BTreeMap<String, Vec<(Function, usize)>> = BTreeMap::new();
for m in root.modules.clone() {
for f in m.functions.clone() {
for (i, a) in f.args.iter().enumerate() {
type_to_fn_arg
.entry(a.r#type.clone())
.or_default()
.push((f.clone(), i));
// checks if the current ledger protocol version falls between
// supported protocol versions of this function
if check_function_protocol_is_in_range(&f) {
for (i, a) in f.args.iter().enumerate() {
type_to_fn_arg
.entry(a.r#type.clone())
.or_default()
.push((f.clone(), i));
}
}
}
}
Expand Down Expand Up @@ -324,6 +338,7 @@ pub fn generate_hostfn_call_with_invalid_obj_handles(
.modules
.iter()
.flat_map(|m| m.functions.clone().into_iter())
.filter(check_function_protocol_is_in_range)
.flat_map(|f| {
f.args
.clone()
Expand Down

0 comments on commit a54b40f

Please sign in to comment.