Skip to content

Commit

Permalink
perf: directly provide payload to wasm triggers and smart contracts
Browse files Browse the repository at this point in the history
Signed-off-by: Marin Veršić <[email protected]>
  • Loading branch information
mversic committed Oct 29, 2024
1 parent 3d01a20 commit df808b1
Show file tree
Hide file tree
Showing 9 changed files with 93 additions and 128 deletions.
2 changes: 1 addition & 1 deletion crates/iroha/src/secrecy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ impl SecretString {
}
}

const REDACTED: &'static str = "[REDACTED]";
const REDACTED: &str = "[REDACTED]";

impl Serialize for SecretString {
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
Expand Down
107 changes: 32 additions & 75 deletions crates/iroha_core/src/smartcontracts/wasm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,6 @@ const WASM_MODULE: &str = "iroha";
mod export {
pub const EXECUTE_ISI: &str = "execute_instruction";
pub const EXECUTE_QUERY: &str = "execute_query";
pub const GET_SMART_CONTRACT_CONTEXT: &str = "get_smart_contract_context";
pub const GET_TRIGGER_CONTEXT: &str = "get_trigger_context";
pub const SET_DATA_MODEL: &str = "set_data_model";

pub const DBG: &str = "dbg";
Expand Down Expand Up @@ -771,11 +769,11 @@ where
let instance = self.instantiate_module(module, &mut store)?;

let validate_fn = Self::get_typed_func(&instance, &mut store, validate_fn_name)?;
let payload = Self::get_validate_payload(&instance, &mut store);
let context = Self::get_validate_context(&instance, &mut store);

// NOTE: This function takes ownership of the pointer
let offset = validate_fn
.call(&mut store, payload)
.call(&mut store, context)
.map_err(ExportFnCallError::from)?;

let memory =
Expand All @@ -797,19 +795,19 @@ where
Ok(validation_res)
}

fn get_validate_payload(
fn get_validate_context(
instance: &Instance,
store: &mut Store<CommonState<W, Validate<T>>>,
) -> WasmUsize {
let state = store.data();
let payload = payloads::Validate {
let context = payloads::Validate {
context: payloads::ExecutorContext {
authority: state.authority.clone(),
curr_block: state.specific_state.curr_block,
},
target: state.specific_state.to_validate.clone(),
};
Runtime::encode_payload(instance, store, payload)
Self::encode_payload(instance, store, context)
}
}

Expand Down Expand Up @@ -940,14 +938,15 @@ impl<'wrld, 'block: 'wrld, 'state: 'block> Runtime<state::SmartContract<'wrld, '
state: state::SmartContract<'wrld, 'block, 'state>,
) -> Result<()> {
let mut store = self.create_store(state);
let smart_contract = self.create_smart_contract(&mut store, bytes)?;
let instance = self.create_smart_contract(&mut store, bytes)?;

let main_fn: TypedFunc<_, ()> =
Self::get_typed_func(&smart_contract, &mut store, import::SMART_CONTRACT_MAIN)?;
Self::get_typed_func(&instance, &mut store, import::SMART_CONTRACT_MAIN)?;
let context = Self::get_smart_contract_context(&instance, &mut store);

// NOTE: This function takes ownership of the pointer
main_fn
.call(&mut store, ())
.call(&mut store, context)
.map_err(ExportFnCallError::from)?;
let mut state = store.into_data();
let executed_queries = state.take_executed_queries();
Expand All @@ -956,12 +955,16 @@ impl<'wrld, 'block: 'wrld, 'state: 'block> Runtime<state::SmartContract<'wrld, '
Ok(())
}

#[codec::wrap]
fn get_smart_contract_context(state: &state::SmartContract) -> payloads::SmartContractContext {
payloads::SmartContractContext {
fn get_smart_contract_context(
instance: &Instance,
store: &mut Store<state::SmartContract<'wrld, 'block, 'state>>,
) -> WasmUsize {
let state = store.data();
let payload = payloads::SmartContractContext {
authority: state.authority.clone(),
curr_block: state.state.0.curr_block,
}
};
Runtime::encode_payload(instance, store, payload)
}
}

Expand Down Expand Up @@ -1019,10 +1022,11 @@ impl<'wrld, 'block: 'wrld, 'state: 'block> Runtime<state::Trigger<'wrld, 'block,

let main_fn: TypedFunc<_, ()> =
Self::get_typed_func(&instance, &mut store, import::TRIGGER_MAIN)?;
let context = Self::get_trigger_context(&instance, &mut store);

// NOTE: This function takes ownership of the pointer
main_fn
.call(&mut store, ())
.call(&mut store, context)
.map_err(ExportFnCallError::from)?;

let mut state = store.into_data();
Expand All @@ -1032,14 +1036,18 @@ impl<'wrld, 'block: 'wrld, 'state: 'block> Runtime<state::Trigger<'wrld, 'block,
Ok(())
}

