Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

feat(gcli): introduce metadata executor #3366

Merged
merged 27 commits into from
Oct 26, 2023
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
9ebe52a
feat(gcli): introduce metadata exectuor
clearloop Sep 27, 2023
bd06a1a
feat(gcli): introduce host functions for the metadata executor
clearloop Sep 27, 2023
d20b210
chore(gcli): use wasmi with std
clearloop Sep 27, 2023
94dbdd6
feat(gcli): implement gr_reply in metadata executor
clearloop Sep 27, 2023
6adf16d
chore(gcli): make clippy happy
clearloop Sep 27, 2023
6fde222
Merge branch 'master' into cl/issue-3222
clearloop Sep 27, 2023
be3de93
feat(gcli): clean unwraps
clearloop Sep 27, 2023
e56e55c
Merge branch 'master' into cl/issue-3222
clearloop Sep 27, 2023
7892855
chore(gcli): clean stdouts
clearloop Sep 27, 2023
a33afee
chore(gcli): introduce gr_block_height in metadata executor
clearloop Sep 28, 2023
4f9f357
chore(gcli): address comments
clearloop Sep 30, 2023
608b6be
chore(gcli): address comments
clearloop Oct 6, 2023
ed0edcb
chore(gcli): log panicking message
clearloop Oct 6, 2023
1d0a396
docs(gcli): explain why some of the host functions are missing logics
clearloop Oct 6, 2023
1329581
chore(gcli): get back block_timestamp and block_height
clearloop Oct 6, 2023
2b8a9d4
Merge branch 'master' into cl/issue-3222
clearloop Oct 12, 2023
3ebc93b
feat(gcli): introduce macro for declaring host functions
clearloop Oct 16, 2023
18cdf6b
feat(gcli): complete all imports
clearloop Oct 18, 2023
fd13f9f
Merge branch 'master' into cl/issue-3222
clearloop Oct 18, 2023
b5f15ef
chore(gcli): wrap results for method free
clearloop Oct 19, 2023
ad94391
chore(gcli): wrap method call_metadata
clearloop Oct 19, 2023
6d9ab5a
chore(gcli): parse metadata from local node
clearloop Oct 19, 2023
c44843b
feat(gcli): add back vara-runtime
clearloop Oct 20, 2023
e152f65
chore(check): update Cargo.lock
clearloop Oct 20, 2023
e123edb
feat(gcli): introduce function iterator for metadata executor
clearloop Oct 24, 2023
e0c0404
Merge branch 'master' into cl/issue-3222
clearloop Oct 24, 2023
b07464d
ci(clippy): make clippy happy
clearloop Oct 24, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.lock

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

