Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement the InvokeHostFunctionOp part of CAP-52 #3450

Merged
merged 16 commits into from
Jun 17, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

45 changes: 44 additions & 1 deletion src/protocol-next/xdr/Stellar-transaction.x
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// under the Apache License, Version 2.0. See the COPYING file at the root
// of this distribution or at http://www.apache.org/licenses/LICENSE-2.0

%#include "xdr/Stellar-contract.h"
%#include "xdr/Stellar-ledger-entries.h"

namespace stellar
Expand Down Expand Up @@ -64,7 +65,8 @@ enum OperationType
CLAWBACK_CLAIMABLE_BALANCE = 20,
SET_TRUST_LINE_FLAGS = 21,
LIQUIDITY_POOL_DEPOSIT = 22,
LIQUIDITY_POOL_WITHDRAW = 23
LIQUIDITY_POOL_WITHDRAW = 23,
INVOKE_HOST_FUNCTION = 24
};

/* CreateAccount
Expand Down Expand Up @@ -472,6 +474,24 @@ struct LiquidityPoolWithdrawOp
int64 minAmountB; // minimum amount of second asset to withdraw
};

enum HostFunction
{
HOST_FN_CALL = 0,
HOST_FN_CREATE_CONTRACT = 1
};

struct InvokeHostFunctionOp
{
// The host function to invoke
HostFunction function;

// Parameters to the host function
SCVec parameters;

// The footprint for this invocation
LedgerFootprint footprint;
};

/* An operation is the lowest unit of work that a transaction does */
struct Operation
{
Expand Down Expand Up @@ -530,6 +550,8 @@ struct Operation
LiquidityPoolDepositOp liquidityPoolDepositOp;
case LIQUIDITY_POOL_WITHDRAW:
LiquidityPoolWithdrawOp liquidityPoolWithdrawOp;
case INVOKE_HOST_FUNCTION:
InvokeHostFunctionOp invokeHostFunctionOp;
}
body;
};
Expand Down Expand Up @@ -1595,6 +1617,25 @@ case LIQUIDITY_POOL_WITHDRAW_UNDER_MINIMUM:
void;
};

enum InvokeHostFunctionResultCode
{
// codes considered as "success" for the operation
INVOKE_HOST_FUNCTION_SUCCESS = 0,

// codes considered as "failure" for the operation
INVOKE_HOST_FUNCTION_MALFORMED = -1,
INVOKE_HOST_FUNCTION_TRAPPED = -2
};

union InvokeHostFunctionResult switch (InvokeHostFunctionResultCode code)
{
case INVOKE_HOST_FUNCTION_SUCCESS:
void;
graydon marked this conversation as resolved.
Show resolved Hide resolved
case INVOKE_HOST_FUNCTION_MALFORMED:
case INVOKE_HOST_FUNCTION_TRAPPED:
void;
};