#[codec::wrap]
fn get_trigger_context(state: &state::Trigger) -> payloads::TriggerContext {
payloads::TriggerContext {
fn get_trigger_context(
instance: &Instance,
store: &mut Store<state::Trigger<'wrld, 'block, 'state>>,
) -> WasmUsize {
let state = store.data();
let payload = payloads::TriggerContext {
id: state.specific_state.id.clone(),
authority: state.authority.clone(),
curr_block: state.state.0.curr_block,
event: state.specific_state.triggering_event.clone(),
}
};
Runtime::encode_payload(instance, store, payload)
}
}

Expand Down Expand Up @@ -1302,24 +1310,24 @@ impl<'wrld, 'block, 'state> Runtime<state::executor::Migrate<'wrld, 'block, 'sta

let migrate_fn: TypedFunc<WasmUsize, ()> =
Self::get_typed_func(&instance, &mut store, import::EXECUTOR_MIGRATE)?;
let payload = Self::get_migrate_payload(&instance, &mut store);
let context = Self::get_migrate_context(&instance, &mut store);

migrate_fn
.call(&mut store, payload)
.call(&mut store, context)
.map_err(ExportFnCallError::from)?;

Ok(())
}

fn get_migrate_payload(
fn get_migrate_context(
instance: &Instance,
store: &mut Store<CommonState<WithMut<'wrld, 'block, 'state>, Migrate>>,
) -> WasmUsize {
let payload = payloads::ExecutorContext {
let context = payloads::ExecutorContext {
authority: store.data().authority.clone(),
curr_block: store.data().state.0.curr_block,
};
Self::encode_payload(instance, store, payload)
Self::encode_payload(instance, store, context)
}
}

Expand Down Expand Up @@ -1436,7 +1444,6 @@ impl<'wrld, 'block, 'state> RuntimeBuilder<state::SmartContract<'wrld, 'block, '
create_imports!(linker, state::SmartContract<'wrld, 'block, 'state>,
export::EXECUTE_ISI => |caller: ::wasmtime::Caller<state::SmartContract<'wrld, 'block, 'state>>, offset, len| Runtime::execute_instruction(caller, offset, len),
export::EXECUTE_QUERY => |caller: ::wasmtime::Caller<state::SmartContract<'wrld, 'block, 'state>>, offset, len| Runtime::execute_query(caller, offset, len),
export::GET_SMART_CONTRACT_CONTEXT => |caller: ::wasmtime::Caller<state::SmartContract<'wrld, 'block, 'state>>| Runtime::get_smart_contract_context(caller),
)?;
Ok(linker)
})
Expand All @@ -1456,7 +1463,6 @@ impl<'wrld, 'block, 'state> RuntimeBuilder<state::Trigger<'wrld, 'block, 'state>
create_imports!(linker, state::Trigger<'wrld, 'block, 'state>,
export::EXECUTE_ISI => |caller: ::wasmtime::Caller<state::Trigger<'wrld, 'block, 'state>>, offset, len| Runtime::execute_instruction(caller, offset, len),
export::EXECUTE_QUERY => |caller: ::wasmtime::Caller<state::Trigger<'wrld, 'block, 'state>>, offset, len| Runtime::execute_query(caller, offset, len),
export::GET_TRIGGER_CONTEXT => |caller: ::wasmtime::Caller<state::Trigger<'wrld, 'block, 'state>>| Runtime::get_trigger_context(caller),
)?;
Ok(linker)
})
Expand Down Expand Up @@ -1895,53 +1901,4 @@ mod tests {

Ok(())
}