1 change: 1 addition & 0 deletions gcli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ reqwest = { workspace = true, default-features = false, features = [ "json", "ru
etc.workspace = true
sp-io = { workspace = true, features = [ "std" ] }
sp-core = { workspace = true, features = [ "std" ] }
wasmi = { workspace = true, features = ["std"] }
vara-runtime = { workspace = true, features = [ "std", "dev" ] }

[dev-dependencies]
Expand Down
218 changes: 218 additions & 0 deletions gcli/src/meta/executor.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,218 @@
// This file is part of Gear.
//
// Copyright (C) 2021-2023 Gear Technologies Inc.
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.

use anyhow::{anyhow, Result};
use wasmi::{AsContextMut, Engine, Extern, Linker, Memory, MemoryType, Module, Store};

const PAGE_STORAGE_PREFIX: [u8; 32] = *b"gcligcligcligcligcligcligcligcli";

/// HostState for the WASM executor
#[derive(Default)]
pub struct HostState {
pub msg: Vec<u8>,
pub timestamp: u64,
ark0f marked this conversation as resolved.
Show resolved Hide resolved
pub height: u64,
clearloop marked this conversation as resolved.
Show resolved Hide resolved
}

/// Executes the WASM code.
pub fn execute(wasm: &[u8], method: &str) -> Result<Vec<u8>> {
assert!(gear_lazy_pages_interface::try_to_enable_lazy_pages(
PAGE_STORAGE_PREFIX
));

let engine = Engine::default();
let module = Module::new(&engine, wasm).unwrap();

let mut store = Store::new(&engine, HostState::default());
let mut linker = <Linker<HostState>>::new();

// Execution environment
{
let memory = Memory::new(store.as_context_mut(), MemoryType::new(256, None)).unwrap();
linker.define("env", "memory", Extern::Memory(memory))?;
breathx marked this conversation as resolved.
Show resolved Hide resolved
linker.define("env", "gr_read", funcs::gr_read(&mut store, memory))?;
linker.define("env", "alloc", funcs::alloc(&mut store, memory))?;
linker.define("env", "free", funcs::free(&mut store))?;
linker.define("env", "gr_size", funcs::gr_size(&mut store, memory))?;
linker.define("env", "gr_reply", funcs::gr_reply(&mut store, memory))?;
clearloop marked this conversation as resolved.
Show resolved Hide resolved
linker.define("env", "gr_panic", funcs::gr_panic(&mut store, memory))?;
linker.define("env", "gr_oom_panic", funcs::gr_oom_panic(&mut store))?;
linker.define("env", "gr_out_of_gas", funcs::gr_out_of_gas(&mut store))?;
linker.define("env", "gr_block_height", funcs::gr_block_height(&mut store))?;
linker.define(
"env",
"gr_block_timestamp",
funcs::gr_block_timestamp(&mut store),
)?;
}

let instance = linker
.instantiate(&mut store, &module)
.unwrap()
.start(&mut store)?;

let metadata = instance
.get_export(&store, method)
.and_then(Extern::into_func)
.ok_or_else(|| anyhow!("could not find function \"{}\"", method))?
.typed::<(), (), _>(&mut store)?;

metadata.call(&mut store, ())?;
Ok(store.state().msg.clone())
}

mod funcs {
use super::HostState;
use wasmi::{
core::{memory_units::Pages, Trap, TrapCode},
AsContext, AsContextMut, Caller, Extern, Func, Memory, Store,
};

pub fn alloc(store: &mut Store<HostState>, memory: Memory) -> Extern {
Extern::Func(Func::wrap(
store,
move |mut caller: Caller<'_, HostState>, pages: i32| {
memory
.clone()
.grow(caller.as_context_mut(), Pages(pages as usize))
.map_or_else(
|err| {
log::error!("{err:?}");
u32::MAX as i32
},
|pages| pages.0 as i32,
)
},
))
}

pub fn free(ctx: impl AsContextMut) -> Extern {
Extern::Func(Func::wrap(ctx, |_: i32| 0))
}

pub fn gr_panic(ctx: &mut Store<HostState>, _memory: Memory) -> Extern {
Extern::Func(Func::wrap(
ctx,
move |mut _caller: Caller<'_, HostState>, _ptr: u32, _len: i32| {},
ark0f marked this conversation as resolved.
Show resolved Hide resolved
))
}

pub fn gr_oom_panic(ctx: impl AsContextMut) -> Extern {
Extern::Func(Func::wrap(ctx, || {
log::error!("OOM panic occurred");
Ok(())
}))
}

pub fn gr_read(ctx: &mut Store<HostState>, memory: Memory) -> Extern {
Extern::Func(Func::wrap(
ctx,
move |mut caller: Caller<'_, HostState>, at: u32, len: i32, buff: i32, err: i32| {
let (at, len, buff, err) = (at as _, len as _, buff as _, err as _);

let msg = &caller.host_data().msg;
let mut payload = vec![0; len];
if at + len <= msg.len() {
payload.copy_from_slice(&msg[at..(at + len)]);
clearloop marked this conversation as resolved.
Show resolved Hide resolved
} else {
return Err(Trap::Code(TrapCode::MemoryAccessOutOfBounds));
}

let len: u32 = memory
.clone()
.write(caller.as_context_mut(), buff, &payload)
.map_err(|e| log::error!("{:?}", e))
.is_err()
.into();

memory
.clone()
.write(caller.as_context_mut(), err, &len.to_le_bytes())
.map_err(|e| {
log::error!("{:?}", e);
Trap::Code(TrapCode::MemoryAccessOutOfBounds)
})?;

Ok(())
},
))
}

pub fn gr_reply(ctx: &mut Store<HostState>, memory: Memory) -> Extern {
Extern::Func(Func::wrap(
ctx,
move |mut caller: Caller<'_, HostState>, ptr: u32, len: i32, _value: i32, _err: i32| {
let mut result = vec![0; len as usize];

memory
.read(caller.as_context(), ptr as usize, &mut result)
.map_err(|e| {
log::error!("{:?}", e);
Trap::Code(TrapCode::MemoryAccessOutOfBounds)
})?;
caller.host_data_mut().msg = result;

Ok(())
},
))
}