/* High level Operation Result */
enum OperationResultCode
{
Expand Down Expand Up @@ -1661,6 +1702,8 @@ case opINNER:
LiquidityPoolDepositResult liquidityPoolDepositResult;
case LIQUIDITY_POOL_WITHDRAW:
LiquidityPoolWithdrawResult liquidityPoolWithdrawResult;
case INVOKE_HOST_FUNCTION:
InvokeHostFunctionResult invokeHostFunctionResult;
}
tr;
case opBAD_AUTH:
Expand Down
2 changes: 1 addition & 1 deletion src/rust/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,6 @@ log = "0.4.17"
cxx = "1.0"
im-rc = "15.0.0"
base64 = "0.13.0"
stellar-contract-env-host = { git = "https://github.com/stellar/rs-stellar-contract-env", rev = "9a783c26cbc022e0351538a3e9582a58510bc694", features = [
stellar-contract-env-host = { git = "https://github.com/stellar/rs-stellar-contract-env", rev = "7c6958bd460e00bc6378b23a133b9ab4736b56c2", features = [
"vm",
] }
178 changes: 89 additions & 89 deletions src/rust/src/contract.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,39 +2,39 @@
// under the Apache License, Version 2.0. See the COPYING file at the root
// of this distribution or at http://www.apache.org/licenses/LICENSE-2.0

use crate::{log::partition::TX, rust_bridge::XDRBuf};
use crate::{
log::partition::TX,
rust_bridge::{Bytes, XDRBuf},
};
use log::info;
use std::io::Cursor;

use cxx::CxxString;
use im_rc::OrdMap;
use std::error::Error;
use stellar_contract_env_host::{
storage::{self, AccessType, Key, Storage},
storage::{self, AccessType, Storage},
xdr,
xdr::{
ContractDataEntry, Hash, LedgerEntry, LedgerEntryData, LedgerKey, ReadXdr, ScObject,
ScStatic, ScVal, ScVec, WriteXdr,
HostFunction, LedgerEntry, LedgerEntryData, LedgerKey, LedgerKeyAccount,
LedgerKeyContractData, LedgerKeyTrustLine, ReadXdr, ScVec, WriteXdr,
},
Host, HostError, Vm,
Host, HostError,
};

/// Helper for [`build_storage_footprint_from_xdr`] that inserts a copy of some
/// [`AccessType`] `ty` into a [`storage::Footprint`] for every [`LedgerKey`] in
/// `keys`.
fn populate_access_map_with_contract_data_keys(
access: &mut OrdMap<Key, AccessType>,
fn populate_access_map(
access: &mut OrdMap<LedgerKey, AccessType>,
keys: Vec<LedgerKey>,
ty: AccessType,
) -> Result<(), HostError> {
for lk in keys {
match lk {
LedgerKey::ContractData(xdr::LedgerKeyContractData { contract_id, key }) => {
let sk = Key { contract_id, key };
access.insert(sk, ty.clone());
}
_ => return Err(HostError::General("unexpected ledger key type").into()),
}
LedgerKey::Account(_) | LedgerKey::Trustline(_) | LedgerKey::ContractData(_) => (),
_ => return Err(HostError::General("unexpected ledger entry type")),
};
access.insert(lk, ty.clone());
}
Ok(())
}
Expand All @@ -49,112 +49,112 @@ fn build_storage_footprint_from_xdr(footprint: &XDRBuf) -> Result<storage::Footp
} = xdr::LedgerFootprint::read_xdr(&mut Cursor::new(footprint.data.as_slice()))?;
let mut access = OrdMap::new();

populate_access_map_with_contract_data_keys(
&mut access,
read_only.to_vec(),
AccessType::ReadOnly,
)?;
populate_access_map_with_contract_data_keys(
&mut access,
read_write.to_vec(),
AccessType::ReadWrite,
)?;
populate_access_map(&mut access, read_only.to_vec(), AccessType::ReadOnly)?;
populate_access_map(&mut access, read_write.to_vec(), AccessType::ReadWrite)?;
Ok(storage::Footprint(access))
}

fn ledger_entry_to_ledger_key(le: &LedgerEntry) -> Result<LedgerKey, HostError> {
match &le.data {
LedgerEntryData::Account(a) => Ok(LedgerKey::Account(LedgerKeyAccount {
account_id: a.account_id.clone(),
})),
LedgerEntryData::Trustline(tl) => Ok(LedgerKey::Trustline(LedgerKeyTrustLine {
account_id: tl.account_id.clone(),
asset: tl.asset.clone(),
})),
LedgerEntryData::ContractData(cd) => Ok(LedgerKey::ContractData(LedgerKeyContractData {
contract_id: cd.contract_id.clone(),
key: cd.key.clone(),
})),
_ => Err(HostError::General("unexpected ledger key")),
}
}

