Skip to content

Commit

Permalink
perf: Persistent executor
Browse files Browse the repository at this point in the history
Signed-off-by: Dmitry Murzin <[email protected]>
  • Loading branch information
dima74 committed Sep 17, 2024
1 parent c2cc968 commit 6849085
Show file tree
Hide file tree
Showing 6 changed files with 249 additions and 50 deletions.
5 changes: 3 additions & 2 deletions crates/iroha_core/benches/validation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use iroha_core::{
block::*,
prelude::*,
query::store::LiveQueryStore,
smartcontracts::{isi::Registrable as _, Execute},
smartcontracts::{isi::Registrable as _, wasm::cache::WasmCache, Execute},
state::{State, World},
};
use iroha_data_model::{
Expand Down Expand Up @@ -132,10 +132,11 @@ fn validate_transaction(criterion: &mut Criterion) {
.expect("Failed to accept transaction.");
let mut success_count = 0;
let mut failure_count = 0;
let mut wasm_cache = WasmCache::new();
let _ = criterion.bench_function("validate", move |b| {
b.iter(|| {
let mut state_block = state.block();
match state_block.validate(transaction.clone()) {
match state_block.validate(transaction.clone(), &mut wasm_cache) {
Ok(_) => success_count += 1,
Err(_) => failure_count += 1,
}
Expand Down
14 changes: 9 additions & 5 deletions crates/iroha_core/src/block.rs
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ mod pending {
use nonzero_ext::nonzero;

use super::*;
use crate::state::StateBlock;
use crate::{smartcontracts::wasm::cache::WasmCache, state::StateBlock};

/// First stage in the life-cycle of a [`Block`].
/// In the beginning the block is assumed to be verified and to contain only accepted transactions.
Expand Down Expand Up @@ -217,9 +217,10 @@ mod pending {
transactions: Vec<AcceptedTransaction>,
state_block: &mut StateBlock<'_>,
) -> Vec<CommittedTransaction> {
let mut wasm_cache = WasmCache::new();
transactions
.into_iter()
.map(|tx| match state_block.validate(tx) {
.map(|tx| match state_block.validate(tx, &mut wasm_cache) {
Ok(tx) => CommittedTransaction {
value: tx,
error: None,
Expand Down Expand Up @@ -286,7 +287,9 @@ mod valid {
use mv::storage::StorageReadOnly;

use super::*;
use crate::{state::StateBlock, sumeragi::network_topology::Role};
use crate::{
smartcontracts::wasm::cache::WasmCache, state::StateBlock, sumeragi::network_topology::Role,
};

/// Block that was validated and accepted
#[derive(Debug, Clone)]
Expand Down Expand Up @@ -595,6 +598,7 @@ mod valid {
(params.sumeragi().max_clock_drift(), params.transaction)
};

let mut wasm_cache = WasmCache::new();
block
.transactions()
// TODO: Unnecessary clone?
Expand All @@ -617,13 +621,13 @@ mod valid {
}?;

if error.is_some() {
match state_block.validate(tx) {
match state_block.validate(tx, &mut wasm_cache) {
Err(rejected_transaction) => Ok(rejected_transaction),
Ok(_) => Err(TransactionValidationError::RejectedIsValid),
}?;
} else {
state_block
.validate(tx)
.validate(tx, &mut wasm_cache)
.map_err(|(_tx, error)| TransactionValidationError::NotValid(error))?;
}

Expand Down
19 changes: 9 additions & 10 deletions crates/iroha_core/src/executor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ use serde::{
};

use crate::{
smartcontracts::{wasm, Execute as _},
smartcontracts::{wasm, wasm::cache::WasmCache, Execute as _},
state::{deserialize::WasmSeed, StateReadOnly, StateTransaction},
WorldReadOnly as _,
};
Expand Down Expand Up @@ -122,6 +122,7 @@ impl Executor {
state_transaction: &mut StateTransaction<'_, '_>,
authority: &AccountId,
transaction: SignedTransaction,
wasm_cache: &mut WasmCache<'_, '_, '_>,
) -> Result<(), ValidationFail> {
trace!("Running transaction validation");

Expand All @@ -140,18 +141,16 @@ impl Executor {
Ok(())
}
Self::UserProvided(loaded_executor) => {
let runtime =
wasm::RuntimeBuilder::<wasm::state::executor::ValidateTransaction>::new()
.with_engine(state_transaction.engine.clone()) // Cloning engine is cheap, see [`wasmtime::Engine`] docs
.with_config(state_transaction.world.parameters().executor)
.build()?;

runtime.execute_executor_validate_transaction(
let wasm_cache = WasmCache::change_lifetime(wasm_cache);
let mut runtime =
wasm_cache.create_runtime_cached(state_transaction, &loaded_executor.module)?;
let result = runtime.execute_executor_validate_transaction(
state_transaction,
authority,
&loaded_executor.module,
transaction,
)?
)?;
wasm_cache.save_cached_runtime(runtime);
result
}
}
}
Expand Down
169 changes: 140 additions & 29 deletions crates/iroha_core/src/smartcontracts/wasm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,16 +21,19 @@ use iroha_logger::debug;
use iroha_logger::{error_span as wasm_log_span, prelude::tracing::Span};
use iroha_wasm_codec::{self as codec, WasmUsize};
use wasmtime::{
Caller, Config as WasmtimeConfig, Engine, Linker, Module, Store, StoreLimits,
Caller, Config as WasmtimeConfig, Engine, Instance, Linker, Module, Store, StoreLimits,
StoreLimitsBuilder, TypedFunc,
};

use crate::{
query::store::LiveQueryStoreHandle,
smartcontracts::{query::ValidQueryRequest, Execute},
smartcontracts::{query::ValidQueryRequest, wasm::state::CommonState, Execute},
state::{StateReadOnly, StateTransaction, WorldReadOnly},
};

/// Cache for WASM Runtime
pub mod cache;

/// Name of the exported memory
const WASM_MEMORY: &str = "memory";
const WASM_MODULE: &str = "iroha";
Expand Down Expand Up @@ -538,7 +541,9 @@ pub mod state {
use super::*;

/// State for executing `validate_transaction()` entrypoint
pub type ValidateTransaction<'wrld, 'block, 'state> = CommonState<
pub type ValidateTransaction<'wrld, 'block, 'state> =
Option<ValidateTransactionInner<'wrld, 'block, 'state>>;
type ValidateTransactionInner<'wrld, 'block, 'state> = CommonState<
chain_state::WithMut<'wrld, 'block, 'state>,
specific::executor::ValidateTransaction,
>;
Expand Down Expand Up @@ -572,7 +577,7 @@ pub mod state {
}

impl_blank_validate_operations!(
ValidateTransaction<'_, '_, '_>,
ValidateTransactionInner<'_, '_, '_>,
ValidateInstruction<'_, '_, '_>,
Migrate<'_, '_, '_>,
);
Expand All @@ -596,6 +601,14 @@ pub struct Runtime<S> {
config: Config,
}

/// `Runtime` with instantiated module.
/// Needed to reuse `instance` for multiple transactions during validation.
pub struct RuntimeFull<S> {
runtime: Runtime<S>,
store: Store<S>,
instance: Instance,
}

impl<S> Runtime<S> {
fn get_memory(caller: &mut impl GetExport) -> Result<wasmtime::Memory, ExportError> {
caller
Expand Down Expand Up @@ -749,6 +762,17 @@ impl<W, S> Runtime<state::CommonState<W, S>> {
}
}

impl<W, S> Runtime<Option<state::CommonState<W, S>>> {
#[codec::wrap]
fn log(
(log_level, msg): (u8, String),
state: &Option<state::CommonState<W, S>>,
) -> Result<(), WasmtimeError> {
let state = state.as_ref().unwrap();
Runtime::<state::CommonState<W, S>>::__log_inner((log_level, msg), state)
}
}

impl<W: state::chain_state::ConstState, S> Runtime<state::CommonState<W, S>> {
fn execute_executor_validate_internal(
&self,
Expand All @@ -759,31 +783,77 @@ impl<W: state::chain_state::ConstState, S> Runtime<state::CommonState<W, S>> {
let mut store = self.create_store(state);
let instance = self.instantiate_module(module, &mut store)?;

let validate_fn = Self::get_typed_func(&instance, &mut store, validate_fn_name)?;
let validation_res =
execute_executor_validate_part1(&mut store, &instance, validate_fn_name)?;

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

Ok(validation_res)
}
}

impl<W: state::chain_state::ConstState, S> RuntimeFull<Option<state::CommonState<W, S>>> {
fn execute_executor_validate_internal(
&mut self,
state: state::CommonState<W, S>,
validate_fn_name: &'static str,
) -> Result<executor::Result> {
self.set_store_state(state);

let memory =
Self::get_memory(&mut (&instance, &mut store)).expect("Checked at instantiation step");
let dealloc_fn =
Self::get_typed_func(&instance, &mut store, import::SMART_CONTRACT_DEALLOC)
.expect("Checked at instantiation step");
let validation_res =
codec::decode_with_length_prefix_from_memory(&memory, &dealloc_fn, &mut store, offset)
.map_err(Error::Decode)?;
execute_executor_validate_part1(&mut self.store, &self.instance, validate_fn_name)?;

let mut state = store.into_data();
let executed_queries = state.take_executed_queries();
forget_all_executed_queries(
state.state.state().borrow().query_handle(),
executed_queries,
);
let state =
self.store.data_mut().take().expect(
"Store data was set at the beginning of execute_executor_validate_internal",
);
execute_executor_validate_part2(state);

Ok(validation_res)
}

fn set_store_state(&mut self, state: CommonState<W, S>) {
*self.store.data_mut() = Some(state);

self.store
.limiter(|s| &mut s.as_mut().unwrap().store_limits);

// Need to set fuel again for each transaction since store is shared across transactions
self.store
.set_fuel(self.runtime.config.fuel.get())
.expect("Fuel consumption is enabled");
}
}

fn execute_executor_validate_part1<S>(
store: &mut Store<S>,
instance: &Instance,
validate_fn_name: &'static str,
) -> Result<executor::Result> {
let validate_fn = Runtime::get_typed_func(instance, &mut *store, validate_fn_name)?;

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

let memory = Runtime::<S>::get_memory(&mut (instance, &mut *store))
.expect("Checked at instantiation step");
let dealloc_fn = Runtime::get_typed_func(instance, &mut *store, import::SMART_CONTRACT_DEALLOC)
.expect("Checked at instantiation step");
codec::decode_with_length_prefix_from_memory(&memory, &dealloc_fn, &mut *store, offset)
.map_err(Error::Decode)
}

fn execute_executor_validate_part2<W: state::chain_state::ConstState, S>(
mut state: state::CommonState<W, S>,
) {
let executed_queries = state.take_executed_queries();
forget_all_executed_queries(
state.state.state().borrow().query_handle(),
executed_queries,
);
}

impl<W, S> Runtime<state::CommonState<W, S>>
Expand Down Expand Up @@ -1074,6 +1144,46 @@ where
}
}

impl<'wrld, 'block, 'state, R, S>
import::traits::ExecuteOperations<
Option<state::CommonState<state::chain_state::WithMut<'wrld, 'block, 'state>, S>>,
> for R
where
R: ExecuteOperationsAsExecutorMut<
Option<state::CommonState<state::chain_state::WithMut<'wrld, 'block, 'state>, S>>,
>,
state::CommonState<state::chain_state::WithMut<'wrld, 'block, 'state>, S>:
state::ValidateQueryOperation,
{
#[codec::wrap]
fn execute_query(
query_request: QueryRequest,
state: &mut Option<
state::CommonState<state::chain_state::WithMut<'wrld, 'block, 'state>, S>,
>,
) -> Result<QueryResponse, ValidationFail> {
debug!(?query_request, "Executing as executor");

let state = state.as_mut().unwrap();
Runtime::default_execute_query(query_request, state)
}

#[codec::wrap]
fn execute_instruction(
instruction: InstructionBox,
state: &mut Option<
state::CommonState<state::chain_state::WithMut<'wrld, 'block, 'state>, S>,
>,
) -> Result<(), ValidationFail> {
debug!(%instruction, "Executing as executor");

let state = state.as_mut().unwrap();
instruction
.execute(&state.authority.clone(), state.state.0)
.map_err(Into::into)
}
}

/// Marker trait to auto-implement [`import_traits::SetExecutorDataModel`] for a concrete [`Runtime`].
///
/// Useful because *Executor* exposes more entrypoints than just `migrate()` which is the
Expand All @@ -1096,7 +1206,9 @@ where
}
}

impl<'wrld, 'block, 'state> Runtime<state::executor::ValidateTransaction<'wrld, 'block, 'state>> {
impl<'wrld, 'block, 'state>
RuntimeFull<state::executor::ValidateTransaction<'wrld, 'block, 'state>>
{
/// Execute `validate_transaction()` entrypoint of the given module of runtime executor
///
/// # Errors
Expand All @@ -1106,19 +1218,17 @@ impl<'wrld, 'block, 'state> Runtime<state::executor::ValidateTransaction<'wrld,
/// - if the execution of the smartcontract fails
/// - if unable to decode [`executor::Result`]
pub fn execute_executor_validate_transaction(
&self,
&mut self,
state_transaction: &'wrld mut StateTransaction<'block, 'state>,
authority: &AccountId,
module: &wasmtime::Module,
transaction: SignedTransaction,
) -> Result<executor::Result> {
let span = wasm_log_span!("Running `validate_transaction()`");

self.execute_executor_validate_internal(
module,
state::executor::ValidateTransaction::new(
CommonState::new(
authority.clone(),
self.config,
self.runtime.config,
span,
state::chain_state::WithMut(state_transaction),
state::specific::executor::ValidateTransaction::new(transaction),
Expand Down Expand Up @@ -1148,6 +1258,7 @@ impl<'wrld, 'block, 'state>
fn get_validate_transaction_payload(
state: &state::executor::ValidateTransaction<'wrld, 'block, 'state>,
) -> Validate<SignedTransaction> {
let state = state.as_ref().unwrap();
Validate {
authority: state.authority.clone(),
block_height: state.state.0.height() as u64,
Expand Down Expand Up @@ -1513,7 +1624,7 @@ macro_rules! create_imports {
$linker.func_wrap(
WASM_MODULE,
export::LOG,
|caller: ::wasmtime::Caller<$ty>, offset, len| Runtime::log(caller, offset, len),
|caller: ::wasmtime::Caller<$ty>, offset, len| Runtime::<$ty>::log(caller, offset, len),
)
.and_then(|l| {
l.func_wrap(
Expand Down
Loading

0 comments on commit 6849085

Please sign in to comment.