Skip to content

Commit

Permalink
feat: prototype extension executor and extension trait
Browse files Browse the repository at this point in the history
  • Loading branch information
indirection42 committed May 29, 2024
1 parent 3bbcb6d commit 0867526
Show file tree
Hide file tree
Showing 10 changed files with 190 additions and 84 deletions.
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.

9 changes: 7 additions & 2 deletions poc/executor/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,12 @@ impl<Ctx: XcqExecutorContext> XcqExecutor<Ctx> {
}
}

pub fn execute(&mut self, raw_blob: &[u8], input: &[u8]) -> Result<Vec<u8>, XcqExecutorError> {
pub fn execute(
&mut self,
raw_blob: &[u8],
method: impl AsRef<[u8]>,
input: &[u8],
) -> Result<Vec<u8>, XcqExecutorError> {
let blob = ProgramBlob::parse(raw_blob.into())?;
let module = Module::from_blob(&self.engine, &Default::default(), blob)?;
let instance_pre = self.linker.instantiate_pre(&module)?;
Expand All @@ -71,7 +76,7 @@ impl<Ctx: XcqExecutorContext> XcqExecutor<Ctx> {
0
};

let res = instance.call_typed::<(u32, u32), u64>(&mut self.context, "main", (input_ptr, input.len() as u32))?;
let res = instance.call_typed::<(u32, u32), u64>(&mut self.context, method, (input_ptr, input.len() as u32))?;
let res_ptr = (res >> 32) as u32;
let res_size = (res & 0xffffffff) as u32;
let result = instance
Expand Down
3 changes: 2 additions & 1 deletion poc/extensions/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@ version = "0.1.0"
edition = "2021"

[dependencies]
scale-info = { version = "2.6.0", default-features = false }
poc-executor = { path = "../executor", default-features = false }

[features]
default = ["std"]
std = ["poc-executor/std"]
std = ["scale-info/std", "poc-executor/std"]
28 changes: 28 additions & 0 deletions poc/extensions/src/extension_core.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
use super::{Extension, ExtensionTypeId};
pub trait ExtensionCore: Extension {
// ExtensionId should be generated by the macro
// It should normalize the order of methods and parameter names
const TYPE_ID: ExtensionTypeId = 0u64;
// TODO: Actual args and return values are complex types
// and we adapt them to polkavm ABI in `impl XcqExecutorContext for HostFunctions`
fn some_host_function() -> u32;
fn another_host_function() -> u32;
}

#[derive(Clone)]
struct ExtensionCoreImpl;

impl Extension for ExtensionCoreImpl {
fn methods(&self) -> Vec<String> {
vec!["some_host_function".to_string(), "another_host_function".to_string()]
}
}

impl ExtensionCore for ExtensionCoreImpl {
fn some_host_function() -> u32 {
100
}
fn another_host_function() -> u32 {
42
}
}
28 changes: 28 additions & 0 deletions poc/extensions/src/extension_fungibles.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
use super::{Extension, ExtensionTypeId};
pub trait ExtensionFungibles: Extension {
// ExtensionId should be generated by the macro
// It should normalize the order of methods and parameter names
const TYPE_ID: ExtensionTypeId = 1u64;
// TODO: Actual args and return values are complex types
// and we adapt them to polkavm ABI in `impl XcqExecutorContext for HostFunctions`
fn transfer(from: u32, to: u32, amount: u64) -> u64;
fn balance(account: u32) -> u64;
}

#[derive(Clone)]
struct ExtensionFungiblesImpl;

impl Extension for ExtensionFungiblesImpl {
fn methods(&self) -> Vec<String> {
vec!["transfer".to_string(), "balance".to_string()]
}
}

impl ExtensionFungibles for ExtensionFungiblesImpl {
fn transfer(_from: u32, _to: u32, _amount: u64) -> u64 {
unimplemented!()
}
fn balance(_account: u32) -> u64 {
unimplemented!()
}
}
40 changes: 40 additions & 0 deletions poc/extensions/src/guest.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
use super::ExtensionTypeId;
pub trait Guest {
fn type_id(&self) -> ExtensionTypeId;
fn program(&self) -> &[u8];
}

struct GuestImpl {
extension_type: ExtensionTypeId,
program: Vec<u8>,
}

impl Guest for GuestImpl {
fn type_id(&self) -> ExtensionTypeId {
self.extension_type
}
fn program(&self) -> &[u8] {
&self.program
}
}

type Method = String;

pub trait Input {
fn method(&self) -> Method;
fn args(&self) -> &[u8];
}

struct InputImpl {
method: Method,
args: Vec<u8>,
}

impl Input for InputImpl {
fn method(&self) -> Method {
self.method.clone()
}
fn args(&self) -> &[u8] {
&self.args
}
}
149 changes: 71 additions & 78 deletions poc/extensions/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,102 +1,95 @@
#![cfg_attr(not(feature = "std"), no_std)]

use poc_executor::{XcqExecutor, XcqExecutorContext};
#[cfg(not(feature = "std"))]
use scale_info::prelude::{format, string::String};
pub type XcqResponse = Vec<u8>;
pub type XcqError = String;
pub type XcqResult = Result<XcqResponse, XcqError>;

mod extension_core;
pub use extension_core::ExtensionCore;
mod extension_fungibles;
pub use extension_fungibles::ExtensionFungibles;
mod guest;
pub use guest::{Guest, Input};

mod macros;

type ExtensionTypeId = u32;
type Error = String;

// Runtime Side
// General trait for all host extension interfaces
pub trait HostInterface {
fn type_id(&self) -> ExtensionTypeId;
fn methods(&self) -> Vec<String>;
}

// Example extension trait
// Implemented by Runtime
pub trait SomeHostInterface {
type HostFunctions: XcqExecutorContext;
// TODO: should be generated automatically by macro
const EXTENSION_TYPE_ID: ExtensionTypeId;
fn some_host_function() -> XcqResult;
fn another_host_function() -> XcqResult;
}

// Guest Side
pub trait Guest {
fn type_id(&self) -> ExtensionTypeId;
type ExtensionTypeId = u64;
// General trait for all extension interfaces
pub trait Extension {
fn methods(&self) -> Vec<String>;
}

struct GuestImpl {
program: Vec<u8>,
// Aggregated struct for all extension functions that runtime supports
// Generated by some macros
pub struct HostFunctions<E1: ExtensionCore, E2: ExtensionFungibles> {
phantom: core::marker::PhantomData<(E1, E2)>,
}

impl Guest for GuestImpl {
fn type_id(&self) -> ExtensionTypeId {
unimplemented!()
}
fn methods(&self) -> Vec<String> {
unimplemented!()
// Generated by some macros
impl<E1: ExtensionCore, E2: ExtensionFungibles> XcqExecutorContext for HostFunctions<E1, E2> {
fn register_host_functions<T>(&mut self, linker: &mut poc_executor::Linker<T>) {
// As aforementioned, we may use macros to directly copy the functions impls to here
linker
.func_wrap("some_host_function", move || -> u32 {
<E1 as ExtensionCore>::some_host_function()
})
.unwrap();
linker
.func_wrap("another_host_function", || -> u32 {
<E1 as ExtensionCore>::another_host_function()
})
.unwrap();
linker
.func_wrap("transfer", move |from: u32, to: u32, amount: u64| -> u64 {
<E2 as ExtensionFungibles>::transfer(from, to, amount)
})
.unwrap();
linker
.func_wrap("balance", move |account: u32| -> u64 {
<E2 as ExtensionFungibles>::balance(account)
})
.unwrap();
}
}

type Method = String;
// Which should be implemented by the runtime
pub trait ExtensionsExecutor {
type SafeGuard: PermController;
fn register_host_interface<H: HostInterface>(&mut self, host_interface: H) -> Result<(), Error>;
fn support_extension_types() -> Result<Vec<ExtensionTypeId>, Error>;
// extension type is opaque to the runtime
// or we parse it before
fn discover_methods<G: Guest>(guest: G) -> Result<Vec<Method>, Error>;
fn execute_method<G: Guest>(&self, guest: G, method: Method, input: Vec<u8>) -> XcqResult;
}
pub trait PermController {
fn check<G: Guest>(extension: &E, method: &Method) -> Result<(), Error>;
}
// pub trait ExtensionsExecutor {
// type ExecutorType;
// fn from_extensions()->
// fn execute_method(&self, guest: Guest, input: Input) -> XcqResult;
// }

struct SimplePermController;
impl PermController for SimplePermController {
fn check<E: Extension>(extension: &E, method: &Method) -> Result<(), Error> {
unimplemented!()
}
// Expanded
// In practice, generics are generated by macros
struct ExtensionsExecutor<E1: ExtensionCore, E2: ExtensionFungibles> {
executor: XcqExecutor<HostFunctions<E1, E2>>,
}

// Mock implementation
struct ExtensionApiImpl;
impl ExtensionsExecutor for ExtensionApiImpl {
type SafeGuard = SimplePermController;
fn support_extension_types() -> Result<Vec<ExtensionTypeId>, Error> {
unimplemented!()
impl<E1: ExtensionCore, E2: ExtensionFungibles> ExtensionsExecutor<E1, E2> {
// Generated by macro
pub fn from_extensions(_extension_core: E1, _extension_fungibles: E2) -> Self {
let host_functions = HostFunctions::<E1, E2> {
phantom: core::marker::PhantomData,
};
let executor = XcqExecutor::new(Default::default(), host_functions);
Self { executor }
}
fn register_host_interface<H: HostInterface>(&mut self, host_interface: H) -> Result<(), Error> {
unimplemented!()
}
fn discover_methods<G: Guest>(guest_impl: G) -> Result<Vec<Method>, Error> {
// TODO: extension is opaque to the runtime,
// we need to use polkavm discover symbols to get the methods
unimplemented!()
}
fn execute_method<G: Guest>(&self, guest: G, method: Method, input: Vec<u8>) -> XcqResult {
// Check if the extension is supported
let extension_ids = Self::support_extension_types()?;
if !extension_ids.contains(&guest.type_id()) {
return Err("Extension not supported".to_string());
}
// Check if the method is supported
let methods = Self::discover_methods(&guest)?;
if !methods.contains(&method) {
return Err("Method not supported".to_string());
// In PoC, guest and input are opague to the runtime
// In SDK, we can make them has type
fn execute_method<G: Guest, I: Input>(&self, guest: G, input: I) -> XcqResult {
let extension_id = guest.type_id();
// In practice, these match statements should be generated by some macros
// Check if the runtime supports the extension
match extension_id {
// TODO: E1, E2 information are discarded after `HostFunctions` is generated
// How to fix it?
E1::TYPE_ID | E2::TYPE_ID => {}
_ => return Err(format!("Unsupported extension: {}", extension_id)),
}
// Check if the method pass the safe guard
Self::SafeGuard::check(&guest, &method)?;
// TODO: Execute the method
Ok(vec![])
self.executor
.execute(guest.program(), input.method(), input.args())
.map_err(|e| format!("{:?}", e))
}
}
10 changes: 10 additions & 0 deletions poc/extensions/src/perm_controller.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
pub trait PermController {
fn check<G: Guest>(extension: &E, method: &Method) -> Result<(), Error>;
}

struct SimplePermController;
impl PermController for SimplePermController {
fn check<E: Extension>(extension: &E, method: &Method) -> Result<(), Error> {
unimplemented!()
}
}
4 changes: 2 additions & 2 deletions poc/hosts/pass-custom-type/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,9 +54,9 @@ fn main() {
let config = Config::from_env().unwrap();

let mut executor: poc_executor::XcqExecutor<HostFunctions> = poc_executor::XcqExecutor::new(config, HostFunctions);
let res = executor.execute(raw_blob, &[0u8]).unwrap();
let res = executor.execute(raw_blob, "main", &[0u8]).unwrap();
println!("Result: {:?}", res);

let res = executor.execute(raw_blob, &[1u8, 40u8]).unwrap();
let res = executor.execute(raw_blob, "main", &[1u8, 40u8]).unwrap();
println!("Result: {:?}", res);
}
2 changes: 1 addition & 1 deletion poc/runtime/src/xcq.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ impl poc_executor::XcqExecutorContext for HostFunctions {

pub fn execute_query(query: Vec<u8>, input: Vec<u8>) -> XcqResult {
let mut executor = poc_executor::XcqExecutor::new(Default::default(), HostFunctions);
executor.execute(&query, &input).map_err(|e| format!("{:?}", e))
executor.execute(&query, "main", &input).map_err(|e| format!("{:?}", e))
}

#[cfg(test)]
Expand Down

0 comments on commit 0867526

Please sign in to comment.