From ee63df242aadb0ffd6a0c8a52e8fc3921ec668de Mon Sep 17 00:00:00 2001 From: Lohachov Mykhailo Date: Thu, 12 Jun 2025 16:20:24 +0900 Subject: [PATCH 1/7] refactor: consolidate wasm execution Signed-off-by: Lohachov Mykhailo --- crates/iroha/src/client.rs | 1 - crates/iroha_cli/src/main.rs | 10 +- crates/iroha_core/src/executor.rs | 7 +- .../iroha_core/src/smartcontracts/isi/mod.rs | 15 ++ .../src/smartcontracts/isi/triggers/mod.rs | 58 +++++++ .../src/smartcontracts/isi/triggers/set.rs | 162 ++++++++++++++---- .../isi/triggers/specialized.rs | 2 +- crates/iroha_core/src/smartcontracts/wasm.rs | 43 ++--- crates/iroha_core/src/state.rs | 64 +++---- crates/iroha_core/src/tx.rs | 109 ++++++------ crates/iroha_data_model/src/block.rs | 11 +- crates/iroha_data_model/src/isi.rs | 125 +++++++++++++- crates/iroha_data_model/src/lib.rs | 2 + crates/iroha_data_model/src/transaction.rs | 89 +++++----- crates/iroha_data_model/src/visit.rs | 21 ++- crates/iroha_executor/src/default/mod.rs | 26 ++- crates/iroha_executor_derive/src/default.rs | 3 + crates/iroha_genesis/src/lib.rs | 14 +- crates/iroha_schema_gen/src/lib.rs | 15 +- docs/source/references/schema.json | 108 +++++++++--- integration_tests/tests/queries/mod.rs | 4 +- .../tests/triggers/by_call_trigger.rs | 4 +- 22 files changed, 618 insertions(+), 275 deletions(-) diff --git a/crates/iroha/src/client.rs b/crates/iroha/src/client.rs index a0ff894f8c3..3a30c223e01 100644 --- a/crates/iroha/src/client.rs +++ b/crates/iroha/src/client.rs @@ -201,7 +201,6 @@ impl Client { let mut tx_builder = match instructions.into() { Executable::Instructions(instructions) => tx_builder.with_instructions(instructions), - Executable::Wasm(wasm) => tx_builder.with_wasm(wasm), }; if let Some(transaction_ttl) = self.transaction_ttl { diff --git a/crates/iroha_cli/src/main.rs b/crates/iroha_cli/src/main.rs index f080ff09adb..3a535f0d71f 100644 --- a/crates/iroha_cli/src/main.rs +++ b/crates/iroha_cli/src/main.rs @@ -125,15 +125,17 @@ trait RunContext { /// Submit instructions or dump them to stdout depending on the flag fn finish(&mut self, instructions: impl Into) -> Result<()> { let mut instructions = match instructions.into() { - Executable::Wasm(wasm) => { - if self.input_instructions() || self.output_instructions() { + Executable::Instructions(instructions) => { + let has_wasm = instructions + .iter() + .any(|i| matches!(i, InstructionBox::ExecuteWasm(_))); + if has_wasm && (self.input_instructions() || self.output_instructions()) { eyre::bail!( "Incompatible `--input` `--output` flags with `iroha transaction wasm`" ) } - return self.submit(wasm); + instructions.into_vec() } - Executable::Instructions(instructions) => instructions.into_vec(), }; if self.input_instructions() { let mut acc: Vec = parse_json5_stdin_unchecked()?; diff --git a/crates/iroha_core/src/executor.rs b/crates/iroha_core/src/executor.rs index b55f40082fb..d394f97c8c4 100644 --- a/crates/iroha_core/src/executor.rs +++ b/crates/iroha_core/src/executor.rs @@ -128,12 +128,7 @@ impl Executor { match self { Self::Initial => { - let (_authority, Executable::Instructions(instructions)) = transaction.into() - else { - return Err(ValidationFail::NotPermitted( - "Genesis transaction must not be a smart contract".to_owned(), - )); - }; + let (_authority, Executable::Instructions(instructions)) = transaction.into(); for isi in instructions { isi.execute(authority, state_transaction)? diff --git a/crates/iroha_core/src/smartcontracts/isi/mod.rs b/crates/iroha_core/src/smartcontracts/isi/mod.rs index 888b94f10b1..61d8f58cfa5 100644 --- a/crates/iroha_core/src/smartcontracts/isi/mod.rs +++ b/crates/iroha_core/src/smartcontracts/isi/mod.rs @@ -53,6 +53,7 @@ impl Execute for InstructionBox { Self::Grant(isi) => isi.execute(authority, state_transaction), Self::Revoke(isi) => isi.execute(authority, state_transaction), Self::ExecuteTrigger(isi) => isi.execute(authority, state_transaction), + Self::ExecuteWasm(isi) => isi.execute(authority, state_transaction), Self::SetParameter(isi) => isi.execute(authority, state_transaction), Self::Upgrade(isi) => isi.execute(authority, state_transaction), Self::Log(isi) => isi.execute(authority, state_transaction), @@ -63,6 +64,20 @@ impl Execute for InstructionBox { } } +impl Execute for ExecuteWasmBox { + #[iroha_logger::log(name = "wasm", skip_all, fields(id))] + fn execute( + self, + authority: &AccountId, + state_transaction: &mut StateTransaction<'_, '_>, + ) -> Result<(), Error> { + match self { + Self::Smartcontract(isi) => isi.execute(authority, state_transaction), + Self::Trigger(isi) => isi.execute(authority, state_transaction), + } + } +} + impl Execute for RegisterBox { #[iroha_logger::log(name = "register", skip_all, fields(id))] fn execute( diff --git a/crates/iroha_core/src/smartcontracts/isi/triggers/mod.rs b/crates/iroha_core/src/smartcontracts/isi/triggers/mod.rs index fc55ec04d18..89b1f08e9d3 100644 --- a/crates/iroha_core/src/smartcontracts/isi/triggers/mod.rs +++ b/crates/iroha_core/src/smartcontracts/isi/triggers/mod.rs @@ -308,6 +308,64 @@ pub mod isi { Ok(()) } } + + use crate::smartcontracts::wasm; + impl Execute for WasmExecutable { + #[metrics(+"execute_wasm")] + fn execute( + self, + authority: &AccountId, + state_transaction: &mut StateTransaction<'_, '_>, + ) -> Result<(), Error> { + error!("WASM IS EXECUTED"); + let mut wasm_runtime = wasm::RuntimeBuilder::::new() + .with_config(state_transaction.world().parameters().smart_contract) + .with_engine(state_transaction.engine().clone()) // Cloning engine is cheap + .build() + .expect("failed to create wasm runtime"); + wasm_runtime + .execute(state_transaction, authority.clone(), self.object()) + .map_err(|error| { + Error::WasmExecution(crate::smartcontracts::isi::error::WasmExecutionError { + reason: format!("{:?}", eyre::Report::from(error)), + }) + }) + } + } + + impl Execute for WasmExecutable { + #[metrics(+"execute_wasm_trigger")] + fn execute( + self, + authority: &AccountId, + state_transaction: &mut StateTransaction<'_, '_>, + ) -> Result<(), Error> { + let module = state_transaction + .world() + .triggers() + .get_compiled_contract(self.object().hash()) + .expect("INTERNAL BUG: contract is not present") + .clone(); + wasm::RuntimeBuilder::::new() + .with_config(state_transaction.world().parameters().smart_contract) + .with_engine(state_transaction.engine.clone()) // Cloning engine is cheap + .build() + .and_then(|mut wasm_runtime| { + wasm_runtime.execute_trigger_module( + state_transaction, + self.object().id().as_ref().unwrap(), + authority.clone(), + &module, + self.object().event().clone().unwrap(), + ) + }) + .map_err(|error| { + Error::WasmExecution(crate::smartcontracts::isi::error::WasmExecutionError { + reason: format!("{:?}", eyre::Report::from(error)), + }) + }) + } + } } pub mod query { diff --git a/crates/iroha_core/src/smartcontracts/isi/triggers/set.rs b/crates/iroha_core/src/smartcontracts/isi/triggers/set.rs index fda21f9d420..89f5db929bf 100644 --- a/crates/iroha_core/src/smartcontracts/isi/triggers/set.rs +++ b/crates/iroha_core/src/smartcontracts/isi/triggers/set.rs @@ -324,14 +324,37 @@ pub trait SetReadOnly { } = action; let original_executable = match executable { - ExecutableRef::Wasm(ref blob_hash) => { - let original_wasm = self - .get_original_contract(blob_hash) - .cloned() - .expect("No original smartcontract saved for trigger. This is a bug."); - Executable::Wasm(original_wasm) - } - ExecutableRef::Instructions(isi) => Executable::Instructions(isi), + // ExecutableRef::Wasm(ref blob_hash) => { + // let original_wasm = self + // .get_original_contract(blob_hash) + // .cloned() + // .expect("No original smartcontract saved for trigger. This is a bug."); + // // Executable::Wasm(original_wasm) + // Executable::Instructions( + // [WasmExecutable::binary(original_wasm).into()] + // .into_iter() + // .collect::>() + // .into(), + // ) + // } + ExecutableRef::Instructions(isi) => { + Executable::Instructions(isi + .into_iter() + .map(|isi| -> InstructionBox{ + match isi { + InstructionBox::ExecuteWasm(ExecuteWasmBox::Trigger(trigger_module)) => { + WasmExecutable::binary(self + .get_original_contract(trigger_module.object().hash()) + .cloned() + .expect("No original smartcontract saved for trigger. This is a bug.")).into() + } + _ => isi + } + }) + .collect::>() + .into() + ) + }, }; SpecializedAction { @@ -688,30 +711,105 @@ impl<'block, 'set> SetTransaction<'block, 'set> { } let loaded_executable = match executable { - Executable::Wasm(bytes) => { - let hash = HashOf::new(&bytes); - // Store original executable representation to respond to queries with. - if let Some(WasmSmartContractEntry { count, .. }) = self.contracts.get_mut(&hash) { - // Considering 1 trigger registration takes 1 second, - // it would take 584 942 417 355 years to overflow. - *count = count.checked_add(1).expect( + Executable::Instructions(instructions) => { + ExecutableRef::Instructions( + instructions + .into_iter() + .map(|isi| -> Result { + match isi { + InstructionBox::ExecuteWasm(ExecuteWasmBox::Smartcontract( + wasm, + )) => { + let hash = HashOf::new(wasm.object()); + // Store original executable representation to respond to queries with. + if let Some(WasmSmartContractEntry { count, .. }) = + self.contracts.get_mut(&hash) + { + // Considering 1 trigger registration takes 1 second, + // it would take 584 942 417 355 years to overflow. + *count = count.checked_add(1).expect( "There is no way someone could register 2^64 amount of same triggers", ); - // Cloning module is cheap, under Arc inside - } else { - let module = wasm::load_module(engine, &bytes)?; - self.contracts.insert( - hash, - WasmSmartContractEntry { - original_contract: bytes, - compiled_contract: module, - count: NonZeroU64::MIN, - }, - ); - } - ExecutableRef::Wasm(hash) - } - Executable::Instructions(instructions) => ExecutableRef::Instructions(instructions), + // Cloning module is cheap, under Arc inside + } else { + let module = wasm::load_module(engine, wasm.object())?; + self.contracts.insert( + hash, + WasmSmartContractEntry { + original_contract: wasm.object().clone(), + compiled_contract: module, + count: NonZeroU64::MIN, + }, + ); + } + Ok(WasmExecutable::module(TriggerModule::from_hash(hash)) + .into()) + } + _ => Ok(isi), + } + }) + .collect::, _>>()? + .into(), + ) + // if instructions.len() == 1 { + // if let InstructionBox::ExecuteWasm(wasm_contract) = instructions[0].clone() { + // let wasm_contract = match wasm_contract { + // ExecuteWasmBox::Smartcontract(w) => w, + // _ => panic!("unreachable"), + // }; + // let hash = HashOf::new(wasm_contract.object()); + // // Store original executable representation to respond to queries with. + // if let Some(WasmSmartContractEntry { count, .. }) = + // self.contracts.get_mut(&hash) + // { + // // Considering 1 trigger registration takes 1 second, + // // it would take 584 942 417 355 years to overflow. + // *count = count.checked_add(1).expect( + // "There is no way someone could register 2^64 amount of same triggers", + // ); + // // Cloning module is cheap, under Arc inside + // } else { + // let module = wasm::load_module(engine, wasm_contract.object())?; + // self.contracts.insert( + // hash, + // WasmSmartContractEntry { + // original_contract: wasm_contract.object().clone(), + // compiled_contract: module, + // count: NonZeroU64::MIN, + // }, + // ); + // } + // ExecutableRef::Wasm(hash) + // } else { + // ExecutableRef::Instructions(instructions) + // } + // } else { + // ExecutableRef::Instructions(instructions) + // } + } // Executable::Wasm(bytes) => { + // let hash = HashOf::new(&bytes); + // // Store original executable representation to respond to queries with. + // if let Some(WasmSmartContractEntry { count, .. }) = self.contracts.get_mut(&hash) { + // // Considering 1 trigger registration takes 1 second, + // // it would take 584 942 417 355 years to overflow. + // *count = count.checked_add(1).expect( + // "There is no way someone could register 2^64 amount of same triggers", + // ); + // // Cloning module is cheap, under Arc inside + // } else { + // let module = wasm::load_module(engine, &bytes)?; + // self.contracts.insert( + // hash, + // WasmSmartContractEntry { + // original_contract: bytes, + // compiled_contract: module, + // count: NonZeroU64::MIN, + // }, + // ); + // } + // ExecutableRef::Wasm(hash) + // } + // Executable::Instructions(instructions) => ExecutableRef::Instructions(instructions), }; map(self).insert( trigger_id.clone(), @@ -924,7 +1022,7 @@ impl<'block, 'set> SetTransaction<'block, 'set> { #[derive(Clone, Serialize, Deserialize)] pub enum ExecutableRef { /// Loaded WASM - Wasm(HashOf), + // Wasm(HashOf), /// Vector of ISI Instructions(ConstVec), } @@ -932,7 +1030,7 @@ pub enum ExecutableRef { impl core::fmt::Debug for ExecutableRef { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { - Self::Wasm(hash) => f.debug_tuple("Wasm").field(hash).finish(), + // Self::Wasm(hash) => f.debug_tuple("Wasm").field(hash).finish(), Self::Instructions(instructions) => { f.debug_tuple("Instructions").field(instructions).finish() } diff --git a/crates/iroha_core/src/smartcontracts/isi/triggers/specialized.rs b/crates/iroha_core/src/smartcontracts/isi/triggers/specialized.rs index d5bf7304c3f..cc59aab15b3 100644 --- a/crates/iroha_core/src/smartcontracts/isi/triggers/specialized.rs +++ b/crates/iroha_core/src/smartcontracts/isi/triggers/specialized.rs @@ -127,7 +127,7 @@ pub struct LoadedAction { impl LoadedAction { pub(super) fn extract_blob_hash(&self) -> Option> { match self.executable { - ExecutableRef::Wasm(blob_hash) => Some(blob_hash), + // ExecutableRef::Wasm(blob_hash) => Some(blob_hash), ExecutableRef::Instructions(_) => None, } } diff --git a/crates/iroha_core/src/smartcontracts/wasm.rs b/crates/iroha_core/src/smartcontracts/wasm.rs index b25a3b1f557..05d4127ce48 100644 --- a/crates/iroha_core/src/smartcontracts/wasm.rs +++ b/crates/iroha_core/src/smartcontracts/wasm.rs @@ -970,38 +970,18 @@ impl<'wrld, 'block: 'wrld, 'state: 'block> Runtime, ) -> Result<()> { let span = wasm_log_span!("Smart contract execution", %authority); - let state = state::SmartContract::new( - authority, - self.config, - span, - state::chain_state::WithMut(state_transaction), - state::specific::SmartContract::new(None), - ); - - self.execute_smart_contract_with_state(bytes, state) - } + let max_instrucions = state_transaction + .world() + .parameters() + .transaction() + .max_instructions(); - /// Validates that the given smartcontract is eligible for execution - /// - /// # Errors - /// - /// - if instructions failed to validate, but queries are permitted - /// - if instruction limits are not obeyed - /// - if execution of the smartcontract fails (check [`Self::execute`]) - pub fn validate( - &mut self, - state_transaction: &'wrld mut StateTransaction<'block, 'state>, - authority: AccountId, - bytes: impl AsRef<[u8]>, - max_instruction_count: NonZeroU64, - ) -> Result<()> { - let span = wasm_log_span!("Smart contract validation", %authority); let state = state::SmartContract::new( authority, self.config, span, state::chain_state::WithMut(state_transaction), - state::specific::SmartContract::new(Some(LimitsExecutor::new(max_instruction_count))), + state::specific::SmartContract::new(Some(LimitsExecutor::new(max_instrucions))), ); self.execute_smart_contract_with_state(bytes, state) @@ -1012,6 +992,7 @@ impl<'wrld, 'block: 'wrld, 'state: 'block> Runtime, state: state::SmartContract<'wrld, 'block, 'state>, ) -> Result<()> { + // TODO: Re-use state/fuel from the transation let mut store = self.create_store(state); let smart_contract = self.create_smart_contract(&mut store, bytes)?; @@ -1092,6 +1073,7 @@ impl<'wrld, 'block: 'wrld, 'state: 'block> Runtime Runtime GetExport for (&wasmtime::Instance, C) { mod tests { use iroha_crypto::KeyPair; use iroha_test_samples::gen_account_in; - use nonzero_ext::nonzero; use parity_scale_codec::Encode; use tokio::test; @@ -1894,7 +1876,12 @@ mod tests { .header(); let mut state_block = state.block(block_header); let mut state_transaction = state_block.transaction(); - let res = runtime.validate(&mut state_transaction, authority, wat, nonzero!(1_u64)); + state_transaction + .world + .parameters + .transaction + .max_instructions = NonZeroU64::new(1_u64).unwrap(); + let res = runtime.execute(&mut state_transaction, authority, wat); state_transaction.apply(); state_block.commit(); diff --git a/crates/iroha_core/src/state.rs b/crates/iroha_core/src/state.rs index ef990a5f906..c7d6fac9814 100644 --- a/crates/iroha_core/src/state.rs +++ b/crates/iroha_core/src/state.rs @@ -1465,7 +1465,7 @@ impl<'state> StateBlock<'state> { trg_id, action.authority(), action.executable(), - (*time_event).into(), + &(*time_event).into(), ) .and_then(|()| transaction.execute_data_triggers_dfs())?; transaction @@ -1520,7 +1520,7 @@ impl<'state> StateBlock<'state> { if block.error(idx).is_none() { // Execute each transaction in its own transactional state let mut transaction = self.transaction(); - transaction.apply_executable(tx.instructions(), tx.authority().clone()); + transaction.apply_executable(tx.instructions(), tx.authority()); transaction .execute_data_triggers_dfs() .expect("should be no errors"); @@ -1570,7 +1570,7 @@ impl StateTransaction<'_, '_> { (action.authority().clone(), action.executable().clone()) }; self.world.external_event_buf.push(event.clone().into()); - self.execute_trigger(id, &authority, &executable, event.into())?; + self.execute_trigger(id, &authority, &executable, &event.into())?; self.world.triggers.decrease_repeats([id].into_iter()); Ok(()) @@ -1606,7 +1606,7 @@ impl StateTransaction<'_, '_> { (action.authority().clone(), action.executable().clone()) }; - self.execute_trigger(&trg_id, &authority, &executable, event.clone().into())?; + self.execute_trigger(&trg_id, &authority, &executable, &event.into())?; let depleted = self.world.triggers.decrease_repeats([&trg_id].into_iter()); stack.retain(|(_, trg_id, _)| !depleted.contains(trg_id)); @@ -1645,32 +1645,30 @@ impl StateTransaction<'_, '_> { id: &TriggerId, authority: &AccountId, executable: &ExecutableRef, - event: EventBox, + event: &EventBox, ) -> Result<(), TransactionRejectionReason> { let res = match executable { - ExecutableRef::Instructions(instructions) => self - .execute_instructions(instructions.iter().cloned(), authority) - .map_err(ValidationFail::from), - ExecutableRef::Wasm(blob_hash) => { - let module = self - .world - .triggers - .get_compiled_contract(blob_hash) - .expect("INTERNAL BUG: contract is not present") - .clone(); - wasm::RuntimeBuilder::::new() - .with_config(self.world().parameters().smart_contract) - .with_engine(self.engine.clone()) // Cloning engine is cheap - .build() - .and_then(|mut wasm_runtime| { - wasm_runtime.execute_trigger_module( - self, - id, - authority.clone(), - &module, - event, - ) + ExecutableRef::Instructions(instructions) => { + // Convert plain trigger modules to those ready for the executing by supplying execution context. + let instructions: Vec<_> = instructions + .iter() + .cloned() + .map(|isi| -> InstructionBox { + match isi { + InstructionBox::ExecuteWasm(ExecuteWasmBox::Trigger(tg)) => { + WasmExecutable::module(TriggerModule::from_event( + *tg.object().hash(), + id.clone(), + event.clone(), + )) + .into() + } + _ => isi, + } }) + .collect::>(); + + self.execute_instructions(instructions.iter().cloned(), authority) .map_err(ValidationFail::from) } }; @@ -1698,22 +1696,12 @@ impl StateTransaction<'_, '_> { /// Apply a non-erroneous executable in the given committed block. #[cfg(any(test, feature = "bench"))] - fn apply_executable(&mut self, executable: &Executable, authority: AccountId) { + fn apply_executable(&mut self, executable: &Executable, authority: &AccountId) { match executable { Executable::Instructions(instructions) => { self.execute_instructions(instructions.iter().cloned(), &authority) .expect("should be no errors"); } - Executable::Wasm(bytes) => { - let mut wasm_runtime = wasm::RuntimeBuilder::::new() - .with_config(self.world().parameters().smart_contract) - .with_engine(self.engine.clone()) // Cloning engine is cheap - .build() - .expect("failed to create wasm runtime"); - wasm_runtime - .execute(self, authority, bytes) - .expect("should be no errors"); - } } } } diff --git a/crates/iroha_core/src/tx.rs b/crates/iroha_core/src/tx.rs index e053af477f1..110ddc3a50a 100644 --- a/crates/iroha_core/src/tx.rs +++ b/crates/iroha_core/src/tx.rs @@ -23,7 +23,7 @@ use iroha_macro::FromVariant; use mv::storage::StorageReadOnly; use crate::{ - smartcontracts::{wasm, wasm::cache::WasmCache}, + smartcontracts::wasm::cache::WasmCache, state::{StateBlock, StateTransaction}, }; @@ -127,6 +127,54 @@ impl AcceptedTransaction { match &tx.instructions() { Executable::Instructions(instructions) => { + // First, validate supplied wasms. + instructions + .iter() + .filter_map(|i| match i { + InstructionBox::ExecuteWasm(w) => Some(w), + _ => None, + }) + .try_for_each(|wasm| { + // TODO: Can we check the number of instructions in wasm? Because we do this check + // when executing wasm where we deny wasm if number of instructions exceeds the limit. + // + // Should we allow infinite instructions in wasm? And deny only based on fuel and size + let smart_contract_size_limit = limits + .smart_contract_size + .get() + .try_into() + .expect("INTERNAL BUG: smart contract size exceeds usize::MAX"); + + // Inputs are restricted to binaries only. + // The error type will be changed after the instruction interface is fixed. + let wasm = match wasm { + ExecuteWasmBox::Smartcontract(w) => w, + _ => { + return Err(AcceptTransactionFail::TransactionLimit( + TransactionLimitError { + reason: "only `Smartcontract`-type WASM are allowed" + .to_string(), + }, + )) + } + }; + + if wasm.object().size_bytes() > smart_contract_size_limit { + Err(AcceptTransactionFail::TransactionLimit( + TransactionLimitError { + reason: format!( + "WASM binary size is too large: max {}, got {} \ + (configured by \"Parameter::SmartContractLimits\")", + limits.smart_contract_size, + wasm.object().size_bytes() + ), + }, + )) + } else { + Ok(()) + } + })?; + let instruction_limit = limits .max_instructions .get() @@ -145,30 +193,6 @@ impl AcceptedTransaction { )); } } - // TODO: Can we check the number of instructions in wasm? Because we do this check - // when executing wasm where we deny wasm if number of instructions exceeds the limit. - // - // Should we allow infinite instructions in wasm? And deny only based on fuel and size - Executable::Wasm(smart_contract) => { - let smart_contract_size_limit = limits - .smart_contract_size - .get() - .try_into() - .expect("INTERNAL BUG: smart contract size exceeds usize::MAX"); - - if smart_contract.size_bytes() > smart_contract_size_limit { - return Err(AcceptTransactionFail::TransactionLimit( - TransactionLimitError { - reason: format!( - "WASM binary size is too large: max {}, got {} \ - (configured by \"Parameter::SmartContractLimits\")", - limits.smart_contract_size, - smart_contract.size_bytes() - ), - }, - )); - } - } } Ok(Self(tx)) @@ -208,7 +232,7 @@ impl StateBlock<'_> { ) -> Result, TransactionRejectionReason)> { let mut state_transaction = self.transaction(); if let Err(rejection_reason) = - Self::validate_transaction_internal(tx.clone(), &mut state_transaction, wasm_cache) + Self::validate_transaction_internal(&tx, &mut state_transaction, wasm_cache) { return Err((tx.0.into(), rejection_reason)); } @@ -218,7 +242,7 @@ impl StateBlock<'_> { } fn validate_transaction_internal( - tx: AcceptedTransaction, + tx: &AcceptedTransaction, state_transaction: &mut StateTransaction<'_, '_>, wasm_cache: &mut WasmCache<'_, '_, '_>, ) -> Result<(), TransactionRejectionReason> { @@ -237,10 +261,6 @@ impl StateBlock<'_> { wasm_cache, )?; - if let (authority, Executable::Wasm(bytes)) = tx.into() { - Self::validate_wasm(authority, state_transaction, bytes)? - } - debug!("Transaction validated successfully; processing data triggers"); state_transaction.execute_data_triggers_dfs()?; debug!("Data triggers executed successfully"); @@ -248,33 +268,6 @@ impl StateBlock<'_> { Ok(()) } - fn validate_wasm( - authority: AccountId, - state_transaction: &mut StateTransaction<'_, '_>, - wasm: WasmSmartContract, - ) -> Result<(), TransactionRejectionReason> { - debug!("Validating wasm"); - - wasm::RuntimeBuilder::::new() - .build() - .and_then(|mut wasm_runtime| { - wasm_runtime.validate( - state_transaction, - authority, - wasm, - state_transaction - .world - .parameters - .transaction - .max_instructions, - ) - }) - .map_err(|error| WasmExecutionFail { - reason: format!("{:?}", eyre::Report::from(error)), - }) - .map_err(TransactionRejectionReason::WasmExecution) - } - /// Validate transaction with runtime executors. /// /// Note: transaction instructions will be executed on the given `state_transaction`. diff --git a/crates/iroha_data_model/src/block.rs b/crates/iroha_data_model/src/block.rs index 82c1826fda9..0916ea21b35 100644 --- a/crates/iroha_data_model/src/block.rs +++ b/crates/iroha_data_model/src/block.rs @@ -501,20 +501,11 @@ mod candidate { return Err("Genesis transaction must not contain errors"); } - for transaction in transactions { - let Executable::Instructions(_) = transaction.instructions() else { - return Err("Genesis transaction must contain instructions"); - }; - } - let Some(transaction_executor) = transactions.first() else { return Err("Genesis block must contain at least one transaction"); }; let Executable::Instructions(instructions_executor) = - transaction_executor.instructions() - else { - return Err("Genesis transaction must contain instructions"); - }; + transaction_executor.instructions(); let [crate::isi::InstructionBox::Upgrade(_)] = instructions_executor.as_ref() else { return Err( "First transaction must contain single `Upgrade` instruction to set executor", diff --git a/crates/iroha_data_model/src/isi.rs b/crates/iroha_data_model/src/isi.rs index 2e8814fe2a2..5a3ed6e4f9c 100644 --- a/crates/iroha_data_model/src/isi.rs +++ b/crates/iroha_data_model/src/isi.rs @@ -120,6 +120,10 @@ mod model { #[debug(fmt = "{_0:?}")] Custom(CustomInstruction), + + #[debug(fmt = "{_0:?}")] + #[enum_ref(transparent)] + ExecuteWasm(ExecuteWasmBox), } } @@ -177,6 +181,8 @@ impl_instruction! { SetParameter, Upgrade, ExecuteTrigger, + WasmExecutable, + WasmExecutable, Log, } @@ -934,6 +940,71 @@ mod transparent { } } + isi! { + #[derive(Display)] + #[display(fmt = "EXECUTE wasm")] + /// Generic instruction for executing wasm smartcontracts and triggers. + pub struct WasmExecutable { + /// Executable object `T`. + pub object: T, + } + } + + isi! { + #[derive(Display)] + #[display(fmt = "EXECUTE wasm")] + /// Temporary stub to handle trigger executions. + /// Triggers need `TriggerId` and `EventBox` for the execution context. + pub struct TriggerModule { + /// Precompiled trigger module hash. + pub hash: HashOf, + /// Execution context: `TriggerId` + pub id: Option, + /// Execution context: `EventBox` + pub event: Option, + } + } + + impl TriggerModule { + /// Build `TriggerModule` from the precompiled module. + pub fn from_hash(hash: HashOf) -> Self { + Self { + hash, + id: None, + event: None, + } + } + /// Build ready-to-execute `TriggerModule`. + pub fn from_event(hash: HashOf, id: TriggerId, event: EventBox) -> Self { + Self { + hash, + id: Some(id), + event: Some(event), + } + } + } + + impl WasmExecutable { + /// Wasm executable from binary source. + pub fn binary(wasm: WasmSmartContract) -> Self { + Self { object: wasm } + } + } + + impl WasmExecutable { + /// Wasm executable from trigger module [should be restricted]. + pub fn module(module: TriggerModule) -> Self { + Self { object: module } + } + } + + impl_into_box! { + WasmExecutable | + WasmExecutable + => ExecuteWasmBox => InstructionBox[ExecuteWasm], + => ExecuteWasmBoxRef<'a> => InstructionBoxRef<'a>[ExecuteWasm] + } + isi! { /// Generic instruction for upgrading runtime objects. #[derive(Constructor, Display)] @@ -1013,6 +1084,21 @@ macro_rules! isi_box { }; } +isi_box! { + #[strum_discriminants( + vis(pub(crate)), + name(WasmType), + derive(Encode), + )] + /// Enum with all supported [`WasmExecutable`] instructions. + pub enum ExecuteWasmBox { + /// Execute [`WasmSmartContract`]. + Smartcontract(WasmExecutable), + /// Execute [`TriggerModule`] . + Trigger(WasmExecutable), + } +} + isi_box! { #[strum_discriminants( vis(pub(crate)), @@ -1265,8 +1351,39 @@ pub mod error { #[skip_try_from] String, ), + + /// Failure in WebAssembly execution + WasmExecution(#[cfg_attr(feature = "std", source)] WasmExecutionError), } + /// Instruction execution failed because execution of `WebAssembly` binary failed + #[derive( + Debug, + Display, + Clone, + PartialEq, + Eq, + PartialOrd, + Ord, + Decode, + Encode, + Deserialize, + Serialize, + IntoSchema, + )] + #[display(fmt = "Error in executing wasm binary: {reason}")] + #[serde(transparent)] + #[repr(transparent)] + // SAFETY: `WasmExecutionError` has no trap representation in `String` + #[ffi_type(unsafe {robust})] + pub struct WasmExecutionError { + /// Error which happened during execution + pub reason: String, + } + + #[cfg(feature = "std")] + impl std::error::Error for WasmExecutionError {} + /// Evaluation error. This error indicates instruction is not a valid Iroha DSL #[derive( Debug, @@ -1485,9 +1602,9 @@ pub mod error { /// The prelude re-exports most commonly used traits, structs and macros from this crate. pub mod prelude { pub use super::{ - Burn, BurnBox, CustomInstruction, ExecuteTrigger, Grant, GrantBox, InstructionBox, Log, - Mint, MintBox, Register, RegisterBox, RemoveKeyValue, RemoveKeyValueBox, Revoke, RevokeBox, - SetKeyValue, SetKeyValueBox, SetParameter, Transfer, TransferBox, Unregister, - UnregisterBox, Upgrade, + Burn, BurnBox, CustomInstruction, ExecuteTrigger, ExecuteWasmBox, Grant, GrantBox, + InstructionBox, Log, Mint, MintBox, Register, RegisterBox, RemoveKeyValue, + RemoveKeyValueBox, Revoke, RevokeBox, SetKeyValue, SetKeyValueBox, SetParameter, Transfer, + TransferBox, TriggerModule, Unregister, UnregisterBox, Upgrade, WasmExecutable, }; } diff --git a/crates/iroha_data_model/src/lib.rs b/crates/iroha_data_model/src/lib.rs index 73186db5fbf..74b3882aca5 100644 --- a/crates/iroha_data_model/src/lib.rs +++ b/crates/iroha_data_model/src/lib.rs @@ -119,6 +119,8 @@ mod seal { SetParameter, Upgrade, ExecuteTrigger, + WasmExecutable, + WasmExecutable, Log, // Boxed queries diff --git a/crates/iroha_data_model/src/transaction.rs b/crates/iroha_data_model/src/transaction.rs index 6384d256931..827aeff5102 100644 --- a/crates/iroha_data_model/src/transaction.rs +++ b/crates/iroha_data_model/src/transaction.rs @@ -22,7 +22,7 @@ use serde::{Deserialize, Serialize}; pub use self::model::*; use crate::{ account::AccountId, - isi::{Instruction, InstructionBox}, + isi::{Instruction, InstructionBox, WasmExecutable}, metadata::Metadata, ChainId, }; @@ -54,8 +54,8 @@ mod model { /// Ordered set of instructions. #[debug(fmt = "{_0:?}")] Instructions(ConstVec), - /// WebAssembly smartcontract - Wasm(WasmSmartContract), + // WebAssembly smartcontract + // Wasm(WasmSmartContract), } /// Wrapper for byte representation of [`Executable::Wasm`]. @@ -177,7 +177,12 @@ impl> From for Executable { impl From for Executable { fn from(source: WasmSmartContract) -> Self { - Self::Wasm(source) + Self::Instructions( + [WasmExecutable::binary(source).into()] + .into_iter() + .collect::>() + .into(), + ) } } @@ -355,12 +360,6 @@ impl TransactionBuilder { self } - /// Add wasm to this transaction - pub fn with_wasm(mut self, wasm: WasmSmartContract) -> Self { - self.payload.instructions = wasm.into(); - self - } - /// Set executable for this transaction pub fn with_executable(mut self, executable: Executable) -> Self { self.payload.instructions = executable; @@ -442,10 +441,9 @@ mod candidate { #[cfg(not(target_family = "wasm"))] fn validate_instructions(&self) -> Result<(), &'static str> { - if let Executable::Instructions(instructions) = &self.payload.instructions { - if instructions.is_empty() { - return Err("Transaction is empty"); - } + let Executable::Instructions(instructions) = &self.payload.instructions; + if instructions.is_empty() { + return Err("Transaction is empty"); } Ok(()) @@ -580,30 +578,30 @@ pub mod error { pub reason: String, } - /// Transaction was rejected because execution of `WebAssembly` binary failed - #[derive( - Debug, - Display, - Clone, - PartialEq, - Eq, - PartialOrd, - Ord, - Decode, - Encode, - Deserialize, - Serialize, - IntoSchema, - )] - #[display(fmt = "Failed to execute wasm binary: {reason}")] - #[serde(transparent)] - #[repr(transparent)] - // SAFETY: `WasmExecutionFail` has no trap representation in `String` - #[ffi_type(unsafe {robust})] - pub struct WasmExecutionFail { - /// Error which happened during execution - pub reason: String, - } + // /// Transaction was rejected because execution of `WebAssembly` binary failed + // #[derive( + // Debug, + // Display, + // Clone, + // PartialEq, + // Eq, + // PartialOrd, + // Ord, + // Decode, + // Encode, + // Deserialize, + // Serialize, + // IntoSchema, + // )] + // #[display(fmt = "Failed to execute wasm binary: {reason}")] + // #[serde(transparent)] + // #[repr(transparent)] + // // SAFETY: `WasmExecutionFail` has no trap representation in `String` + // #[ffi_type(unsafe {robust})] + // pub struct WasmExecutionFail { + // /// Error which happened during execution + // pub reason: String, + // } /// Possible reasons for trigger-specific execution failure. #[derive( @@ -668,8 +666,8 @@ pub mod error { InstructionExecution( #[cfg_attr(feature = "std", source)] Box, ), - /// Failure in WebAssembly execution - WasmExecution(#[cfg_attr(feature = "std", source)] WasmExecutionFail), + // /// Failure in WebAssembly execution + // WasmExecution(#[cfg_attr(feature = "std", source)] WasmExecutionFail), /// Execution of a time trigger or an invoked data trigger failed. TriggerExecution(#[cfg_attr(feature = "std", source)] TriggerExecutionFail), } @@ -693,6 +691,7 @@ pub mod error { Upgrade(_) => "upgrade", Log(_) => "log", Custom(_) => "custom", + ExecuteWasm(_) => "wasm", }; write!( f, @@ -708,8 +707,8 @@ pub mod error { #[cfg(feature = "std")] impl std::error::Error for InstructionExecutionFail {} - #[cfg(feature = "std")] - impl std::error::Error for WasmExecutionFail {} + // #[cfg(feature = "std")] + // impl std::error::Error for WasmExecutionFail {} #[cfg(feature = "std")] impl std::error::Error for TriggerExecutionFail {} @@ -718,8 +717,10 @@ pub mod error { //! The prelude re-exports most commonly used traits, structs and macros from this module. pub use super::{ - InstructionExecutionFail, TransactionRejectionReason, TriggerExecutionFail, - WasmExecutionFail, + InstructionExecutionFail, + TransactionRejectionReason, + TriggerExecutionFail, + // WasmExecutionFail, }; } } diff --git a/crates/iroha_data_model/src/visit.rs b/crates/iroha_data_model/src/visit.rs index 74a9c67f125..424b5862835 100644 --- a/crates/iroha_data_model/src/visit.rs +++ b/crates/iroha_data_model/src/visit.rs @@ -29,7 +29,6 @@ pub trait Visit { // Visit SignedTransaction visit_transaction(&SignedTransaction), visit_instruction(&InstructionBox), - visit_wasm(&WasmSmartContract), visit_query(&AnyQueryBox), visit_singular_query(&SingularQueryBox), visit_iter_query(&QueryWithParams), @@ -47,6 +46,7 @@ pub trait Visit { visit_upgrade(&Upgrade), visit_execute_trigger(&ExecuteTrigger), + visit_execute_wasm(&ExecuteWasmBox), visit_set_parameter(&SetParameter), visit_log(&Log), visit_custom_instruction(&CustomInstruction), @@ -129,12 +129,16 @@ pub trait Visit { visit_revoke_account_permission(&Revoke), visit_revoke_account_role(&Revoke), visit_revoke_role_permission(&Revoke), + + // + visit_execute_wasm_smartcontract(&WasmExecutable), + visit_execute_wasm_trigger(&WasmExecutable), } } pub fn visit_transaction(visitor: &mut V, transaction: &SignedTransaction) { match transaction.instructions() { - Executable::Wasm(wasm) => visitor.visit_wasm(wasm), + // Executable::Wasm(wasm) => visitor.visit_wasm(wasm), Executable::Instructions(instructions) => { for isi in instructions { visitor.visit_instruction(isi); @@ -194,8 +198,6 @@ pub fn visit_query(visitor: &mut V, query: &AnyQueryBox) { } } -pub fn visit_wasm(_visitor: &mut V, _wasm: &WasmSmartContract) {} - /// Default validation for [`InstructionBox`]. /// /// # Warning @@ -221,6 +223,7 @@ pub fn visit_instruction(visitor: &mut V, isi: &InstructionBo InstructionBox::Unregister(variant_value) => visitor.visit_unregister(variant_value), InstructionBox::Upgrade(variant_value) => visitor.visit_upgrade(variant_value), InstructionBox::Custom(custom) => visitor.visit_custom_instruction(custom), + InstructionBox::ExecuteWasm(variant_value) => visitor.visit_execute_wasm(variant_value), } } @@ -236,6 +239,13 @@ pub fn visit_register(visitor: &mut V, isi: &RegisterBox) { } } +pub fn visit_execute_wasm(visitor: &mut V, isi: &ExecuteWasmBox) { + match isi { + ExecuteWasmBox::Smartcontract(obj) => visitor.visit_execute_wasm_smartcontract(obj), + ExecuteWasmBox::Trigger(obj) => visitor.visit_execute_wasm_trigger(obj), + } +} + pub fn visit_unregister(visitor: &mut V, isi: &UnregisterBox) { match isi { UnregisterBox::Peer(obj) => visitor.visit_unregister_peer(obj), @@ -360,6 +370,9 @@ leaf_visitors! { visit_upgrade(&Upgrade), visit_set_parameter(&SetParameter), visit_execute_trigger(&ExecuteTrigger), + // visit_execute_wasm(&ExecuteWasmBox), + visit_execute_wasm_smartcontract(&WasmExecutable), + visit_execute_wasm_trigger(&WasmExecutable), visit_log(&Log), visit_custom_instruction(&CustomInstruction), diff --git a/crates/iroha_executor/src/default/mod.rs b/crates/iroha_executor/src/default/mod.rs index 05bf9a20ff8..f4efd4a983d 100644 --- a/crates/iroha_executor/src/default/mod.rs +++ b/crates/iroha_executor/src/default/mod.rs @@ -19,6 +19,7 @@ pub use domain::{ }; pub use executor::visit_upgrade; use iroha_smart_contract::data_model::{prelude::*, visit::Visit}; +use iroha_smart_contract_utils::error; pub use isi::visit_custom_instruction; pub use log::visit_log; pub use nft::{ @@ -37,6 +38,7 @@ pub use trigger::{ visit_register_trigger, visit_remove_trigger_key_value, visit_set_trigger_key_value, visit_unregister_trigger, }; +pub use wasm::{visit_execute_wasm_smartcontract, visit_execute_wasm_trigger}; use crate::{ deny, execute, @@ -63,7 +65,7 @@ pub fn visit_transaction( transaction: &SignedTransaction, ) { match transaction.instructions() { - Executable::Wasm(wasm) => executor.visit_wasm(wasm), + // Executable::Wasm(wasm) => executor.visit_wasm(wasm), Executable::Instructions(instructions) => { for isi in instructions { if executor.verdict().is_ok() { @@ -88,6 +90,10 @@ pub fn visit_instruction(executor: &mut V, isi: &In InstructionBox::ExecuteTrigger(isi) => { executor.visit_execute_trigger(isi); } + InstructionBox::ExecuteWasm(isi) => { + error!("VISITING WASM in CUSTOM EXECUTOR"); + executor.visit_execute_wasm(isi); + } InstructionBox::Burn(isi) => { executor.visit_burn(isi); } @@ -1534,6 +1540,24 @@ pub mod trigger { } } +mod wasm { + use super::*; + + pub fn visit_execute_wasm_smartcontract( + executor: &mut V, + isi: &WasmExecutable, + ) { + execute!(executor, isi); + } + + pub fn visit_execute_wasm_trigger( + executor: &mut V, + isi: &WasmExecutable, + ) { + execute!(executor, isi); + } +} + pub mod permission { use super::*; diff --git a/crates/iroha_executor_derive/src/default.rs b/crates/iroha_executor_derive/src/default.rs index aaa0fe8d234..eb7e4e852ef 100644 --- a/crates/iroha_executor_derive/src/default.rs +++ b/crates/iroha_executor_derive/src/default.rs @@ -152,6 +152,9 @@ pub fn impl_derive_visit(emitter: &mut Emitter, input: &syn::DeriveInput) -> Tok "fn visit_mint_trigger_repetitions(operation: &Mint)", "fn visit_burn_trigger_repetitions(operation: &Burn)", "fn visit_execute_trigger(operation: &ExecuteTrigger)", + // "fn visit_execute_wasm(operation: &ExecuteWasm)", + "fn visit_execute_wasm_smartcontract(operation: &WasmExecutable)", + "fn visit_execute_wasm_trigger(operation: &WasmExecutable)", "fn visit_set_parameter(operation: &SetParameter)", "fn visit_upgrade(operation: &Upgrade)", "fn visit_log(operation: &Log)", diff --git a/crates/iroha_genesis/src/lib.rs b/crates/iroha_genesis/src/lib.rs index 69297840440..efb69155b22 100644 --- a/crates/iroha_genesis/src/lib.rs +++ b/crates/iroha_genesis/src/lib.rs @@ -45,7 +45,7 @@ pub struct RawGenesisTransaction { /// Path to the directory that contains *.wasm libraries wasm_dir: WasmPath, /// Triggers whose executable is wasm, not instructions - wasm_triggers: Vec, + wasm_triggers: Vec, /// Initial topology topology: Vec, } @@ -200,7 +200,7 @@ pub struct GenesisBuilder { parameters: Vec, instructions: Vec, wasm_dir: PathBuf, - wasm_triggers: Vec, + wasm_triggers: Vec, topology: Vec, } @@ -212,7 +212,7 @@ pub struct GenesisDomainBuilder { parameters: Vec, instructions: Vec, wasm_dir: PathBuf, - wasm_triggers: Vec, + wasm_triggers: Vec, topology: Vec, domain_id: DomainId, } @@ -272,7 +272,7 @@ impl GenesisBuilder { } /// Entry a wasm trigger to the end of entries. - pub fn append_wasm_trigger(mut self, wasm_trigger: GenesisWasmTrigger) -> Self { + pub fn append_wasm_trigger(mut self, wasm_trigger: GenesisTriggerModule) -> Self { self.wasm_triggers.push(wasm_trigger); self } @@ -391,7 +391,7 @@ impl WasmPath { /// Human-readable alternative to [`Trigger`] whose action has wasm executable #[derive(Debug, Clone, Serialize, Deserialize, IntoSchema, Encode, Decode, Constructor)] -pub struct GenesisWasmTrigger { +pub struct GenesisTriggerModule { id: TriggerId, action: GenesisWasmAction, } @@ -422,10 +422,10 @@ impl GenesisWasmAction { } } -impl TryFrom for Trigger { +impl TryFrom for Trigger { type Error = eyre::Report; - fn try_from(value: GenesisWasmTrigger) -> Result { + fn try_from(value: GenesisTriggerModule) -> Result { Ok(Trigger::new(value.id, value.action.try_into()?)) } } diff --git a/crates/iroha_schema_gen/src/lib.rs b/crates/iroha_schema_gen/src/lib.rs index f9920054621..0abc8aa721f 100644 --- a/crates/iroha_schema_gen/src/lib.rs +++ b/crates/iroha_schema_gen/src/lib.rs @@ -249,6 +249,7 @@ types!( DomainProjection, DomainProjection, EventBox, + Option, EventFilterBox, EventMessage, EventSubscriptionRequest, @@ -256,6 +257,9 @@ types!( ExecuteTrigger, ExecuteTriggerEvent, ExecuteTriggerEventFilter, + ExecuteWasmBox, + WasmExecutable, + WasmExecutable, ExecutionTime, Executor, ExecutorDataModel, @@ -286,7 +290,7 @@ types!( FindTriggers, ForwardCursor, GenesisWasmAction, - GenesisWasmTrigger, + GenesisTriggerModule, Grant, Grant, Grant, @@ -596,7 +600,7 @@ types!( Vec, Vec, Vec, - Vec, + Vec, Vec, Vec, Vec, @@ -634,8 +638,10 @@ types!( Vec, Vec, Vec, - WasmExecutionFail, + WasmExecutionError, WasmSmartContract, + HashOf, + TriggerModule, (), [u16; 8], @@ -673,6 +679,7 @@ pub mod complete_data_model { error::{ InstructionEvaluationError, InstructionExecutionError, InvalidParameterError, MathError, MintabilityError, Mismatch, RepetitionError, TypeError, + WasmExecutionError, }, InstructionType, }, @@ -696,7 +703,7 @@ pub mod complete_data_model { }, Level, }; - pub use iroha_genesis::{GenesisWasmAction, GenesisWasmTrigger, WasmPath}; + pub use iroha_genesis::{GenesisTriggerModule, GenesisWasmAction, WasmPath}; pub use iroha_primitives::{ addr::{Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrHost, SocketAddrV4, SocketAddrV6}, const_vec::ConstVec, diff --git a/docs/source/references/schema.json b/docs/source/references/schema.json index 8b5f3686562..8bedc9042df 100644 --- a/docs/source/references/schema.json +++ b/docs/source/references/schema.json @@ -1994,11 +1994,6 @@ "tag": "Instructions", "discriminant": 0, "type": "Vec" - }, - { - "tag": "Wasm", - "discriminant": 1, - "type": "WasmSmartContract" } ] }, @@ -2042,6 +2037,20 @@ } ] }, + "ExecuteWasmBox": { + "Enum": [ + { + "tag": "Smartcontract", + "discriminant": 0, + "type": "WasmExecutable" + }, + { + "tag": "Trigger", + "discriminant": 1, + "type": "WasmExecutable" + } + ] + }, "ExecutionTime": { "Enum": [ { @@ -2247,6 +2256,18 @@ } ] }, + "GenesisTriggerModule": { + "Struct": [ + { + "name": "id", + "type": "TriggerId" + }, + { + "name": "action", + "type": "GenesisWasmAction" + } + ] + }, "GenesisWasmAction": { "Struct": [ { @@ -2267,18 +2288,6 @@ } ] }, - "GenesisWasmTrigger": { - "Struct": [ - { - "name": "id", - "type": "TriggerId" - }, - { - "name": "action", - "type": "GenesisWasmAction" - } - ] - }, "Grant": { "Struct": [ { @@ -2339,6 +2348,7 @@ "HashOf>": "Hash", "HashOf": "Hash", "HashOf>": "Hash", + "HashOf": "Hash", "IdBox": { "Enum": [ { @@ -2464,6 +2474,11 @@ "tag": "Custom", "discriminant": 13, "type": "CustomInstruction" + }, + { + "tag": "ExecuteWasm", + "discriminant": 14, + "type": "ExecuteWasmBox" } ] }, @@ -2532,6 +2547,11 @@ "tag": "InvariantViolation", "discriminant": 8, "type": "String" + }, + { + "tag": "WasmExecution", + "discriminant": 9, + "type": "WasmExecutionError" } ] }, @@ -2604,6 +2624,10 @@ { "tag": "Custom", "discriminant": 13 + }, + { + "tag": "ExecuteWasm", + "discriminant": 14 } ] }, @@ -3396,6 +3420,9 @@ "Option": { "Option": "DomainId" }, + "Option": { + "Option": "EventBox" + }, "Option": { "Option": "ForwardCursor" }, @@ -4393,7 +4420,7 @@ }, { "name": "wasm_triggers", - "type": "Vec" + "type": "Vec" }, { "name": "topology", @@ -5568,14 +5595,9 @@ "discriminant": 3, "type": "InstructionExecutionFail" }, - { - "tag": "WasmExecution", - "discriminant": 4, - "type": "WasmExecutionFail" - }, { "tag": "TriggerExecution", - "discriminant": 5, + "discriminant": 4, "type": "TriggerExecutionFail" } ] @@ -5881,6 +5903,22 @@ } ] }, + "TriggerModule": { + "Struct": [ + { + "name": "hash", + "type": "HashOf" + }, + { + "name": "id", + "type": "Option" + }, + { + "name": "event", + "type": "Option" + } + ] + }, "TriggerNumberOfExecutionsChanged": { "Struct": [ { @@ -6178,8 +6216,8 @@ "Vec": { "Vec": "EventFilterBox" }, - "Vec": { - "Vec": "GenesisWasmTrigger" + "Vec": { + "Vec": "GenesisTriggerModule" }, "Vec>": { "Vec": "HashOf" @@ -6274,7 +6312,23 @@ "Vec": { "Vec": "u8" }, - "WasmExecutionFail": { + "WasmExecutable": { + "Struct": [ + { + "name": "object", + "type": "TriggerModule" + } + ] + }, + "WasmExecutable": { + "Struct": [ + { + "name": "object", + "type": "WasmSmartContract" + } + ] + }, + "WasmExecutionError": { "Struct": [ { "name": "reason", diff --git a/integration_tests/tests/queries/mod.rs b/integration_tests/tests/queries/mod.rs index 1057170879c..2028e06e15f 100644 --- a/integration_tests/tests/queries/mod.rs +++ b/integration_tests/tests/queries/mod.rs @@ -71,9 +71,7 @@ fn find_transactions_reversed() -> eyre::Result<()> { let txs = client.query(FindTransactions).execute_all()?; // check that latest transaction is register domain - let Executable::Instructions(instructions) = txs[0].as_ref().instructions() else { - panic!("Expected instructions"); - }; + let Executable::Instructions(instructions) = txs[0].as_ref().instructions(); assert_eq!(instructions.len(), 1); assert_eq!( instructions[0], diff --git a/integration_tests/tests/triggers/by_call_trigger.rs b/integration_tests/tests/triggers/by_call_trigger.rs index a9375c5412b..d2237898edd 100644 --- a/integration_tests/tests/triggers/by_call_trigger.rs +++ b/integration_tests/tests/triggers/by_call_trigger.rs @@ -361,9 +361,7 @@ fn unregister_trigger() -> Result<()> { .filter_with(|trigger| trigger.id.eq(trigger_id.clone())) .execute_single()?; let found_action = found_trigger.action(); - let Executable::Instructions(found_instructions) = found_action.executable() else { - panic!("Expected instructions"); - }; + let Executable::Instructions(found_instructions) = found_action.executable(); let found_trigger = Trigger::new( found_trigger.id().clone(), Action::new( From a29452b706413c8b29b8701a5e2b12525121990c Mon Sep 17 00:00:00 2001 From: Lohachov Mykhailo Date: Thu, 12 Jun 2025 17:41:07 +0900 Subject: [PATCH 2/7] fix: fmt Signed-off-by: Lohachov Mykhailo --- .../src/smartcontracts/isi/triggers/mod.rs | 1 - .../src/smartcontracts/isi/triggers/set.rs | 76 +------------------ .../isi/triggers/specialized.rs | 1 - crates/iroha_core/src/state.rs | 2 +- crates/iroha_data_model/src/transaction.rs | 32 -------- crates/iroha_data_model/src/visit.rs | 4 +- crates/iroha_executor/src/default/mod.rs | 3 - crates/iroha_executor_derive/src/default.rs | 1 - crates/iroha_genesis/src/lib.rs | 8 +- 9 files changed, 5 insertions(+), 123 deletions(-) diff --git a/crates/iroha_core/src/smartcontracts/isi/triggers/mod.rs b/crates/iroha_core/src/smartcontracts/isi/triggers/mod.rs index 89b1f08e9d3..fd1725ddfb4 100644 --- a/crates/iroha_core/src/smartcontracts/isi/triggers/mod.rs +++ b/crates/iroha_core/src/smartcontracts/isi/triggers/mod.rs @@ -317,7 +317,6 @@ pub mod isi { authority: &AccountId, state_transaction: &mut StateTransaction<'_, '_>, ) -> Result<(), Error> { - error!("WASM IS EXECUTED"); let mut wasm_runtime = wasm::RuntimeBuilder::::new() .with_config(state_transaction.world().parameters().smart_contract) .with_engine(state_transaction.engine().clone()) // Cloning engine is cheap diff --git a/crates/iroha_core/src/smartcontracts/isi/triggers/set.rs b/crates/iroha_core/src/smartcontracts/isi/triggers/set.rs index 89f5db929bf..fed2ffff14d 100644 --- a/crates/iroha_core/src/smartcontracts/isi/triggers/set.rs +++ b/crates/iroha_core/src/smartcontracts/isi/triggers/set.rs @@ -324,19 +324,6 @@ pub trait SetReadOnly { } = action; let original_executable = match executable { - // ExecutableRef::Wasm(ref blob_hash) => { - // let original_wasm = self - // .get_original_contract(blob_hash) - // .cloned() - // .expect("No original smartcontract saved for trigger. This is a bug."); - // // Executable::Wasm(original_wasm) - // Executable::Instructions( - // [WasmExecutable::binary(original_wasm).into()] - // .into_iter() - // .collect::>() - // .into(), - // ) - // } ExecutableRef::Instructions(isi) => { Executable::Instructions(isi .into_iter() @@ -751,65 +738,7 @@ impl<'block, 'set> SetTransaction<'block, 'set> { .collect::, _>>()? .into(), ) - // if instructions.len() == 1 { - // if let InstructionBox::ExecuteWasm(wasm_contract) = instructions[0].clone() { - // let wasm_contract = match wasm_contract { - // ExecuteWasmBox::Smartcontract(w) => w, - // _ => panic!("unreachable"), - // }; - // let hash = HashOf::new(wasm_contract.object()); - // // Store original executable representation to respond to queries with. - // if let Some(WasmSmartContractEntry { count, .. }) = - // self.contracts.get_mut(&hash) - // { - // // Considering 1 trigger registration takes 1 second, - // // it would take 584 942 417 355 years to overflow. - // *count = count.checked_add(1).expect( - // "There is no way someone could register 2^64 amount of same triggers", - // ); - // // Cloning module is cheap, under Arc inside - // } else { - // let module = wasm::load_module(engine, wasm_contract.object())?; - // self.contracts.insert( - // hash, - // WasmSmartContractEntry { - // original_contract: wasm_contract.object().clone(), - // compiled_contract: module, - // count: NonZeroU64::MIN, - // }, - // ); - // } - // ExecutableRef::Wasm(hash) - // } else { - // ExecutableRef::Instructions(instructions) - // } - // } else { - // ExecutableRef::Instructions(instructions) - // } - } // Executable::Wasm(bytes) => { - // let hash = HashOf::new(&bytes); - // // Store original executable representation to respond to queries with. - // if let Some(WasmSmartContractEntry { count, .. }) = self.contracts.get_mut(&hash) { - // // Considering 1 trigger registration takes 1 second, - // // it would take 584 942 417 355 years to overflow. - // *count = count.checked_add(1).expect( - // "There is no way someone could register 2^64 amount of same triggers", - // ); - // // Cloning module is cheap, under Arc inside - // } else { - // let module = wasm::load_module(engine, &bytes)?; - // self.contracts.insert( - // hash, - // WasmSmartContractEntry { - // original_contract: bytes, - // compiled_contract: module, - // count: NonZeroU64::MIN, - // }, - // ); - // } - // ExecutableRef::Wasm(hash) - // } - // Executable::Instructions(instructions) => ExecutableRef::Instructions(instructions), + } }; map(self).insert( trigger_id.clone(), @@ -1021,8 +950,6 @@ impl<'block, 'set> SetTransaction<'block, 'set> { /// Which can be used to obtain compiled by `wasmtime` module #[derive(Clone, Serialize, Deserialize)] pub enum ExecutableRef { - /// Loaded WASM - // Wasm(HashOf), /// Vector of ISI Instructions(ConstVec), } @@ -1030,7 +957,6 @@ pub enum ExecutableRef { impl core::fmt::Debug for ExecutableRef { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { - // Self::Wasm(hash) => f.debug_tuple("Wasm").field(hash).finish(), Self::Instructions(instructions) => { f.debug_tuple("Instructions").field(instructions).finish() } diff --git a/crates/iroha_core/src/smartcontracts/isi/triggers/specialized.rs b/crates/iroha_core/src/smartcontracts/isi/triggers/specialized.rs index cc59aab15b3..94dc2ea2b6e 100644 --- a/crates/iroha_core/src/smartcontracts/isi/triggers/specialized.rs +++ b/crates/iroha_core/src/smartcontracts/isi/triggers/specialized.rs @@ -127,7 +127,6 @@ pub struct LoadedAction { impl LoadedAction { pub(super) fn extract_blob_hash(&self) -> Option> { match self.executable { - // ExecutableRef::Wasm(blob_hash) => Some(blob_hash), ExecutableRef::Instructions(_) => None, } } diff --git a/crates/iroha_core/src/state.rs b/crates/iroha_core/src/state.rs index c7d6fac9814..caa15cb099d 100644 --- a/crates/iroha_core/src/state.rs +++ b/crates/iroha_core/src/state.rs @@ -1699,7 +1699,7 @@ impl StateTransaction<'_, '_> { fn apply_executable(&mut self, executable: &Executable, authority: &AccountId) { match executable { Executable::Instructions(instructions) => { - self.execute_instructions(instructions.iter().cloned(), &authority) + self.execute_instructions(instructions.iter().cloned(), authority) .expect("should be no errors"); } } diff --git a/crates/iroha_data_model/src/transaction.rs b/crates/iroha_data_model/src/transaction.rs index 827aeff5102..c56a675e0fe 100644 --- a/crates/iroha_data_model/src/transaction.rs +++ b/crates/iroha_data_model/src/transaction.rs @@ -54,8 +54,6 @@ mod model { /// Ordered set of instructions. #[debug(fmt = "{_0:?}")] Instructions(ConstVec), - // WebAssembly smartcontract - // Wasm(WasmSmartContract), } /// Wrapper for byte representation of [`Executable::Wasm`]. @@ -578,31 +576,6 @@ pub mod error { pub reason: String, } - // /// Transaction was rejected because execution of `WebAssembly` binary failed - // #[derive( - // Debug, - // Display, - // Clone, - // PartialEq, - // Eq, - // PartialOrd, - // Ord, - // Decode, - // Encode, - // Deserialize, - // Serialize, - // IntoSchema, - // )] - // #[display(fmt = "Failed to execute wasm binary: {reason}")] - // #[serde(transparent)] - // #[repr(transparent)] - // // SAFETY: `WasmExecutionFail` has no trap representation in `String` - // #[ffi_type(unsafe {robust})] - // pub struct WasmExecutionFail { - // /// Error which happened during execution - // pub reason: String, - // } - /// Possible reasons for trigger-specific execution failure. #[derive( Debug, @@ -666,8 +639,6 @@ pub mod error { InstructionExecution( #[cfg_attr(feature = "std", source)] Box, ), - // /// Failure in WebAssembly execution - // WasmExecution(#[cfg_attr(feature = "std", source)] WasmExecutionFail), /// Execution of a time trigger or an invoked data trigger failed. TriggerExecution(#[cfg_attr(feature = "std", source)] TriggerExecutionFail), } @@ -707,9 +678,6 @@ pub mod error { #[cfg(feature = "std")] impl std::error::Error for InstructionExecutionFail {} - // #[cfg(feature = "std")] - // impl std::error::Error for WasmExecutionFail {} - #[cfg(feature = "std")] impl std::error::Error for TriggerExecutionFail {} diff --git a/crates/iroha_data_model/src/visit.rs b/crates/iroha_data_model/src/visit.rs index 424b5862835..1e5c2227d97 100644 --- a/crates/iroha_data_model/src/visit.rs +++ b/crates/iroha_data_model/src/visit.rs @@ -130,7 +130,7 @@ pub trait Visit { visit_revoke_account_role(&Revoke), visit_revoke_role_permission(&Revoke), - // + // Visit ExecuteWasmBox visit_execute_wasm_smartcontract(&WasmExecutable), visit_execute_wasm_trigger(&WasmExecutable), } @@ -138,7 +138,6 @@ pub trait Visit { pub fn visit_transaction(visitor: &mut V, transaction: &SignedTransaction) { match transaction.instructions() { - // Executable::Wasm(wasm) => visitor.visit_wasm(wasm), Executable::Instructions(instructions) => { for isi in instructions { visitor.visit_instruction(isi); @@ -370,7 +369,6 @@ leaf_visitors! { visit_upgrade(&Upgrade), visit_set_parameter(&SetParameter), visit_execute_trigger(&ExecuteTrigger), - // visit_execute_wasm(&ExecuteWasmBox), visit_execute_wasm_smartcontract(&WasmExecutable), visit_execute_wasm_trigger(&WasmExecutable), visit_log(&Log), diff --git a/crates/iroha_executor/src/default/mod.rs b/crates/iroha_executor/src/default/mod.rs index f4efd4a983d..4445d100455 100644 --- a/crates/iroha_executor/src/default/mod.rs +++ b/crates/iroha_executor/src/default/mod.rs @@ -19,7 +19,6 @@ pub use domain::{ }; pub use executor::visit_upgrade; use iroha_smart_contract::data_model::{prelude::*, visit::Visit}; -use iroha_smart_contract_utils::error; pub use isi::visit_custom_instruction; pub use log::visit_log; pub use nft::{ @@ -65,7 +64,6 @@ pub fn visit_transaction( transaction: &SignedTransaction, ) { match transaction.instructions() { - // Executable::Wasm(wasm) => executor.visit_wasm(wasm), Executable::Instructions(instructions) => { for isi in instructions { if executor.verdict().is_ok() { @@ -91,7 +89,6 @@ pub fn visit_instruction(executor: &mut V, isi: &In executor.visit_execute_trigger(isi); } InstructionBox::ExecuteWasm(isi) => { - error!("VISITING WASM in CUSTOM EXECUTOR"); executor.visit_execute_wasm(isi); } InstructionBox::Burn(isi) => { diff --git a/crates/iroha_executor_derive/src/default.rs b/crates/iroha_executor_derive/src/default.rs index eb7e4e852ef..2e88df01add 100644 --- a/crates/iroha_executor_derive/src/default.rs +++ b/crates/iroha_executor_derive/src/default.rs @@ -152,7 +152,6 @@ pub fn impl_derive_visit(emitter: &mut Emitter, input: &syn::DeriveInput) -> Tok "fn visit_mint_trigger_repetitions(operation: &Mint)", "fn visit_burn_trigger_repetitions(operation: &Burn)", "fn visit_execute_trigger(operation: &ExecuteTrigger)", - // "fn visit_execute_wasm(operation: &ExecuteWasm)", "fn visit_execute_wasm_smartcontract(operation: &WasmExecutable)", "fn visit_execute_wasm_trigger(operation: &WasmExecutable)", "fn visit_set_parameter(operation: &SetParameter)", diff --git a/crates/iroha_genesis/src/lib.rs b/crates/iroha_genesis/src/lib.rs index efb69155b22..adc4dd4f44a 100644 --- a/crates/iroha_genesis/src/lib.rs +++ b/crates/iroha_genesis/src/lib.rs @@ -513,9 +513,7 @@ mod tests { { let transaction = transactions[0]; let instructions = transaction.instructions(); - let Executable::Instructions(instructions) = instructions else { - panic!("Expected instructions"); - }; + let Executable::Instructions(instructions) = instructions; assert_eq!( instructions[0], @@ -527,9 +525,7 @@ mod tests { // Second transaction let transaction = transactions[1]; let instructions = transaction.instructions(); - let Executable::Instructions(instructions) = instructions else { - panic!("Expected instructions"); - }; + let Executable::Instructions(instructions) = instructions; { let domain_id: DomainId = "wonderland".parse().unwrap(); From 89a2b59032beea4a3d5ba659cf7ebc7a3825d988 Mon Sep 17 00:00:00 2001 From: Lohachov Mykhailo Date: Thu, 19 Jun 2025 18:19:24 +0900 Subject: [PATCH 3/7] fix: remove wasm trigger calls from isi Signed-off-by: Lohachov Mykhailo --- .../iroha_core/src/smartcontracts/isi/mod.rs | 26 ++-- .../src/smartcontracts/isi/triggers/mod.rs | 4 +- .../src/smartcontracts/isi/triggers/set.rs | 85 +++++++---- crates/iroha_core/src/state.rs | 78 +++++----- crates/iroha_core/src/tx.rs | 14 -- crates/iroha_data_model/src/isi.rs | 134 ++++++++++++------ crates/iroha_data_model/src/visit.rs | 18 +-- crates/iroha_executor/src/default/mod.rs | 11 +- crates/iroha_executor_derive/src/default.rs | 1 - crates/iroha_schema_gen/src/lib.rs | 5 - 10 files changed, 206 insertions(+), 170 deletions(-) diff --git a/crates/iroha_core/src/smartcontracts/isi/mod.rs b/crates/iroha_core/src/smartcontracts/isi/mod.rs index 61d8f58cfa5..981a7cd1abb 100644 --- a/crates/iroha_core/src/smartcontracts/isi/mod.rs +++ b/crates/iroha_core/src/smartcontracts/isi/mod.rs @@ -64,19 +64,19 @@ impl Execute for InstructionBox { } } -impl Execute for ExecuteWasmBox { - #[iroha_logger::log(name = "wasm", skip_all, fields(id))] - fn execute( - self, - authority: &AccountId, - state_transaction: &mut StateTransaction<'_, '_>, - ) -> Result<(), Error> { - match self { - Self::Smartcontract(isi) => isi.execute(authority, state_transaction), - Self::Trigger(isi) => isi.execute(authority, state_transaction), - } - } -} +// impl Execute for ExecuteWasmBox { +// #[iroha_logger::log(name = "wasm", skip_all, fields(id))] +// fn execute( +// self, +// authority: &AccountId, +// state_transaction: &mut StateTransaction<'_, '_>, +// ) -> Result<(), Error> { +// match self { +// Self::Smartcontract(isi) => isi.execute(authority, state_transaction), +// // Self::Trigger(isi) => isi.execute(authority, state_transaction), +// } +// } +// } impl Execute for RegisterBox { #[iroha_logger::log(name = "register", skip_all, fields(id))] diff --git a/crates/iroha_core/src/smartcontracts/isi/triggers/mod.rs b/crates/iroha_core/src/smartcontracts/isi/triggers/mod.rs index fd1725ddfb4..e0ed39bd47f 100644 --- a/crates/iroha_core/src/smartcontracts/isi/triggers/mod.rs +++ b/crates/iroha_core/src/smartcontracts/isi/triggers/mod.rs @@ -352,10 +352,10 @@ pub mod isi { .and_then(|mut wasm_runtime| { wasm_runtime.execute_trigger_module( state_transaction, - self.object().id().as_ref().unwrap(), + self.object().id(), authority.clone(), &module, - self.object().event().clone().unwrap(), + self.object().event().clone(), ) }) .map_err(|error| { diff --git a/crates/iroha_core/src/smartcontracts/isi/triggers/set.rs b/crates/iroha_core/src/smartcontracts/isi/triggers/set.rs index fed2ffff14d..4ca931b091b 100644 --- a/crates/iroha_core/src/smartcontracts/isi/triggers/set.rs +++ b/crates/iroha_core/src/smartcontracts/isi/triggers/set.rs @@ -14,7 +14,10 @@ use std::{fmt, marker::PhantomData, num::NonZeroU64}; use iroha_crypto::HashOf; use iroha_data_model::{ events::EventFilter, - isi::error::{InstructionExecutionError, MathError}, + isi::{ + error::{InstructionExecutionError, MathError}, + InstructionBoxHashed, + }, prelude::*, query::error::FindError, transaction::WasmSmartContract, @@ -274,6 +277,32 @@ impl<'de> DeserializeSeed<'de> for WasmSeed<'_, WasmSmartContractEntry> { deserializer.deserialize_map(WasmSmartContractEntryVisitor { loader: self }) } } + +/// Converts [`InstructionBoxHashed`] to [`InstructionBox`]. +/// `wasm_hash_resolver` is required to obtain wasm bytes from hash +pub fn hashed_to_isi(isi_hashed: InstructionBoxHashed, wasm_hash_resolver: F) -> InstructionBox +where + F: FnOnce(HashOf) -> InstructionBox, +{ + match isi_hashed { + InstructionBoxHashed::Register(o) => InstructionBox::Register(o), + InstructionBoxHashed::Unregister(o) => InstructionBox::Unregister(o), + InstructionBoxHashed::Mint(o) => InstructionBox::Mint(o), + InstructionBoxHashed::Burn(o) => InstructionBox::Burn(o), + InstructionBoxHashed::Transfer(o) => InstructionBox::Transfer(o), + InstructionBoxHashed::SetKeyValue(o) => InstructionBox::SetKeyValue(o), + InstructionBoxHashed::RemoveKeyValue(o) => InstructionBox::RemoveKeyValue(o), + InstructionBoxHashed::Grant(o) => InstructionBox::Grant(o), + InstructionBoxHashed::Revoke(o) => InstructionBox::Revoke(o), + InstructionBoxHashed::ExecuteTrigger(o) => InstructionBox::ExecuteTrigger(o), + InstructionBoxHashed::SetParameter(o) => InstructionBox::SetParameter(o), + InstructionBoxHashed::Upgrade(o) => InstructionBox::Upgrade(o), + InstructionBoxHashed::Log(o) => InstructionBox::Log(o), + InstructionBoxHashed::Custom(o) => InstructionBox::Custom(o), + InstructionBoxHashed::ExecuteWasm(o) => wasm_hash_resolver(o), + } +} + /// Trait to perform read-only operations on [`SetBlock`], [`SetTransaction`] and [`SetView`] #[allow(missing_docs)] pub trait SetReadOnly { @@ -289,9 +318,9 @@ pub trait SetReadOnly { fn contracts(&self) -> &impl StorageReadOnly, WasmSmartContractEntry>; - /// Get original [`WasmSmartContract`] for [`TriggerId`]. - /// Returns `None` if there's no [`Trigger`] - /// with specified `id` that has WASM executable + /// Get original [`WasmSmartContract`] for [`HashOf`]. + /// Returns `None` if there's no [`WasmSmartContract`] + /// with specified `hash` #[inline] fn get_original_contract( &self, @@ -324,24 +353,21 @@ pub trait SetReadOnly { } = action; let original_executable = match executable { - ExecutableRef::Instructions(isi) => { - Executable::Instructions(isi - .into_iter() - .map(|isi| -> InstructionBox{ - match isi { - InstructionBox::ExecuteWasm(ExecuteWasmBox::Trigger(trigger_module)) => { - WasmExecutable::binary(self - .get_original_contract(trigger_module.object().hash()) - .cloned() - .expect("No original smartcontract saved for trigger. This is a bug.")).into() - } - _ => isi - } - }) - .collect::>() - .into() - ) - }, + ExecutableRef::Instructions(isi) => Executable::Instructions( + isi.into_iter() + .map(|isi| -> InstructionBox { + hashed_to_isi(isi, |hash| -> InstructionBox { + WasmExecutable::binary( + self.get_original_contract(&hash).cloned().expect( + "No original smartcontract saved for trigger. This is a bug.", + ), + ) + .into() + }) + }) + .collect::>() + .into(), + ), }; SpecializedAction { @@ -702,11 +728,9 @@ impl<'block, 'set> SetTransaction<'block, 'set> { ExecutableRef::Instructions( instructions .into_iter() - .map(|isi| -> Result { + .map(|isi| -> Result { match isi { - InstructionBox::ExecuteWasm(ExecuteWasmBox::Smartcontract( - wasm, - )) => { + InstructionBox::ExecuteWasm(ref wasm) => { let hash = HashOf::new(wasm.object()); // Store original executable representation to respond to queries with. if let Some(WasmSmartContractEntry { count, .. }) = @@ -729,10 +753,11 @@ impl<'block, 'set> SetTransaction<'block, 'set> { }, ); } - Ok(WasmExecutable::module(TriggerModule::from_hash(hash)) - .into()) + Ok(isi.into()) + // Ok(WasmExecutable::module(TriggerModule::from_hash(hash)) + // .into()) } - _ => Ok(isi), + _ => Ok(isi.into()), } }) .collect::, _>>()? @@ -951,7 +976,7 @@ impl<'block, 'set> SetTransaction<'block, 'set> { #[derive(Clone, Serialize, Deserialize)] pub enum ExecutableRef { /// Vector of ISI - Instructions(ConstVec), + Instructions(ConstVec), } impl core::fmt::Debug for ExecutableRef { diff --git a/crates/iroha_core/src/state.rs b/crates/iroha_core/src/state.rs index caa15cb099d..a16b93e2010 100644 --- a/crates/iroha_core/src/state.rs +++ b/crates/iroha_core/src/state.rs @@ -15,7 +15,10 @@ use iroha_data_model::{ EventBox, }, executor::ExecutorDataModel, - isi::error::{InstructionExecutionError as Error, MathError}, + isi::{ + error::{InstructionExecutionError as Error, MathError}, + InstructionBoxHashed, + }, parameter::Parameters, permission::Permissions, prelude::*, @@ -49,7 +52,7 @@ use crate::{ smartcontracts::{ triggers::{ set::{ - ExecutableRef, Set as TriggerSet, SetBlock as TriggerSetBlock, + hashed_to_isi, ExecutableRef, Set as TriggerSet, SetBlock as TriggerSetBlock, SetReadOnly as TriggerSetReadOnly, SetTransaction as TriggerSetTransaction, SetView as TriggerSetView, }, @@ -1647,31 +1650,31 @@ impl StateTransaction<'_, '_> { executable: &ExecutableRef, event: &EventBox, ) -> Result<(), TransactionRejectionReason> { - let res = match executable { - ExecutableRef::Instructions(instructions) => { - // Convert plain trigger modules to those ready for the executing by supplying execution context. - let instructions: Vec<_> = instructions - .iter() - .cloned() - .map(|isi| -> InstructionBox { - match isi { - InstructionBox::ExecuteWasm(ExecuteWasmBox::Trigger(tg)) => { - WasmExecutable::module(TriggerModule::from_event( - *tg.object().hash(), - id.clone(), - event.clone(), - )) - .into() - } - _ => isi, - } - }) - .collect::>(); - - self.execute_instructions(instructions.iter().cloned(), authority) - .map_err(ValidationFail::from) + let ExecutableRef::Instructions(instructions) = executable; + let res = instructions.iter().cloned().try_for_each(|isi| { + // First, convert to the original InstructionBox + // In the case of wasm execution, execute as trigger module + let original_isi = hashed_to_isi(isi.clone(), |hash| -> InstructionBox { + WasmExecutable::binary( + self.world() + .triggers() + .get_original_contract(&hash) + .expect("No smartcontracts saved for trigger. This is a bug.") + .clone(), + ) + .into() + }); + { + if let InstructionBoxHashed::ExecuteWasm(hash) = isi { + let trigger_execute = + WasmExecutable::module(TriggerModule::new(hash, id.clone(), event.clone())); + trigger_execute.execute(authority, self) + } else { + original_isi.execute(authority, self) + } } - }; + .map_err(ValidationFail::from) + }); let outcome = match &res { Ok(()) => TriggerCompletedOutcome::Success, @@ -1683,17 +1686,6 @@ impl StateTransaction<'_, '_> { res.map_err(Into::into) } - fn execute_instructions( - &mut self, - instructions: impl IntoIterator, - authority: &AccountId, - ) -> Result<(), Error> { - instructions.into_iter().try_for_each(|instruction| { - instruction.execute(authority, self)?; - Ok(()) - }) - } - /// Apply a non-erroneous executable in the given committed block. #[cfg(any(test, feature = "bench"))] fn apply_executable(&mut self, executable: &Executable, authority: &AccountId) { @@ -1704,6 +1696,18 @@ impl StateTransaction<'_, '_> { } } } + + #[allow(dead_code)] + fn execute_instructions( + &mut self, + instructions: impl IntoIterator, + authority: &AccountId, + ) -> Result<(), Error> { + instructions.into_iter().try_for_each(|instruction| { + instruction.execute(authority, self)?; + Ok(()) + }) + } } /// Bounds for `range` queries diff --git a/crates/iroha_core/src/tx.rs b/crates/iroha_core/src/tx.rs index 110ddc3a50a..6284e4b77f4 100644 --- a/crates/iroha_core/src/tx.rs +++ b/crates/iroha_core/src/tx.rs @@ -145,20 +145,6 @@ impl AcceptedTransaction { .try_into() .expect("INTERNAL BUG: smart contract size exceeds usize::MAX"); - // Inputs are restricted to binaries only. - // The error type will be changed after the instruction interface is fixed. - let wasm = match wasm { - ExecuteWasmBox::Smartcontract(w) => w, - _ => { - return Err(AcceptTransactionFail::TransactionLimit( - TransactionLimitError { - reason: "only `Smartcontract`-type WASM are allowed" - .to_string(), - }, - )) - } - }; - if wasm.object().size_bytes() > smart_contract_size_limit { Err(AcceptTransactionFail::TransactionLimit( TransactionLimitError { diff --git a/crates/iroha_data_model/src/isi.rs b/crates/iroha_data_model/src/isi.rs index 5a3ed6e4f9c..13a395cd0b9 100644 --- a/crates/iroha_data_model/src/isi.rs +++ b/crates/iroha_data_model/src/isi.rs @@ -123,7 +123,65 @@ mod model { #[debug(fmt = "{_0:?}")] #[enum_ref(transparent)] - ExecuteWasm(ExecuteWasmBox), + ExecuteWasm(WasmExecutable), + } + + #[derive( + DebugCustom, + Display, + Clone, + PartialEq, + Eq, + PartialOrd, + Ord, + EnumDiscriminants, + FromVariant, + Decode, + Encode, + Deserialize, + Serialize, + IntoSchema, + )] + pub enum InstructionBoxHashed { + Register(RegisterBox), + Unregister(UnregisterBox), + Mint(MintBox), + Burn(BurnBox), + Transfer(TransferBox), + SetKeyValue(SetKeyValueBox), + RemoveKeyValue(RemoveKeyValueBox), + Grant(GrantBox), + Revoke(RevokeBox), + ExecuteTrigger(ExecuteTrigger), + SetParameter(SetParameter), + Upgrade(Upgrade), + Log(Log), + Custom(CustomInstruction), + ExecuteWasm(HashOf), + } + + impl From for InstructionBoxHashed { + fn from(isi: InstructionBox) -> Self { + match isi { + InstructionBox::Register(o) => InstructionBoxHashed::Register(o), + InstructionBox::Unregister(o) => InstructionBoxHashed::Unregister(o), + InstructionBox::Mint(o) => InstructionBoxHashed::Mint(o), + InstructionBox::Burn(o) => InstructionBoxHashed::Burn(o), + InstructionBox::Transfer(o) => InstructionBoxHashed::Transfer(o), + InstructionBox::SetKeyValue(o) => InstructionBoxHashed::SetKeyValue(o), + InstructionBox::RemoveKeyValue(o) => InstructionBoxHashed::RemoveKeyValue(o), + InstructionBox::Grant(o) => InstructionBoxHashed::Grant(o), + InstructionBox::Revoke(o) => InstructionBoxHashed::Revoke(o), + InstructionBox::ExecuteTrigger(o) => InstructionBoxHashed::ExecuteTrigger(o), + InstructionBox::SetParameter(o) => InstructionBoxHashed::SetParameter(o), + InstructionBox::Upgrade(o) => InstructionBoxHashed::Upgrade(o), + InstructionBox::Log(o) => InstructionBoxHashed::Log(o), + InstructionBox::Custom(o) => InstructionBoxHashed::Custom(o), + InstructionBox::ExecuteWasm(o) => { + InstructionBoxHashed::ExecuteWasm(HashOf::new(o.object())) + } + } + } } } @@ -182,7 +240,6 @@ impl_instruction! { Upgrade, ExecuteTrigger, WasmExecutable, - WasmExecutable, Log, } @@ -952,35 +1009,22 @@ mod transparent { isi! { #[derive(Display)] - #[display(fmt = "EXECUTE wasm")] - /// Temporary stub to handle trigger executions. - /// Triggers need `TriggerId` and `EventBox` for the execution context. + #[display(fmt = "EXECUTE wasm tigger module {hash} trigger for {id}")] + /// Internal instruction to handle wasm trigger executions. pub struct TriggerModule { /// Precompiled trigger module hash. pub hash: HashOf, /// Execution context: `TriggerId` - pub id: Option, + pub id: TriggerId, /// Execution context: `EventBox` - pub event: Option, + pub event: EventBox, } } impl TriggerModule { - /// Build `TriggerModule` from the precompiled module. - pub fn from_hash(hash: HashOf) -> Self { - Self { - hash, - id: None, - event: None, - } - } /// Build ready-to-execute `TriggerModule`. - pub fn from_event(hash: HashOf, id: TriggerId, event: EventBox) -> Self { - Self { - hash, - id: Some(id), - event: Some(event), - } + pub fn new(hash: HashOf, id: TriggerId, event: EventBox) -> Self { + Self { hash, id, event } } } @@ -998,12 +1042,12 @@ mod transparent { } } - impl_into_box! { - WasmExecutable | - WasmExecutable - => ExecuteWasmBox => InstructionBox[ExecuteWasm], - => ExecuteWasmBoxRef<'a> => InstructionBoxRef<'a>[ExecuteWasm] - } + // impl_into_box! { + // WasmExecutable + // // WasmExecutable + // => ExecuteWasmBox => InstructionBox[ExecuteWasm], + // => ExecuteWasmBoxRef<'a> => InstructionBoxRef<'a>[ExecuteWasm] + // } isi! { /// Generic instruction for upgrading runtime objects. @@ -1084,20 +1128,20 @@ macro_rules! isi_box { }; } -isi_box! { - #[strum_discriminants( - vis(pub(crate)), - name(WasmType), - derive(Encode), - )] - /// Enum with all supported [`WasmExecutable`] instructions. - pub enum ExecuteWasmBox { - /// Execute [`WasmSmartContract`]. - Smartcontract(WasmExecutable), - /// Execute [`TriggerModule`] . - Trigger(WasmExecutable), - } -} +// isi_box! { +// #[strum_discriminants( +// vis(pub(crate)), +// name(WasmType), +// derive(Encode), +// )] +// /// Enum with all supported [`WasmExecutable`] instructions. +// pub enum ExecuteWasmBox { +// /// Execute [`WasmSmartContract`]. +// Smartcontract(WasmExecutable), +// // /// Execute [`TriggerModule`] . +// // Trigger(WasmExecutable), +// } +// } isi_box! { #[strum_discriminants( @@ -1602,9 +1646,9 @@ pub mod error { /// The prelude re-exports most commonly used traits, structs and macros from this crate. pub mod prelude { pub use super::{ - Burn, BurnBox, CustomInstruction, ExecuteTrigger, ExecuteWasmBox, Grant, GrantBox, - InstructionBox, Log, Mint, MintBox, Register, RegisterBox, RemoveKeyValue, - RemoveKeyValueBox, Revoke, RevokeBox, SetKeyValue, SetKeyValueBox, SetParameter, Transfer, - TransferBox, TriggerModule, Unregister, UnregisterBox, Upgrade, WasmExecutable, + Burn, BurnBox, CustomInstruction, ExecuteTrigger, Grant, GrantBox, InstructionBox, Log, + Mint, MintBox, Register, RegisterBox, RemoveKeyValue, RemoveKeyValueBox, Revoke, RevokeBox, + SetKeyValue, SetKeyValueBox, SetParameter, Transfer, TransferBox, TriggerModule, + Unregister, UnregisterBox, Upgrade, WasmExecutable, }; } diff --git a/crates/iroha_data_model/src/visit.rs b/crates/iroha_data_model/src/visit.rs index 1e5c2227d97..6778dcf683c 100644 --- a/crates/iroha_data_model/src/visit.rs +++ b/crates/iroha_data_model/src/visit.rs @@ -46,7 +46,7 @@ pub trait Visit { visit_upgrade(&Upgrade), visit_execute_trigger(&ExecuteTrigger), - visit_execute_wasm(&ExecuteWasmBox), + visit_execute_wasm_smartcontract(&WasmExecutable), visit_set_parameter(&SetParameter), visit_log(&Log), visit_custom_instruction(&CustomInstruction), @@ -129,10 +129,6 @@ pub trait Visit { visit_revoke_account_permission(&Revoke), visit_revoke_account_role(&Revoke), visit_revoke_role_permission(&Revoke), - - // Visit ExecuteWasmBox - visit_execute_wasm_smartcontract(&WasmExecutable), - visit_execute_wasm_trigger(&WasmExecutable), } } @@ -222,7 +218,9 @@ pub fn visit_instruction(visitor: &mut V, isi: &InstructionBo InstructionBox::Unregister(variant_value) => visitor.visit_unregister(variant_value), InstructionBox::Upgrade(variant_value) => visitor.visit_upgrade(variant_value), InstructionBox::Custom(custom) => visitor.visit_custom_instruction(custom), - InstructionBox::ExecuteWasm(variant_value) => visitor.visit_execute_wasm(variant_value), + InstructionBox::ExecuteWasm(variant_value) => { + visitor.visit_execute_wasm_smartcontract(variant_value) + } } } @@ -238,13 +236,6 @@ pub fn visit_register(visitor: &mut V, isi: &RegisterBox) { } } -pub fn visit_execute_wasm(visitor: &mut V, isi: &ExecuteWasmBox) { - match isi { - ExecuteWasmBox::Smartcontract(obj) => visitor.visit_execute_wasm_smartcontract(obj), - ExecuteWasmBox::Trigger(obj) => visitor.visit_execute_wasm_trigger(obj), - } -} - pub fn visit_unregister(visitor: &mut V, isi: &UnregisterBox) { match isi { UnregisterBox::Peer(obj) => visitor.visit_unregister_peer(obj), @@ -370,7 +361,6 @@ leaf_visitors! { visit_set_parameter(&SetParameter), visit_execute_trigger(&ExecuteTrigger), visit_execute_wasm_smartcontract(&WasmExecutable), - visit_execute_wasm_trigger(&WasmExecutable), visit_log(&Log), visit_custom_instruction(&CustomInstruction), diff --git a/crates/iroha_executor/src/default/mod.rs b/crates/iroha_executor/src/default/mod.rs index 4445d100455..e37e0d51b76 100644 --- a/crates/iroha_executor/src/default/mod.rs +++ b/crates/iroha_executor/src/default/mod.rs @@ -37,7 +37,7 @@ pub use trigger::{ visit_register_trigger, visit_remove_trigger_key_value, visit_set_trigger_key_value, visit_unregister_trigger, }; -pub use wasm::{visit_execute_wasm_smartcontract, visit_execute_wasm_trigger}; +pub use wasm::visit_execute_wasm_smartcontract; use crate::{ deny, execute, @@ -89,7 +89,7 @@ pub fn visit_instruction(executor: &mut V, isi: &In executor.visit_execute_trigger(isi); } InstructionBox::ExecuteWasm(isi) => { - executor.visit_execute_wasm(isi); + executor.visit_execute_wasm_smartcontract(isi); } InstructionBox::Burn(isi) => { executor.visit_burn(isi); @@ -1546,13 +1546,6 @@ mod wasm { ) { execute!(executor, isi); } - - pub fn visit_execute_wasm_trigger( - executor: &mut V, - isi: &WasmExecutable, - ) { - execute!(executor, isi); - } } pub mod permission { diff --git a/crates/iroha_executor_derive/src/default.rs b/crates/iroha_executor_derive/src/default.rs index 2e88df01add..2e92d029951 100644 --- a/crates/iroha_executor_derive/src/default.rs +++ b/crates/iroha_executor_derive/src/default.rs @@ -153,7 +153,6 @@ pub fn impl_derive_visit(emitter: &mut Emitter, input: &syn::DeriveInput) -> Tok "fn visit_burn_trigger_repetitions(operation: &Burn)", "fn visit_execute_trigger(operation: &ExecuteTrigger)", "fn visit_execute_wasm_smartcontract(operation: &WasmExecutable)", - "fn visit_execute_wasm_trigger(operation: &WasmExecutable)", "fn visit_set_parameter(operation: &SetParameter)", "fn visit_upgrade(operation: &Upgrade)", "fn visit_log(operation: &Log)", diff --git a/crates/iroha_schema_gen/src/lib.rs b/crates/iroha_schema_gen/src/lib.rs index 0abc8aa721f..fd055083a8f 100644 --- a/crates/iroha_schema_gen/src/lib.rs +++ b/crates/iroha_schema_gen/src/lib.rs @@ -249,7 +249,6 @@ types!( DomainProjection, DomainProjection, EventBox, - Option, EventFilterBox, EventMessage, EventSubscriptionRequest, @@ -257,9 +256,7 @@ types!( ExecuteTrigger, ExecuteTriggerEvent, ExecuteTriggerEventFilter, - ExecuteWasmBox, WasmExecutable, - WasmExecutable, ExecutionTime, Executor, ExecutorDataModel, @@ -640,8 +637,6 @@ types!( Vec, WasmExecutionError, WasmSmartContract, - HashOf, - TriggerModule, (), [u16; 8], From 585c0623ff48ff8f72c4db563c29a4e20d553994 Mon Sep 17 00:00:00 2001 From: Lohachov Mykhailo Date: Thu, 19 Jun 2025 18:38:00 +0900 Subject: [PATCH 4/7] fix: fmt Signed-off-by: Lohachov Mykhailo --- crates/iroha_core/src/state.rs | 2 +- docs/source/references/schema.json | 44 +----------------------------- 2 files changed, 2 insertions(+), 44 deletions(-) diff --git a/crates/iroha_core/src/state.rs b/crates/iroha_core/src/state.rs index 4909c02f90f..356ef35dd11 100644 --- a/crates/iroha_core/src/state.rs +++ b/crates/iroha_core/src/state.rs @@ -61,7 +61,7 @@ use crate::{ }, specialized::{LoadedAction, LoadedActionTrait}, }, - wasm, + wasm, Execute, }, state::storage_transactions::{ TransactionsBlock, TransactionsReadOnly, TransactionsStorage, TransactionsView, diff --git a/docs/source/references/schema.json b/docs/source/references/schema.json index 8bedc9042df..57385449106 100644 --- a/docs/source/references/schema.json +++ b/docs/source/references/schema.json @@ -2037,20 +2037,6 @@ } ] }, - "ExecuteWasmBox": { - "Enum": [ - { - "tag": "Smartcontract", - "discriminant": 0, - "type": "WasmExecutable" - }, - { - "tag": "Trigger", - "discriminant": 1, - "type": "WasmExecutable" - } - ] - }, "ExecutionTime": { "Enum": [ { @@ -2348,7 +2334,6 @@ "HashOf>": "Hash", "HashOf": "Hash", "HashOf>": "Hash", - "HashOf": "Hash", "IdBox": { "Enum": [ { @@ -2478,7 +2463,7 @@ { "tag": "ExecuteWasm", "discriminant": 14, - "type": "ExecuteWasmBox" + "type": "WasmExecutable" } ] }, @@ -3420,9 +3405,6 @@ "Option": { "Option": "DomainId" }, - "Option": { - "Option": "EventBox" - }, "Option": { "Option": "ForwardCursor" }, @@ -5903,22 +5885,6 @@ } ] }, - "TriggerModule": { - "Struct": [ - { - "name": "hash", - "type": "HashOf" - }, - { - "name": "id", - "type": "Option" - }, - { - "name": "event", - "type": "Option" - } - ] - }, "TriggerNumberOfExecutionsChanged": { "Struct": [ { @@ -6312,14 +6278,6 @@ "Vec": { "Vec": "u8" }, - "WasmExecutable": { - "Struct": [ - { - "name": "object", - "type": "TriggerModule" - } - ] - }, "WasmExecutable": { "Struct": [ { From a7469fcb9231ba63e280b505ab84075c535c557a Mon Sep 17 00:00:00 2001 From: Lohachov Mykhailo Date: Sun, 22 Jun 2025 21:45:53 +0900 Subject: [PATCH 5/7] fix: add tests for multu-wasm execution Signed-off-by: Lohachov Mykhailo --- crates/iroha_data_model/src/isi.rs | 2 + integration_tests/tests/smart_contract.rs | 72 +++++++++++++ integration_tests/tests/triggers/mod.rs | 1 + integration_tests/tests/triggers/wasm.rs | 100 ++++++++++++++++++ wasm/Cargo.lock | 11 ++ .../mint_rose_smartcontract/Cargo.toml | 19 ++++ .../mint_rose_smartcontract/src/lib.rs | 34 ++++++ 7 files changed, 239 insertions(+) create mode 100644 integration_tests/tests/smart_contract.rs create mode 100644 integration_tests/tests/triggers/wasm.rs create mode 100644 wasm/samples/mint_rose_smartcontract/Cargo.toml create mode 100644 wasm/samples/mint_rose_smartcontract/src/lib.rs diff --git a/crates/iroha_data_model/src/isi.rs b/crates/iroha_data_model/src/isi.rs index 13a395cd0b9..9d9c2e70b8c 100644 --- a/crates/iroha_data_model/src/isi.rs +++ b/crates/iroha_data_model/src/isi.rs @@ -1414,6 +1414,7 @@ pub mod error { Deserialize, Serialize, IntoSchema, + Getters, )] #[display(fmt = "Error in executing wasm binary: {reason}")] #[serde(transparent)] @@ -1422,6 +1423,7 @@ pub mod error { #[ffi_type(unsafe {robust})] pub struct WasmExecutionError { /// Error which happened during execution + #[getset(get = "pub")] pub reason: String, } diff --git a/integration_tests/tests/smart_contract.rs b/integration_tests/tests/smart_contract.rs new file mode 100644 index 00000000000..f1858ea368a --- /dev/null +++ b/integration_tests/tests/smart_contract.rs @@ -0,0 +1,72 @@ +#![allow(missing_docs)] + +use eyre::Result; +use iroha::data_model::prelude::*; +use iroha_data_model::isi::error::WasmExecutionError; +use iroha_test_network::*; +use iroha_test_samples::{load_sample_wasm, ALICE_ID}; + +#[test] +fn multiple_smartcontracts_in_transaction() -> Result<()> { + let (network, _rt) = NetworkBuilder::new().start_blocking()?; + let client = network.client(); + + let rose: AssetDefinitionId = "rose#wonderland".parse()?; + + let roses_before = client + .query(FindAssets::new()) + .filter_with(|asset| asset.id.account.eq(ALICE_ID.clone())) + .filter_with(|asset| asset.id.definition.eq(rose.clone())) + .execute_single() + .unwrap(); + + let transaction = client.build_transaction( + vec![ + WasmExecutable::binary(load_sample_wasm("mint_rose_smartcontract")), + WasmExecutable::binary(load_sample_wasm("mint_rose_smartcontract")), + WasmExecutable::binary(load_sample_wasm("mint_rose_smartcontract")), + ], + Metadata::default(), + ); + client.submit_transaction_blocking(&transaction)?; + + let roses_after = client + .query(FindAssets::new()) + .filter_with(|asset| asset.id.account.eq(ALICE_ID.clone())) + .filter_with(|asset| asset.id.definition.eq(rose.clone())) + .execute_single() + .unwrap(); + + assert_eq!( + roses_before.value().checked_add(3_u32.into()).unwrap(), + roses_after.value().clone() + ); + + Ok(()) +} + +#[test] +fn trigger_wasm_execute_fail_outside_of_trigger() -> Result<()> { + let (network, _rt) = NetworkBuilder::new().start_blocking()?; + let client = network.client(); + + let transaction = client.build_transaction( + vec![ + WasmExecutable::binary(load_sample_wasm("mint_rose_smartcontract")), + WasmExecutable::binary(load_sample_wasm("mint_rose_trigger")), + ], + Metadata::default(), + ); + let error = client + .submit_transaction_blocking(&transaction) + .unwrap_err(); + + assert!(error + .root_cause() + .downcast_ref::() + .unwrap() + .reason() + .contains("Export error")); + + Ok(()) +} diff --git a/integration_tests/tests/triggers/mod.rs b/integration_tests/tests/triggers/mod.rs index 1c857cd3703..b39756a910d 100644 --- a/integration_tests/tests/triggers/mod.rs +++ b/integration_tests/tests/triggers/mod.rs @@ -12,6 +12,7 @@ mod orphans; // FIXME: rewrite all in async and with shorter timings mod time_trigger; mod trigger_rollback; +mod wasm; fn get_asset_value(client: &Client, asset_id: AssetId) -> Numeric { let asset = client diff --git a/integration_tests/tests/triggers/wasm.rs b/integration_tests/tests/triggers/wasm.rs new file mode 100644 index 00000000000..193a176e5d6 --- /dev/null +++ b/integration_tests/tests/triggers/wasm.rs @@ -0,0 +1,100 @@ +#![allow(missing_docs)] + +use eyre::Result; +use iroha::data_model::prelude::*; +use iroha_data_model::isi::error::{InstructionExecutionError, WasmExecutionError}; +use iroha_test_network::*; +use iroha_test_samples::{load_sample_wasm, ALICE_ID}; +use mint_rose_trigger_data_model::MintRoseArgs; + +#[test] +fn multiple_wasm_triggers_in_transaction() -> Result<()> { + let (network, _rt) = NetworkBuilder::new().start_blocking()?; + let client = network.client(); + + let rose: AssetDefinitionId = "rose#wonderland".parse()?; + + let roses_before = client + .query(FindAssets::new()) + .filter_with(|asset| asset.id.account.eq(ALICE_ID.clone())) + .filter_with(|asset| asset.id.definition.eq(rose.clone())) + .execute_single() + .unwrap(); + + let trigger_id = "rose_farm".parse::()?; + let trigger_instructions = vec![ + WasmExecutable::binary(load_sample_wasm("mint_rose_trigger")), + WasmExecutable::binary(load_sample_wasm("mint_rose_trigger")), + WasmExecutable::binary(load_sample_wasm("mint_rose_trigger")), + ]; + let register_trigger = Register::trigger(Trigger::new( + trigger_id.clone(), + Action::new( + trigger_instructions, + Repeats::Indefinitely, + ALICE_ID.clone(), + ExecuteTriggerEventFilter::new() + .for_trigger(trigger_id.clone()) + .under_authority(ALICE_ID.clone()), + ), + )); + client.submit_blocking(register_trigger)?; + + // Args are shared between calls + let args = &MintRoseArgs { val: 1 }; + client.submit_blocking(ExecuteTrigger::new(trigger_id).with_args(args))?; + + let roses_after = client + .query(FindAssets::new()) + .filter_with(|asset| asset.id.account.eq(ALICE_ID.clone())) + .filter_with(|asset| asset.id.definition.eq(rose.clone())) + .execute_single() + .unwrap(); + + assert_eq!( + roses_before.value().checked_add(3_u32.into()).unwrap(), + roses_after.value().clone() + ); + + Ok(()) +} + +#[test] +fn smartcontract_execute_fail_in_trigger() -> Result<()> { + let (network, _rt) = NetworkBuilder::new().start_blocking()?; + let client = network.client(); + + let trigger_id = "rose_farm".parse::()?; + let trigger_instructions = vec![ + WasmExecutable::binary(load_sample_wasm("mint_rose_trigger")), + WasmExecutable::binary(load_sample_wasm("mint_rose_smartcontract")), + ]; + let register_trigger = Register::trigger(Trigger::new( + trigger_id.clone(), + Action::new( + trigger_instructions, + Repeats::Indefinitely, + ALICE_ID.clone(), + ExecuteTriggerEventFilter::new() + .for_trigger(trigger_id.clone()) + .under_authority(ALICE_ID.clone()), + ), + )); + client.submit_blocking(register_trigger)?; + + // Args are shared between calls + let args = &MintRoseArgs { val: 1 }; + let error = client + .submit_blocking(ExecuteTrigger::new(trigger_id).with_args(args)) + .unwrap_err(); + + let Some(InstructionExecutionError::InvariantViolation(msg)) = error + .root_cause() + .downcast_ref::( + ) else { + panic!("unexpected error") + }; + assert!(msg.contains("Validation failed")); + + Ok(()) +} diff --git a/wasm/Cargo.lock b/wasm/Cargo.lock index db0952261e8..5406342ab22 100644 --- a/wasm/Cargo.lock +++ b/wasm/Cargo.lock @@ -1201,6 +1201,17 @@ dependencies = [ "panic-halt", ] +[[package]] +name = "mint_rose_smartcontract" +version = "2.0.0-rc.2.0" +dependencies = [ + "dlmalloc", + "iroha_smart_contract", + "nonzero_ext", + "panic-halt", + "parity-scale-codec", +] + [[package]] name = "mint_rose_trigger_data_model" version = "2.0.0-rc.2.0" diff --git a/wasm/samples/mint_rose_smartcontract/Cargo.toml b/wasm/samples/mint_rose_smartcontract/Cargo.toml new file mode 100644 index 00000000000..43babc93f73 --- /dev/null +++ b/wasm/samples/mint_rose_smartcontract/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "mint_rose_smartcontract" + +edition.workspace = true +version.workspace = true +authors.workspace = true + +license.workspace = true + +[lib] +crate-type = ['cdylib'] + +[dependencies] +iroha_smart_contract.workspace = true + +panic-halt.workspace = true +dlmalloc.workspace = true +parity-scale-codec.workspace = true +nonzero_ext.workspace = true diff --git a/wasm/samples/mint_rose_smartcontract/src/lib.rs b/wasm/samples/mint_rose_smartcontract/src/lib.rs new file mode 100644 index 00000000000..b13fe1c6aed --- /dev/null +++ b/wasm/samples/mint_rose_smartcontract/src/lib.rs @@ -0,0 +1,34 @@ +//! Smart contract which executes [`MintAsset`] for the caller. +//! TODO: Extend the smartcontact interface to accept input arguments (same as triggers)? + +#![no_std] + +#[cfg(not(test))] +extern crate panic_halt; + +use dlmalloc::GlobalDlmalloc; +use iroha_smart_contract::{ + data_model::query::{ + builder::QueryExecutor, + dsl::{CompoundPredicate, SelectorTuple}, + parameters::{ForwardCursor, QueryParams}, + QueryWithFilter, QueryWithParams, + }, + prelude::*, +}; +use nonzero_ext::nonzero; +use parity_scale_codec::{Decode, DecodeAll, Encode}; + +#[global_allocator] +static ALLOC: GlobalDlmalloc = GlobalDlmalloc; + +/// Execute [`MintAsset`] for the caller. +/// NOTE: DON'T TAKE THIS AS AN EXAMPLE, THIS IS ONLY FOR TESTING INTERNALS OF IROHA +#[iroha_smart_contract::main] +fn main(host: Iroha, context: Context) { + let rose_definition_id = "rose#wonderland".parse().unwrap(); + let rose_id = AssetId::new(rose_definition_id, context.authority); + + host.submit(&Mint::asset_numeric(1_u32, rose_id)) + .dbg_expect("Failed to mint rose"); +} From d3494e7b424f507e66ae4085f499a56cd1a7282a Mon Sep 17 00:00:00 2001 From: Lohachov Mykhailo Date: Sat, 12 Jul 2025 13:07:54 +0900 Subject: [PATCH 6/7] chore: clippy Signed-off-by: Lohachov Mykhailo --- crates/iroha_core/src/block.rs | 7 +++--- .../iroha_core/src/smartcontracts/isi/mod.rs | 14 ------------ .../src/smartcontracts/isi/triggers/set.rs | 2 -- crates/iroha_core/src/smartcontracts/wasm.rs | 6 ++--- crates/iroha_core/src/state.rs | 5 +---- crates/iroha_core/src/tx.rs | 4 ++-- crates/iroha_data_model/src/isi.rs | 22 ------------------- crates/iroha_data_model/src/transaction.rs | 5 +---- 8 files changed, 10 insertions(+), 55 deletions(-) diff --git a/crates/iroha_core/src/block.rs b/crates/iroha_core/src/block.rs index e2e2f48b689..8b2189cd176 100644 --- a/crates/iroha_core/src/block.rs +++ b/crates/iroha_core/src/block.rs @@ -677,7 +677,7 @@ mod valid { let accepted_tx = AcceptedTransaction::new_unchecked(tx.clone()); let (hash, result) = - state_block.validate_transaction(accepted_tx, &mut wasm_cache); + state_block.validate_transaction(&accepted_tx, &mut wasm_cache); match &result { Err(reason) => { @@ -961,9 +961,8 @@ mod valid { if transaction.authority() != genesis_account { return Err(InvalidGenesisError::UnexpectedAuthority); } - let Executable::Instructions(isi) = transaction.instructions() else { - return Err(InvalidGenesisError::NotInstructions); - }; + + let Executable::Instructions(isi) = transaction.instructions(); if i == 0 { let [InstructionBox::Upgrade(_)] = isi.as_ref() else { return Err(InvalidGenesisError::MustUpgrade); diff --git a/crates/iroha_core/src/smartcontracts/isi/mod.rs b/crates/iroha_core/src/smartcontracts/isi/mod.rs index 1708d5bbcc7..0225781b154 100644 --- a/crates/iroha_core/src/smartcontracts/isi/mod.rs +++ b/crates/iroha_core/src/smartcontracts/isi/mod.rs @@ -56,20 +56,6 @@ impl Execute for InstructionBox { } } -// impl Execute for ExecuteWasmBox { -// #[iroha_logger::log(name = "wasm", skip_all, fields(id))] -// fn execute( -// self, -// authority: &AccountId, -// state_transaction: &mut StateTransaction<'_, '_>, -// ) -> Result<(), Error> { -// match self { -// Self::Smartcontract(isi) => isi.execute(authority, state_transaction), -// // Self::Trigger(isi) => isi.execute(authority, state_transaction), -// } -// } -// } - impl Execute for RegisterBox { #[iroha_logger::log(name = "register", skip_all, fields(id))] fn execute( diff --git a/crates/iroha_core/src/smartcontracts/isi/triggers/set.rs b/crates/iroha_core/src/smartcontracts/isi/triggers/set.rs index 4ca931b091b..8fe06be03bc 100644 --- a/crates/iroha_core/src/smartcontracts/isi/triggers/set.rs +++ b/crates/iroha_core/src/smartcontracts/isi/triggers/set.rs @@ -754,8 +754,6 @@ impl<'block, 'set> SetTransaction<'block, 'set> { ); } Ok(isi.into()) - // Ok(WasmExecutable::module(TriggerModule::from_hash(hash)) - // .into()) } _ => Ok(isi.into()), } diff --git a/crates/iroha_core/src/smartcontracts/wasm.rs b/crates/iroha_core/src/smartcontracts/wasm.rs index 089044f0be3..86de4589116 100644 --- a/crates/iroha_core/src/smartcontracts/wasm.rs +++ b/crates/iroha_core/src/smartcontracts/wasm.rs @@ -997,7 +997,7 @@ impl<'wrld, 'block: 'wrld, 'state: 'block> Runtime, state: state::SmartContract<'wrld, 'block, 'state>, ) -> Result<()> { - // TODO: Re-use state/fuel from the transation + // TODO: Re-use state/fuel from the transation #5494 let mut store = self.create_store(state); let smart_contract = self.create_smart_contract(&mut store, bytes)?; @@ -1078,7 +1078,7 @@ impl<'wrld, 'block: 'wrld, 'state: 'block> Runtime Runtime { self.execute_instructions(vec![original_isi].into(), authority) } } - .map_err(ValidationFail::from) }) .try_fold( ExecutionStep(ConstVec::new_empty()), |acc, item| -> Result { let step = item?; - Ok(ExecutionStep(ConstVec::from_iter( - acc.0.into_iter().chain(step.0.into_iter()), - ))) + Ok(ExecutionStep(acc.0.into_iter().chain(step.0).collect())) }, ); diff --git a/crates/iroha_core/src/tx.rs b/crates/iroha_core/src/tx.rs index 8060fb43d53..9876ac0d4fd 100644 --- a/crates/iroha_core/src/tx.rs +++ b/crates/iroha_core/src/tx.rs @@ -259,12 +259,12 @@ impl StateBlock<'_> { /// Returns the hash and the result of the transaction -- the trigger sequence on success, or the rejection reason on failure. pub fn validate_transaction( &mut self, - tx: AcceptedTransaction, + tx: &AcceptedTransaction, wasm_cache: &mut WasmCache<'_, '_, '_>, ) -> (HashOf, TransactionResultInner) { let mut state_transaction = self.transaction(); let hash = tx.as_ref().hash_as_entrypoint(); - let result = Self::validate_transaction_internal(&tx, &mut state_transaction, wasm_cache); + let result = Self::validate_transaction_internal(tx, &mut state_transaction, wasm_cache); if result.is_ok() { state_transaction.apply(); } diff --git a/crates/iroha_data_model/src/isi.rs b/crates/iroha_data_model/src/isi.rs index b73b7686e4c..c5a36ce8fb0 100644 --- a/crates/iroha_data_model/src/isi.rs +++ b/crates/iroha_data_model/src/isi.rs @@ -1042,13 +1042,6 @@ mod transparent { } } - // impl_into_box! { - // WasmExecutable - // WasmExecutable - // => ExecuteWasmBox => InstructionBox[ExecuteWasm], - // => ExecuteWasmBoxRef<'a> => InstructionBoxRef<'a>[ExecuteWasm] - // } - isi! { /// Generic instruction for upgrading runtime objects. #[derive(Constructor, Display)] @@ -1128,21 +1121,6 @@ macro_rules! isi_box { }; } -// isi_box! { -// #[strum_discriminants( -// vis(pub(crate)), -// name(WasmType), -// derive(Encode), -// )] -// /// Enum with all supported [`WasmExecutable`] instructions. -// pub enum ExecuteWasmBox { -// /// Execute [`WasmSmartContract`]. -// Smartcontract(WasmExecutable), -// // /// Execute [`TriggerModule`] . -// // Trigger(WasmExecutable), -// } -// } - isi_box! { #[strum_discriminants( vis(pub(crate)), diff --git a/crates/iroha_data_model/src/transaction.rs b/crates/iroha_data_model/src/transaction.rs index c7170bbb339..57cc5844205 100644 --- a/crates/iroha_data_model/src/transaction.rs +++ b/crates/iroha_data_model/src/transaction.rs @@ -841,10 +841,7 @@ pub mod error { //! The prelude re-exports most commonly used traits, structs and macros from this module. pub use super::{ - InstructionExecutionFail, - TransactionRejectionReason, - TriggerExecutionFail, - // WasmExecutionFail, + InstructionExecutionFail, TransactionRejectionReason, TriggerExecutionFail, }; } } From 9735657bba17f631d989888eb5f5a7c07414e4d2 Mon Sep 17 00:00:00 2001 From: Lohachov Mykhailo Date: Sat, 12 Jul 2025 19:23:53 +0900 Subject: [PATCH 7/7] chore: clippy Signed-off-by: Lohachov Mykhailo --- crates/iroha_core/benches/validation.rs | 2 +- crates/iroha_data_model/src/transaction.rs | 4 +--- integration_tests/tests/queries/mod.rs | 4 +--- integration_tests/tests/triggers/wasm.rs | 2 +- wasm/samples/mint_rose_smartcontract/src/lib.rs | 12 +----------- 5 files changed, 5 insertions(+), 19 deletions(-) diff --git a/crates/iroha_core/benches/validation.rs b/crates/iroha_core/benches/validation.rs index b07e1807619..74c10393058 100644 --- a/crates/iroha_core/benches/validation.rs +++ b/crates/iroha_core/benches/validation.rs @@ -172,7 +172,7 @@ fn validate_transaction(criterion: &mut Criterion) { let _ = criterion.bench_function("validate", |b| { b.iter(|| { match state_block - .validate_transaction(transaction.clone(), &mut wasm_cache) + .validate_transaction(&transaction, &mut wasm_cache) .1 { Ok(_) => success_count += 1, diff --git a/crates/iroha_data_model/src/transaction.rs b/crates/iroha_data_model/src/transaction.rs index 57cc5844205..a392aff305f 100644 --- a/crates/iroha_data_model/src/transaction.rs +++ b/crates/iroha_data_model/src/transaction.rs @@ -433,9 +433,7 @@ impl SignedTransaction { extra_instructions: impl IntoIterator>, ) { let SignedTransaction::V1(tx) = self; - let Executable::Instructions(instructions) = &mut tx.payload.instructions else { - unimplemented!("Wasm executables are not subject to fault injection") - }; + let Executable::Instructions(instructions) = &mut tx.payload.instructions; let mut modified = instructions.clone().into_vec(); modified.extend(extra_instructions.into_iter().map(Into::into)); *instructions = modified.into(); diff --git a/integration_tests/tests/queries/mod.rs b/integration_tests/tests/queries/mod.rs index 781b01164ae..4af2163c609 100644 --- a/integration_tests/tests/queries/mod.rs +++ b/integration_tests/tests/queries/mod.rs @@ -74,9 +74,7 @@ fn find_transactions_reversed() -> eyre::Result<()> { let TransactionEntrypoint::External(entrypoint) = txs[0].entrypoint() else { eyre::bail!("entrypoint should be external transaction"); }; - let Executable::Instructions(instructions) = entrypoint.instructions() else { - eyre::bail!("entrypoint should be builtin instructions"); - }; + let Executable::Instructions(instructions) = entrypoint.instructions(); assert_eq!(instructions.len(), 1); assert_eq!( instructions[0], diff --git a/integration_tests/tests/triggers/wasm.rs b/integration_tests/tests/triggers/wasm.rs index 193a176e5d6..a96af8dc7b3 100644 --- a/integration_tests/tests/triggers/wasm.rs +++ b/integration_tests/tests/triggers/wasm.rs @@ -2,7 +2,7 @@ use eyre::Result; use iroha::data_model::prelude::*; -use iroha_data_model::isi::error::{InstructionExecutionError, WasmExecutionError}; +use iroha_data_model::isi::error::InstructionExecutionError; use iroha_test_network::*; use iroha_test_samples::{load_sample_wasm, ALICE_ID}; use mint_rose_trigger_data_model::MintRoseArgs; diff --git a/wasm/samples/mint_rose_smartcontract/src/lib.rs b/wasm/samples/mint_rose_smartcontract/src/lib.rs index b13fe1c6aed..eac3c52eeea 100644 --- a/wasm/samples/mint_rose_smartcontract/src/lib.rs +++ b/wasm/samples/mint_rose_smartcontract/src/lib.rs @@ -7,17 +7,7 @@ extern crate panic_halt; use dlmalloc::GlobalDlmalloc; -use iroha_smart_contract::{ - data_model::query::{ - builder::QueryExecutor, - dsl::{CompoundPredicate, SelectorTuple}, - parameters::{ForwardCursor, QueryParams}, - QueryWithFilter, QueryWithParams, - }, - prelude::*, -}; -use nonzero_ext::nonzero; -use parity_scale_codec::{Decode, DecodeAll, Encode}; +use iroha_smart_contract::prelude::*; #[global_allocator] static ALLOC: GlobalDlmalloc = GlobalDlmalloc;