pub fn gr_size(ctx: &mut Store<HostState>, memory: Memory) -> Extern {
Extern::Func(Func::wrap(
ctx,
move |mut caller: Caller<'_, HostState>, size_ptr: u32| {
let size = caller.host_data().msg.len() as u32;

memory
.clone()
.write(
caller.as_context_mut(),
size_ptr as usize,
&size.to_le_bytes(),
)
.map_err(|e| {
log::error!("{:?}", e);
Trap::Code(TrapCode::MemoryAccessOutOfBounds)
})?;

Ok(())
},
))
}

pub fn gr_out_of_gas(ctx: &mut Store<HostState>) -> Extern {
Extern::Func(Func::wrap(
ctx,
move |_caller: Caller<'_, HostState>| Ok(()),
))
}

pub fn gr_block_height(ctx: &mut Store<HostState>) -> Extern {
Extern::Func(Func::wrap(
ctx,
move |_caller: Caller<'_, HostState>, _height: u32| Ok(()),
))
}

pub fn gr_block_timestamp(ctx: &mut Store<HostState>) -> Extern {
Extern::Func(Func::wrap(
ctx,
move |_caller: Caller<'_, HostState>, _timestamp: u32| Ok(()),
))
}
}
31 changes: 4 additions & 27 deletions gcli/src/meta/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,13 @@
// along with this program. If not, see <https://www.gnu.org/licenses/>.

//! Program metadata parser
mod executor;
mod registry;
#[cfg(test)]
mod tests;

use crate::result::{Error, Result};
use core_processor::configs::BlockInfo;
use gear_core::code::{Code, CodeAndId, InstrumentedCode, InstrumentedCodeAndId, TryNewCodeConfig};
use gear_core::code::{Code, CodeAndId, InstrumentedCodeAndId, TryNewCodeConfig};
use gmeta::{MetadataRepr, MetawasmData, TypesRepr};
use registry::LocalRegistry as _;
use scale_info::{scale::Decode, PortableRegistry};
Expand Down Expand Up @@ -73,8 +73,6 @@ pub enum Meta {
}

impl Meta {
const PAGE_STORAGE_PREFIX: [u8; 32] = *b"gcligcligcligcligcligcligcligcli";

fn format_metadata(meta: &MetadataRepr, fmt: &mut fmt::Formatter) -> fmt::Result {
let registry =
PortableRegistry::decode(&mut meta.registry.as_ref()).map_err(|_| fmt::Error)?;
Expand Down Expand Up @@ -107,26 +105,6 @@ impl Meta {
display.finish()
}

/// Execute meta method.
fn execute(wasm: InstrumentedCode, method: &str) -> Result<Vec<u8>> {
assert!(gear_lazy_pages_interface::try_to_enable_lazy_pages(
Self::PAGE_STORAGE_PREFIX
));

sp_io::TestExternalities::default().execute_with(|| {
core_processor::informational::execute_for_reply::<core_processor::Ext, String>(
method.into(),
wasm,
None,
None,
Default::default(),
u64::MAX,
BlockInfo::default(),
)
.map_err(Error::WasmExecution)
})
}

/// Decode metawasm from wasm binary.
pub fn decode_wasm(wasm: &[u8]) -> Result<Self> {
let code = Code::try_new_mock_const_or_no_rules(
Expand All @@ -135,10 +113,9 @@ impl Meta {
TryNewCodeConfig::new_no_exports_check(),
)?;
let (code, _) = InstrumentedCodeAndId::from(CodeAndId::new(code)).into_parts();
let result = executor::execute(code.code(), "metadata")?;

Ok(Self::Wasm(MetawasmData::decode(
&mut Self::execute(code, "metadata")?.as_ref(),
)?))
Ok(Self::Wasm(MetawasmData::decode(&mut result.as_ref())?))
}

/// Decode metadata from hex bytes.
Expand Down
2 changes: 2 additions & 0 deletions gcli/src/result.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,8 @@ pub enum Error {
InvalidWasm,
#[error("Wasm execution error {0}")]
WasmExecution(String),
#[error("Wasmi execution error {0}")]
Wasmi(wasmi::Error),
#[error(transparent)]
Etc(#[from] etc::Error),
#[error("Metadata parsing error {0:?}")]
Expand Down