Skip to content

(chore): update pvm and improve execution error kind #44

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

Merged
merged 2 commits into from
Oct 15, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 7 additions & 5 deletions Cargo.lock

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

8 changes: 4 additions & 4 deletions poc/guests/Cargo.lock

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

2 changes: 1 addition & 1 deletion poc/runtime/src/xcq.rs
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ mod tests {

#[test]
fn call_fungibles_hex() {
let raw_blob = include_bytes!("../../../output/poc-guest-test-xcq-api.polkavm");
let raw_blob = include_bytes!("../../../output/poc-guest-sum-balance.polkavm");
let alice_public = sr25519::Pair::from_string("//Alice", None)
.expect("static values are valid; qed")
.public();
Expand Down
2 changes: 1 addition & 1 deletion vendor/polkavm
Submodule polkavm updated 278 files
71 changes: 39 additions & 32 deletions xcq-executor/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,44 +6,49 @@ pub use alloc::vec::Vec;
pub use polkavm::{Caller, Config, Engine, Linker, Module, ProgramBlob};

pub trait XcqExecutorContext {
fn register_host_functions<T>(&mut self, linker: &mut Linker<T>);
type UserData;
type UserError;
fn register_host_functions(&mut self, linker: &mut Linker<Self::UserData, Self::UserError>);
fn data(&mut self) -> &mut Self::UserData;
}

pub struct XcqExecutor<Ctx: XcqExecutorContext> {
engine: Engine,
linker: Linker<Ctx>,
linker: Linker<Ctx::UserData, Ctx::UserError>,
context: Ctx,
}

#[derive(Debug)]
pub enum XcqExecutorError {
ProgramParseError(polkavm::ProgramParseError),
PrepareError(polkavm::Error),
ExecutionError(polkavm::ExecutionError<polkavm::Error>),
pub enum XcqExecutorError<UserError> {
MemoryAllocationError,
MemoryAccessError(polkavm::MemoryAccessError),
PolkavmError(polkavm::Error),
CallError(polkavm::CallError<UserError>),
}

impl From<polkavm::ProgramParseError> for XcqExecutorError {
fn from(err: polkavm::ProgramParseError) -> Self {
Self::ProgramParseError(err)
impl<UserError> From<polkavm::MemoryAccessError> for XcqExecutorError<UserError> {
fn from(err: polkavm::MemoryAccessError) -> Self {
Self::MemoryAccessError(err)
}
}

impl From<polkavm::Error> for XcqExecutorError {
impl<UserError> From<polkavm::Error> for XcqExecutorError<UserError> {
fn from(err: polkavm::Error) -> Self {
Self::PrepareError(err)
Self::PolkavmError(err)
}
}

impl From<polkavm::ExecutionError<polkavm::Error>> for XcqExecutorError {
fn from(err: polkavm::ExecutionError<polkavm::Error>) -> Self {
Self::ExecutionError(err)
impl<UserError> From<polkavm::CallError<UserError>> for XcqExecutorError<UserError> {
fn from(err: polkavm::CallError<UserError>) -> Self {
Self::CallError(err)
}
}

impl<Ctx: XcqExecutorContext> XcqExecutor<Ctx> {
pub fn new(config: Config, mut context: Ctx) -> Self {
let engine = Engine::new(&config).unwrap();
let mut linker = Linker::<Ctx>::new(&engine);
let mut linker = Linker::<Ctx::UserData, Ctx::UserError>::new();
// Register user-defined host functions
context.register_host_functions(&mut linker);
Self {
engine,
Expand All @@ -55,39 +60,41 @@ impl<Ctx: XcqExecutorContext> XcqExecutor<Ctx> {
pub fn execute(
&mut self,
raw_blob: &[u8],
method: impl AsRef<[u8]>,
method: &str,
input: &[u8],
) -> Result<Vec<u8>, XcqExecutorError> {
let blob = ProgramBlob::parse(raw_blob.into())?;
) -> Result<Vec<u8>, XcqExecutorError<Ctx::UserError>> {
let blob = ProgramBlob::parse(raw_blob.into()).map_err(polkavm::Error::from)?;
let module = Module::from_blob(&self.engine, &Default::default(), blob)?;
let instance_pre = self.linker.instantiate_pre(&module)?;
let instance = instance_pre.instantiate()?;
let mut instance = instance_pre.instantiate()?;

let input_ptr = if !input.is_empty() {
// First sbrk call to get the start of the heap
let start_ptr = instance.sbrk(0)?.expect("should not fail because we don't allocate");
let start_ptr = instance
.sbrk(0)
.expect("should not fail because we don't allocate")
.expect("should not fail because we don't allocate");
// Second sbrk call to check the allocation doesn't exceed the heap limit
if instance.sbrk(input.len() as u32)?.is_none() {
return Err(XcqExecutorError::ExecutionError(polkavm::ExecutionError::Error(
polkavm::Error::from("cannot srk enough memory"),
)));
};
// Args are passed via guest's heap
instance
.write_memory(start_ptr, input)
.map_err(|e| XcqExecutorError::ExecutionError(polkavm::ExecutionError::Trap(e)))?;
.sbrk(input.len() as u32)
.map_err(|_| XcqExecutorError::MemoryAllocationError)?
.ok_or(XcqExecutorError::MemoryAllocationError)?;
// Args are passed via guest's heap
instance.write_memory(start_ptr, input)?;
start_ptr
} else {
0
};
tracing::info!("(passing args): input_ptr: {}, input_len: {:?}", input_ptr, input.len());

let res = instance.call_typed::<(u32, u32), u64>(&mut self.context, method, (input_ptr, input.len() as u32))?;
let res = instance.call_typed_and_get_result::<u64, (u32, u32)>(
self.context.data(),
method,
(input_ptr, input.len() as u32),
)?;
let res_size = (res >> 32) as u32;
let res_ptr = (res & 0xffffffff) as u32;
let result = instance
.read_memory_into_vec(res_ptr, res_size)
.map_err(|e| XcqExecutorError::ExecutionError(polkavm::ExecutionError::Trap(e)))?;
let result = instance.read_memory(res_ptr, res_size)?;
Ok(result)
}
}
1 change: 1 addition & 0 deletions xcq-extension/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ version.workspace = true

[dependencies]
parity-scale-codec = { workspace = true }
polkavm = { workspace = true }
scale-info = { workspace = true }
xcq-executor = { workspace = true }
fortuples = { workspace = true }
Expand Down
9 changes: 8 additions & 1 deletion xcq-extension/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,15 @@ use parity_scale_codec::Error as CodeCError;
#[derive(Debug)]
pub enum ExtensionError {
PermissionError,
PolkavmError,
MemoryAllocationError,
MemoryAccessError(polkavm::MemoryAccessError),
DecodeError(CodeCError),
DispatchError(DispatchError),
UnsupportedExtension,
}

impl From<polkavm::MemoryAccessError> for ExtensionError {
fn from(e: polkavm::MemoryAccessError) -> Self {
Self::MemoryAccessError(e)
}
}
107 changes: 57 additions & 50 deletions xcq-extension/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,77 +40,84 @@ pub trait CallDataTuple {

struct Context<C: CallDataTuple, P: PermController> {
invoke_source: InvokeSource,
user_data: (),
_marker: PhantomData<(C, P)>,
}

impl<C: CallDataTuple, P: PermController> Context<C, P> {
pub fn new(invoke_source: InvokeSource) -> Self {
Self {
invoke_source,
user_data: (),
_marker: PhantomData,
}
}
}

impl<C: CallDataTuple, P: PermController> XcqExecutorContext for Context<C, P> {
fn register_host_functions<T>(&mut self, linker: &mut Linker<T>) {
type UserData = ();
type UserError = ExtensionError;
fn data(&mut self) -> &mut Self::UserData {
&mut self.user_data
}
fn register_host_functions(&mut self, linker: &mut Linker<Self::UserData, Self::UserError>) {
let invoke_source = self.invoke_source;
linker
.func_wrap(
.define_typed(
"host_call",
move |mut caller: Caller<_>, extension_id: u64, call_ptr: u32, call_len: u32| -> u64 {
move |caller: Caller<'_, Self::UserData>,
extension_id: u64,
call_ptr: u32,
call_len: u32|
-> Result<u64, ExtensionError> {
// useful closure to handle early return
let mut func_with_result = || -> Result<u64, ExtensionError> {
let call_bytes = caller
.read_memory_into_vec(call_ptr, call_len)
.map_err(|_| ExtensionError::PolkavmError)?;
tracing::info!("(host call): call_ptr: {}, call_len: {:?}", call_ptr, call_len);
tracing::info!(
"(host call): extension_id: {}, call_bytes: {:?}",
extension_id,
call_bytes
);
if !P::is_allowed(extension_id, &call_bytes, invoke_source) {
return Err(ExtensionError::PermissionError);
}
let res_bytes = C::dispatch(extension_id, &call_bytes)?;
tracing::debug!("(host call): res_bytes: {:?}", res_bytes);
let res_bytes_len = res_bytes.len();
let res_ptr = caller.sbrk(0).ok_or(ExtensionError::PolkavmError)?;
if caller.sbrk(res_bytes_len as u32).is_none() {
return Err(ExtensionError::PolkavmError);
}
caller
.write_memory(res_ptr, &res_bytes)
.map_err(|_| ExtensionError::PolkavmError)?;
Ok(((res_bytes_len as u64) << 32) | (res_ptr as u64))
};
let result = func_with_result();
tracing::trace!("(host call): result: {:?}", result);
result.unwrap_or(0)
let call_bytes = caller.instance.read_memory(call_ptr, call_len)?;
tracing::info!("(host call): call_ptr: {}, call_len: {:?}", call_ptr, call_len);
tracing::info!(
"(host call): extension_id: {}, call_bytes: {:?}",
extension_id,
call_bytes
);
if !P::is_allowed(extension_id, &call_bytes, invoke_source) {
return Err(ExtensionError::PermissionError);
}
let res_bytes = C::dispatch(extension_id, &call_bytes)?;
tracing::debug!("(host call): res_bytes: {:?}", res_bytes);
let res_bytes_len = res_bytes.len();
let res_ptr = caller
.instance
.sbrk(0)
.map_err(|_| ExtensionError::MemoryAllocationError)?
.ok_or(ExtensionError::MemoryAllocationError)?;
caller
.instance
.sbrk(res_bytes_len as u32)
.map_err(|_| ExtensionError::MemoryAllocationError)?
.ok_or(ExtensionError::MemoryAllocationError)?;
caller.instance.write_memory(res_ptr, &res_bytes)?;
Ok(((res_bytes_len as u64) << 32) | (res_ptr as u64))
},
)
.unwrap();
linker
.func_wrap(
.define_typed(
"return_ty",
move |mut caller: Caller<_>, extension_id: u64, call_index: u32| -> u64 {
let mut func_with_result = || -> Result<u64, ExtensionError> {
let res_bytes = C::return_ty(extension_id, call_index)?;
tracing::debug!("(host call): res_bytes: {:?}", res_bytes);
let res_bytes_len = res_bytes.len();
let res_ptr = caller.sbrk(0).ok_or(ExtensionError::PolkavmError)?;
if caller.sbrk(res_bytes_len as u32).is_none() {
return Err(ExtensionError::PolkavmError);
}
caller
.write_memory(res_ptr, &res_bytes)
.map_err(|_| ExtensionError::PolkavmError)?;
Ok(((res_bytes_len as u64) << 32) | (res_ptr as u64))
};
let result = func_with_result();
tracing::trace!("(host call): result: {:?}", result);
result.unwrap_or(0)
move |caller: Caller<_>, extension_id: u64, call_index: u32| -> Result<u64, ExtensionError> {
let res_bytes = C::return_ty(extension_id, call_index)?;
tracing::debug!("(host call): res_bytes: {:?}", res_bytes);
let res_bytes_len = res_bytes.len();
let res_ptr = caller
.instance
.sbrk(0)
.map_err(|_| ExtensionError::MemoryAllocationError)?
.ok_or(ExtensionError::MemoryAllocationError)?;
caller
.instance
.sbrk(res_bytes_len as u32)
.map_err(|_| ExtensionError::MemoryAllocationError)?
.ok_or(ExtensionError::MemoryAllocationError)?;
caller.instance.write_memory(res_ptr, &res_bytes)?;
Ok(((res_bytes_len as u64) << 32) | (res_ptr as u64))
},
)
.unwrap();
Expand All @@ -131,7 +138,7 @@ impl<C: CallDataTuple, P: PermController> ExtensionsExecutor<C, P> {
#[allow(dead_code)]
pub fn execute_method<G: Guest, I: Input>(&mut self, guest: G, input: I) -> XcqResult {
self.executor
.execute(guest.program(), input.method(), input.args())
.execute(guest.program(), &input.method(), input.args())
.map_err(|e| format!("{:?}", e))
}
}