Skip to content

Commit de6ef9f

Browse files
authored
feat: add gas limit (#57)
1 parent 411bc73 commit de6ef9f

File tree

14 files changed

+430
-960
lines changed

14 files changed

+430
-960
lines changed

Cargo.lock

Lines changed: 218 additions & 825 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,19 @@ polkavm-derive = { path = "vendor/polkavm/crates/polkavm-derive", default-featur
4646

4747
# polkadot-sdk
4848
sp-api = { path = "vendor/polkadot-sdk/substrate/primitives/api", default-features = false }
49+
frame = { package = "polkadot-sdk-frame", path = "vendor/polkadot-sdk/substrate/frame", default-features = false }
50+
pallet-balances = { path = "vendor/polkadot-sdk/substrate/frame/balances", default-features = false }
51+
pallet-assets = { path = "vendor/polkadot-sdk/substrate/frame/assets", default-features = false }
52+
pallet-sudo = { path = "vendor/polkadot-sdk/substrate/frame/sudo", default-features = false }
53+
pallet-timestamp = { path = "vendor/polkadot-sdk/substrate/frame/timestamp", default-features = false }
54+
pallet-transaction-payment = { path = "vendor/polkadot-sdk/substrate/frame/transaction-payment", default-features = false }
55+
pallet-transaction-payment-rpc-runtime-api = { path = "vendor/polkadot-sdk/substrate/frame/transaction-payment/rpc/runtime-api", default-features = false }
56+
57+
# genesis builder that allows us to interacto with runtime genesis config
58+
sp-genesis-builder = { path = "vendor/polkadot-sdk/substrate/primitives/genesis-builder", default-features = false }
59+
60+
# wasm builder
61+
substrate-wasm-builder = { version = "22.0.1" }
4962

5063
# nostd
5164
parity-scale-codec = { version = "3.6.12", default-features = false, features = [

poc/runtime/Cargo.toml

Lines changed: 10 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -6,34 +6,31 @@ edition = "2021"
66
[dependencies]
77
parity-scale-codec = { workspace = true }
88
scale-info = { workspace = true }
9-
# this is a frame-based runtime, thus importing `frame` with runtime feature enabled.
10-
frame = { version = "0.2.0", package = "polkadot-sdk-frame", default-features = false, features = [
11-
"experimental",
12-
"runtime",
13-
] }
9+
frame = { workspace = true, features = ["experimental", "runtime"] }
1410

1511
# pallets that we want to use
16-
pallet-balances = { version = "34.0.0", default-features = false }
17-
pallet-assets = { version = "34.0.0", default-features = false }
18-
pallet-sudo = { version = "33.0.0", default-features = false }
19-
pallet-timestamp = { version = "32.0.0", default-features = false }
20-
pallet-transaction-payment = { version = "33.0.0", default-features = false }
21-
pallet-transaction-payment-rpc-runtime-api = { version = "33.0.0", default-features = false }
12+
pallet-balances = { workspace = true }
13+
pallet-assets = { workspace = true }
14+
pallet-sudo = { workspace = true }
15+
pallet-timestamp = { workspace = true }
16+
pallet-transaction-payment = { workspace = true }
17+
pallet-transaction-payment-rpc-runtime-api = { workspace = true }
2218

2319
# genesis builder that allows us to interacto with runtime genesis config
24-
sp-genesis-builder = { version = "0.12.0", default-features = false }
20+
sp-genesis-builder = { workspace = true }
2521

2622
pvq-executor = { workspace = true }
2723
pvq-extension = { workspace = true }
2824
pvq-extension-core = { workspace = true }
2925
pvq-extension-fungibles = { workspace = true }
3026
pvq-primitives = { workspace = true }
27+
pvq-runtime-api = { workspace = true }
3128

3229
[dev-dependencies]
3330
hex = "0.4"
3431

3532
[build-dependencies]
36-
substrate-wasm-builder = { version = "22.0.1", optional = true }
33+
substrate-wasm-builder = { workspace = true, optional = true }
3734

3835
[features]
3936
default = ["std"]

poc/runtime/src/lib.rs

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@
44
#[cfg(feature = "std")]
55
include!(concat!(env!("OUT_DIR"), "/wasm_binary.rs"));
66

7+
extern crate alloc;
8+
use alloc::{vec, vec::Vec};
9+
710
mod pvq;
811

912
use frame::{
@@ -13,24 +16,21 @@ use frame::{
1316
},
1417
prelude::*,
1518
runtime::{
16-
apis::{
17-
self, impl_runtime_apis, ApplyExtrinsicResult, CheckInherentsResult, ExtrinsicInclusionMode, OpaqueMetadata,
18-
},
19+
apis::{self, impl_runtime_apis},
1920
prelude::*,
2021
},
21-
traits::AsEnsureOriginWithArg,
2222
};
2323

2424
#[runtime_version]
2525
pub const VERSION: RuntimeVersion = RuntimeVersion {
26-
spec_name: create_runtime_str!("pvq-poc"),
27-
impl_name: create_runtime_str!("pvq-poc"),
26+
spec_name: alloc::borrow::Cow::Borrowed("pvq-poc"),
27+
impl_name: alloc::borrow::Cow::Borrowed("pvq-poc"),
2828
authoring_version: 1,
2929
spec_version: 0,
3030
impl_version: 1,
3131
apis: RUNTIME_API_VERSIONS,
3232
transaction_version: 1,
33-
state_version: 1,
33+
system_version: 1,
3434
};
3535

3636
/// The version information used to identify this runtime when compiled natively.
@@ -110,6 +110,8 @@ type RuntimeExecutive = Executive<Runtime, Block, frame_system::ChainContext<Run
110110

111111
use pallet_transaction_payment::{FeeDetails, RuntimeDispatchInfo};
112112

113+
const ONE_SECOND_IN_GAS: i64 = 100000;
114+
113115
impl_runtime_apis! {
114116
impl apis::Core<Block> for Runtime {
115117
fn version() -> RuntimeVersion {
@@ -225,9 +227,10 @@ impl_runtime_apis! {
225227
}
226228
}
227229

228-
impl pvq::PvqApi<Block> for Runtime {
229-
fn execute_query(query: Vec<u8>, input: Vec<u8>) -> pvq::PvqResult {
230-
pvq::execute_query(&query, &input)
230+
impl pvq_runtime_api::PvqApi<Block> for Runtime {
231+
fn execute_query(program: Vec<u8>, args: Vec<u8>, gas_limit: Option<i64>) -> pvq_primitives::PvqResult {
232+
// Set a default gas limit of 2 seconds
233+
pvq::execute_query(&program, &args, gas_limit.unwrap_or(ONE_SECOND_IN_GAS * 2))
231234
}
232235
fn metadata() -> Vec<u8> {
233236
pvq::metadata().encode()

poc/runtime/src/pvq.rs

Lines changed: 3 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,8 @@
11
#[allow(unused_imports)]
22
use frame::deps::scale_info::prelude::{format, string::String};
3-
use frame::deps::sp_api::decl_runtime_apis;
4-
use frame::prelude::*;
53

64
use pvq_extension::metadata::Metadata;
7-
pub use pvq_primitives::PvqResult;
8-
95
use pvq_extension::{extensions_impl, ExtensionsExecutor, InvokeSource};
10-
decl_runtime_apis! {
11-
pub trait PvqApi {
12-
fn execute_query(query: Vec<u8>, input: Vec<u8>) -> PvqResult;
13-
fn metadata() -> Vec<u8>;
14-
}
15-
}
166

177
#[extensions_impl]
188
pub mod extensions {
@@ -42,9 +32,10 @@ pub mod extensions {
4232
}
4333
}
4434

45-
pub fn execute_query(query: &[u8], input: &[u8]) -> PvqResult {
35+
pub fn execute_query(program: &[u8], args: &[u8], gas_limit: i64) -> pvq_primitives::PvqResult {
4636
let mut executor = ExtensionsExecutor::<extensions::Extensions, ()>::new(InvokeSource::RuntimeAPI);
47-
executor.execute_method(query, input, 0)
37+
let (result, _) = executor.execute(program, args, Some(gas_limit));
38+
result
4839
}
4940

5041
pub fn metadata() -> Metadata {

pvq-executor/src/context.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
use polkavm::Linker;
2+
3+
pub trait PvqExecutorContext {
4+
type UserData;
5+
type UserError;
6+
fn register_host_functions(&mut self, linker: &mut Linker<Self::UserData, Self::UserError>);
7+
fn data(&mut self) -> &mut Self::UserData;
8+
}

pvq-executor/src/error.rs

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
use pvq_primitives::PvqError;
2+
#[derive(Debug)]
3+
pub enum PvqExecutorError<UserError> {
4+
InvalidProgramFormat,
5+
MemoryAccessError(polkavm::MemoryAccessError),
6+
// Extract from the PVM CallError
7+
Trap,
8+
// Extract from the PVM CallError
9+
NotEnoughGas,
10+
// Usually a custom error type from the extension system definition
11+
User(UserError),
12+
// Other errors directly from the PVM
13+
OtherPvmError(polkavm::Error),
14+
}
15+
16+
impl<UserError> From<polkavm::MemoryAccessError> for PvqExecutorError<UserError> {
17+
fn from(err: polkavm::MemoryAccessError) -> Self {
18+
Self::MemoryAccessError(err)
19+
}
20+
}
21+
22+
impl<UserError> From<polkavm::Error> for PvqExecutorError<UserError> {
23+
fn from(err: polkavm::Error) -> Self {
24+
Self::OtherPvmError(err)
25+
}
26+
}
27+
28+
impl<UserError> From<polkavm::CallError<UserError>> for PvqExecutorError<UserError> {
29+
fn from(err: polkavm::CallError<UserError>) -> Self {
30+
match err {
31+
polkavm::CallError::Trap => Self::Trap,
32+
polkavm::CallError::NotEnoughGas => Self::NotEnoughGas,
33+
polkavm::CallError::Error(e) => Self::OtherPvmError(e),
34+
polkavm::CallError::User(e) => Self::User(e),
35+
}
36+
}
37+
}
38+
39+
impl<UserError> From<PvqExecutorError<UserError>> for PvqError {
40+
fn from(e: PvqExecutorError<UserError>) -> PvqError {
41+
match e {
42+
PvqExecutorError::InvalidProgramFormat => PvqError::InvalidPvqProgramFormat,
43+
PvqExecutorError::MemoryAccessError(_) => PvqError::MemoryAccessError,
44+
PvqExecutorError::Trap => PvqError::Trap,
45+
PvqExecutorError::NotEnoughGas => PvqError::QueryExceedsWeightLimit,
46+
PvqExecutorError::User(_) => PvqError::HostCallError,
47+
PvqExecutorError::OtherPvmError(_) => PvqError::Other,
48+
}
49+
}
50+
}

pvq-executor/src/executor.rs

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
use alloc::vec::Vec;
2+
use polkavm::{Config, Engine, Linker, Module, ModuleConfig, ProgramBlob};
3+
4+
use crate::context::PvqExecutorContext;
5+
use crate::error::PvqExecutorError;
6+
7+
type PvqExecutorResult<UserError> = Result<Vec<u8>, PvqExecutorError<UserError>>;
8+
type GasLimit = Option<i64>;
9+
10+
pub struct PvqExecutor<Ctx: PvqExecutorContext> {
11+
engine: Engine,
12+
linker: Linker<Ctx::UserData, Ctx::UserError>,
13+
context: Ctx,
14+
}
15+
16+
impl<Ctx: PvqExecutorContext> PvqExecutor<Ctx> {
17+
pub fn new(config: Config, mut context: Ctx) -> Self {
18+
let engine = Engine::new(&config).unwrap();
19+
let mut linker = Linker::<Ctx::UserData, Ctx::UserError>::new();
20+
// Register user-defined host functions
21+
context.register_host_functions(&mut linker);
22+
Self {
23+
engine,
24+
linker,
25+
context,
26+
}
27+
}
28+
29+
pub fn execute(
30+
&mut self,
31+
program: &[u8],
32+
args: &[u8],
33+
gas_limit: GasLimit,
34+
) -> (PvqExecutorResult<Ctx::UserError>, GasLimit) {
35+
let blob = match ProgramBlob::parse(program.into()) {
36+
Ok(blob) => blob,
37+
Err(_) => return (Err(PvqExecutorError::InvalidProgramFormat), gas_limit),
38+
};
39+
40+
let mut module_config = ModuleConfig::new();
41+
module_config.set_aux_data_size(args.len() as u32);
42+
if gas_limit.is_some() {
43+
module_config.set_gas_metering(Some(polkavm::GasMeteringKind::Sync));
44+
}
45+
46+
let module = match Module::from_blob(&self.engine, &module_config, blob) {
47+
Ok(module) => module,
48+
Err(err) => return (Err(err.into()), gas_limit),
49+
};
50+
51+
let instance_pre = match self.linker.instantiate_pre(&module) {
52+
Ok(instance_pre) => instance_pre,
53+
Err(err) => return (Err(err.into()), gas_limit),
54+
};
55+
56+
let mut instance = match instance_pre.instantiate() {
57+
Ok(instance) => instance,
58+
Err(err) => return (Err(err.into()), gas_limit),
59+
};
60+
61+
if let Some(gas_limit) = gas_limit {
62+
instance.set_gas(gas_limit);
63+
}
64+
65+
// From this point on, we include instance.gas() in the return value
66+
let result = (|| {
67+
instance.write_memory(module.memory_map().aux_data_address(), args)?;
68+
69+
tracing::info!("Calling entrypoint with args: {:?}", args);
70+
let res = instance.call_typed_and_get_result::<u64, (u32, u32)>(
71+
self.context.data(),
72+
"pvq",
73+
(module.memory_map().aux_data_address(), args.len() as u32),
74+
)?;
75+
76+
let res_size = (res >> 32) as u32;
77+
let res_ptr = (res & 0xffffffff) as u32;
78+
79+
let result = instance.read_memory(res_ptr, res_size)?;
80+
81+
tracing::info!("Result: {:?}", result);
82+
Ok(result)
83+
})();
84+
85+
if gas_limit.is_some() {
86+
(result, Some(instance.gas()))
87+
} else {
88+
(result, None)
89+
}
90+
}
91+
}

0 commit comments

Comments
 (0)