/// Deserializes a sequence of [`xdr::LedgerEntry`] structures from a vector of
/// [`XDRBuf`] buffers, inserts them into an [`OrdMap`] with entries
/// additionally keyed by the provided contract ID, checks that the entries
/// match the provided [`storage::Footprint`], and returns the constructed map.
fn build_storage_map_from_xdr_ledger_entries(
footprint: &storage::Footprint,
ledger_entries: &Vec<XDRBuf>,
) -> Result<OrdMap<Key, Option<ScVal>>, HostError> {
) -> Result<OrdMap<LedgerKey, Option<LedgerEntry>>, HostError> {
let mut map = OrdMap::new();
for buf in ledger_entries {
let le = LedgerEntry::read_xdr(&mut Cursor::new(buf.data.as_slice()))?;
match le.data {
LedgerEntryData::ContractData(ContractDataEntry {
key,
val,
contract_id,
}) => {
let sk = Key { contract_id, key };
if !footprint.0.contains_key(&sk) {
return Err(HostError::General("ledger entry not found in footprint").into());
}
map.insert(sk.clone(), Some(val));
}
_ => return Err(HostError::General("unexpected ledger entry type").into()),
let key = ledger_entry_to_ledger_key(&le)?;
if !footprint.0.contains_key(&key) {
return Err(HostError::General("ledger entry not found in footprint").into());
}
map.insert(key, Some(le));
}
for k in footprint.0.keys() {
if !map.contains_key(k) {
return Err(HostError::General("ledger entry not found for footprint entry").into());
map.insert(k.clone(), None);
}
}
Ok(map)
}

/// Looks up an [`ScObject`] un the provided map under the ledger key
/// [`ScStatic::LedgerKeyContractCodeWasm`] and returns a copy of its binary
/// data, which should be WASM bytecode.
fn extract_contract_wasm_from_storage_map(
contract_id: &Hash,
map: &OrdMap<Key, Option<ScVal>>,
) -> Result<Vec<u8>, HostError> {
let wasm_key = Key {
contract_id: contract_id.clone(),
key: ScVal::Static(ScStatic::LedgerKeyContractCodeWasm),
};
let wasm = match map.get(&wasm_key) {
Some(Some(ScVal::Object(Some(ScObject::Binary(blob))))) => blob.clone(),
Some(_) => {
return Err(HostError::General(
"unexpected value type for LEDGER_KEY_CONTRACT_CODE_WASM",
)
.into())
}
None => {
return Err(
HostError::General("missing value for LEDGER_KEY_CONTRACT_CODE_WASM").into(),
)
/// Iterates over the storage map and serializes the read-write ledger entries
/// back to XDR.
fn build_xdr_ledger_entries_from_storage_map(
footprint: &storage::Footprint,
storage_map: &OrdMap<LedgerKey, Option<LedgerEntry>>,
) -> Result<Vec<Bytes>, HostError> {
let mut res = Vec::new();
for (lk, ole) in storage_map {
let mut xdr_buf: Vec<u8> = Vec::new();
match footprint.0.get(lk) {
Some(AccessType::ReadOnly) => (),
Some(AccessType::ReadWrite) => {
if let Some(le) = ole {
le.write_xdr(&mut Cursor::new(&mut xdr_buf))?;
res.push(Bytes { vec: xdr_buf });
}
}
None => return Err(HostError::General("ledger entry not in footprint")),
}
};
Ok(wasm.to_vec())
}
Ok(res)
}

/// Deserializes an [`xdr::Hash`] contract ID, an [`ScVec`] XDR object of
/// arguments, an [`xdr::LedgerFootprint`] and a sequence of [`xdr::LedgerEntry`]
/// entries containing all the data the contract intends to read. Then loads
/// some WASM bytecode out of the provided ledger entries (keyed under
/// [`ScStatic::LedgerKeyContractCodeWasm`]), instantiates a [`Host`] and [`Vm`]
/// with the provided WASM, invokes the requested function in the WASM, and
/// serializes an [`xdr::ScVal`] back into a return value.
pub(crate) fn invoke_contract(
contract_id: &XDRBuf,
func: &CxxString,
args: &XDRBuf,
footprint: &XDRBuf,
/// Deserializes an [`xdr::HostFunction`] host function identifier, an [`xdr::ScVec`] XDR object of
/// arguments, an [`xdr::Footprint`] and a sequence of [`xdr::LedgerEntry`] entries containing all
/// the data the invocation intends to read. Then calls the host function with the specified
/// arguments, discards the [`xdr::ScVal`] return value, and returns the [`ReadWrite`] ledger
/// entries in serialized form. Ledger entries not returned have been deleted.
pub(crate) fn invoke_host_function(
hf_buf: &XDRBuf,
args_buf: &XDRBuf,
footprint_buf: &XDRBuf,
ledger_entries: &Vec<XDRBuf>,
) -> Result<Vec<u8>, Box<dyn Error>> {
let contract_id = Hash::read_xdr(&mut Cursor::new(contract_id.data.as_slice()))?;
let arg_scvals = ScVec::read_xdr(&mut Cursor::new(args.data.as_slice()))?;
) -> Result<Vec<Bytes>, Box<dyn Error>> {
let hf = HostFunction::read_xdr(&mut Cursor::new(hf_buf.data.as_slice()))?;
let args = ScVec::read_xdr(&mut Cursor::new(args_buf.data.as_slice()))?;

let footprint = build_storage_footprint_from_xdr(footprint)?;
let footprint = build_storage_footprint_from_xdr(footprint_buf)?;
let map = build_storage_map_from_xdr_ledger_entries(&footprint, ledger_entries)?;
let wasm = extract_contract_wasm_from_storage_map(&contract_id, &map)?;

let func_str = func.to_str()?;

let storage = Storage::with_enforcing_footprint_and_map(footprint, map);
let mut host = Host::with_storage(storage);
let vm = Vm::new(&host, contract_id, wasm.as_slice())?;

info!(target: TX, "Invoking contract function '{}'", func);
let res = vm.invoke_function(&mut host, func_str, &arg_scvals)?;
match hf {
HostFunction::Call => {
info!(target: TX, "Invoking host function 'Call'");
host.invoke_function(hf, args)?;
}
HostFunction::CreateContract => {
info!(target: TX, "Invoking host function 'CreateContract'");
todo!();
}
};

let mut ret_xdr_buf: Vec<u8> = Vec::new();
res.write_xdr(&mut Cursor::new(&mut ret_xdr_buf))?;
Ok(ret_xdr_buf)
let storage = host
.try_recover_storage()
.map_err(|_h| HostError::General("could not get storage from host"))?;
Ok(build_xdr_ledger_entries_from_storage_map(
&storage.footprint,
&storage.map,
)?)
}
Loading