#[test]
async fn trigger_related_func_is_not_linked_for_smart_contract() -> Result<(), Error> {
let (authority, _authority_keypair) = gen_account_in("wonderland");
let kura = Kura::blank_kura_for_testing();
let query_handle = LiveQueryStore::start_test();
let state = State::new(world_with_test_account(&authority), kura, query_handle);

let wat = format!(
r#"
(module
;; Import host function to execute
(import "iroha" "{get_trigger_payload_fn_name}"
(func $exec_fn (param) (result i32)))
{memory_and_alloc}
;; Function which starts the smartcontract execution
(func (export "{main_fn_name}") (param)
(call $exec_fn)
;; No use of return values
drop))
"#,
main_fn_name = import::SMART_CONTRACT_MAIN,
get_trigger_payload_fn_name = export::GET_TRIGGER_CONTEXT,
// this test doesn't use the memory
memory_and_alloc = memory_and_alloc(""),
);

let mut runtime = RuntimeBuilder::<state::SmartContract>::new().build()?;
let block_header = ValidBlock::new_dummy(&KeyPair::random().into_parts().1)
.as_ref()
.header();
let mut state_block = state.block(block_header);
let mut state_transaction = state_block.transaction();
let err = runtime
.execute(&mut state_transaction, authority, wat)
.expect_err("Execution should fail");
state_transaction.apply();
state_block.commit();

assert!(matches!(
err,
Error::Instantiation(InstantiationError::Linker(_))
));

Ok(())
}
}
2 changes: 1 addition & 1 deletion crates/iroha_crypto/src/hash.rs
Original file line number Diff line number Diff line change
Expand Up @@ -278,7 +278,7 @@ impl<T> FromStr for HashOf<T> {
type Err = ParseError;

fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(s.parse::<Hash>().map(Self::from_untyped_unchecked)?)
s.parse::<Hash>().map(Self::from_untyped_unchecked)
}
}

Expand Down
43 changes: 29 additions & 14 deletions crates/iroha_executor/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,35 +31,51 @@ pub mod log {
}

/// Get context for `validate_transaction()` entrypoint.
///
/// # Safety
///
/// It's safe to call this function as long as it's safe to construct, from the given
/// pointer, byte array of prefix length and `Box<[u8]>` containing the encoded object
#[cfg(not(test))]
pub fn decode_execute_transaction_context(
pub unsafe fn decode_execute_transaction_context(
context: *const u8,
) -> payloads::Validate<SignedTransaction> {
// Safety: ownership of the provided context is transferred into `_decode_from_raw`
unsafe { decode_with_length_prefix_from_raw(context) }
decode_with_length_prefix_from_raw(context)
}

/// Get context for `validate_instruction()` entrypoint.
///
/// # Safety
///
/// It's safe to call this function as long as it's safe to construct, from the given
/// pointer, byte array of prefix length and `Box<[u8]>` containing the encoded object
#[cfg(not(test))]
pub fn decode_execute_instruction_context(
pub unsafe fn decode_execute_instruction_context(
context: *const u8,
) -> payloads::Validate<InstructionBox> {
// Safety: ownership of the provided context is transferred into `_decode_from_raw`
unsafe { decode_with_length_prefix_from_raw(context) }
decode_with_length_prefix_from_raw(context)
}

/// Get context for `validate_query()` entrypoint.
///
/// # Safety
///
/// It's safe to call this function as long as it's safe to construct, from the given
/// pointer, byte array of prefix length and `Box<[u8]>` containing the encoded object
#[cfg(not(test))]
pub fn decode_validate_query_context(context: *const u8) -> payloads::Validate<AnyQueryBox> {
// Safety: ownership of the provided context is transferred into `_decode_from_raw`
unsafe { decode_with_length_prefix_from_raw(context) }
pub unsafe fn decode_validate_query_context(context: *const u8) -> payloads::Validate<AnyQueryBox> {
decode_with_length_prefix_from_raw(context)
}

/// Get context for `migrate()` entrypoint.
///
/// # Safety
///
/// It's safe to call this function as long as it's safe to construct, from the given
/// pointer, byte array of prefix length and `Box<[u8]>` containing the encoded object
#[cfg(not(test))]
pub fn decode_migrate_context(context: *const u8) -> payloads::ExecutorContext {
// Safety: ownership of the provided context is transferred into `_decode_from_raw`
unsafe { decode_with_length_prefix_from_raw(context) }
pub unsafe fn decode_migrate_context(context: *const u8) -> payloads::ExecutorContext {
decode_with_length_prefix_from_raw(context)
}

/// Set new [`ExecutorDataModel`].
Expand Down Expand Up @@ -227,8 +243,7 @@ impl DataModelBuilder {
let account_permissions = host
.query(FindPermissionsByAccountId::new(account.id().clone()))
.execute()
.unwrap()
.into_iter();
.unwrap();

for permission in account_permissions.map(|permission| permission.unwrap()) {
if !self.permissions.contains(permission.name()) {
Expand Down
17 changes: 9 additions & 8 deletions crates/iroha_smart_contract/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -226,10 +226,17 @@ pub fn stub_getrandom(_dest: &mut [u8]) -> Result<(), getrandom::Error> {
}

/// Get context for smart contract `main()` entrypoint.
///
/// # Safety
///
/// It's safe to call this function as long as it's safe to construct, from the given
/// pointer, byte array of prefix length and `Box<[u8]>` containing the encoded object
#[cfg(not(test))]
pub fn get_smart_contract_context() -> data_model::smart_contract::payloads::SmartContractContext {
pub unsafe fn decode_smart_contract_context(
context: *const u8,
) -> data_model::smart_contract::payloads::SmartContractContext {
// Safety: ownership of the returned result is transferred into `_decode_from_raw`
unsafe { decode_with_length_prefix_from_raw(host::get_smart_contract_context()) }
decode_with_length_prefix_from_raw(context)
}

#[cfg(not(test))]
Expand All @@ -253,12 +260,6 @@ mod host {
/// This function doesn't take ownership of the provided allocation
/// but it does transfer ownership of the result to the caller
pub(super) fn execute_instruction(ptr: *const u8, len: usize) -> *const u8;

/// Get context for smart contract `main()` entrypoint.
/// # Warning
///
/// This function transfers ownership of the result to the caller
pub(super) fn get_smart_contract_context() -> *const u8;
}
}

Expand Down
4 changes: 2 additions & 2 deletions crates/iroha_smart_contract_derive/src/entrypoint.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,9 @@ pub fn impl_entrypoint(emitter: &mut Emitter, item: syn::ItemFn) -> TokenStream
/// Smart contract entrypoint
#[no_mangle]
#[doc(hidden)]
unsafe extern "C" fn #main_fn_name() {
unsafe extern "C" fn #main_fn_name(context: *const u8) {
let host = ::iroha_smart_contract::Iroha;
let context = ::iroha_smart_contract::get_smart_contract_context();
let context = ::iroha_trigger::decode_trigger_context(context);
#fn_name(host, context)
}

Expand Down
28 changes: 10 additions & 18 deletions crates/iroha_trigger/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,26 +12,18 @@ pub mod log {
pub use iroha_smart_contract_utils::{debug, error, event, info, log::*, trace, warn};
}

/// Get context for smart contract `main()` entrypoint.
///
/// # Safety
///
/// It's safe to call this function as long as it's safe to construct, from the given
/// pointer, byte array of prefix length and `Box<[u8]>` containing the encoded object
#[cfg(not(test))]
mod host {
#[link(wasm_import_module = "iroha")]
extern "C" {
/// Get context for trigger `main()` entrypoint.
///
/// # Warning
///
/// This function does transfer ownership of the result to the caller
pub(super) fn get_trigger_context() -> *const u8;
}
}

/// Get context for trigger `main()` entrypoint.
#[cfg(not(test))]
pub fn get_trigger_context() -> data_model::smart_contract::payloads::TriggerContext {
pub unsafe fn decode_trigger_context(
context: *const u8,
) -> data_model::smart_contract::payloads::TriggerContext {
// Safety: ownership of the returned result is transferred into `_decode_from_raw`
unsafe {
iroha_smart_contract_utils::decode_with_length_prefix_from_raw(host::get_trigger_context())
}
iroha_smart_contract_utils::decode_with_length_prefix_from_raw(context)
}

pub mod prelude {
Expand Down
Loading

0 comments on commit df808b1

Please sign in to comment.