diff --git a/Cargo.lock b/Cargo.lock index 9e3dbec63..1f60d5b09 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -875,6 +875,7 @@ version = "6.6.1" dependencies = [ "cere-dev-runtime", "cere-runtime", + "cere-runtime-interfaces", "frame-benchmarking", "frame-system", "frame-system-rpc-runtime-api", @@ -1142,6 +1143,26 @@ dependencies = [ "sp-std", ] +[[package]] +name = "cere-runtime-interfaces" +version = "6.6.1" +dependencies = [ + "cere-wasm-interface", + "environmental", + "log", + "parity-scale-codec", + "sc-allocator", + "sc-executor", + "sp-core", + "sp-runtime", + "sp-runtime-interface-macro", + "sp-runtime-interface-proc-macro-local", + "sp-std", + "sp-wasm-interface", + "thiserror", + "wasmi 0.13.2", +] + [[package]] name = "cere-service" version = "6.6.1" @@ -1189,6 +1210,24 @@ dependencies = [ "sp-trie", ] +[[package]] +name = "cere-wasm-interface" +version = "21.0.1" +dependencies = [ + "Inflector", + "anyhow", + "expander", + "impl-trait-for-tuples", + "log", + "parity-scale-codec", + "proc-macro-crate 3.2.0", + "proc-macro2", + "quote", + "sp-wasm-interface", + "syn 2.0.79", + "wasmtime", +] + [[package]] name = "cexpr" version = "0.6.0" @@ -4723,6 +4762,12 @@ dependencies = [ "hash-db", ] +[[package]] +name = "memory_units" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8452105ba047068f40ff7093dd1d9da90898e63dd61736462e9cdda6a90ad3c3" + [[package]] name = "merlin" version = "3.0.0" @@ -5571,7 +5616,7 @@ dependencies = [ "staging-xcm", "staging-xcm-builder", "wasm-instrument", - "wasmi", + "wasmi 0.32.3", ] [[package]] @@ -9848,6 +9893,31 @@ dependencies = [ "static_assertions", ] +[[package]] +name = "sp-runtime-interface-macro" +version = "28.0.0" +dependencies = [ + "bytes", + "cere-runtime-interfaces", + "cere-wasm-interface", + "impl-trait-for-tuples", + "parity-scale-codec", + "polkavm-derive", + "primitive-types", + "rustversion", + "sp-core", + "sp-externalities", + "sp-io", + "sp-runtime-interface-proc-macro-local", + "sp-state-machine", + "sp-std", + "sp-storage", + "sp-tracing", + "sp-wasm-interface", + "static_assertions", + "trybuild", +] + [[package]] name = "sp-runtime-interface-proc-macro" version = "18.0.0" @@ -9861,6 +9931,21 @@ dependencies = [ "syn 2.0.79", ] +[[package]] +name = "sp-runtime-interface-proc-macro-local" +version = "18.0.0" +dependencies = [ + "Inflector", + "cere-wasm-interface", + "expander", + "parity-scale-codec", + "proc-macro-crate 3.2.0", + "proc-macro2", + "quote", + "sp-wasm-interface", + "syn 2.0.79", +] + [[package]] name = "sp-session" version = "36.0.0" @@ -10484,6 +10569,12 @@ version = "0.12.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1" +[[package]] +name = "target-triple" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42a4d50cdb458045afc8131fd91b64904da29548bcb63c7236e0844936c13078" + [[package]] name = "tempfile" version = "3.13.0" @@ -10971,6 +11062,21 @@ version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" +[[package]] +name = "trybuild" +version = "1.0.101" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8dcd332a5496c026f1e14b7f3d2b7bd98e509660c04239c58b0ba38a12daded4" +dependencies = [ + "glob", + "serde", + "serde_derive", + "serde_json", + "target-triple", + "termcolor", + "toml 0.8.19", +] + [[package]] name = "tt-call" version = "1.0.9" @@ -11343,6 +11449,17 @@ dependencies = [ "web-sys", ] +[[package]] +name = "wasmi" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06c326c93fbf86419608361a2c925a31754cf109da1b8b55737070b4d6669422" +dependencies = [ + "parity-wasm", + "wasmi-validation", + "wasmi_core 0.2.1", +] + [[package]] name = "wasmi" version = "0.32.3" @@ -11356,10 +11473,19 @@ dependencies = [ "smallvec", "spin 0.9.8", "wasmi_collections", - "wasmi_core", + "wasmi_core 0.32.3", "wasmparser-nostd", ] +[[package]] +name = "wasmi-validation" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91ff416ad1ff0c42e5a926ed5d5fab74c0f098749aa0ad8b2a34b982ce0e867b" +dependencies = [ + "parity-wasm", +] + [[package]] name = "wasmi_collections" version = "0.32.3" @@ -11371,6 +11497,19 @@ dependencies = [ "string-interner", ] +[[package]] +name = "wasmi_core" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57d20cb3c59b788653d99541c646c561c9dd26506f25c0cebfe810659c54c6d7" +dependencies = [ + "downcast-rs", + "libm", + "memory_units", + "num-rational", + "num-traits", +] + [[package]] name = "wasmi_core" version = "0.32.3" diff --git a/Cargo.toml b/Cargo.toml index 022688607..dd1aae857 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,18 +24,28 @@ members = [ "primitives", "runtime/cere", "runtime/cere-dev", + "node/runtime-interfaces", + "node/runtime-interface-macro" + ] resolver = "2" [workspace.dependencies] # 3rd-party dependencies +Inflector = { version = "0.11.4" } +expander = { version = "2.0.0" } blake2 = { version = "0.10.6", default-features = false } +bytes = { version = "1.4.0", default-features = false } +impl-trait-for-tuples = { version = "0.2.2" } +primitive-types = { version = "0.12.1", default-features = false } byte-unit = { version = "4.0.19", default-features = false, features = ["u128"] } chrono = { version = "0.4.31", default-features = false } clap = { version = "4.2.5", features = ["derive"] } codec = { package = "parity-scale-codec", version = "3.6.12", default-features = false, features = ["derive", "max-encoded-len"] } futures = { version = "0.3.30" } hex = { version = "0.4.3", default-features = false, features = ["alloc"] } +anyhow = { version = "1.0.81", default-features = false } +wasmtime = { version = "8.0.1", default-features = false } hex-literal = { version = "^0.4.1", default-features = false } jsonrpsee = { version = "0.24.3", default-features = false, features = ["server"] } lazy_static = { version = "1.5.0", default-features = false } @@ -50,7 +60,14 @@ serde_json = { version = "1.0.128", default-features = false } static_assertions = { version = "1.1.0" } url = { version = "2.5.2" } array-bytes = { version = "6.2.2" } +rustversion = { version = "1.0.17" } +trybuild = { version = "1.0.89" } +polkavm-derive = "0.9.1" itertools = { version = "0.13.0", default-features = false, features = ["use_alloc"] } +proc-macro-crate = { version = "3.0.0" } +proc-macro2 = { version = "1.0.64" } +quote = { version = "1.0.37" } +syn = { version = "2.0.65" } # Substrate Dependencies # Please keey format such that: @@ -124,6 +141,7 @@ sc-consensus-grandpa = { git = "https://github.com/paritytech/polkadot-sdk.git", sc-consensus-grandpa-rpc = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-stable2409", default-features = false } sc-consensus-slots = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-stable2409", default-features = false } sc-executor = { git = "https://github.com/paritytech/polkadot-sdk", tag = "polkadot-stable2409", default-features = false } +sc-allocator = { git = "https://github.com/paritytech/polkadot-sdk", tag = "polkadot-stable2409", default-features = false } sc-network = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-stable2409", default-features = false } sc-network-common = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-stable2409", default-features = false } sc-rpc = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-stable2409", default-features = false } @@ -151,11 +169,16 @@ sp-consensus-grandpa-rpc = { git = "https://github.com/paritytech/polkadot-sdk.g sp-core = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-stable2409", default-features = false, features = ["serde"] } sp-inherents = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-stable2409", default-features = false } sp-io = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-stable2409", default-features = false } +sp-state-machine = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-stable2409", default-features = false } +#sp-runtime-interface-test-wasm = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-stable2409", default-features = false } +sp-externalities = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-stable2409", default-features = false } sp-keystore = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-stable2409", default-features = false } sp-offchain = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-stable2409", default-features = false } sp-rpc = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-stable2409", default-features = false } sp-rpc-api = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-stable2409", default-features = false } sp-runtime = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-stable2409", default-features = false } +#sp-runtime-interface = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-stable2409", default-features = false } +#sp-runtime-interface-proc-macro = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-stable2409", default-features = false } sp-genesis-builder = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-stable2409", default-features = false } sp-session = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-stable2409", default-features = false } sp-staking = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-stable2409", default-features = false } @@ -167,6 +190,7 @@ sp-transaction-pool = { git = "https://github.com/paritytech/polkadot-sdk.git", sp-transaction-storage-proof = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-stable2409", default-features = false } sp-trie = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-stable2409", default-features = false } sp-version = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-stable2409", default-features = false } +sp-wasm-interface = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-stable2409", default-features = false } substrate-build-script-utils = { git = "https://github.com/paritytech/polkadot-sdk", tag = "polkadot-stable2409", default-features = false } substrate-frame-rpc-system = { git = "https://github.com/paritytech/polkadot-sdk", tag = "polkadot-stable2409", default-features = false } substrate-state-trie-migration-rpc = { git = "https://github.com/paritytech/polkadot-sdk", tag = "polkadot-stable2409", default-features = false } @@ -179,6 +203,8 @@ cere-dev-runtime = { path = "runtime/cere-dev" } cere-rpc = { path = "node/rpc" } cere-runtime = { path = "runtime/cere" } cere-runtime-common = { path = "runtime/common", default-features = false } +cere-runtime-interfaces = { path = "node/runtime-interfaces" } +cere-wasm-interface = { path = "node/runtime-interface-macro/cere-wasm-interface"} cere-service = { path = "node/service" } ddc-primitives = { path = "primitives", default-features = false } pallet-chainbridge = { path = "pallets/chainbridge", default-features = false } @@ -191,6 +217,10 @@ pallet-ddc-staking = { path = "pallets/ddc-staking", default-features = false } pallet-erc20 = { path = "pallets/erc20", default-features = false } pallet-erc721 = { path = "pallets/erc721", default-features = false } pallet-origins = { path = "pallets/origins", default-features = false } +sp-runtime-interface-macro = { path = "node/runtime-interface-macro", default-features = false } +#sp-runtime-interface-test-wasm = { path = "node/runtime-interface-macro/test-wasm", default-features = false, version = "2.0.0" } +sp-runtime-interface-proc-macro-local = { path = "node/runtime-interface-macro/proc-macro", default-features = false } +#sp-runtime-interface-test-wasm-deprecated = { path = "node/runtime-interface-macro/test-wasm-deprecated" , default-features = false} [profile.release] panic = "unwind" diff --git a/node/client/Cargo.toml b/node/client/Cargo.toml index cb8febb61..07b4e1276 100644 --- a/node/client/Cargo.toml +++ b/node/client/Cargo.toml @@ -41,6 +41,7 @@ sp-transaction-pool = { workspace = true, default-features = true } # Local cere-dev-runtime = { workspace = true, optional = true } cere-runtime = { workspace = true, optional = true } +cere-runtime-interfaces = { workspace = true } [features] default = ["cere"] diff --git a/node/client/src/lib.rs b/node/client/src/lib.rs index c476f3a10..4b4119777 100644 --- a/node/client/src/lib.rs +++ b/node/client/src/lib.rs @@ -20,11 +20,11 @@ use sp_storage::{ChildInfo, StorageData, StorageKey}; pub type FullBackend = sc_service::TFullBackend; #[cfg(not(feature = "runtime-benchmarks"))] -pub type HostFunctions = sp_io::SubstrateHostFunctions; +pub type HostFunctions = (sp_io::SubstrateHostFunctions,cere_runtime_interfaces::sandbox::HostFunctions); #[cfg(feature = "runtime-benchmarks")] pub type HostFunctions = - (sp_io::SubstrateHostFunctions, frame_benchmarking::benchmarking::HostFunctions); + (sp_io::SubstrateHostFunctions, frame_benchmarking::benchmarking::HostFunctions,cere_runtime_interfaces::sandbox::HostFunctions); pub type ChainExecutor = WasmExecutor; pub type FullClient = sc_service::TFullClient; diff --git a/node/runtime-interface-macro/Cargo.toml b/node/runtime-interface-macro/Cargo.toml new file mode 100644 index 000000000..4b3c7218f --- /dev/null +++ b/node/runtime-interface-macro/Cargo.toml @@ -0,0 +1,68 @@ +[package] +name = "sp-runtime-interface-macro" +version = "28.0.0" +authors.workspace = true +edition.workspace = true +license = "Apache-2.0" +homepage.workspace = true +repository.workspace = true +description = "Substrate runtime interface" +documentation = "https://docs.rs/sp-runtime-interface/" +readme = "README.md" + +#[lints] +#workspace = true + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] + +[dependencies] +bytes = { workspace = true } +sp-wasm-interface.workspace = true +sp-std.workspace = true +sp-tracing.workspace = true +sp-runtime-interface-proc-macro-local.workspace = true +sp-runtime-interface-proc-macro-local.default-features = true +sp-externalities.workspace = true +codec = { features = ["bytes"], workspace = true } +static_assertions = { workspace = true, default-features = true } +primitive-types = { workspace = true } +sp-storage.workspace = true +impl-trait-for-tuples = { workspace = true } +cere-wasm-interface = { workspace = true } +#sp-core = { workspace = true } +#sp-io = { workspace = true } +#sp-state-machine = { workspace = true, default-features = false } + +[target.'cfg(all(any(target_arch = "riscv32", target_arch = "riscv64"), substrate_runtime))'.dependencies] +polkavm-derive = { workspace = true } + +[dev-dependencies] +#sp-runtime-interface-test-wasm = { workspace = true } +sp-state-machine = { default-features = true, workspace = true } +sp-core = { default-features = true, workspace = true } +sp-io = { default-features = true, workspace = true} +rustversion = { workspace = true } +trybuild = { workspace = true } +cere-runtime-interfaces = { workspace = true } + +[features] +default = ["std"] +std = [ + "bytes/std", + "codec/std", + "primitive-types/std", + "sp-externalities/std", + "sp-std/std", + "sp-storage/std", + "sp-tracing/std", + "sp-wasm-interface/std", +] + +# ATTENTION +# +# Only use when you know what you are doing. +# +# Disables static assertions in `impls.rs` that checks the word size. To prevent any footgun, the +# check is changed into a runtime check. +disable_target_static_assertions = [] diff --git a/node/runtime-interface-macro/README.md b/node/runtime-interface-macro/README.md new file mode 100644 index 000000000..8ac5ef12e --- /dev/null +++ b/node/runtime-interface-macro/README.md @@ -0,0 +1,94 @@ +Substrate runtime interface + +This crate provides types, traits and macros around runtime interfaces. A runtime interface is a fixed interface between +a Substrate runtime and a Substrate node. For a native runtime the interface maps to a direct function call of the +implementation. For a wasm runtime the interface maps to an external function call. These external functions are +exported by the wasm executor and they map to the same implementation as the native calls. + +# Using a type in a runtime interface + +Any type that should be used in a runtime interface as argument or return value needs to implement [`RIType`]. The +associated type +[`FFIType`](https:/docs.rs/sp-runtime-interface/latest/sp_runtime_interface/trait.RIType.html#associatedtype.FFIType) is +the type that is used in the FFI function to represent the actual type. For example `[T]` is represented by an `u64`. +The slice pointer and the length will be mapped to an `u64` value. For more information see this +[table](https:/docs.rs/sp-runtime-interface/latest/sp_runtime_interface/#ffi-type-and-conversion). The FFI function +definition is used when calling from the wasm runtime into the node. + +Traits are used to convert from a type to the corresponding +[`RIType::FFIType`](https:/docs.rs/sp-runtime-interface/latest/sp_runtime_interface/trait.RIType.html#associatedtype.FFIType). +Depending on where and how a type should be used in a function signature, a combination of the following traits need to +be implemented: + +1. Pass as function argument: [`wasm::IntoFFIValue`] and [`host::FromFFIValue`] +2. As function return value: [`wasm::FromFFIValue`] and [`host::IntoFFIValue`] +3. Pass as mutable function argument: [`host::IntoPreallocatedFFIValue`] + +The traits are implemented for most of the common types like `[T]`, `Vec`, arrays and primitive types. + +For custom types, we provide the +[`PassBy`](https://docs.rs/sp-runtime-interface/latest/sp_runtime_interface/pass_by#PassBy) trait and strategies that +define how a type is passed between the wasm runtime and the node. Each strategy also provides a derive macro to +simplify the implementation. + +# Performance + +To not waste any more performance when calling into the node, not all types are SCALE encoded when being passed as +arguments between the wasm runtime and the node. For most types that are raw bytes like `Vec`, `[u8]` or `[u8; N]` +we pass them directly, without SCALE encoding them in front of. The implementation of [`RIType`] each type provides more +information on how the data is passed. + +# Declaring a runtime interface + +Declaring a runtime interface is similar to declaring a trait in Rust: + +```rust +#[sp_runtime_interface::runtime_interface] +trait RuntimeInterface { + fn some_function(value: &[u8]) -> bool { + value.iter().all(|v| *v > 125) + } +} +``` + +For more information on declaring a runtime interface, see +[`#[runtime_interface]`](https://docs.rs/sp-runtime-interface/latest/sp_runtime_interface/attr.runtime_interface.html). + +# FFI type and conversion + +The following table documents how values of types are passed between the wasm and the host side and how they are +converted into the corresponding type. + + +| Type | FFI type | Conversion | +|----|----|----| +| `u8` | `u8` | `Identity` | +| `u16` | `u16` | `Identity` | +| `u32` | `u32` | `Identity` | +| `u64` | `u64` | `Identity` | +| `i128` | `u32` | `v.as_ptr()` (pointer to a 16 byte array) | +| `i8` | `i8` | `Identity` | +| `i16` | `i16` | `Identity` | +| `i32` | `i32` | `Identity` | +| `i64` | `i64` | `Identity` | +| `u128` | `u32` | `v.as_ptr()` (pointer to a 16 byte array) | +| `bool` | `u8` | `if v { 1 } else { 0 }` | +| `&str` | `u64` | v.len() 32bit << 32 | v.as_ptr() 32bit | +| `&[u8]` | `u64` | v.len() 32bit << 32 | v.as_ptr() 32bit | +| `Vec` | `u64` | v.len() 32bit << 32 | v.as_ptr() 32bit | +| `Vec where T: Encode` | `u64` | `let e = v.encode();`

e.len() 32bit << 32 | e.as_ptr() 32bit | +| `&[T] where T: Encode` | `u64` | `let e = v.encode();`

e.len() 32bit << 32 | e.as_ptr() 32bit | +| `[u8; N]` | `u32` | `v.as_ptr()` | +| `*const T` | `u32` | `Identity` | +| `Option` | `u64` | `let e = v.encode();`

e.len() 32bit << 32 | e.as_ptr() 32bit | +| [`T where T: PassBy`](https://docs.rs/sp-runtime-interface/latest/sp_runtime_interface/pass_by#Inner) | Depends on inner | Depends on inner | +| [`T where T: PassBy`](https://docs.rs/sp-runtime-interface/latest/sp_runtime_interface/pass_by#Codec) | `u64`| v.len() 32bit << 32 | v.as_ptr() 32bit | + +`Identity` means that the value is converted directly into the corresponding FFI type. + +License: Apache-2.0 + + +## Release + +Polkadot SDK stable2409 diff --git a/node/runtime-interface-macro/cere-wasm-interface/Cargo.toml b/node/runtime-interface-macro/cere-wasm-interface/Cargo.toml new file mode 100644 index 000000000..3f73b3dc9 --- /dev/null +++ b/node/runtime-interface-macro/cere-wasm-interface/Cargo.toml @@ -0,0 +1,42 @@ +[package] +name = "cere-wasm-interface" +version = "21.0.1" +authors.workspace = true +edition.workspace = true +license = "Apache-2.0" +homepage.workspace = true +#repository.workspace = true +description = "This crate provides procedural macros for usage within the context of the Substrate runtime interface." +documentation = "https://docs.rs/sp-runtime-interface-proc-macro" + +#[lints] +#workspace = true + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] + +#[lib] +#proc-macro = true + +[dependencies] +Inflector = { workspace = true } +codec = { features = ["derive"], workspace = true } +proc-macro-crate = { workspace = true } +proc-macro2 = { workspace = true } +quote = { workspace = true } +expander = { workspace = true } +syn = { features = ["extra-traits", "fold", "full", "visit"], workspace = true } +sp-wasm-interface = { workspace = true } +anyhow = { optional = true, workspace = true } +log = { optional = true, workspace = true, default-features = true } +wasmtime = { optional = true, workspace = true } +impl-trait-for-tuples = { workspace = true } + +[features] +default = ["std"] +std = [ + "anyhow?/std", + "codec/std", + "log/std", +] +wasmtime = ["anyhow", "dep:wasmtime"] diff --git a/node/runtime-interface-macro/cere-wasm-interface/README.md b/node/runtime-interface-macro/cere-wasm-interface/README.md new file mode 100644 index 000000000..96e931ea4 --- /dev/null +++ b/node/runtime-interface-macro/cere-wasm-interface/README.md @@ -0,0 +1,5 @@ + + +## Release + +Polkadot SDK stable2409 diff --git a/node/runtime-interface-macro/cere-wasm-interface/src/lib.rs b/node/runtime-interface-macro/cere-wasm-interface/src/lib.rs new file mode 100644 index 000000000..0f8ad6063 --- /dev/null +++ b/node/runtime-interface-macro/cere-wasm-interface/src/lib.rs @@ -0,0 +1,631 @@ +//! Types and traits for interfacing between the host and the wasm runtime. + +#![cfg_attr(not(feature = "std"), no_std)] +extern crate alloc; +use alloc::{borrow::Cow, vec, vec::Vec}; +use core::{iter::Iterator, marker::PhantomData, mem, result}; +use sp_wasm_interface::{Result as WResult}; + + +#[cfg(not(all(feature = "std", feature = "wasmtime")))] +#[macro_export] +macro_rules! if_wasmtime_is_enabled { + ($($token:tt)*) => {}; +} + +#[cfg(all(feature = "std", feature = "wasmtime"))] +#[macro_export] +macro_rules! if_wasmtime_is_enabled { + ($($token:tt)*) => { + $($token)* + } +} + +if_wasmtime_is_enabled! { + // Reexport wasmtime so that its types are accessible from the procedural macro. + pub use wasmtime; + + // Wasmtime uses anyhow types but doesn't reexport them. + pub use anyhow; +} + +/// Sandbox memory identifier. +pub type MemoryId = u32; + +/// Result type used by traits in this crate. +#[cfg(feature = "std")] +pub type Result = result::Result; +#[cfg(not(feature = "std"))] +pub type Result = result::Result; + +/// Value types supported by Substrate on the boundary between host/Wasm. +#[derive(Copy, Clone, PartialEq, Debug, Eq)] +pub enum ValueType { + /// An `i32` value type. + I32, + /// An `i64` value type. + I64, + /// An `f32` value type. + F32, + /// An `f64` value type. + F64, +} + +impl From for u8 { + fn from(val: ValueType) -> u8 { + match val { + ValueType::I32 => 0, + ValueType::I64 => 1, + ValueType::F32 => 2, + ValueType::F64 => 3, + } + } +} + +impl TryFrom for ValueType { + type Error = (); + + fn try_from(val: u8) -> core::result::Result { + match val { + 0 => Ok(Self::I32), + 1 => Ok(Self::I64), + 2 => Ok(Self::F32), + 3 => Ok(Self::F64), + _ => Err(()), + } + } +} + +/// Values supported by Substrate on the boundary between host/Wasm. +#[derive(PartialEq, Debug, Clone, Copy, codec::Encode, codec::Decode)] +pub enum Value { + /// A 32-bit integer. + I32(i32), + /// A 64-bit integer. + I64(i64), + /// A 32-bit floating-point number stored as raw bit pattern. + /// + /// You can materialize this value using `f32::from_bits`. + F32(u32), + /// A 64-bit floating-point number stored as raw bit pattern. + /// + /// You can materialize this value using `f64::from_bits`. + F64(u64), +} + +impl Value { + /// Returns the type of this value. + pub fn value_type(&self) -> ValueType { + match self { + Value::I32(_) => ValueType::I32, + Value::I64(_) => ValueType::I64, + Value::F32(_) => ValueType::F32, + Value::F64(_) => ValueType::F64, + } + } + + /// Return `Self` as `i32`. + pub fn as_i32(&self) -> Option { + match self { + Self::I32(val) => Some(*val), + _ => None, + } + } +} + +/// Provides `Sealed` trait to prevent implementing trait `PointerType` and `WasmTy` outside of this +/// crate. +mod private { + pub trait Sealed {} + + impl Sealed for u8 {} + impl Sealed for u16 {} + impl Sealed for u32 {} + impl Sealed for u64 {} + + impl Sealed for i32 {} + impl Sealed for i64 {} +} + +/// Something that can be wrapped in a wasm `Pointer`. +/// +/// This trait is sealed. +pub trait PointerType: Sized + private::Sealed { + /// The size of the type in wasm. + const SIZE: u32 = mem::size_of::() as u32; +} + +impl PointerType for u8 {} +impl PointerType for u16 {} +impl PointerType for u32 {} +impl PointerType for u64 {} + +/// Type to represent a pointer in wasm at the host. +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +pub struct Pointer { + ptr: u32, + _marker: PhantomData, +} + +impl Pointer { + /// Create a new instance of `Self`. + pub fn new(ptr: u32) -> Self { + Self { ptr, _marker: Default::default() } + } + + /// Calculate the offset from this pointer. + /// + /// `offset` is in units of `T`. So, `3` means `3 * mem::size_of::()` as offset to the + /// pointer. + /// + /// Returns an `Option` to respect that the pointer could probably overflow. + pub fn offset(self, offset: u32) -> Option { + offset + .checked_mul(T::SIZE) + .and_then(|o| self.ptr.checked_add(o)) + .map(|ptr| Self { ptr, _marker: Default::default() }) + } + + /// Create a null pointer. + pub fn null() -> Self { + Self::new(0) + } + + /// Cast this pointer of type `T` to a pointer of type `R`. + pub fn cast(self) -> Pointer { + Pointer::new(self.ptr) + } +} + +impl From for Pointer { + fn from(ptr: u32) -> Self { + Pointer::new(ptr) + } +} + +impl From> for u32 { + fn from(ptr: Pointer) -> Self { + ptr.ptr + } +} + +impl From> for u64 { + fn from(ptr: Pointer) -> Self { + u64::from(ptr.ptr) + } +} + +impl From> for usize { + fn from(ptr: Pointer) -> Self { + ptr.ptr as _ + } +} + +impl IntoValue for Pointer { + const VALUE_TYPE: ValueType = ValueType::I32; + fn into_value(self) -> Value { + Value::I32(self.ptr as _) + } +} + +impl TryFromValue for Pointer { + fn try_from_value(val: Value) -> Option { + match val { + Value::I32(val) => Some(Self::new(val as _)), + _ => None, + } + } +} + +/// The word size used in wasm. Normally known as `usize` in Rust. +pub type WordSize = u32; + +#[derive(Eq, PartialEq, Debug, Clone)] +pub struct Signature { + /// The arguments of a function. + pub args: Cow<'static, [ValueType]>, + /// The optional return value of a function. + pub return_value: Option, +} + +impl Signature { + /// Create a new instance of `Signature`. + pub fn new>>( + args: T, + return_value: Option, + ) -> Self { + Self { args: args.into(), return_value } + } + + /// Create a new instance of `Signature` with the given `args` and without any return value. + pub fn new_with_args>>(args: T) -> Self { + Self { args: args.into(), return_value: None } + } +} + +/// A trait that requires `RefUnwindSafe` when `feature = std`. +#[cfg(feature = "std")] +pub trait MaybeRefUnwindSafe: std::panic::RefUnwindSafe {} +#[cfg(feature = "std")] +impl MaybeRefUnwindSafe for T {} + +/// A trait that requires `RefUnwindSafe` when `feature = std`. +#[cfg(not(feature = "std"))] +pub trait MaybeRefUnwindSafe {} +#[cfg(not(feature = "std"))] +impl MaybeRefUnwindSafe for T {} + +/// Something that provides a function implementation on the host for a wasm function. +pub trait Function: MaybeRefUnwindSafe + Send + Sync { + /// Returns the name of this function. + fn name(&self) -> &str; + /// Returns the signature of this function. + fn signature(&self) -> Signature; + /// Execute this function with the given arguments. + fn execute( + &self, + context: &mut dyn FunctionContext, + args: &mut dyn Iterator, + ) -> Result>; +} + +impl PartialEq for dyn Function { + fn eq(&self, other: &Self) -> bool { + other.name() == self.name() && other.signature() == self.signature() + } +} + +/// Something that can be converted into a wasm compatible `Value`. +pub trait IntoValue { + /// The type of the value in wasm. + const VALUE_TYPE: ValueType; + + /// Convert `self` into a wasm `Value`. + fn into_value(self) -> Value; +} + +/// Something that can may be created from a wasm `Value`. +pub trait TryFromValue: Sized { + /// Try to convert the given `Value` into `Self`. + fn try_from_value(val: Value) -> Option; +} + +pub trait FunctionContext { + /// Read memory from `address` into a vector. + fn read_memory(&self, address: Pointer, size: WordSize) -> Result> { + let mut vec = vec![0; size as usize]; + self.read_memory_into(address, &mut vec)?; + Ok(vec) + } + /// Read memory into the given `dest` buffer from `address`. + fn read_memory_into(&self, address: Pointer, dest: &mut [u8]) -> Result<()>; + /// Write the given data at `address` into the memory. + fn write_memory(&mut self, address: Pointer, data: &[u8]) -> Result<()>; + /// Allocate a memory instance of `size` bytes. + fn allocate_memory(&mut self, size: WordSize) -> Result>; + /// Deallocate a given memory instance. + fn deallocate_memory(&mut self, ptr: Pointer) -> Result<()>; + /// Provides access to the sandbox. + fn sandbox(&mut self) -> &mut dyn Sandbox; + /// Registers a panic error message within the executor. + /// + /// This is meant to be used in situations where the runtime + /// encounters an unrecoverable error and intends to panic. + /// + /// Panicking in WASM is done through the [`unreachable`](https://webassembly.github.io/spec/core/syntax/instructions.html#syntax-instr-control) + /// instruction which causes an unconditional trap and immediately aborts + /// the execution. It does not however allow for any diagnostics to be + /// passed through to the host, so while we do know that *something* went + /// wrong we don't have any direct indication of what *exactly* went wrong. + /// + /// As a workaround we use this method right before the execution is + /// actually aborted to pass an error message to the host so that it + /// can associate it with the next trap, and return that to the caller. + /// + /// A WASM trap should be triggered immediately after calling this method; + /// otherwise the error message might be associated with a completely + /// unrelated trap. + /// + /// It should only be called once, however calling it more than once + /// is harmless and will overwrite the previously set error message. + fn register_panic_error_message(&mut self, message: &str); +} + +/// Something that provides access to the sandbox. +pub trait Sandbox { + /// Get sandbox memory from the `memory_id` instance at `offset` into the given buffer. + fn memory_get( + &mut self, + memory_id: MemoryId, + offset: WordSize, + buf_ptr: Pointer, + buf_len: WordSize, + ) -> WResult; + /// Set sandbox memory from the given value. + fn memory_set( + &mut self, + memory_id: MemoryId, + offset: WordSize, + val_ptr: Pointer, + val_len: WordSize, + ) -> WResult; + /// Delete a memory instance. + fn memory_teardown(&mut self, memory_id: MemoryId) -> WResult<()>; + /// Create a new memory instance with the given `initial` size and the `maximum` size. + /// The size is given in wasm pages. + fn memory_new(&mut self, initial: u32, maximum: u32) -> WResult; + /// Invoke an exported function by a name. + fn invoke( + &mut self, + instance_id: u32, + export_name: &str, + args: &[u8], + return_val: Pointer, + return_val_len: WordSize, + state: u32, + ) -> WResult; + /// Delete a sandbox instance. + fn instance_teardown(&mut self, instance_id: u32) -> WResult<()>; + /// Create a new sandbox instance. + // fn instance_new( + // &mut self, + // dispatch_thunk_id: u32, + // wasm: &[u8], + // raw_env_def: &[u8], + // state: u32, + // ) -> Result; + + /// Get the value from a global with the given `name`. The sandbox is determined by the + /// given `instance_idx` instance. + /// + /// Returns `Some(_)` when the requested global variable could be found. + fn get_global_val(&self, instance_idx: u32, name: &str) -> WResult>; + + /// Instantiate a new sandbox instance with the given `wasm_code`. + fn instantiate( + &mut self, + dispatch_thunk: u32, + wasm_code: &[u8], + env_def: &[u8], + state_ptr: Pointer, + ) -> u32; +} + + +if_wasmtime_is_enabled! { + /// A trait used to statically register host callbacks with the WASM executor, + /// so that they call be called from within the runtime with minimal overhead. + /// + /// This is used internally to interface the wasmtime-based executor with the + /// host functions' definitions generated through the runtime interface macro, + /// and is not meant to be used directly. + pub trait HostFunctionRegistry { + type State; + type Error; + type FunctionContext: FunctionContext; + + /// Wraps the given `caller` in a type which implements `FunctionContext` + /// and calls the given `callback`. + fn with_function_context( + caller: wasmtime::Caller, + callback: impl FnOnce(&mut dyn FunctionContext) -> R, + ) -> R; + + /// Registers a given host function with the WASM executor. + /// + /// The function has to be statically callable, and all of its arguments + /// and its return value have to be compatible with WASM FFI. + fn register_static( + &mut self, + fn_name: &str, + func: impl wasmtime::IntoFunc + 'static, + ) -> core::result::Result<(), Self::Error>; + } +} + +/// Something that provides implementations for host functions. +pub trait HostFunctions: 'static + Send + Sync { + /// Returns the host functions `Self` provides. + fn host_functions() -> Vec<&'static dyn Function>; + + if_wasmtime_is_enabled! { + /// Statically registers the host functions. + fn register_static(registry: &mut T) -> core::result::Result<(), T::Error> + where + T: HostFunctionRegistry; + } +} + +#[impl_trait_for_tuples::impl_for_tuples(30)] +impl HostFunctions for Tuple { + fn host_functions() -> Vec<&'static dyn Function> { + let mut host_functions = Vec::new(); + + for_tuples!( #( host_functions.extend(Tuple::host_functions()); )* ); + + host_functions + } + + #[cfg(all(feature = "std", feature = "wasmtime"))] + fn register_static(registry: &mut T) -> core::result::Result<(), T::Error> + where + T: HostFunctionRegistry, + { + for_tuples!( + #( Tuple::register_static(registry)?; )* + ); + + Ok(()) + } +} + +/// A wrapper which merges two sets of host functions, and allows the second set to override +/// the host functions from the first set. +pub struct ExtendedHostFunctions { + phantom: PhantomData<(Base, Overlay)>, +} + +impl HostFunctions for ExtendedHostFunctions + where + Base: HostFunctions, + Overlay: HostFunctions, +{ + fn host_functions() -> Vec<&'static dyn Function> { + let mut base = Base::host_functions(); + let overlay = Overlay::host_functions(); + base.retain(|host_fn| { + !overlay.iter().any(|ext_host_fn| host_fn.name() == ext_host_fn.name()) + }); + base.extend(overlay); + base + } + + if_wasmtime_is_enabled! { + fn register_static(registry: &mut T) -> core::result::Result<(), T::Error> + where + T: HostFunctionRegistry, + { + struct Proxy<'a, T> { + registry: &'a mut T, + seen_overlay: std::collections::HashSet, + seen_base: std::collections::HashSet, + overlay_registered: bool, + } + + impl<'a, T> HostFunctionRegistry for Proxy<'a, T> + where + T: HostFunctionRegistry, + { + type State = T::State; + type Error = T::Error; + type FunctionContext = T::FunctionContext; + + fn with_function_context( + caller: wasmtime::Caller, + callback: impl FnOnce(&mut dyn FunctionContext) -> R, + ) -> R { + T::with_function_context(caller, callback) + } + + fn register_static( + &mut self, + fn_name: &str, + func: impl wasmtime::IntoFunc + 'static, + ) -> core::result::Result<(), Self::Error> { + if self.overlay_registered { + if !self.seen_base.insert(fn_name.to_owned()) { + log::warn!( + target: "extended_host_functions", + "Duplicate base host function: '{}'", + fn_name, + ); + + // TODO: Return an error here? + return Ok(()) + } + + if self.seen_overlay.contains(fn_name) { + // Was already registered when we went through the overlay, so just ignore it. + log::debug!( + target: "extended_host_functions", + "Overriding base host function: '{}'", + fn_name, + ); + + return Ok(()) + } + } else if !self.seen_overlay.insert(fn_name.to_owned()) { + log::warn!( + target: "extended_host_functions", + "Duplicate overlay host function: '{}'", + fn_name, + ); + + // TODO: Return an error here? + return Ok(()) + } + + self.registry.register_static(fn_name, func) + } + } + + let mut proxy = Proxy { + registry, + seen_overlay: Default::default(), + seen_base: Default::default(), + overlay_registered: false, + }; + + // The functions from the `Overlay` can override those from the `Base`, + // so `Overlay` is registered first, and then we skip those functions + // in `Base` if they were already registered from the `Overlay`. + Overlay::register_static(&mut proxy)?; + proxy.overlay_registered = true; + Base::register_static(&mut proxy)?; + + Ok(()) + } + } +} + +macro_rules! impl_into_and_from_value { + ( + $( + $type:ty, $( < $gen:ident >, )? $value_variant:ident, + )* + ) => { + $( + impl $( <$gen> )? IntoValue for $type { + const VALUE_TYPE: ValueType = ValueType::$value_variant; + fn into_value(self) -> Value { Value::$value_variant(self as _) } + } + + impl $( <$gen> )? TryFromValue for $type { + fn try_from_value(val: Value) -> Option { + match val { + Value::$value_variant(val) => Some(val as _), + _ => None, + } + } + } + )* + } +} + +impl_into_and_from_value! { + u8, I32, + u16, I32, + u32, I32, + u64, I64, + i8, I32, + i16, I32, + i32, I32, + i64, I64, +} + +/// Typed value that can be returned from a function. +/// +/// Basically a `TypedValue` plus `Unit`, for functions which return nothing. +#[derive(Clone, Copy, PartialEq, codec::Encode, codec::Decode, Debug)] +pub enum ReturnValue { + /// For returning nothing. + Unit, + /// For returning some concrete value. + Value(Value), +} + +impl From for ReturnValue { + fn from(v: Value) -> ReturnValue { + ReturnValue::Value(v) + } +} + +impl ReturnValue { + /// Maximum number of bytes `ReturnValue` might occupy when serialized with `SCALE`. + /// + /// Breakdown: + /// 1 byte for encoding unit/value variant + /// 1 byte for encoding value type + /// 8 bytes for encoding the biggest value types available in wasm: f64, i64. + pub const ENCODED_MAX_SIZE: usize = 10; +} diff --git a/node/runtime-interface-macro/proc-macro/Cargo.toml b/node/runtime-interface-macro/proc-macro/Cargo.toml new file mode 100644 index 000000000..01cba5391 --- /dev/null +++ b/node/runtime-interface-macro/proc-macro/Cargo.toml @@ -0,0 +1,30 @@ +[package] +name = "sp-runtime-interface-proc-macro-local" +version = "18.0.0" +authors.workspace = true +edition.workspace = true +license = "Apache-2.0" +homepage.workspace = true +#repository.workspace = true +description = "This crate provides procedural macros for usage within the context of the Substrate runtime interface." +documentation = "https://docs.rs/sp-runtime-interface-proc-macro" + +#[lints] +#workspace = true + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] + +[lib] +proc-macro = true + +[dependencies] +Inflector = { workspace = true } +codec = { features = ["derive"], workspace = true } +proc-macro-crate = { workspace = true } +proc-macro2 = { workspace = true } +quote = { workspace = true } +expander = { workspace = true } +syn = { features = ["extra-traits", "fold", "full", "visit"], workspace = true } +sp-wasm-interface = { workspace = true } +cere-wasm-interface = { workspace = true } diff --git a/node/runtime-interface-macro/proc-macro/README.md b/node/runtime-interface-macro/proc-macro/README.md new file mode 100644 index 000000000..96e931ea4 --- /dev/null +++ b/node/runtime-interface-macro/proc-macro/README.md @@ -0,0 +1,5 @@ + + +## Release + +Polkadot SDK stable2409 diff --git a/node/runtime-interface-macro/proc-macro/src/lib.rs b/node/runtime-interface-macro/proc-macro/src/lib.rs new file mode 100644 index 000000000..e6f060c21 --- /dev/null +++ b/node/runtime-interface-macro/proc-macro/src/lib.rs @@ -0,0 +1,109 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! This crate provides procedural macros for usage within the context of the Substrate runtime +//! interface. +//! +//! The following macros are provided: +//! +//! 1. The [`#[runtime_interface]`](attr.runtime_interface.html) attribute macro for generating the +//! runtime interfaces. +//! 2. The [`PassByCodec`](derive.PassByCodec.html) derive macro for implementing `PassBy` with +//! `Codec`. 3. The [`PassByEnum`](derive.PassByInner.html) derive macro for implementing `PassBy` +//! with `Enum`. 4. The [`PassByInner`](derive.PassByInner.html) derive macro for implementing +//! `PassBy` with `Inner`. + +use syn::{ + parse::{Parse, ParseStream}, + parse_macro_input, DeriveInput, ItemTrait, Result, Token, +}; + +mod pass_by; +mod runtime_interface; +mod utils; + +struct Options { + wasm_only: bool, + tracing: bool, +} + +impl Options { + fn unpack(self) -> (bool, bool) { + (self.wasm_only, self.tracing) + } +} +impl Default for Options { + fn default() -> Self { + Options { wasm_only: false, tracing: true } + } +} + +impl Parse for Options { + fn parse(input: ParseStream) -> Result { + let mut res = Self::default(); + while !input.is_empty() { + let lookahead = input.lookahead1(); + if lookahead.peek(runtime_interface::keywords::wasm_only) { + let _ = input.parse::(); + res.wasm_only = true; + } else if lookahead.peek(runtime_interface::keywords::no_tracing) { + let _ = input.parse::(); + res.tracing = false; + } else if lookahead.peek(Token![,]) { + let _ = input.parse::(); + } else { + return Err(lookahead.error()) + } + } + Ok(res) + } +} + +#[proc_macro_attribute] +pub fn runtime_interface( + attrs: proc_macro::TokenStream, + input: proc_macro::TokenStream, +) -> proc_macro::TokenStream { + let trait_def = parse_macro_input!(input as ItemTrait); + let (wasm_only, tracing) = parse_macro_input!(attrs as Options).unpack(); + + runtime_interface::runtime_interface_impl(trait_def, wasm_only, tracing) + .unwrap_or_else(|e| e.to_compile_error()) + .into() +} + +#[proc_macro_derive(PassByCodec)] +pub fn pass_by_codec(input: proc_macro::TokenStream) -> proc_macro::TokenStream { + let input = parse_macro_input!(input as DeriveInput); + pass_by::codec_derive_impl(input) + .unwrap_or_else(|e| e.to_compile_error()) + .into() +} + +#[proc_macro_derive(PassByInner)] +pub fn pass_by_inner(input: proc_macro::TokenStream) -> proc_macro::TokenStream { + let input = parse_macro_input!(input as DeriveInput); + pass_by::inner_derive_impl(input) + .unwrap_or_else(|e| e.to_compile_error()) + .into() +} + +#[proc_macro_derive(PassByEnum)] +pub fn pass_by_enum(input: proc_macro::TokenStream) -> proc_macro::TokenStream { + let input = parse_macro_input!(input as DeriveInput); + pass_by::enum_derive_impl(input).unwrap_or_else(|e| e.to_compile_error()).into() +} diff --git a/node/runtime-interface-macro/proc-macro/src/pass_by/codec.rs b/node/runtime-interface-macro/proc-macro/src/pass_by/codec.rs new file mode 100644 index 000000000..a1b7bccd3 --- /dev/null +++ b/node/runtime-interface-macro/proc-macro/src/pass_by/codec.rs @@ -0,0 +1,59 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Derive macro implementation of `PassBy` with the associated type set to `Codec`. +//! +//! It is required that the type implements `Encode` and `Decode` from the `parity-scale-codec` +//! crate. + +use crate::utils::{generate_crate_access, generate_runtime_interface_include}; + +use syn::{parse_quote, DeriveInput, Generics, Result}; + +use quote::quote; + +use proc_macro2::TokenStream; + +/// The derive implementation for `PassBy` with `Codec`. +pub fn derive_impl(mut input: DeriveInput) -> Result { + add_trait_bounds(&mut input.generics); + let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl(); + let crate_include = generate_runtime_interface_include(); + let crate_ = generate_crate_access(); + let ident = input.ident; + + let res = quote! { + const _: () = { + #crate_include + + impl #impl_generics #crate_::pass_by::PassBy for #ident #ty_generics #where_clause { + type PassBy = #crate_::pass_by::Codec<#ident>; + } + }; + }; + + Ok(res) +} + +/// Add the `codec::Codec` trait bound to every type parameter. +fn add_trait_bounds(generics: &mut Generics) { + let crate_ = generate_crate_access(); + + generics + .type_params_mut() + .for_each(|type_param| type_param.bounds.push(parse_quote!(#crate_::codec::Codec))); +} diff --git a/node/runtime-interface-macro/proc-macro/src/pass_by/enum_.rs b/node/runtime-interface-macro/proc-macro/src/pass_by/enum_.rs new file mode 100644 index 000000000..d0eb382cd --- /dev/null +++ b/node/runtime-interface-macro/proc-macro/src/pass_by/enum_.rs @@ -0,0 +1,101 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Derive macro implementation of `PassBy` with the associated type set to `Enum`. +//! +//! Besides `PassBy`, `TryFrom` and `From for u8` are implemented for the type. + +use crate::utils::{generate_crate_access, generate_runtime_interface_include}; + +use syn::{Data, DeriveInput, Error, Fields, Ident, Result}; + +use quote::quote; + +use proc_macro2::{Span, TokenStream}; + +/// The derive implementation for `PassBy` with `Enum`. +pub fn derive_impl(input: DeriveInput) -> Result { + let crate_include = generate_runtime_interface_include(); + let crate_ = generate_crate_access(); + let ident = input.ident; + let enum_fields = get_enum_field_idents(&input.data)? + .enumerate() + .map(|(i, v)| { + let i = i as u8; + + v.map(|v| (quote!(#i => Ok(#ident::#v)), quote!(#ident::#v => #i))) + }) + .collect::>>()?; + let try_from_variants = enum_fields.iter().map(|i| &i.0); + let into_variants = enum_fields.iter().map(|i| &i.1); + + let res = quote! { + const _: () = { + #crate_include + + impl #crate_::pass_by::PassBy for #ident { + type PassBy = #crate_::pass_by::Enum<#ident>; + } + + impl TryFrom for #ident { + type Error = (); + + fn try_from(inner: u8) -> core::result::Result { + match inner { + #( #try_from_variants, )* + _ => Err(()), + } + } + } + + impl From<#ident> for u8 { + fn from(var: #ident) -> u8 { + match var { + #( #into_variants ),* + } + } + } + }; + }; + + Ok(res) +} + +/// Get the enum fields idents of the given `data` object as iterator. +/// +/// Returns an error if the number of variants is greater than `256`, the given `data` is not an +/// enum or a variant is not an unit. +fn get_enum_field_idents(data: &Data) -> Result>> { + match data { + Data::Enum(d) => + if d.variants.len() <= 256 { + Ok(d.variants.iter().map(|v| { + if let Fields::Unit = v.fields { + Ok(&v.ident) + } else { + Err(Error::new( + Span::call_site(), + "`PassByEnum` only supports unit variants.", + )) + } + })) + } else { + Err(Error::new(Span::call_site(), "`PassByEnum` only supports `256` variants.")) + }, + _ => Err(Error::new(Span::call_site(), "`PassByEnum` only supports enums as input type.")), + } +} diff --git a/node/runtime-interface-macro/proc-macro/src/pass_by/inner.rs b/node/runtime-interface-macro/proc-macro/src/pass_by/inner.rs new file mode 100644 index 000000000..cc51fe44f --- /dev/null +++ b/node/runtime-interface-macro/proc-macro/src/pass_by/inner.rs @@ -0,0 +1,110 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Derive macro implementation of `PassBy` with the associated type set to `Inner` and of the +//! helper trait `PassByInner`. +//! +//! It is required that the type is a newtype struct, otherwise an error is generated. + +use crate::utils::{generate_crate_access, generate_runtime_interface_include}; + +use syn::{parse_quote, Data, DeriveInput, Error, Fields, Generics, Ident, Result, Type}; + +use quote::quote; + +use proc_macro2::{Span, TokenStream}; + +/// The derive implementation for `PassBy` with `Inner` and `PassByInner`. +pub fn derive_impl(mut input: DeriveInput) -> Result { + add_trait_bounds(&mut input.generics); + let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl(); + let crate_include = generate_runtime_interface_include(); + let crate_ = generate_crate_access(); + let ident = input.ident; + let (inner_ty, inner_name) = extract_inner_ty_and_name(&input.data)?; + + let access_inner = match inner_name { + Some(ref name) => quote!(self.#name), + None => quote!(self.0), + }; + + let from_inner = match inner_name { + Some(name) => quote!(Self { #name: inner }), + None => quote!(Self(inner)), + }; + + let res = quote! { + const _: () = { + #crate_include + + impl #impl_generics #crate_::pass_by::PassBy for #ident #ty_generics #where_clause { + type PassBy = #crate_::pass_by::Inner; + } + + impl #impl_generics #crate_::pass_by::PassByInner for #ident #ty_generics #where_clause { + type Inner = #inner_ty; + + fn into_inner(self) -> Self::Inner { + #access_inner + } + + fn inner(&self) -> &Self::Inner { + &#access_inner + } + + fn from_inner(inner: Self::Inner) -> Self { + #from_inner + } + } + }; + }; + + Ok(res) +} + +/// Add the `RIType` trait bound to every type parameter. +fn add_trait_bounds(generics: &mut Generics) { + let crate_ = generate_crate_access(); + + generics + .type_params_mut() + .for_each(|type_param| type_param.bounds.push(parse_quote!(#crate_::RIType))); +} + +/// Extract the inner type and optional name from given input data. +/// +/// It also checks that the input data is a newtype struct. +fn extract_inner_ty_and_name(data: &Data) -> Result<(Type, Option)> { + if let Data::Struct(ref struct_data) = data { + match struct_data.fields { + Fields::Named(ref named) if named.named.len() == 1 => { + let field = &named.named[0]; + return Ok((field.ty.clone(), field.ident.clone())) + }, + Fields::Unnamed(ref unnamed) if unnamed.unnamed.len() == 1 => { + let field = &unnamed.unnamed[0]; + return Ok((field.ty.clone(), field.ident.clone())) + }, + _ => {}, + } + } + + Err(Error::new( + Span::call_site(), + "Only newtype/one field structs are supported by `PassByInner`!", + )) +} diff --git a/node/runtime-interface-macro/proc-macro/src/pass_by/mod.rs b/node/runtime-interface-macro/proc-macro/src/pass_by/mod.rs new file mode 100644 index 000000000..f3d51d362 --- /dev/null +++ b/node/runtime-interface-macro/proc-macro/src/pass_by/mod.rs @@ -0,0 +1,26 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! All the `PassBy*` derive implementations. + +mod codec; +mod enum_; +mod inner; + +pub use self::codec::derive_impl as codec_derive_impl; +pub use enum_::derive_impl as enum_derive_impl; +pub use inner::derive_impl as inner_derive_impl; diff --git a/node/runtime-interface-macro/proc-macro/src/runtime_interface/bare_function_interface.rs b/node/runtime-interface-macro/proc-macro/src/runtime_interface/bare_function_interface.rs new file mode 100644 index 000000000..065fdbe6c --- /dev/null +++ b/node/runtime-interface-macro/proc-macro/src/runtime_interface/bare_function_interface.rs @@ -0,0 +1,262 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Generates the bare function interface for a given trait definition. +//! +//! The bare functions are the ones that will be called by the user. On the native/host side, these +//! functions directly execute the provided implementation. On the wasm side, these +//! functions will prepare the parameters for the FFI boundary, call the external host function +//! exported into wasm and convert back the result. +//! +//! [`generate`] is the entry point for generating for each +//! trait method one bare function. +//! +//! [`function_for_method`] generates the bare +//! function per trait method. Each bare function contains both implementations. The implementations +//! are feature-gated, so that one is compiled for the native and the other for the wasm side. + +use crate::utils::{ + create_exchangeable_host_function_ident, create_function_ident_with_version, + generate_crate_access, get_function_argument_names, get_function_arguments, + get_runtime_interface, RuntimeInterfaceFunction, +}; + +use syn::{ + parse_quote, spanned::Spanned, FnArg, Ident, ItemTrait, Result, Signature, Token, TraitItemFn, +}; + +use proc_macro2::{Span, TokenStream}; + +use quote::{quote, quote_spanned}; + +use std::iter; + +/// Generate one bare function per trait method. The name of the bare function is equal to the name +/// of the trait method. +pub fn generate(trait_def: &ItemTrait, is_wasm_only: bool, tracing: bool) -> Result { + let trait_name = &trait_def.ident; + let runtime_interface = get_runtime_interface(trait_def)?; + + // latest version dispatch + let token_stream: Result = runtime_interface.latest_versions_to_call().try_fold( + TokenStream::new(), + |mut t, (latest_version, method)| { + t.extend(function_for_method(method, latest_version, is_wasm_only)?); + Ok(t) + }, + ); + + // earlier versions compatibility dispatch (only std variant) + let result: Result = + runtime_interface + .all_versions() + .try_fold(token_stream?, |mut t, (version, method)| { + t.extend(function_std_impl(trait_name, method, version, is_wasm_only, tracing)?); + Ok(t) + }); + + result +} + +/// Generates the bare function implementation for the given method for the host and wasm side. +fn function_for_method( + method: &RuntimeInterfaceFunction, + latest_version: u32, + is_wasm_only: bool, +) -> Result { + let std_impl = + if !is_wasm_only { function_std_latest_impl(method, latest_version)? } else { quote!() }; + + let no_std_impl = function_no_std_impl(method, is_wasm_only)?; + + Ok(quote! { + #std_impl + + #no_std_impl + }) +} + +/// Generates the bare function implementation for `cfg(not(feature = "std"))`. +fn function_no_std_impl( + method: &RuntimeInterfaceFunction, + is_wasm_only: bool, +) -> Result { + let function_name = &method.sig.ident; + let host_function_name = create_exchangeable_host_function_ident(&method.sig.ident); + let args = get_function_arguments(&method.sig); + let arg_names = get_function_argument_names(&method.sig); + let return_value = if method.should_trap_on_return() { + syn::ReturnType::Type( + ]>::default(), + Box::new(syn::TypeNever { bang_token: ::default() }.into()), + ) + } else { + method.sig.output.clone() + }; + let maybe_unreachable = if method.should_trap_on_return() { + quote! { + ; + #[cfg(target_family = "wasm")] + { core::arch::wasm32::unreachable(); } + + #[cfg(any(target_arch = "riscv32", target_arch = "riscv64"))] + unsafe { core::arch::asm!("unimp", options(noreturn)); } + } + } else { + quote! {} + }; + + let attrs = method.attrs.iter().filter(|a| !a.path().is_ident("version")); + + let cfg_wasm_only = if is_wasm_only { + quote! { #[cfg(substrate_runtime)] } + } else { + quote! {} + }; + + Ok(quote! { + #cfg_wasm_only + #[cfg(not(feature = "std"))] + #( #attrs )* + pub fn #function_name( #( #args, )* ) #return_value { + // Call the host function + #host_function_name.get()( #( #arg_names, )* ) + #maybe_unreachable + } + }) +} + +/// Generate call to latest function version for `cfg((feature = "std")` +/// +/// This should generate simple `fn func(..) { func_version_(..) }`. +fn function_std_latest_impl(method: &TraitItemFn, latest_version: u32) -> Result { + let function_name = &method.sig.ident; + let args = get_function_arguments(&method.sig).map(FnArg::Typed); + let arg_names = get_function_argument_names(&method.sig).collect::>(); + let return_value = &method.sig.output; + let attrs = method.attrs.iter().filter(|a| !a.path().is_ident("version")); + let latest_function_name = + create_function_ident_with_version(&method.sig.ident, latest_version); + + Ok(quote_spanned! { method.span() => + #[cfg(feature = "std")] + #( #attrs )* + pub fn #function_name( #( #args, )* ) #return_value { + #latest_function_name( + #( #arg_names, )* + ) + } + }) +} + +/// Generates the bare function implementation for `cfg(feature = "std")`. +fn function_std_impl( + trait_name: &Ident, + method: &TraitItemFn, + version: u32, + is_wasm_only: bool, + tracing: bool, +) -> Result { + let function_name = create_function_ident_with_version(&method.sig.ident, version); + let function_name_str = function_name.to_string(); + + let crate_ = generate_crate_access(); + let args = get_function_arguments(&method.sig).map(FnArg::Typed).chain( + // Add the function context as last parameter when this is a wasm only interface. + iter::from_fn(|| { + if is_wasm_only { + Some(parse_quote!( + mut __function_context__: &mut dyn #crate_::cere_wasm_interface::FunctionContext + )) + } else { + None + } + }) + .take(1), + ); + let return_value = &method.sig.output; + let attrs = method.attrs.iter().filter(|a| !a.path().is_ident("version")); + // Don't make the function public accessible when this is a wasm only interface. + let call_to_trait = generate_call_to_trait(trait_name, method, version, is_wasm_only); + let call_to_trait = if !tracing { + call_to_trait + } else { + parse_quote!( + #crate_::sp_tracing::within_span! { #crate_::sp_tracing::trace_span!(#function_name_str); + #call_to_trait + } + ) + }; + + Ok(quote_spanned! { method.span() => + #[cfg(feature = "std")] + #( #attrs )* + fn #function_name( #( #args, )* ) #return_value { + #call_to_trait + } + }) +} + +/// Generate the call to the interface trait. +fn generate_call_to_trait( + trait_name: &Ident, + method: &TraitItemFn, + version: u32, + is_wasm_only: bool, +) -> TokenStream { + let crate_ = generate_crate_access(); + let method_name = create_function_ident_with_version(&method.sig.ident, version); + let expect_msg = + format!("`{}` called outside of an Externalities-provided environment.", method_name); + let arg_names = get_function_argument_names(&method.sig); + + if takes_self_argument(&method.sig) { + let instance = if is_wasm_only { + Ident::new("__function_context__", Span::call_site()) + } else { + Ident::new("__externalities__", Span::call_site()) + }; + + let impl_ = quote!( #trait_name::#method_name(&mut #instance, #( #arg_names, )*) ); + + if is_wasm_only { + quote_spanned! { method.span() => #impl_ } + } else { + quote_spanned! { method.span() => + #crate_::with_externalities(|mut #instance| #impl_).expect(#expect_msg) + } + } + } else { + // The name of the trait the interface trait is implemented for + let impl_trait_name = if is_wasm_only { + quote!( #crate_::cere_wasm_interface::FunctionContext ) + } else { + quote!( #crate_::Externalities ) + }; + + quote_spanned! { method.span() => + <&mut dyn #impl_trait_name as #trait_name>::#method_name( + #( #arg_names, )* + ) + } + } +} + +/// Returns if the given `Signature` takes a `self` argument. +fn takes_self_argument(sig: &Signature) -> bool { + matches!(sig.inputs.first(), Some(FnArg::Receiver(_))) +} diff --git a/node/runtime-interface-macro/proc-macro/src/runtime_interface/host_function_interface.rs b/node/runtime-interface-macro/proc-macro/src/runtime_interface/host_function_interface.rs new file mode 100644 index 000000000..b420b73c5 --- /dev/null +++ b/node/runtime-interface-macro/proc-macro/src/runtime_interface/host_function_interface.rs @@ -0,0 +1,466 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Generates the extern host functions and the implementation for these host functions. +//! +//! The extern host functions will be called by the bare function interface from the Wasm side. +//! The implementation of these host functions will be called on the host side from the Wasm +//! executor. These implementations call the bare function interface. + +use crate::utils::{ + create_exchangeable_host_function_ident, create_function_ident_with_version, + create_host_function_ident, generate_crate_access, get_function_argument_names, + get_function_argument_names_and_types_without_ref, get_function_argument_types, + get_function_argument_types_ref_and_mut, get_function_argument_types_without_ref, + get_function_arguments, get_runtime_interface, RuntimeInterfaceFunction, +}; + +use syn::{ + spanned::Spanned, Error, Ident, ItemTrait, Pat, Result, ReturnType, Signature, TraitItemFn, +}; + +use proc_macro2::{Span, TokenStream}; + +use quote::quote; + +use inflector::Inflector; + +use std::iter::Iterator; + +/// Generate the extern host functions for wasm and the `HostFunctions` struct that provides the +/// implementations for the host functions on the host. +pub fn generate(trait_def: &ItemTrait, is_wasm_only: bool) -> Result { + let trait_name = &trait_def.ident; + let extern_host_function_impls = get_runtime_interface(trait_def)? + .latest_versions_to_call() + .try_fold(TokenStream::new(), |mut t, (version, method)| { + t.extend(generate_extern_host_function(method, version, trait_name)?); + Ok::<_, Error>(t) + })?; + let exchangeable_host_functions = get_runtime_interface(trait_def)? + .latest_versions_to_call() + .try_fold(TokenStream::new(), |mut t, (_, m)| { + t.extend(generate_exchangeable_host_function(m)?); + Ok::<_, Error>(t) + })?; + let host_functions_struct = generate_host_functions_struct(trait_def, is_wasm_only)?; + + Ok(quote! { + /// The implementations of the extern host functions. This special implementation module + /// is required to change the extern host functions signature to + /// `unsafe fn name(args) -> ret` to make the function implementations exchangeable. + #[cfg(not(feature = "std"))] + mod extern_host_function_impls { + use super::*; + + #extern_host_function_impls + } + + #exchangeable_host_functions + + #host_functions_struct + }) +} + +/// Generate the extern host function for the given method. +fn generate_extern_host_function( + method: &TraitItemFn, + version: u32, + trait_name: &Ident, +) -> Result { + let crate_ = generate_crate_access(); + let args = get_function_arguments(&method.sig); + let arg_types = get_function_argument_types_without_ref(&method.sig); + let arg_types2 = get_function_argument_types_without_ref(&method.sig); + let arg_names = get_function_argument_names(&method.sig); + let arg_names2 = get_function_argument_names(&method.sig); + let arg_names3 = get_function_argument_names(&method.sig); + let function = &method.sig.ident; + let ext_function = create_host_function_ident(&method.sig.ident, version, trait_name); + let doc_string = format!( + " Default extern host function implementation for [`super::{}`].", + method.sig.ident, + ); + let return_value = &method.sig.output; + let cfg_attrs = method.attrs.iter().filter(|a| a.path().is_ident("cfg")); + + let ffi_return_value = match method.sig.output { + ReturnType::Default => quote!(), + ReturnType::Type(_, ref ty) => quote! { + -> <#ty as #crate_::RIType>::FFIType + }, + }; + + let convert_return_value = match return_value { + ReturnType::Default => quote!(), + ReturnType::Type(_, ref ty) => quote! { + <#ty as #crate_::wasm::FromFFIValue>::from_ffi_value(result) + }, + }; + + Ok(quote! { + #(#cfg_attrs)* + #[doc = #doc_string] + pub fn #function ( #( #args ),* ) #return_value { + #[cfg_attr(any(target_arch = "riscv32", target_arch = "riscv64"), #crate_::polkavm::polkavm_import(abi = #crate_::polkavm::polkavm_abi))] + extern "C" { + pub fn #ext_function ( + #( #arg_names: <#arg_types as #crate_::RIType>::FFIType ),* + ) #ffi_return_value; + } + + // Generate all wrapped ffi values. + #( + let #arg_names2 = <#arg_types2 as #crate_::wasm::IntoFFIValue>::into_ffi_value( + &#arg_names2, + ); + )* + + let result = unsafe { #ext_function( #( #arg_names3.get() ),* ) }; + + #convert_return_value + } + }) +} + +/// Generate the host exchangeable function for the given method. +fn generate_exchangeable_host_function(method: &TraitItemFn) -> Result { + let crate_ = generate_crate_access(); + let arg_types = get_function_argument_types(&method.sig); + let function = &method.sig.ident; + let exchangeable_function = create_exchangeable_host_function_ident(&method.sig.ident); + let doc_string = format!(" Exchangeable host function used by [`{}`].", method.sig.ident); + let output = &method.sig.output; + let cfg_attrs = method.attrs.iter().filter(|a| a.path().is_ident("cfg")); + + Ok(quote! { + #(#cfg_attrs)* + #[cfg(not(feature = "std"))] + #[allow(non_upper_case_globals)] + #[doc = #doc_string] + pub static #exchangeable_function : #crate_::wasm::ExchangeableFunction< + fn ( #( #arg_types ),* ) #output + > = #crate_::wasm::ExchangeableFunction::new(extern_host_function_impls::#function); + }) +} + +/// Generate the `HostFunctions` struct that implements `wasm-interface::HostFunctions` to provide +/// implementations for the extern host functions. +fn generate_host_functions_struct( + trait_def: &ItemTrait, + is_wasm_only: bool, +) -> Result { + let crate_ = generate_crate_access(); + + let mut host_function_impls = Vec::new(); + let mut register_bodies = Vec::new(); + let mut append_hf_bodies = Vec::new(); + + for (version, method) in get_runtime_interface(trait_def)?.all_versions() { + let (implementation, register_body, append_hf_body) = + generate_host_function_implementation(&trait_def.ident, method, version, is_wasm_only)?; + host_function_impls.push(implementation); + register_bodies.push(register_body); + append_hf_bodies.push(append_hf_body); + } + + Ok(quote! { + #(#host_function_impls)* + + /// Provides implementations for the extern host functions. + #[cfg(feature = "std")] + pub struct HostFunctions; + + #[cfg(feature = "std")] + impl #crate_::cere_wasm_interface::HostFunctions for HostFunctions { + fn host_functions() -> Vec<&'static dyn #crate_::cere_wasm_interface::Function> { + let mut host_functions_list = Vec::new(); + #(#append_hf_bodies)* + host_functions_list + } + + #crate_::cere_wasm_interface::if_wasmtime_is_enabled! { + fn register_static(registry: &mut T) -> core::result::Result<(), T::Error> + where T: #crate_::cere_wasm_interface::HostFunctionRegistry + { + #(#register_bodies)* + Ok(()) + } + } + } + }) +} + +/// Generates the host function struct that implements `wasm_interface::Function` and returns a +/// static reference to this struct. +/// +/// When calling from wasm into the host, we will call the `execute` function that calls the native +/// implementation of the function. +fn generate_host_function_implementation( + trait_name: &Ident, + method: &RuntimeInterfaceFunction, + version: u32, + is_wasm_only: bool, +) -> Result<(TokenStream, TokenStream, TokenStream)> { + let name = create_host_function_ident(&method.sig.ident, version, trait_name).to_string(); + let struct_name = Ident::new(&name.to_pascal_case(), Span::call_site()); + let crate_ = generate_crate_access(); + let signature = generate_wasm_interface_signature_for_host_function(&method.sig)?; + + let fn_name = create_function_ident_with_version(&method.sig.ident, version); + let ref_and_mut = get_function_argument_types_ref_and_mut(&method.sig); + + // List of variable names containing WASM FFI-compatible arguments. + let mut ffi_names = Vec::new(); + + // List of `$name: $ty` tokens containing WASM FFI-compatible arguments. + let mut ffi_args_prototype = Vec::new(); + + // List of variable names containing arguments already converted into native Rust types. + // Also includes the preceding `&` or `&mut`. To be used to call the actual implementation of + // the host function. + let mut host_names_with_ref = Vec::new(); + + // List of code snippets to copy over the results returned from a host function through + // any `&mut` arguments back into WASM's linear memory. + let mut copy_data_into_ref_mut_args = Vec::new(); + + // List of code snippets to convert dynamic FFI args (`Value` enum) into concrete static FFI + // types (`u32`, etc.). + let mut convert_args_dynamic_ffi_to_static_ffi = Vec::new(); + + // List of code snippets to convert static FFI args (`u32`, etc.) into native Rust types. + let mut convert_args_static_ffi_to_host = Vec::new(); + + for ((host_name, host_ty), ref_and_mut) in + get_function_argument_names_and_types_without_ref(&method.sig).zip(ref_and_mut) + { + let ffi_name = generate_ffi_value_var_name(&host_name)?; + let host_name_ident = match *host_name { + Pat::Ident(ref pat_ident) => pat_ident.ident.clone(), + _ => unreachable!("`generate_ffi_value_var_name` above would return an error on `Pat` != `Ident`; qed"), + }; + + let ffi_ty = quote! { <#host_ty as #crate_::RIType>::FFIType }; + ffi_args_prototype.push(quote! { #ffi_name: #ffi_ty }); + ffi_names.push(quote! { #ffi_name }); + + let convert_arg_error = format!( + "could not marshal the '{}' argument through the WASM FFI boundary while executing '{}' from interface '{}'", + host_name_ident, + method.sig.ident, + trait_name + ); + convert_args_static_ffi_to_host.push(quote! { + let mut #host_name = <#host_ty as #crate_::host::FromFFIValue>::from_ffi_value(__function_context__, #ffi_name) + .map_err(|err| format!("{}: {}", err, #convert_arg_error))?; + }); + + let ref_and_mut_tokens = + ref_and_mut.map(|(token_ref, token_mut)| quote!(#token_ref #token_mut)); + + host_names_with_ref.push(quote! { #ref_and_mut_tokens #host_name }); + + if ref_and_mut.map(|(_, token_mut)| token_mut.is_some()).unwrap_or(false) { + copy_data_into_ref_mut_args.push(quote! { + <#host_ty as #crate_::host::IntoPreallocatedFFIValue>::into_preallocated_ffi_value( + #host_name, + __function_context__, + #ffi_name, + )?; + }); + } + + let arg_count_mismatch_error = format!( + "missing argument '{}': number of arguments given to '{}' from interface '{}' does not match the expected number of arguments", + host_name_ident, + method.sig.ident, + trait_name + ); + convert_args_dynamic_ffi_to_static_ffi.push(quote! { + let #ffi_name = args.next().ok_or_else(|| #arg_count_mismatch_error.to_owned())?; + let #ffi_name: #ffi_ty = #crate_::cere_wasm_interface::TryFromValue::try_from_value(#ffi_name) + .ok_or_else(|| #convert_arg_error.to_owned())?; + }); + } + + let ffi_return_ty = match &method.sig.output { + ReturnType::Type(_, ty) => quote! { <#ty as #crate_::RIType>::FFIType }, + ReturnType::Default => quote! { () }, + }; + + let convert_return_value_host_to_static_ffi = match &method.sig.output { + ReturnType::Type(_, ty) => quote! { + let __result__ = <#ty as #crate_::host::IntoFFIValue>::into_ffi_value( + __result__, + __function_context__ + ); + }, + ReturnType::Default => quote! { + let __result__ = Ok(__result__); + }, + }; + + let convert_return_value_static_ffi_to_dynamic_ffi = match &method.sig.output { + ReturnType::Type(_, _) => quote! { + let __result__ = Ok(Some(#crate_::cere_wasm_interface::IntoValue::into_value(__result__))); + }, + ReturnType::Default => quote! { + let __result__ = Ok(None); + }, + }; + + if is_wasm_only { + host_names_with_ref.push(quote! { + __function_context__ + }); + } + + let cfg_attrs: Vec<_> = + method.attrs.iter().filter(|a| a.path().is_ident("cfg")).cloned().collect(); + if version > 1 && !cfg_attrs.is_empty() { + return Err(Error::new( + method.span(), + "Conditional compilation is not supported for versioned functions", + )) + } + + let implementation = quote! { + #(#cfg_attrs)* + #[cfg(feature = "std")] + struct #struct_name; + + #(#cfg_attrs)* + #[cfg(feature = "std")] + impl #struct_name { + fn call( + __function_context__: &mut dyn #crate_::cere_wasm_interface::FunctionContext, + #(#ffi_args_prototype),* + ) -> std::result::Result<#ffi_return_ty, String> { + #(#convert_args_static_ffi_to_host)* + let __result__ = #fn_name(#(#host_names_with_ref),*); + #(#copy_data_into_ref_mut_args)* + #convert_return_value_host_to_static_ffi + __result__ + } + } + + #(#cfg_attrs)* + #[cfg(feature = "std")] + impl #crate_::cere_wasm_interface::Function for #struct_name { + fn name(&self) -> &str { + #name + } + + fn signature(&self) -> #crate_::cere_wasm_interface::Signature { + #signature + } + + fn execute( + &self, + __function_context__: &mut dyn #crate_::cere_wasm_interface::FunctionContext, + args: &mut dyn Iterator, + ) -> std::result::Result, String> { + #(#convert_args_dynamic_ffi_to_static_ffi)* + let __result__ = Self::call( + __function_context__, + #(#ffi_names),* + )?; + #convert_return_value_static_ffi_to_dynamic_ffi + __result__ + } + } + }; + + let register_body = quote! { + #(#cfg_attrs)* + registry.register_static( + #crate_::cere_wasm_interface::Function::name(&#struct_name), + |mut caller: #crate_::cere_wasm_interface::wasmtime::Caller, #(#ffi_args_prototype),*| + -> std::result::Result<#ffi_return_ty, #crate_::cere_wasm_interface::anyhow::Error> + { + T::with_function_context(caller, move |__function_context__| { + let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| { + #struct_name::call( + __function_context__, + #(#ffi_names,)* + ).map_err(#crate_::cere_wasm_interface::anyhow::Error::msg) + })); + match result { + Ok(result) => result, + Err(panic) => { + let message = + if let Some(message) = panic.downcast_ref::() { + format!("host code panicked while being called by the runtime: {}", message) + } else if let Some(message) = panic.downcast_ref::<&'static str>() { + format!("host code panicked while being called by the runtime: {}", message) + } else { + "host code panicked while being called by the runtime".to_owned() + }; + return Err(#crate_::cere_wasm_interface::anyhow::Error::msg(message)); + } + } + }) + } + )?; + }; + + let append_hf_body = quote! { + #(#cfg_attrs)* + host_functions_list.push(&#struct_name as &dyn #crate_::cere_wasm_interface::Function); + }; + + Ok((implementation, register_body, append_hf_body)) +} + +/// Generate the `wasm_interface::Signature` for the given host function `sig`. +fn generate_wasm_interface_signature_for_host_function(sig: &Signature) -> Result { + let crate_ = generate_crate_access(); + let return_value = match &sig.output { + ReturnType::Type(_, ty) => quote! { + Some( <<#ty as #crate_::RIType>::FFIType as #crate_::cere_wasm_interface::IntoValue>::VALUE_TYPE ) + }, + ReturnType::Default => quote!(None), + }; + let arg_types = get_function_argument_types_without_ref(sig).map(|ty| { + quote! { + <<#ty as #crate_::RIType>::FFIType as #crate_::cere_wasm_interface::IntoValue>::VALUE_TYPE + } + }); + + Ok(quote! { + #crate_::cere_wasm_interface::Signature { + args: std::borrow::Cow::Borrowed(&[ #( #arg_types ),* ][..]), + return_value: #return_value, + } + }) +} + +/// Generate the variable name that stores the FFI value. +fn generate_ffi_value_var_name(pat: &Pat) -> Result { + match pat { + Pat::Ident(pat_ident) => + if let Some(by_ref) = pat_ident.by_ref { + Err(Error::new(by_ref.span(), "`ref` not supported!")) + } else if let Some(sub_pattern) = &pat_ident.subpat { + Err(Error::new(sub_pattern.0.span(), "Not supported!")) + } else { + Ok(Ident::new(&format!("{}_ffi_value", pat_ident.ident), Span::call_site())) + }, + _ => Err(Error::new(pat.span(), "Not supported as variable name!")), + } +} diff --git a/node/runtime-interface-macro/proc-macro/src/runtime_interface/mod.rs b/node/runtime-interface-macro/proc-macro/src/runtime_interface/mod.rs new file mode 100644 index 000000000..d0cc9e7b9 --- /dev/null +++ b/node/runtime-interface-macro/proc-macro/src/runtime_interface/mod.rs @@ -0,0 +1,78 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use crate::utils::generate_runtime_interface_include; + +use proc_macro2::{Span, TokenStream}; + +use syn::{Ident, ItemTrait, Result}; + +use inflector::Inflector; + +use quote::quote; + +mod bare_function_interface; +mod host_function_interface; +mod trait_decl_impl; + +/// Custom keywords supported by the `runtime_interface` attribute. +pub mod keywords { + // Custom keyword `wasm_only` that can be given as attribute to [`runtime_interface`]. + syn::custom_keyword!(wasm_only); + // Disable tracing-macros added to the [`runtime_interface`] by specifying this optional entry + syn::custom_keyword!(no_tracing); +} + +/// Implementation of the `runtime_interface` attribute. +/// +/// It expects the trait definition the attribute was put above and if this should be an wasm only +/// interface. +pub fn runtime_interface_impl( + trait_def: ItemTrait, + is_wasm_only: bool, + tracing: bool, +) -> Result { + let bare_functions = bare_function_interface::generate(&trait_def, is_wasm_only, tracing)?; + let crate_include = generate_runtime_interface_include(); + let mod_name = Ident::new(&trait_def.ident.to_string().to_snake_case(), Span::call_site()); + let trait_decl_impl = trait_decl_impl::process(&trait_def, is_wasm_only)?; + let host_functions = host_function_interface::generate(&trait_def, is_wasm_only)?; + let vis = trait_def.vis; + let attrs = &trait_def.attrs; + + let res = quote! { + #( #attrs )* + #vis mod #mod_name { + use super::*; + #crate_include + + #bare_functions + + #trait_decl_impl + + #host_functions + } + }; + + let res = expander::Expander::new("runtime_interface") + .dry(std::env::var("EXPAND_MACROS").is_err()) + .verbose(true) + .write_to_out_dir(res) + .expect("Does not fail because of IO in OUT_DIR; qed"); + + Ok(res) +} diff --git a/node/runtime-interface-macro/proc-macro/src/runtime_interface/trait_decl_impl.rs b/node/runtime-interface-macro/proc-macro/src/runtime_interface/trait_decl_impl.rs new file mode 100644 index 000000000..7ac49e842 --- /dev/null +++ b/node/runtime-interface-macro/proc-macro/src/runtime_interface/trait_decl_impl.rs @@ -0,0 +1,174 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Checks the trait declaration, makes the trait declaration module local, removes all method +//! default implementations and implements the trait for `&mut dyn Externalities`. + +use crate::utils::{ + create_function_ident_with_version, generate_crate_access, + get_function_argument_types_without_ref, get_runtime_interface, +}; + +use syn::{ + fold::{self, Fold}, + spanned::Spanned, + Error, Generics, ItemTrait, Receiver, Result, TraitItemFn, Type, Visibility, +}; + +use proc_macro2::TokenStream; + +use quote::quote; + +/// Process the given trait definition, by checking that the definition is valid, fold it to the +/// essential definition and implement this essential definition for `dyn Externalities`. +pub fn process(trait_def: &ItemTrait, is_wasm_only: bool) -> Result { + let impl_trait = impl_trait_for_externalities(trait_def, is_wasm_only)?; + let essential_trait_def = declare_essential_trait(trait_def)?; + + Ok(quote! { + #impl_trait + + #essential_trait_def + }) +} + +/// Converts the given trait definition into the essential trait definition without method +/// default implementations and visibility set to inherited. +struct ToEssentialTraitDef { + /// All errors found while doing the conversion. + errors: Vec, + methods: Vec, +} + +impl ToEssentialTraitDef { + fn new() -> Self { + ToEssentialTraitDef { errors: vec![], methods: vec![] } + } + + fn into_methods(self) -> Result> { + let mut errors = self.errors; + let methods = self.methods; + if let Some(first_error) = errors.pop() { + Err(errors.into_iter().fold(first_error, |mut o, n| { + o.combine(n); + o + })) + } else { + Ok(methods) + } + } + + fn process(&mut self, method: &TraitItemFn, version: u32) { + let mut folded = self.fold_trait_item_fn(method.clone()); + folded.sig.ident = create_function_ident_with_version(&folded.sig.ident, version); + self.methods.push(folded); + } + + fn push_error(&mut self, span: &S, msg: &str) { + self.errors.push(Error::new(span.span(), msg)); + } + + fn error_on_generic_parameters(&mut self, generics: &Generics) { + if let Some(param) = generics.params.first() { + self.push_error(param, "Generic parameters not supported."); + } + } +} + +impl Fold for ToEssentialTraitDef { + fn fold_trait_item_fn(&mut self, mut method: TraitItemFn) -> TraitItemFn { + if method.default.take().is_none() { + self.push_error(&method, "Methods need to have an implementation."); + } + + let arg_types = get_function_argument_types_without_ref(&method.sig); + arg_types + .filter_map(|ty| match *ty { + Type::ImplTrait(impl_trait) => Some(impl_trait), + _ => None, + }) + .for_each(|invalid| self.push_error(&invalid, "`impl Trait` syntax not supported.")); + + self.error_on_generic_parameters(&method.sig.generics); + + method.attrs.retain(|a| !a.path().is_ident("version")); + + fold::fold_trait_item_fn(self, method) + } + + fn fold_item_trait(&mut self, mut trait_def: ItemTrait) -> ItemTrait { + self.error_on_generic_parameters(&trait_def.generics); + + trait_def.vis = Visibility::Inherited; + fold::fold_item_trait(self, trait_def) + } + + fn fold_receiver(&mut self, receiver: Receiver) -> Receiver { + if receiver.reference.is_none() { + self.push_error(&receiver, "Taking `Self` by value is not allowed."); + } + + fold::fold_receiver(self, receiver) + } +} + +fn declare_essential_trait(trait_def: &ItemTrait) -> Result { + let trait_ = &trait_def.ident; + + if let Some(param) = trait_def.generics.params.first() { + return Err(Error::new(param.span(), "Generic parameters not supported.")) + } + + let interface = get_runtime_interface(trait_def)?; + let mut folder = ToEssentialTraitDef::new(); + for (version, interface_method) in interface.all_versions() { + folder.process(interface_method, version); + } + let methods = folder.into_methods()?; + + Ok(quote! { + trait #trait_ { + #( #methods )* + } + }) +} + +/// Implements the given trait definition for `dyn Externalities`. +fn impl_trait_for_externalities(trait_def: &ItemTrait, is_wasm_only: bool) -> Result { + let trait_ = &trait_def.ident; + let crate_ = generate_crate_access(); + let interface = get_runtime_interface(trait_def)?; + let methods = interface.all_versions().map(|(version, method)| { + let mut cloned = (*method).clone(); + cloned.attrs.retain(|a| !a.path().is_ident("version")); + cloned.sig.ident = create_function_ident_with_version(&cloned.sig.ident, version); + cloned + }); + + let impl_type = if is_wasm_only { + quote!( &mut dyn #crate_::cere_wasm_interface::FunctionContext ) + } else { + quote!( &mut dyn #crate_::Externalities ) + }; + + Ok(quote! { + #[cfg(feature = "std")] + impl #trait_ for #impl_type { + #( #methods )* + } + }) +} diff --git a/node/runtime-interface-macro/proc-macro/src/utils.rs b/node/runtime-interface-macro/proc-macro/src/utils.rs new file mode 100644 index 000000000..3be50e18f --- /dev/null +++ b/node/runtime-interface-macro/proc-macro/src/utils.rs @@ -0,0 +1,372 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Util function used by this crate. + +use proc_macro2::{Span, TokenStream}; + +use syn::{ + parse::Parse, parse_quote, spanned::Spanned, token, Error, FnArg, Ident, ItemTrait, LitInt, + Pat, PatType, Result, Signature, TraitItem, TraitItemFn, Type, +}; + +use proc_macro_crate::{crate_name, FoundCrate}; + +use std::{ + collections::{btree_map::Entry, BTreeMap}, + env, +}; + +use quote::quote; + +use inflector::Inflector; + +mod attributes { + syn::custom_keyword!(register_only); +} + +/// A concrete, specific version of a runtime interface function. +pub struct RuntimeInterfaceFunction { + item: TraitItemFn, + should_trap_on_return: bool, +} + +impl std::ops::Deref for RuntimeInterfaceFunction { + type Target = TraitItemFn; + fn deref(&self) -> &Self::Target { + &self.item + } +} + +impl RuntimeInterfaceFunction { + fn new(item: &TraitItemFn) -> Result { + let mut item = item.clone(); + let mut should_trap_on_return = false; + item.attrs.retain(|attr| { + if attr.path().is_ident("trap_on_return") { + should_trap_on_return = true; + false + } else { + true + } + }); + + if should_trap_on_return && !matches!(item.sig.output, syn::ReturnType::Default) { + return Err(Error::new( + item.sig.ident.span(), + "Methods marked as #[trap_on_return] cannot return anything", + )) + } + + Ok(Self { item, should_trap_on_return }) + } + + pub fn should_trap_on_return(&self) -> bool { + self.should_trap_on_return + } +} + +/// Runtime interface function with all associated versions of this function. +struct RuntimeInterfaceFunctionSet { + latest_version_to_call: Option, + versions: BTreeMap, +} + +impl RuntimeInterfaceFunctionSet { + fn new(version: VersionAttribute, trait_item: &TraitItemFn) -> Result { + Ok(Self { + latest_version_to_call: version.is_callable().then_some(version.version), + versions: BTreeMap::from([( + version.version, + RuntimeInterfaceFunction::new(trait_item)?, + )]), + }) + } + + /// Returns the latest version of this runtime interface function plus the actual function + /// implementation. + /// + /// This isn't required to be the latest version, because a runtime interface function can be + /// annotated with `register_only` to ensure that the host exposes the host function but it + /// isn't used when compiling the runtime. + pub fn latest_version_to_call(&self) -> Option<(u32, &RuntimeInterfaceFunction)> { + self.latest_version_to_call.map(|v| { + ( + v, + self.versions.get(&v).expect( + "If latest_version_to_call has a value, the key with this value is in the versions; qed", + ), + ) + }) + } + + /// Add a different version of the function. + fn add_version(&mut self, version: VersionAttribute, trait_item: &TraitItemFn) -> Result<()> { + if let Some(existing_item) = self.versions.get(&version.version) { + let mut err = Error::new(trait_item.span(), "Duplicated version attribute"); + err.combine(Error::new( + existing_item.span(), + "Previous version with the same number defined here", + )); + + return Err(err) + } + + self.versions + .insert(version.version, RuntimeInterfaceFunction::new(trait_item)?); + if self.latest_version_to_call.map_or(true, |v| v < version.version) && + version.is_callable() + { + self.latest_version_to_call = Some(version.version); + } + + Ok(()) + } +} + +/// All functions of a runtime interface grouped by the function names. +pub struct RuntimeInterface { + items: BTreeMap, +} + +impl RuntimeInterface { + /// Returns an iterator over all runtime interface function + /// [`latest_version_to_call`](RuntimeInterfaceFunctionSet::latest_version). + pub fn latest_versions_to_call( + &self, + ) -> impl Iterator { + self.items.iter().filter_map(|(_, item)| item.latest_version_to_call()) + } + + pub fn all_versions(&self) -> impl Iterator { + self.items + .iter() + .flat_map(|(_, item)| item.versions.iter()) + .map(|(v, i)| (*v, i)) + } +} + +/// Generates the include for the runtime-interface crate. +pub fn generate_runtime_interface_include() -> TokenStream { + match crate_name("sp-runtime-interface-macro") { + Ok(FoundCrate::Itself) => quote!(), + Ok(FoundCrate::Name(crate_name)) => { + let crate_name = Ident::new(&crate_name, Span::call_site()); + quote!( + #[doc(hidden)] + extern crate #crate_name as proc_macro_runtime_interface; + ) + }, + Err(e) => { + let err = Error::new(Span::call_site(), e).to_compile_error(); + quote!( #err ) + }, + } +} + +/// Generates the access to the `sp-runtime-interface` crate. +pub fn generate_crate_access() -> TokenStream { + if env::var("CARGO_PKG_NAME").unwrap() == "sp-runtime-interface-macro" { + quote!(sp_runtime_interface_macro) + } else { + quote!(proc_macro_runtime_interface) + } +} + +/// Create the exchangeable host function identifier for the given function name. +pub fn create_exchangeable_host_function_ident(name: &Ident) -> Ident { + Ident::new(&format!("host_{}", name), Span::call_site()) +} + +/// Create the host function identifier for the given function name. +pub fn create_host_function_ident(name: &Ident, version: u32, trait_name: &Ident) -> Ident { + Ident::new( + &format!("ext_{}_{}_version_{}", trait_name.to_string().to_snake_case(), name, version), + Span::call_site(), + ) +} + +/// Create the host function identifier for the given function name. +pub fn create_function_ident_with_version(name: &Ident, version: u32) -> Ident { + Ident::new(&format!("{}_version_{}", name, version), Span::call_site()) +} + +/// Returns the function arguments of the given `Signature`, minus any `self` arguments. +pub fn get_function_arguments(sig: &Signature) -> impl Iterator + '_ { + sig.inputs + .iter() + .filter_map(|a| match a { + FnArg::Receiver(_) => None, + FnArg::Typed(pat_type) => Some(pat_type), + }) + .enumerate() + .map(|(i, arg)| { + let mut res = arg.clone(); + if let Pat::Wild(wild) = &*arg.pat { + let ident = + Ident::new(&format!("__runtime_interface_generated_{}_", i), wild.span()); + + res.pat = Box::new(parse_quote!( #ident )) + } + + res + }) +} + +/// Returns the function argument names of the given `Signature`, minus any `self`. +pub fn get_function_argument_names(sig: &Signature) -> impl Iterator> + '_ { + get_function_arguments(sig).map(|pt| pt.pat) +} + +/// Returns the function argument types of the given `Signature`, minus any `Self` type. +pub fn get_function_argument_types(sig: &Signature) -> impl Iterator> + '_ { + get_function_arguments(sig).map(|pt| pt.ty) +} + +/// Returns the function argument types, minus any `Self` type. If any of the arguments +/// is a reference, the underlying type without the ref is returned. +pub fn get_function_argument_types_without_ref( + sig: &Signature, +) -> impl Iterator> + '_ { + get_function_arguments(sig).map(|pt| pt.ty).map(|ty| match *ty { + Type::Reference(type_ref) => type_ref.elem, + _ => ty, + }) +} + +/// Returns the function argument names and types, minus any `self`. If any of the arguments +/// is a reference, the underlying type without the ref is returned. +pub fn get_function_argument_names_and_types_without_ref( + sig: &Signature, +) -> impl Iterator, Box)> + '_ { + get_function_arguments(sig).map(|pt| match *pt.ty { + Type::Reference(type_ref) => (pt.pat, type_ref.elem), + _ => (pt.pat, pt.ty), + }) +} + +/// Returns the `&`/`&mut` for all function argument types, minus the `self` arg. If a function +/// argument is not a reference, `None` is returned. +pub fn get_function_argument_types_ref_and_mut( + sig: &Signature, +) -> impl Iterator)>> + '_ { + get_function_arguments(sig).map(|pt| pt.ty).map(|ty| match *ty { + Type::Reference(type_ref) => Some((type_ref.and_token, type_ref.mutability)), + _ => None, + }) +} + +/// Returns an iterator over all trait methods for the given trait definition. +fn get_trait_methods(trait_def: &ItemTrait) -> impl Iterator { + trait_def.items.iter().filter_map(|i| match i { + TraitItem::Fn(ref method) => Some(method), + _ => None, + }) +} + +/// The version attribute that can be found above a runtime interface function. +/// +/// Supports the following formats: +/// - `#[version(1)]` +/// - `#[version(1, register_only)]` +/// +/// While this struct is only for parsing the inner parts inside the `()`. +struct VersionAttribute { + version: u32, + register_only: Option, +} + +impl VersionAttribute { + /// Is this function version callable? + fn is_callable(&self) -> bool { + self.register_only.is_none() + } +} + +impl Default for VersionAttribute { + fn default() -> Self { + Self { version: 1, register_only: None } + } +} + +impl Parse for VersionAttribute { + fn parse(input: syn::parse::ParseStream) -> Result { + let version: LitInt = input.parse()?; + let register_only = if input.peek(token::Comma) { + let _ = input.parse::(); + Some(input.parse()?) + } else { + if !input.is_empty() { + return Err(Error::new(input.span(), "Unexpected token, expected `,`.")) + } + + None + }; + + Ok(Self { version: version.base10_parse()?, register_only }) + } +} + +/// Return [`VersionAttribute`], if present. +fn get_item_version(item: &TraitItemFn) -> Result> { + item.attrs + .iter() + .find(|attr| attr.path().is_ident("version")) + .map(|attr| attr.parse_args()) + .transpose() +} + +/// Returns all runtime interface members, with versions. +pub fn get_runtime_interface(trait_def: &ItemTrait) -> Result { + let mut functions: BTreeMap = BTreeMap::new(); + + for item in get_trait_methods(trait_def) { + let name = item.sig.ident.clone(); + let version = get_item_version(item)?.unwrap_or_default(); + + if version.version < 1 { + return Err(Error::new(item.span(), "Version needs to be at least `1`.")) + } + + match functions.entry(name.clone()) { + Entry::Vacant(entry) => { + entry.insert(RuntimeInterfaceFunctionSet::new(version, item)?); + }, + Entry::Occupied(mut entry) => { + entry.get_mut().add_version(version, item)?; + }, + } + } + + for function in functions.values() { + let mut next_expected = 1; + for (version, item) in function.versions.iter() { + if next_expected != *version { + return Err(Error::new( + item.span(), + format!( + "Unexpected version attribute: missing version '{}' for this function", + next_expected + ), + )) + } + next_expected += 1; + } + } + + Ok(RuntimeInterface { items: functions }) +} diff --git a/node/runtime-interface-macro/src/host.rs b/node/runtime-interface-macro/src/host.rs new file mode 100644 index 000000000..1f394e91b --- /dev/null +++ b/node/runtime-interface-macro/src/host.rs @@ -0,0 +1,67 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Traits required by the runtime interface from the host side. + +use crate::RIType; + +use sp_wasm_interface::{Result}; +use cere_wasm_interface::FunctionContext; + +/// Something that can be converted into a ffi value. +pub trait IntoFFIValue: RIType { + /// Convert `self` into a ffi value. + fn into_ffi_value(self, context: &mut dyn FunctionContext) -> Result; +} + +/// Something that can be converted into a preallocated ffi value. +/// +/// Every type parameter that should be given as `&mut` into a runtime interface function, needs +/// to implement this trait. After executing the host implementation of the runtime interface +/// function, the value is copied into the preallocated wasm memory. +/// +/// This should only be used for types which have a fixed size, like slices. Other types like a vec +/// do not work with this interface, as we can not call into wasm to reallocate memory. So, this +/// trait should be implemented carefully. +pub trait IntoPreallocatedFFIValue: RIType { + /// As `Self` can be an unsized type, it needs to be represented by a sized type at the host. + /// This `SelfInstance` is the sized type. + type SelfInstance; + + /// Convert `self_instance` into the given preallocated ffi value. + fn into_preallocated_ffi_value( + self_instance: Self::SelfInstance, + context: &mut dyn FunctionContext, + allocated: Self::FFIType, + ) -> Result<()>; +} + +/// Something that can be created from a ffi value. +/// Implementations are safe to assume that the `arg` given to `from_ffi_value` +/// is only generated by the corresponding [`wasm::IntoFFIValue`](crate::wasm::IntoFFIValue) +/// implementation. +pub trait FromFFIValue: RIType { + /// As `Self` can be an unsized type, it needs to be represented by a sized type at the host. + /// This `SelfInstance` is the sized type. + type SelfInstance; + + /// Create `SelfInstance` from the given + fn from_ffi_value( + context: &mut dyn FunctionContext, + arg: Self::FFIType, + ) -> Result; +} diff --git a/node/runtime-interface-macro/src/impls.rs b/node/runtime-interface-macro/src/impls.rs new file mode 100644 index 000000000..fb2bf1b22 --- /dev/null +++ b/node/runtime-interface-macro/src/impls.rs @@ -0,0 +1,539 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Provides implementations for the runtime interface traits. + +#[cfg(feature = "std")] +use crate::host::*; +#[cfg(not(feature = "std"))] +use crate::wasm::*; +use crate::{ + pass_by::{Codec, Enum, Inner, PassBy, PassByInner}, + util::{pack_ptr_and_len, unpack_ptr_and_len}, + Pointer, RIType, +}; + +#[cfg(all(not(feature = "std"), not(feature = "disable_target_static_assertions")))] +use static_assertions::assert_eq_size; + +#[cfg(feature = "std")] +use sp_wasm_interface::{ Result}; +#[cfg(feature = "std")] +use cere_wasm_interface::{FunctionContext}; + +use codec::{Decode, Encode}; + +use core::{any::TypeId, mem}; + +use alloc::vec::Vec; + +#[cfg(feature = "std")] +use alloc::borrow::Cow; + +// Make sure that our assumptions for storing a pointer + its size in `u64` is valid. +#[cfg(all(not(feature = "std"), not(feature = "disable_target_static_assertions")))] +assert_eq_size!(usize, u32); +#[cfg(all(not(feature = "std"), not(feature = "disable_target_static_assertions")))] +assert_eq_size!(*const u8, u32); + +/// Implement the traits for the given primitive traits. +macro_rules! impl_traits_for_primitives { + ( + $( + $rty:ty, $fty:ty, + )* + ) => { + $( + /// The type is passed directly. + impl RIType for $rty { + type FFIType = $fty; + } + + #[cfg(not(feature = "std"))] + impl IntoFFIValue for $rty { + type Owned = (); + + fn into_ffi_value(&self) -> WrappedFFIValue<$fty> { + (*self as $fty).into() + } + } + + #[cfg(not(feature = "std"))] + impl FromFFIValue for $rty { + fn from_ffi_value(arg: $fty) -> $rty { + arg as $rty + } + } + + #[cfg(feature = "std")] + impl FromFFIValue for $rty { + type SelfInstance = $rty; + + fn from_ffi_value(_: &mut dyn FunctionContext, arg: $fty) -> Result<$rty> { + Ok(arg as $rty) + } + } + + #[cfg(feature = "std")] + impl IntoFFIValue for $rty { + fn into_ffi_value(self, _: &mut dyn FunctionContext) -> Result<$fty> { + Ok(self as $fty) + } + } + )* + } +} + +impl_traits_for_primitives! { + u8, u32, + u16, u32, + u32, u32, + u64, u64, + i8, i32, + i16, i32, + i32, i32, + i64, i64, +} + +/// `bool` is passed as `u32`. +/// +/// - `1`: true +/// - `0`: false +impl RIType for bool { + type FFIType = u32; +} + +#[cfg(not(feature = "std"))] +impl IntoFFIValue for bool { + type Owned = (); + + fn into_ffi_value(&self) -> WrappedFFIValue { + if *self { 1 } else { 0 }.into() + } +} + +#[cfg(not(feature = "std"))] +impl FromFFIValue for bool { + fn from_ffi_value(arg: u32) -> bool { + arg == 1 + } +} + +#[cfg(feature = "std")] +impl FromFFIValue for bool { + type SelfInstance = bool; + + fn from_ffi_value(_: &mut dyn FunctionContext, arg: u32) -> Result { + Ok(arg == 1) + } +} + +#[cfg(feature = "std")] +impl IntoFFIValue for bool { + fn into_ffi_value(self, _: &mut dyn FunctionContext) -> Result { + Ok(if self { 1 } else { 0 }) + } +} + +/// The type is passed as `u64`. +/// +/// The `u64` value is build by `length 32bit << 32 | pointer 32bit` +/// +/// If `T == u8` the length and the pointer are taken directly from `Self`. +/// Otherwise `Self` is encoded and the length and the pointer are taken from the encoded vector. +impl RIType for Vec { + type FFIType = u64; +} + +#[cfg(feature = "std")] +impl IntoFFIValue for Vec { + fn into_ffi_value(self, context: &mut dyn FunctionContext) -> Result { + let vec: Cow<'_, [u8]> = if TypeId::of::() == TypeId::of::() { + unsafe { Cow::Borrowed(mem::transmute(&self[..])) } + } else { + Cow::Owned(self.encode()) + }; + + let ptr = context.allocate_memory(vec.as_ref().len() as u32)?; + context.write_memory(ptr, &vec)?; + + Ok(pack_ptr_and_len(ptr.into(), vec.len() as u32)) + } +} + +#[cfg(feature = "std")] +impl FromFFIValue for Vec { + type SelfInstance = Vec; + + fn from_ffi_value(context: &mut dyn FunctionContext, arg: u64) -> Result> { + <[T] as FromFFIValue>::from_ffi_value(context, arg) + } +} + +#[cfg(not(feature = "std"))] +impl IntoFFIValue for Vec { + type Owned = Vec; + + fn into_ffi_value(&self) -> WrappedFFIValue> { + self[..].into_ffi_value() + } +} + +#[cfg(not(feature = "std"))] +impl FromFFIValue for Vec { + fn from_ffi_value(arg: u64) -> Vec { + let (ptr, len) = unpack_ptr_and_len(arg); + let len = len as usize; + + if len == 0 { + return Vec::new() + } + + let data = unsafe { Vec::from_raw_parts(ptr as *mut u8, len, len) }; + + if TypeId::of::() == TypeId::of::() { + unsafe { mem::transmute(data) } + } else { + Self::decode(&mut &data[..]).expect("Host to wasm values are encoded correctly; qed") + } + } +} + +/// The type is passed as `u64`. +/// +/// The `u64` value is build by `length 32bit << 32 | pointer 32bit` +/// +/// If `T == u8` the length and the pointer are taken directly from `Self`. +/// Otherwise `Self` is encoded and the length and the pointer are taken from the encoded vector. +impl RIType for [T] { + type FFIType = u64; +} + +#[cfg(feature = "std")] +impl FromFFIValue for [T] { + type SelfInstance = Vec; + + fn from_ffi_value(context: &mut dyn FunctionContext, arg: u64) -> Result> { + let (ptr, len) = unpack_ptr_and_len(arg); + + let vec = context.read_memory(Pointer::new(ptr), len)?; + + if TypeId::of::() == TypeId::of::() { + Ok(unsafe { mem::transmute(vec) }) + } else { + Ok(Vec::::decode(&mut &vec[..]) + .expect("Wasm to host values are encoded correctly; qed")) + } + } +} + +#[cfg(feature = "std")] +impl IntoPreallocatedFFIValue for [u8] { + type SelfInstance = Vec; + + fn into_preallocated_ffi_value( + self_instance: Self::SelfInstance, + context: &mut dyn FunctionContext, + allocated: u64, + ) -> Result<()> { + let (ptr, len) = unpack_ptr_and_len(allocated); + + if (len as usize) < self_instance.len() { + Err(format!( + "Preallocated buffer is not big enough (given {} vs needed {})!", + len, + self_instance.len() + )) + } else { + Ok(context.write_memory(Pointer::new(ptr), &self_instance)?) + } + } +} + +#[cfg(not(feature = "std"))] +impl IntoFFIValue for [T] { + type Owned = Vec; + + fn into_ffi_value(&self) -> WrappedFFIValue> { + if TypeId::of::() == TypeId::of::() { + let slice = unsafe { mem::transmute::<&[T], &[u8]>(self) }; + pack_ptr_and_len(slice.as_ptr() as u32, slice.len() as u32).into() + } else { + let data = self.encode(); + let ffi_value = pack_ptr_and_len(data.as_ptr() as u32, data.len() as u32); + (ffi_value, data).into() + } + } +} + +/// The type is passed as `u32`. +/// +/// The `u32` is the pointer to the array. +impl RIType for [u8; N] { + type FFIType = u32; +} + +#[cfg(not(feature = "std"))] +impl IntoFFIValue for [u8; N] { + type Owned = (); + + fn into_ffi_value(&self) -> WrappedFFIValue { + (self.as_ptr() as u32).into() + } +} + +#[cfg(not(feature = "std"))] +impl FromFFIValue for [u8; N] { + fn from_ffi_value(arg: u32) -> [u8; N] { + let mut res = [0u8; N]; + let data = unsafe { Vec::from_raw_parts(arg as *mut u8, N, N) }; + + res.copy_from_slice(&data); + + res + } +} + +#[cfg(feature = "std")] +impl FromFFIValue for [u8; N] { + type SelfInstance = [u8; N]; + + fn from_ffi_value(context: &mut dyn FunctionContext, arg: u32) -> Result<[u8; N]> { + let mut res = [0u8; N]; + context.read_memory_into(Pointer::new(arg), &mut res)?; + Ok(res) + } +} + +#[cfg(feature = "std")] +impl IntoFFIValue for [u8; N] { + fn into_ffi_value(self, context: &mut dyn FunctionContext) -> Result { + let addr = context.allocate_memory(N as u32)?; + context.write_memory(addr, &self)?; + Ok(addr.into()) + } +} + +#[cfg(feature = "std")] +impl IntoPreallocatedFFIValue for [u8; N] { + type SelfInstance = [u8; N]; + + fn into_preallocated_ffi_value( + self_instance: Self::SelfInstance, + context: &mut dyn FunctionContext, + allocated: u32, + ) -> Result<()> { + Ok(context.write_memory(Pointer::new(allocated), &self_instance)?) + } +} + +impl PassBy for core::result::Result { + type PassBy = Codec; +} + +impl PassBy for Option { + type PassBy = Codec; +} + +#[impl_trait_for_tuples::impl_for_tuples(30)] +#[tuple_types_no_default_trait_bound] +impl PassBy for Tuple +where + Self: codec::Codec, +{ + type PassBy = Codec; +} + +/// Implement `PassBy` with `Inner` for the given fixed sized hash types. +macro_rules! for_primitive_types { + { $( $hash:ident $n:expr ),* $(,)? } => { + $( + impl PassBy for primitive_types::$hash { + type PassBy = Inner; + } + + impl PassByInner for primitive_types::$hash { + type Inner = [u8; $n]; + + fn inner(&self) -> &Self::Inner { + &self.0 + } + + fn into_inner(self) -> Self::Inner { + self.0 + } + + fn from_inner(inner: Self::Inner) -> Self { + Self(inner) + } + } + )* + } +} + +for_primitive_types! { + H160 20, + H256 32, + H512 64, +} + +/// The type is passed as `u64`. +/// +/// The `u64` value is build by `length 32bit << 32 | pointer 32bit` +/// +/// The length and the pointer are taken directly from `Self`. +impl RIType for str { + type FFIType = u64; +} + +#[cfg(feature = "std")] +impl FromFFIValue for str { + type SelfInstance = String; + + fn from_ffi_value(context: &mut dyn FunctionContext, arg: u64) -> Result { + let (ptr, len) = unpack_ptr_and_len(arg); + + let vec = context.read_memory(Pointer::new(ptr), len)?; + + // The data is valid utf8, as it is stored as `&str` in wasm. + String::from_utf8(vec).map_err(|_| "Invalid utf8 data provided".into()) + } +} + +#[cfg(not(feature = "std"))] +impl IntoFFIValue for str { + type Owned = (); + + fn into_ffi_value(&self) -> WrappedFFIValue { + let bytes = self.as_bytes(); + pack_ptr_and_len(bytes.as_ptr() as u32, bytes.len() as u32).into() + } +} + +#[cfg(feature = "std")] +impl RIType for Pointer { + type FFIType = u32; +} + +/// The type is passed as `u32`. +#[cfg(not(feature = "std"))] +impl RIType for Pointer { + type FFIType = u32; +} + +#[cfg(not(feature = "std"))] +impl IntoFFIValue for Pointer { + type Owned = (); + + fn into_ffi_value(&self) -> WrappedFFIValue { + (*self as u32).into() + } +} + +#[cfg(not(feature = "std"))] +impl FromFFIValue for Pointer { + fn from_ffi_value(arg: u32) -> Self { + arg as _ + } +} + +#[cfg(feature = "std")] +impl FromFFIValue for Pointer { + type SelfInstance = Self; + + fn from_ffi_value(_: &mut dyn FunctionContext, arg: u32) -> Result { + Ok(Pointer::new(arg)) + } +} + +#[cfg(feature = "std")] +impl IntoFFIValue for Pointer { + fn into_ffi_value(self, _: &mut dyn FunctionContext) -> Result { + Ok(self.into()) + } +} + +/// Implement the traits for `u128`/`i128` +macro_rules! for_u128_i128 { + ($type:ty) => { + /// `u128`/`i128` is passed as `u32`. + /// + /// The `u32` is a pointer to an `[u8; 16]` array. + impl RIType for $type { + type FFIType = u32; + } + + #[cfg(not(feature = "std"))] + impl IntoFFIValue for $type { + type Owned = (); + + fn into_ffi_value(&self) -> WrappedFFIValue { + unsafe { (mem::transmute::<&Self, *const u8>(self) as u32).into() } + } + } + + #[cfg(not(feature = "std"))] + impl FromFFIValue for $type { + fn from_ffi_value(arg: u32) -> $type { + <$type>::from_le_bytes(<[u8; mem::size_of::<$type>()]>::from_ffi_value(arg)) + } + } + + #[cfg(feature = "std")] + impl FromFFIValue for $type { + type SelfInstance = $type; + + fn from_ffi_value(context: &mut dyn FunctionContext, arg: u32) -> Result<$type> { + let mut res = [0u8; mem::size_of::<$type>()]; + context.read_memory_into(Pointer::new(arg), &mut res)?; + Ok(<$type>::from_le_bytes(res)) + } + } + + #[cfg(feature = "std")] + impl IntoFFIValue for $type { + fn into_ffi_value(self, context: &mut dyn FunctionContext) -> Result { + let addr = context.allocate_memory(mem::size_of::<$type>() as u32)?; + context.write_memory(addr, &self.to_le_bytes())?; + Ok(addr.into()) + } + } + }; +} + +for_u128_i128!(u128); +for_u128_i128!(i128); + +impl PassBy for cere_wasm_interface::ValueType { + type PassBy = Enum; +} + +impl PassBy for cere_wasm_interface::Value { + type PassBy = Codec; +} + +impl PassBy for sp_storage::TrackedStorageKey { + type PassBy = Codec; +} + +impl PassBy for sp_storage::StateVersion { + type PassBy = Enum; +} + +impl PassBy for sp_externalities::MultiRemovalResults { + type PassBy = Codec; +} diff --git a/node/runtime-interface-macro/src/lib.rs b/node/runtime-interface-macro/src/lib.rs new file mode 100644 index 000000000..d2d550451 --- /dev/null +++ b/node/runtime-interface-macro/src/lib.rs @@ -0,0 +1,417 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Substrate runtime interface +//! +//! This crate provides types, traits and macros around runtime interfaces. A runtime interface is +//! a fixed interface between a Substrate runtime and a Substrate node. For a native runtime the +//! interface maps to a direct function call of the implementation. For a wasm runtime the interface +//! maps to an external function call. These external functions are exported by the wasm executor +//! and they map to the same implementation as the native calls. +//! +//! # Using a type in a runtime interface +//! +//! Any type that should be used in a runtime interface as argument or return value needs to +//! implement [`RIType`]. The associated type +//! [`FFIType`](./trait.RIType.html#associatedtype.FFIType) is the type that is used in the FFI +//! function to represent the actual type. For example `[T]` is represented by an `u64`. The slice +//! pointer and the length will be mapped to an `u64` value. For more information see this +//! [table](#ffi-type-and-conversion). The FFI function definition is used when calling from the +//! wasm runtime into the node. +//! +//! Traits are used to convert from a type to the corresponding +//! [`RIType::FFIType`](./trait.RIType.html#associatedtype.FFIType). +//! Depending on where and how a type should be used in a function signature, a combination of the +//! following traits need to be implemented: +//! +//! 1. Pass as function argument: [`wasm::IntoFFIValue`] and [`host::FromFFIValue`] +//! 2. As function return value: [`wasm::FromFFIValue`] and [`host::IntoFFIValue`] +//! 3. Pass as mutable function argument: [`host::IntoPreallocatedFFIValue`] +//! +//! The traits are implemented for most of the common types like `[T]`, `Vec`, arrays and +//! primitive types. +//! +//! For custom types, we provide the [`PassBy`](./pass_by#PassBy) trait and strategies that define +//! how a type is passed between the wasm runtime and the node. Each strategy also provides a derive +//! macro to simplify the implementation. +//! +//! # Performance +//! +//! To not waste any more performance when calling into the node, not all types are SCALE encoded +//! when being passed as arguments between the wasm runtime and the node. For most types that +//! are raw bytes like `Vec`, `[u8]` or `[u8; N]` we pass them directly, without SCALE encoding +//! them in front of. The implementation of [`RIType`] each type provides more information on how +//! the data is passed. +//! +//! # Declaring a runtime interface +//! +//! Declaring a runtime interface is similar to declaring a trait in Rust: +//! +//! ``` +//! #[sp_runtime_interface::runtime_interface] +//! trait RuntimeInterface { +//! fn some_function(value: &[u8]) -> bool { +//! value.iter().all(|v| *v > 125) +//! } +//! } +//! ``` +//! +//! For more information on declaring a runtime interface, see +//! [`#[runtime_interface]`](./attr.runtime_interface.html). +//! +//! # FFI type and conversion +//! +//! The following table documents how values of types are passed between the wasm and +//! the host side and how they are converted into the corresponding type. +//! +//! | Type | FFI type | Conversion | +//! |----|----|----| +//! | `u8` | `u32` | zero-extended to 32-bits | +//! | `u16` | `u32` | zero-extended to 32-bits | +//! | `u32` | `u32` | `Identity` | +//! | `u64` | `u64` | `Identity` | +//! | `i128` | `u32` | `v.as_ptr()` (pointer to a 16 byte array) | +//! | `i8` | `i32` | sign-extended to 32-bits | +//! | `i16` | `i32` | sign-extended to 32-bits | +//! | `i32` | `i32` | `Identity` | +//! | `i64` | `i64` | `Identity` | +//! | `u128` | `u32` | `v.as_ptr()` (pointer to a 16 byte array) | +//! | `bool` | `u32` | `if v { 1 } else { 0 }` | +//! | `&str` | `u64` | v.len() 32bit << 32 | v.as_ptr() 32bit | +//! | `&[u8]` | `u64` | v.len() 32bit << 32 | v.as_ptr() 32bit | +//! | `Vec` | `u64` | v.len() 32bit << 32 | v.as_ptr() 32bit | +//! | `Vec where T: Encode` | `u64` | `let e = v.encode();`

e.len() 32bit << 32 | e.as_ptr() 32bit | +//! | `&[T] where T: Encode` | `u64` | `let e = v.encode();`

e.len() 32bit << 32 | e.as_ptr() 32bit | +//! | `[u8; N]` | `u32` | `v.as_ptr()` | +//! | `*const T` | `u32` | `Identity` | +//! | `Option` | `u64` | `let e = v.encode();`

e.len() 32bit << 32 | e.as_ptr() 32bit | +//! | [`T where T: PassBy`](./pass_by#Inner) | Depends on inner | Depends on inner | +//! | [`T where T: PassBy`](./pass_by#Codec)|`u64`|v.len() 32bit << 32 |v.as_ptr() 32bit| +//! +//! `Identity` means that the value is converted directly into the corresponding FFI type. + +#![cfg_attr(not(feature = "std"), no_std)] + +extern crate self as sp_runtime_interface_macro; + +extern crate alloc; + +#[doc(hidden)] +#[cfg(feature = "std")] +pub use sp_wasm_interface; + +#[doc(hidden)] +pub use cere_wasm_interface; + +#[doc(hidden)] +pub use sp_tracing; + +#[doc(hidden)] +pub use sp_std; + +/// Attribute macro for transforming a trait declaration into a runtime interface. +/// +/// A runtime interface is a fixed interface between a Substrate compatible runtime and the +/// native node. This interface is callable from a native and a wasm runtime. The macro will +/// generate the corresponding code for the native implementation and the code for calling from +/// the wasm side to the native implementation. +/// +/// The macro expects the runtime interface declaration as trait declaration: +/// +/// ``` +/// # use sp_runtime_interface::runtime_interface; +/// +/// #[runtime_interface] +/// trait Interface { +/// /// A function that can be called from native/wasm. +/// /// +/// /// The implementation given to this function is only compiled on native. +/// fn call(data: &[u8]) -> Vec { +/// // Here you could call some rather complex code that only compiles on native or +/// // is way faster in native than executing it in wasm. +/// Vec::new() +/// } +/// /// Call function, but different version. +/// /// +/// /// For new runtimes, only function with latest version is reachable. +/// /// But old version (above) is still accessible for old runtimes. +/// /// Default version is 1. +/// #[version(2)] +/// fn call(data: &[u8]) -> Vec { +/// // Here you could call some rather complex code that only compiles on native or +/// // is way faster in native than executing it in wasm. +/// [17].to_vec() +/// } +/// +/// /// Call function, different version and only being registered. +/// /// +/// /// This `register_only` version is only being registered, aka exposed to the runtime, +/// /// but the runtime will still use the version 2 of this function. This is useful for when +/// /// new host functions should be introduced. Adding new host functions requires that all +/// /// nodes have the host functions available, because otherwise they fail at instantiation +/// /// of the runtime. With `register_only` the function will not be used when compiling the +/// /// runtime, but it will already be there for a future version of the runtime that will +/// /// switch to using these host function. +/// #[version(3, register_only)] +/// fn call(data: &[u8]) -> Vec { +/// // Here you could call some rather complex code that only compiles on native or +/// // is way faster in native than executing it in wasm. +/// [18].to_vec() +/// } +/// +/// /// A function can take a `&self` or `&mut self` argument to get access to the +/// /// `Externalities`. (The generated method does not require +/// /// this argument, so the function can be called just with the `optional` argument) +/// fn set_or_clear(&mut self, optional: Option>) { +/// match optional { +/// Some(value) => self.set_storage([1, 2, 3, 4].to_vec(), value), +/// None => self.clear_storage(&[1, 2, 3, 4]), +/// } +/// } +/// +/// /// A function can be gated behind a configuration (`cfg`) attribute. +/// /// To prevent ambiguity and confusion about what will be the final exposed host +/// /// functions list, conditionally compiled functions can't be versioned. +/// /// That is, conditionally compiled functions with `version`s greater than 1 +/// /// are not allowed. +/// #[cfg(feature = "experimental-function")] +/// fn gated_call(data: &[u8]) -> Vec { +/// [42].to_vec() +/// } +/// } +/// ``` +/// +/// The given example will generate roughly the following code for native: +/// +/// ``` +/// // The name of the trait is converted to snake case and used as mod name. +/// // +/// // Be aware that this module is not `public`, the visibility of the module is determined based +/// // on the visibility of the trait declaration. +/// mod interface { +/// trait Interface { +/// fn call_version_1(data: &[u8]) -> Vec; +/// fn call_version_2(data: &[u8]) -> Vec; +/// fn call_version_3(data: &[u8]) -> Vec; +/// fn set_or_clear_version_1(&mut self, optional: Option>); +/// #[cfg(feature = "experimental-function")] +/// fn gated_call_version_1(data: &[u8]) -> Vec; +/// } +/// +/// impl Interface for &mut dyn sp_externalities::Externalities { +/// fn call_version_1(data: &[u8]) -> Vec { Vec::new() } +/// fn call_version_2(data: &[u8]) -> Vec { [17].to_vec() } +/// fn call_version_3(data: &[u8]) -> Vec { [18].to_vec() } +/// fn set_or_clear_version_1(&mut self, optional: Option>) { +/// match optional { +/// Some(value) => self.set_storage([1, 2, 3, 4].to_vec(), value), +/// None => self.clear_storage(&[1, 2, 3, 4]), +/// } +/// } +/// #[cfg(feature = "experimental-function")] +/// fn gated_call_version_1(data: &[u8]) -> Vec { [42].to_vec() } +/// } +/// +/// pub fn call(data: &[u8]) -> Vec { +/// // only latest version is exposed +/// call_version_2(data) +/// } +/// +/// fn call_version_1(data: &[u8]) -> Vec { +/// <&mut dyn sp_externalities::Externalities as Interface>::call_version_1(data) +/// } +/// +/// fn call_version_2(data: &[u8]) -> Vec { +/// <&mut dyn sp_externalities::Externalities as Interface>::call_version_2(data) +/// } +/// +/// fn call_version_3(data: &[u8]) -> Vec { +/// <&mut dyn sp_externalities::Externalities as Interface>::call_version_3(data) +/// } +/// +/// pub fn set_or_clear(optional: Option>) { +/// set_or_clear_version_1(optional) +/// } +/// +/// fn set_or_clear_version_1(optional: Option>) { +/// sp_externalities::with_externalities(|mut ext| Interface::set_or_clear_version_1(&mut ext, optional)) +/// .expect("`set_or_clear` called outside of an Externalities-provided environment.") +/// } +/// +/// #[cfg(feature = "experimental-function")] +/// pub fn gated_call(data: &[u8]) -> Vec { +/// gated_call_version_1(data) +/// } +/// +/// #[cfg(feature = "experimental-function")] +/// fn gated_call_version_1(data: &[u8]) -> Vec { +/// <&mut dyn sp_externalities::Externalities as Interface>::gated_call_version_1(data) +/// } +/// +/// /// This type implements the `HostFunctions` trait (from `sp-wasm-interface`) and +/// /// provides the host implementation for the wasm side. The host implementation converts the +/// /// arguments from wasm to native and calls the corresponding native function. +/// /// +/// /// This type needs to be passed to the wasm executor, so that the host functions will be +/// /// registered in the executor. +/// pub struct HostFunctions; +/// } +/// ``` +/// +/// The given example will generate roughly the following code for wasm: +/// +/// ``` +/// mod interface { +/// mod extern_host_functions_impls { +/// /// Every function is exported by the native code as `ext_FUNCTION_NAME_version_VERSION`. +/// /// +/// /// The type for each argument of the exported function depends on +/// /// `::FFIType`. +/// /// +/// /// `key` holds the pointer and the length to the `data` slice. +/// pub fn call(data: &[u8]) -> Vec { +/// extern "C" { pub fn ext_call_version_2(key: u64); } +/// // Should call into external `ext_call_version_2(<[u8] as IntoFFIValue>::into_ffi_value(key))` +/// // But this is too much to replicate in a doc test so here we just return a dummy vector. +/// // Note that we jump into the latest version not marked as `register_only` (i.e. version 2). +/// Vec::new() +/// } +/// +/// /// `key` holds the pointer and the length of the `option` value. +/// pub fn set_or_clear(option: Option>) { +/// extern "C" { pub fn ext_set_or_clear_version_1(key: u64); } +/// // Same as above +/// } +/// +/// /// `key` holds the pointer and the length to the `data` slice. +/// #[cfg(feature = "experimental-function")] +/// pub fn gated_call(data: &[u8]) -> Vec { +/// extern "C" { pub fn ext_gated_call_version_1(key: u64); } +/// /// Same as above +/// Vec::new() +/// } +/// } +/// +/// /// The type is actually `ExchangeableFunction` (from `sp-runtime-interface`) and +/// /// by default this is initialized to jump into the corresponding function in +/// /// `extern_host_functions_impls`. +/// /// +/// /// This can be used to replace the implementation of the `call` function. +/// /// Instead of calling into the host, the callee will automatically call the other +/// /// implementation. +/// /// +/// /// To replace the implementation: +/// /// +/// /// `host_call.replace_implementation(some_other_impl)` +/// pub static host_call: () = (); +/// pub static host_set_or_clear: () = (); +/// #[cfg(feature = "experimental-feature")] +/// pub static gated_call: () = (); +/// +/// pub fn call(data: &[u8]) -> Vec { +/// // This is the actual call: `host_call.get()(data)` +/// // +/// // But that does not work for several reasons in this example, so we just return an +/// // empty vector. +/// Vec::new() +/// } +/// +/// pub fn set_or_clear(optional: Option>) { +/// // Same as above +/// } +/// +/// #[cfg(feature = "experimental-feature")] +/// pub fn gated_call(data: &[u8]) -> Vec { +/// // Same as above +/// Vec::new() +/// } +/// } +/// ``` +/// +/// # Argument types +/// +/// The macro supports any kind of argument type, as long as it implements [`RIType`] and the +/// required `FromFFIValue`/`IntoFFIValue`. The macro will convert each +/// argument to the corresponding FFI representation and will call into the host using this FFI +/// representation. On the host each argument is converted back to the native representation +/// and the native implementation is called. Any return value is handled in the same way. +/// +/// # Wasm only interfaces +/// +/// Some interfaces are only required from within the wasm runtime e.g. the allocator +/// interface. To support this, the macro can be called like `#[runtime_interface(wasm_only)]`. +/// This instructs the macro to make two significant changes to the generated code: +/// +/// 1. The generated functions are not callable from the native side. +/// 2. The trait as shown above is not implemented for [`Externalities`] and is instead +/// implemented for `FunctionContext` (from `sp-wasm-interface`). +/// +/// # Disable tracing +/// By adding `no_tracing` to the list of options you can prevent the wasm-side interface from +/// generating the default `sp-tracing`-calls. Note that this is rarely needed but only meant +/// for the case when that would create a circular dependency. You usually _do not_ want to add +/// this flag, as tracing doesn't cost you anything by default anyways (it is added as a no-op) +/// but is super useful for debugging later. +pub use sp_runtime_interface_proc_macro_local::runtime_interface; +// pub use cere_runtime_interface::*; + +#[doc(hidden)] +#[cfg(feature = "std")] +pub use sp_externalities::{ + set_and_run_with_externalities, with_externalities, ExtensionStore, Externalities, + ExternalitiesExt, +}; + +#[doc(hidden)] +pub use codec; + +#[cfg(all(any(target_arch = "riscv32", target_arch = "riscv64"), substrate_runtime))] +pub mod polkavm; + +#[cfg(feature = "std")] +pub mod host; +pub(crate) mod impls; +pub mod pass_by; +#[cfg(any(not(feature = "std"), doc))] +pub mod wasm; + +mod util; + +pub use util::{pack_ptr_and_len, unpack_ptr_and_len}; + +/// Something that can be used by the runtime interface as type to communicate between wasm and the +/// host. +/// +/// Every type that should be used in a runtime interface function signature needs to implement +/// this trait. +pub trait RIType { + /// The ffi type that is used to represent `Self`. + #[cfg(feature = "std")] + type FFIType: cere_wasm_interface::IntoValue + + cere_wasm_interface::TryFromValue + + sp_wasm_interface::WasmTy; + #[cfg(not(feature = "std"))] + type FFIType; +} + +/// A pointer that can be used in a runtime interface function signature. +#[cfg(not(feature = "std"))] +pub type Pointer = *mut T; + +/// A pointer that can be used in a runtime interface function signature. +#[cfg(feature = "std")] +pub type Pointer = cere_wasm_interface::Pointer; diff --git a/node/runtime-interface-macro/src/pass_by.rs b/node/runtime-interface-macro/src/pass_by.rs new file mode 100644 index 000000000..23d0beed8 --- /dev/null +++ b/node/runtime-interface-macro/src/pass_by.rs @@ -0,0 +1,435 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Provides the [`PassBy`] trait to simplify the implementation of the +//! runtime interface traits for custom types. +//! +//! [`Codec`], [`Inner`] and [`Enum`] are the provided strategy implementations. + +use crate::{ + util::{pack_ptr_and_len, unpack_ptr_and_len}, + RIType, +}; + +#[cfg(feature = "std")] +use crate::host::*; +#[cfg(not(feature = "std"))] +use crate::wasm::*; + +#[cfg(feature = "std")] +use sp_wasm_interface::{Result}; +#[cfg(feature = "std")] +use cere_wasm_interface::{FunctionContext, Pointer}; + +use core::marker::PhantomData; + +#[cfg(not(feature = "std"))] +use alloc::vec::Vec; + +/// Derive macro for implementing [`PassBy`] with the [`Codec`] strategy. +/// +/// This requires that the type implements [`Encode`](codec::Encode) and +/// [`Decode`](codec::Decode) from `parity-scale-codec`. +/// +/// # Example +/// +/// ``` +/// # use sp_runtime_interface::pass_by::PassByCodec; +/// # use codec::{Encode, Decode}; +/// #[derive(PassByCodec, Encode, Decode)] +/// struct EncodableType { +/// name: Vec, +/// param: u32, +/// } +/// ``` +pub use sp_runtime_interface_proc_macro_local::PassByCodec; + +/// Derive macro for implementing [`PassBy`] with the [`Inner`] strategy. +/// +/// Besides implementing [`PassBy`], this derive also implements the helper trait +/// [`PassByInner`]. +/// +/// The type is required to be a struct with just one field. The field type needs to implement +/// the required traits to pass it between the wasm and the native side. (See the runtime +/// interface crate for more information about these traits.) +/// +/// # Example +/// +/// ``` +/// # use sp_runtime_interface::pass_by::PassByInner; +/// #[derive(PassByInner)] +/// struct Data([u8; 32]); +/// ``` +/// +/// ``` +/// # use sp_runtime_interface::pass_by::PassByInner; +/// #[derive(PassByInner)] +/// struct Data { +/// data: [u8; 32], +/// } +/// ``` +pub use sp_runtime_interface_proc_macro_local::PassByInner; + +/// Derive macro for implementing [`PassBy`] with the [`Enum`] strategy. +/// +/// Besides implementing [`PassBy`], this derive also implements `TryFrom` and +/// `From for u8` for the type. +/// +/// The type is required to be an enum with only unit variants and at maximum `256` variants. +/// Also it is required that the type implements `Copy`. +/// +/// # Example +/// +/// ``` +/// # use sp_runtime_interface::pass_by::PassByEnum; +/// #[derive(PassByEnum, Copy, Clone)] +/// enum Data { +/// Okay, +/// NotOkay, +/// // This will not work with the derive. +/// //Why(u32), +/// } +/// ``` +pub use sp_runtime_interface_proc_macro_local::PassByEnum; + +/// Something that should be passed between wasm and the host using the given strategy. +/// +/// See [`Codec`], [`Inner`] or [`Enum`] for more information about the provided strategies. +pub trait PassBy: Sized { + /// The strategy that should be used to pass the type. + type PassBy: PassByImpl; +} + +/// Something that provides a strategy for passing a type between wasm and the host. +/// +/// This trait exposes the same functionality as [`crate::host::IntoFFIValue`] and +/// [`crate::host::FromFFIValue`] to delegate the implementation for a type to a different type. +/// +/// This trait is used for the host implementation. +#[cfg(feature = "std")] +pub trait PassByImpl: RIType { + /// Convert the given instance to the ffi value. + /// + /// For more information see: [`crate::host::IntoFFIValue::into_ffi_value`] + fn into_ffi_value(instance: T, context: &mut dyn FunctionContext) -> Result; + + /// Create `T` from the given ffi value. + /// + /// For more information see: [`crate::host::FromFFIValue::from_ffi_value`] + fn from_ffi_value(context: &mut dyn FunctionContext, arg: Self::FFIType) -> Result; +} + +/// Something that provides a strategy for passing a type between wasm and the host. +/// +/// This trait exposes the same functionality as [`crate::wasm::IntoFFIValue`] and +/// [`crate::wasm::FromFFIValue`] to delegate the implementation for a type to a different type. +/// +/// This trait is used for the wasm implementation. +#[cfg(not(feature = "std"))] +pub trait PassByImpl: RIType { + /// The owned rust type that is stored with the ffi value in [`crate::wasm::WrappedFFIValue`]. + type Owned; + + /// Convert the given `instance` into [`crate::wasm::WrappedFFIValue`]. + /// + /// For more information see: [`crate::wasm::IntoFFIValue::into_ffi_value`] + fn into_ffi_value(instance: &T) -> WrappedFFIValue; + + /// Create `T` from the given ffi value. + /// + /// For more information see: [`crate::wasm::FromFFIValue::from_ffi_value`] + fn from_ffi_value(arg: Self::FFIType) -> T; +} + +impl RIType for T { + type FFIType = ::FFIType; +} + +#[cfg(feature = "std")] +impl IntoFFIValue for T { + fn into_ffi_value( + self, + context: &mut dyn FunctionContext, + ) -> Result<::FFIType> { + T::PassBy::into_ffi_value(self, context) + } +} + +#[cfg(feature = "std")] +impl FromFFIValue for T { + type SelfInstance = Self; + + fn from_ffi_value( + context: &mut dyn FunctionContext, + arg: ::FFIType, + ) -> Result { + T::PassBy::from_ffi_value(context, arg) + } +} + +#[cfg(not(feature = "std"))] +impl IntoFFIValue for T { + type Owned = >::Owned; + + fn into_ffi_value(&self) -> WrappedFFIValue<::FFIType, Self::Owned> { + T::PassBy::into_ffi_value(self) + } +} + +#[cfg(not(feature = "std"))] +impl FromFFIValue for T { + fn from_ffi_value(arg: ::FFIType) -> Self { + T::PassBy::from_ffi_value(arg) + } +} + +/// The implementation of the pass by codec strategy. This strategy uses a SCALE encoded +/// representation of the type between wasm and the host. +/// +/// Use this type as associated type for [`PassBy`] to implement this strategy for a type. +/// +/// This type expects the type that wants to implement this strategy as generic parameter. +/// +/// [`PassByCodec`](derive.PassByCodec.html) is a derive macro to implement this strategy. +/// +/// # Example +/// ``` +/// # use sp_runtime_interface::pass_by::{PassBy, Codec}; +/// #[derive(codec::Encode, codec::Decode)] +/// struct Test; +/// +/// impl PassBy for Test { +/// type PassBy = Codec; +/// } +/// ``` +pub struct Codec(PhantomData); + +#[cfg(feature = "std")] +impl PassByImpl for Codec { + fn into_ffi_value(instance: T, context: &mut dyn FunctionContext) -> Result { + let vec = instance.encode(); + let ptr = context.allocate_memory(vec.len() as u32)?; + context.write_memory(ptr, &vec)?; + + Ok(pack_ptr_and_len(ptr.into(), vec.len() as u32)) + } + + fn from_ffi_value(context: &mut dyn FunctionContext, arg: Self::FFIType) -> Result { + let (ptr, len) = unpack_ptr_and_len(arg); + let vec = context.read_memory(Pointer::new(ptr), len)?; + T::decode(&mut &vec[..]).map_err(|e| format!("Could not decode value from wasm: {}", e)) + } +} + +#[cfg(not(feature = "std"))] +impl PassByImpl for Codec { + type Owned = Vec; + + fn into_ffi_value(instance: &T) -> WrappedFFIValue { + let data = instance.encode(); + let ffi_value = pack_ptr_and_len(data.as_ptr() as u32, data.len() as u32); + (ffi_value, data).into() + } + + fn from_ffi_value(arg: Self::FFIType) -> T { + let (ptr, len) = unpack_ptr_and_len(arg); + let len = len as usize; + + let encoded = if len == 0 { + bytes::Bytes::new() + } else { + bytes::Bytes::from(unsafe { Vec::from_raw_parts(ptr as *mut u8, len, len) }) + }; + + codec::decode_from_bytes(encoded).expect("Host to wasm values are encoded correctly; qed") + } +} + +/// The type is passed as `u64`. +/// +/// The `u64` value is build by `length 32bit << 32 | pointer 32bit` +/// +/// `Self` is encoded and the length and the pointer are taken from the encoded vector. +impl RIType for Codec { + type FFIType = u64; +} + +/// Trait that needs to be implemented by a type that should be passed between wasm and the host, +/// by using the inner type. See [`Inner`] for more information. +pub trait PassByInner: Sized { + /// The inner type that is wrapped by `Self`. + type Inner: RIType; + + /// Consumes `self` and returns the inner type. + fn into_inner(self) -> Self::Inner; + + /// Returns the reference to the inner type. + fn inner(&self) -> &Self::Inner; + + /// Construct `Self` from the given `inner`. + fn from_inner(inner: Self::Inner) -> Self; +} + +/// The implementation of the pass by inner type strategy. The type that uses this strategy will be +/// passed between wasm and the host by using the wrapped inner type. So, this strategy is only +/// usable by newtype structs. +/// +/// Use this type as associated type for [`PassBy`] to implement this strategy for a type. Besides +/// that the `PassByInner` trait need to be implemented as well. +/// +/// This type expects the type that wants to use this strategy as generic parameter `T` and the +/// inner type as generic parameter `I`. +/// +/// [`PassByInner`](derive.PassByInner.html) is a derive macro to implement this strategy. +/// +/// # Example +/// ``` +/// # use sp_runtime_interface::pass_by::{PassBy, Inner, PassByInner}; +/// struct Test([u8; 32]); +/// +/// impl PassBy for Test { +/// type PassBy = Inner; +/// } +/// +/// impl PassByInner for Test { +/// type Inner = [u8; 32]; +/// +/// fn into_inner(self) -> [u8; 32] { +/// self.0 +/// } +/// fn inner(&self) -> &[u8; 32] { +/// &self.0 +/// } +/// fn from_inner(inner: [u8; 32]) -> Self { +/// Self(inner) +/// } +/// } +/// ``` +pub struct Inner, I: RIType>(PhantomData<(T, I)>); + +#[cfg(feature = "std")] +impl, I: RIType> PassByImpl for Inner +where + I: IntoFFIValue + FromFFIValue, +{ + fn into_ffi_value(instance: T, context: &mut dyn FunctionContext) -> Result { + instance.into_inner().into_ffi_value(context) + } + + fn from_ffi_value(context: &mut dyn FunctionContext, arg: Self::FFIType) -> Result { + I::from_ffi_value(context, arg).map(T::from_inner) + } +} + +#[cfg(not(feature = "std"))] +impl, I: RIType> PassByImpl for Inner +where + I: IntoFFIValue + FromFFIValue, +{ + type Owned = I::Owned; + + fn into_ffi_value(instance: &T) -> WrappedFFIValue { + instance.inner().into_ffi_value() + } + + fn from_ffi_value(arg: Self::FFIType) -> T { + T::from_inner(I::from_ffi_value(arg)) + } +} + +/// The type is passed as the inner type. +impl, I: RIType> RIType for Inner { + type FFIType = I::FFIType; +} + +/// The implementation of the pass by enum strategy. This strategy uses an `u8` internally to pass +/// the enum between wasm and the host. So, this strategy only supports enums with unit variants. +/// +/// Use this type as associated type for [`PassBy`] to implement this strategy for a type. +/// +/// This type expects the type that wants to implement this strategy as generic parameter. Besides +/// that the type needs to implement `TryFrom` and `From for u8`. +/// +/// [`PassByEnum`](derive.PassByEnum.html) is a derive macro to implement this strategy. +/// +/// # Example +/// ``` +/// # use sp_runtime_interface::pass_by::{PassBy, Enum}; +/// #[derive(Clone, Copy)] +/// enum Test { +/// Test1, +/// Test2, +/// } +/// +/// impl From for u8 { +/// fn from(val: Test) -> u8 { +/// match val { +/// Test::Test1 => 0, +/// Test::Test2 => 1, +/// } +/// } +/// } +/// +/// impl TryFrom for Test { +/// type Error = (); +/// +/// fn try_from(val: u8) -> Result { +/// match val { +/// 0 => Ok(Test::Test1), +/// 1 => Ok(Test::Test2), +/// _ => Err(()), +/// } +/// } +/// } +/// +/// impl PassBy for Test { +/// type PassBy = Enum; +/// } +/// ``` +pub struct Enum + TryFrom>(PhantomData); + +#[cfg(feature = "std")] +impl + TryFrom> PassByImpl for Enum { + fn into_ffi_value(instance: T, _: &mut dyn FunctionContext) -> Result { + Ok(instance.into() as u32) + } + + fn from_ffi_value(_: &mut dyn FunctionContext, arg: Self::FFIType) -> Result { + T::try_from(arg as u8).map_err(|_| format!("Invalid enum discriminant: {}", arg)) + } +} + +#[cfg(not(feature = "std"))] +impl + TryFrom> PassByImpl for Enum { + type Owned = (); + + fn into_ffi_value(instance: &T) -> WrappedFFIValue { + let value: u8 = (*instance).into(); + (value as u32).into() + } + + fn from_ffi_value(arg: Self::FFIType) -> T { + T::try_from(arg as u8).expect("Host to wasm provides a valid enum discriminant; qed") + } +} + +/// The type is passed as `u32`. +/// +/// The value is corresponds to the discriminant of the variant. +impl + TryFrom> RIType for Enum { + type FFIType = u32; +} diff --git a/node/runtime-interface-macro/src/polkavm.rs b/node/runtime-interface-macro/src/polkavm.rs new file mode 100644 index 000000000..484a269fd --- /dev/null +++ b/node/runtime-interface-macro/src/polkavm.rs @@ -0,0 +1,30 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +pub use polkavm_derive::{polkavm_export, polkavm_import}; + +#[polkavm_derive::polkavm_define_abi(allow_extra_input_registers)] +pub mod polkavm_abi {} + +impl self::polkavm_abi::FromHost for *mut u8 { + type Regs = (u32,); + + #[inline] + fn from_host((value,): Self::Regs) -> Self { + value as *mut u8 + } +} diff --git a/node/runtime-interface-macro/src/util.rs b/node/runtime-interface-macro/src/util.rs new file mode 100644 index 000000000..86c8e4b50 --- /dev/null +++ b/node/runtime-interface-macro/src/util.rs @@ -0,0 +1,60 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Various utilities that help interfacing with wasm runtime code. + +/// Pack a pointer and length into an `u64`. +pub fn pack_ptr_and_len(ptr: u32, len: u32) -> u64 { + // The static assertions from above are changed into a runtime check. + #[cfg(all(not(feature = "std"), feature = "disable_target_static_assertions"))] + assert_eq!(4, core::mem::size_of::()); + + (u64::from(len) << 32) | u64::from(ptr) +} + +/// Unpacks an `u64` into the pointer and length. +/// +/// Runtime API functions return a 64-bit value which encodes a pointer in the least-significant +/// 32-bits and a length in the most-significant 32 bits. This interprets the returned value as a +/// pointer, length tuple. +pub fn unpack_ptr_and_len(val: u64) -> (u32, u32) { + // The static assertions from above are changed into a runtime check. + #[cfg(all(not(feature = "std"), feature = "disable_target_static_assertions"))] + assert_eq!(4, core::mem::size_of::()); + + let ptr = (val & (!0u32 as u64)) as u32; + let len = (val >> 32) as u32; + + (ptr, len) +} + +#[cfg(test)] +mod tests { + use super::{pack_ptr_and_len, unpack_ptr_and_len}; + + #[test] + fn ptr_len_packing_unpacking() { + const PTR: u32 = 0x1337; + const LEN: u32 = 0x7f000000; + + let packed = pack_ptr_and_len(PTR, LEN); + let (ptr, len) = unpack_ptr_and_len(packed); + + assert_eq!(PTR, ptr); + assert_eq!(LEN, len); + } +} diff --git a/node/runtime-interface-macro/src/wasm.rs b/node/runtime-interface-macro/src/wasm.rs new file mode 100644 index 000000000..10bb50c64 --- /dev/null +++ b/node/runtime-interface-macro/src/wasm.rs @@ -0,0 +1,145 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Traits required by the runtime interface from the wasm side. + +use crate::RIType; + +use core::cell::Cell; + +/// Something that can be created from a ffi value. +/// +/// # Safety +/// +/// It is unsafe behavior to call `Something::into_ffi_value().get()` and take this as input for +/// `from_ffi_value`. Implementations are safe to assume that the `arg` given to `from_ffi_value` +/// is only generated by the corresponding [`host::IntoFFIValue`](crate::host::IntoFFIValue) +/// implementation. +pub trait FromFFIValue: Sized + RIType { + /// Create `Self` from the given ffi value. + fn from_ffi_value(arg: Self::FFIType) -> Self; +} + +/// Something that can be converted into a ffi value. +pub trait IntoFFIValue: RIType { + /// The owned rust type that is stored with the ffi value in [`WrappedFFIValue`]. + /// + /// If no owned value is required, `()` can be used as a type. + type Owned; + + /// Convert `self` into a [`WrappedFFIValue`]. + fn into_ffi_value(&self) -> WrappedFFIValue; +} + +/// Represents a wrapped ffi value. +/// +/// It is either the ffi value itself or the ffi value plus some other owned value. By providing +/// support for storing another owned value besides the actual ffi value certain performance +/// optimizations can be applied. For example using the pointer to a `Vec`, while using the +/// pointer to a SCALE encoded `Vec` that is stored in this wrapper for any other `Vec`. +pub enum WrappedFFIValue { + Wrapped(T), + WrappedAndOwned(T, O), +} + +impl WrappedFFIValue { + /// Returns the wrapped ffi value. + pub fn get(&self) -> T { + match self { + Self::Wrapped(data) | Self::WrappedAndOwned(data, _) => *data, + } + } +} + +impl From for WrappedFFIValue { + fn from(val: T) -> Self { + WrappedFFIValue::Wrapped(val) + } +} + +impl From<(T, O)> for WrappedFFIValue { + fn from(val: (T, O)) -> Self { + WrappedFFIValue::WrappedAndOwned(val.0, val.1) + } +} + +/// The state of an exchangeable function. +#[derive(Clone, Copy)] +enum ExchangeableFunctionState { + /// Original function is present + Original, + /// The function has been replaced. + Replaced, +} + +/// A function which implementation can be exchanged. +/// +/// Internally this works by swapping function pointers. +pub struct ExchangeableFunction(Cell<(T, ExchangeableFunctionState)>); + +impl ExchangeableFunction { + /// Create a new instance of `ExchangeableFunction`. + pub const fn new(impl_: T) -> Self { + Self(Cell::new((impl_, ExchangeableFunctionState::Original))) + } +} + +impl ExchangeableFunction { + /// Replace the implementation with `new_impl`. + /// + /// # Panics + /// + /// Panics when trying to replace an already replaced implementation. + /// + /// # Returns + /// + /// Returns the original implementation wrapped in [`RestoreImplementation`]. + pub fn replace_implementation(&'static self, new_impl: T) -> RestoreImplementation { + if let ExchangeableFunctionState::Replaced = self.0.get().1 { + panic!("Trying to replace an already replaced implementation!") + } + + let old = self.0.replace((new_impl, ExchangeableFunctionState::Replaced)); + + RestoreImplementation(self, Some(old.0)) + } + + /// Restore the original implementation. + fn restore_orig_implementation(&self, orig: T) { + self.0.set((orig, ExchangeableFunctionState::Original)); + } + + /// Returns the internal function pointer. + pub fn get(&self) -> T { + self.0.get().0 + } +} + +// Wasm does not support threads, so this is safe; qed. +unsafe impl Sync for ExchangeableFunction {} + +/// Restores a function implementation on drop. +/// +/// Stores a static reference to the function object and the original implementation. +pub struct RestoreImplementation(&'static ExchangeableFunction, Option); + +impl Drop for RestoreImplementation { + fn drop(&mut self) { + self.0 + .restore_orig_implementation(self.1.take().expect("Value is only taken on drop; qed")); + } +} diff --git a/node/runtime-interfaces/Cargo.toml b/node/runtime-interfaces/Cargo.toml new file mode 100644 index 000000000..1e36c629a --- /dev/null +++ b/node/runtime-interfaces/Cargo.toml @@ -0,0 +1,34 @@ +[package] +name = "cere-runtime-interfaces" +version.workspace = true +authors.workspace = true +edition.workspace = true +homepage.workspace = true +license.workspace = true +readme.workspace = true +repository.workspace = true +[dependencies] +codec = { workspace = true } +sp-runtime = { workspace = true } +sp-runtime-interface-macro = { workspace = true } +sp-runtime-interface-proc-macro-local = { workspace = true } +sp-wasm-interface = { workspace = true } +cere-wasm-interface = { workspace = true } +sp-std = { workspace = true } +sp-core = { workspace = true } +sc-allocator = { workspace = true } +sc-executor = { workspace = true } +wasmi = "0.13" +environmental = "1.1.3" +log = {workspace = true} +thiserror = { version = "1.0.48" } + + +[features] +default = [ "std" ] +std = [ + "sp-runtime/std", + "sp-runtime-interface-macro/std", + "sp-wasm-interface/std", + "cere-wasm-interface/std", +] diff --git a/node/runtime-interfaces/src/env.rs b/node/runtime-interfaces/src/env.rs new file mode 100644 index 000000000..94b1c5e46 --- /dev/null +++ b/node/runtime-interfaces/src/env.rs @@ -0,0 +1,120 @@ +// This file is part of Substrate. + +// Copyright (C) 2018-2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Definition of a sandbox environment. + +use codec::{Decode, Encode}; + +use sp_core::RuntimeDebug; +use sp_std::vec::Vec; + +/// Error error that can be returned from host function. +#[derive(Encode, Decode, RuntimeDebug)] +pub struct HostError; + +/// Describes an entity to define or import into the environment. +#[derive(Clone, PartialEq, Eq, Encode, Decode, RuntimeDebug)] +pub enum ExternEntity { + /// Function that is specified by an index in a default table of + /// a module that creates the sandbox. + #[codec(index = 1)] + Function(u32), + + /// Linear memory that is specified by some identifier returned by sandbox + /// module upon creation new sandboxed memory. + #[codec(index = 2)] + Memory(u32), +} + +/// An entry in a environment definition table. +/// +/// Each entry has a two-level name and description of an entity +/// being defined. +#[derive(Clone, PartialEq, Eq, Encode, Decode, RuntimeDebug)] +pub struct Entry { + /// Module name of which corresponding entity being defined. + pub module_name: Vec, + /// Field name in which corresponding entity being defined. + pub field_name: Vec, + /// External entity being defined. + pub entity: ExternEntity, +} + +/// Definition of runtime that could be used by sandboxed code. +#[derive(Clone, PartialEq, Eq, Encode, Decode, RuntimeDebug)] +pub struct EnvironmentDefinition { + /// Vector of all entries in the environment definition. + pub entries: Vec, +} + +/// Constant for specifying no limit when creating a sandboxed +/// memory instance. For FFI purposes. +pub const MEM_UNLIMITED: u32 = -1i32 as u32; + +/// No error happened. +/// +/// For FFI purposes. +pub const ERR_OK: u32 = 0; + +/// Validation or instantiation error occurred when creating new +/// sandboxed module instance. +/// +/// For FFI purposes. +pub const ERR_MODULE: u32 = -1i32 as u32; + +/// Out-of-bounds access attempted with memory or table. +/// +/// For FFI purposes. +pub const ERR_OUT_OF_BOUNDS: u32 = -2i32 as u32; + +/// Execution error occurred (typically trap). +/// +/// For FFI purposes. +pub const ERR_EXECUTION: u32 = -3i32 as u32; + +#[cfg(test)] +mod tests { + use super::*; + use codec::Codec; + use std::fmt; + + fn roundtrip(s: S) { + let encoded = s.encode(); + assert_eq!(S::decode(&mut &encoded[..]).unwrap(), s); + } + + #[test] + fn env_def_roundtrip() { + roundtrip(EnvironmentDefinition { entries: vec![] }); + + roundtrip(EnvironmentDefinition { + entries: vec![Entry { + module_name: b"kernel"[..].into(), + field_name: b"memory"[..].into(), + entity: ExternEntity::Memory(1337), + }], + }); + + roundtrip(EnvironmentDefinition { + entries: vec![Entry { + module_name: b"env"[..].into(), + field_name: b"abort"[..].into(), + entity: ExternEntity::Function(228), + }], + }); + } +} diff --git a/node/runtime-interfaces/src/freeing_bump.rs b/node/runtime-interfaces/src/freeing_bump.rs new file mode 100644 index 000000000..2a346a5eb --- /dev/null +++ b/node/runtime-interfaces/src/freeing_bump.rs @@ -0,0 +1,620 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! This module implements a freeing-bump allocator. +//! +//! The heap is a continuous linear memory and chunks are allocated using a bump allocator. +//! +//! ```ignore +//! +-------------+-------------------------------------------------+ +//! | | | +//! +-------------+-------------------------------------------------+ +//! ^ +//! |_ bumper +//! ``` +//! +//! Only allocations with sizes of power of two can be allocated. If the incoming request has a non +//! power of two size it is increased to the nearest power of two. The power of two of size is +//! referred as **an order**. +//! +//! Each allocation has a header immediately preceding to it. The header is always 8 bytes and can +//! be of two types: free and occupied. +//! +//! For implementing freeing we maintain a linked lists for each order. The maximum supported +//! allocation size is capped, therefore the number of orders and thus the linked lists is as well +//! limited. Currently, the maximum size of an allocation is 32 MiB. +//! +//! When the allocator serves an allocation request it first checks the linked list for the +//! respective order. If it doesn't have any free chunks, the allocator requests memory from the +//! bump allocator. In any case the order is stored in the header of the allocation. +//! +//! Upon deallocation we get the order of the allocation from its header and then add that +//! allocation to the linked list for the respective order. +//! +//! # Caveats +//! +//! This is a fast allocator but it is also dumb. There are specifically two main shortcomings +//! that the user should keep in mind: +//! +//! - Once the bump allocator space is exhausted, there is no way to reclaim the memory. This means +//! that it's possible to end up in a situation where there are no live allocations yet a new +//! allocation will fail. +//! +//! Let's look into an example. Given a heap of 32 MiB. The user makes a 32 MiB allocation that we +//! call `X` . Now the heap is full. Then user deallocates `X`. Since all the space in the bump +//! allocator was consumed by the 32 MiB allocation, allocations of all sizes except 32 MiB will +//! fail. +//! +//! - Sizes of allocations are rounded up to the nearest order. That is, an allocation of 2,00001 +//! MiB will be put into the bucket of 4 MiB. Therefore, any allocation of size `(N, 2N]` will +//! take up to `2N`, thus assuming a uniform distribution of allocation sizes, the average amount +//! in use of a `2N` space on the heap will be `(3N + ε) / 2`. So average utilization is going to +//! be around 75% (`(3N + ε) / 2 / 2N`) meaning that around 25% of the space in allocation will be +//! wasted. This is more pronounced (in terms of absolute heap amounts) with larger allocation +//! sizes. + +pub use sp_core::MAX_POSSIBLE_ALLOCATION; +use cere_wasm_interface::{Pointer, WordSize}; +use std::{ + cmp::{max, min}, + mem, + ops::{Index, IndexMut, Range}, +}; + +/// The minimal alignment guaranteed by this allocator. +/// +/// The alignment of 8 is chosen because it is the maximum size of a primitive type supported by the +/// target version of wasm32: i64's natural alignment is 8. +const ALIGNMENT: u32 = 8; + +// Each pointer is prefixed with 8 bytes, which identify the list index +// to which it belongs. +const HEADER_SIZE: u32 = 8; + +/// The error type used by the allocators. +#[derive(thiserror::Error, Debug)] +pub enum FreeBumpError { + /// Someone tried to allocate more memory than the allowed maximum per allocation. + #[error("Requested allocation size is too large")] + RequestedAllocationTooLarge, + /// Allocator run out of space. + #[error("Allocator ran out of space")] + AllocatorOutOfSpace, + /// The client passed a memory instance which is smaller than previously observed. + #[error("Shrinking of the underlying memory is observed")] + MemoryShrinked, + /// Some other error occurred. + #[error("Other: {0}")] + Other(&'static str), +} + +/// Create an allocator error. +fn error(msg: &'static str) -> FreeBumpError { + FreeBumpError::Other(msg) +} + +const LOG_TARGET: &str = "wasm-heap"; + +// The minimum possible allocation size is chosen to be 8 bytes because in that case we would have +// easier time to provide the guaranteed alignment of 8. +// +// The maximum possible allocation size is set in the primitives to 32MiB. +// +// N_ORDERS - represents the number of orders supported. +// +// This number corresponds to the number of powers between the minimum possible allocation and +// maximum possible allocation, or: 2^3...2^25 (both ends inclusive, hence 23). +const N_ORDERS: usize = 23; +const MIN_POSSIBLE_ALLOCATION: u32 = 8; // 2^3 bytes, 8 bytes + +/// The exponent for the power of two sized block adjusted to the minimum size. +/// +/// This way, if `MIN_POSSIBLE_ALLOCATION == 8`, we would get: +/// +/// power_of_two_size | order +/// 8 | 0 +/// 16 | 1 +/// 32 | 2 +/// 64 | 3 +/// ... +/// 16777216 | 21 +/// 33554432 | 22 +/// +/// and so on. +#[derive(Copy, Clone, PartialEq, Eq, Debug)] +struct Order(u32); + +impl Order { + /// Create `Order` object from a raw order. + /// + /// Returns `Err` if it is greater than the maximum supported order. + fn from_raw(order: u32) -> Result { + if order < N_ORDERS as u32 { + Ok(Self(order)) + } else { + Err(error("invalid order")) + } + } + + /// Compute the order by the given size + /// + /// The size is clamped, so that the following holds: + /// + /// `MIN_POSSIBLE_ALLOCATION <= size <= MAX_POSSIBLE_ALLOCATION` + fn from_size(size: u32) -> Result { + let clamped_size = if size > MAX_POSSIBLE_ALLOCATION { + log::warn!(target: LOG_TARGET, "going to fail due to allocating {:?}", size); + return Err(FreeBumpError::RequestedAllocationTooLarge) + } else if size < MIN_POSSIBLE_ALLOCATION { + MIN_POSSIBLE_ALLOCATION + } else { + size + }; + + // Round the clamped size to the next power of two. + // + // It returns the unchanged value if the value is already a power of two. + let power_of_two_size = clamped_size.next_power_of_two(); + + // Compute the number of trailing zeroes to get the order. We adjust it by the number of + // trailing zeroes in the minimum possible allocation. + let order = power_of_two_size.trailing_zeros() - MIN_POSSIBLE_ALLOCATION.trailing_zeros(); + + Ok(Self(order)) + } + + /// Returns the corresponding size in bytes for this order. + /// + /// Note that it is always a power of two. + fn size(&self) -> u32 { + MIN_POSSIBLE_ALLOCATION << self.0 + } + + /// Extract the order as `u32`. + fn into_raw(self) -> u32 { + self.0 + } +} + +/// A special magic value for a pointer in a link that denotes the end of the linked list. +const NIL_MARKER: u32 = u32::MAX; + +/// A link between headers in the free list. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +enum Link { + /// Nil, denotes that there is no next element. + Nil, + /// Link to the next element represented as a pointer to the a header. + Ptr(u32), +} + +impl Link { + /// Creates a link from raw value. + fn from_raw(raw: u32) -> Self { + if raw != NIL_MARKER { + Self::Ptr(raw) + } else { + Self::Nil + } + } + + /// Converts this link into a raw u32. + fn into_raw(self) -> u32 { + match self { + Self::Nil => NIL_MARKER, + Self::Ptr(ptr) => ptr, + } + } +} + +/// A header of an allocation. +/// +/// The header is encoded in memory as follows. +/// +/// ## Free header +/// +/// ```ignore +/// 64 32 0 +// +--------------+-------------------+ +/// | 0 | next element link | +/// +--------------+-------------------+ +/// ``` +/// ## Occupied header +/// ```ignore +/// 64 32 0 +// +--------------+-------------------+ +/// | 1 | order | +/// +--------------+-------------------+ +/// ``` +#[derive(Clone, Debug, PartialEq, Eq)] +enum Header { + /// A free header contains a link to the next element to form a free linked list. + Free(Link), + /// An occupied header has attached order to know in which free list we should put the + /// allocation upon deallocation. + Occupied(Order), +} + +impl Header { + /// Reads a header from memory. + /// + /// Returns an error if the `header_ptr` is out of bounds of the linear memory or if the read + /// header is corrupted (e.g. the order is incorrect). + fn read_from(memory: &M, header_ptr: u32) -> Result { + let raw_header = memory.read_le_u64(header_ptr)?; + + // Check if the header represents an occupied or free allocation and extract the header data + // by trimming (and discarding) the high bits. + let occupied = raw_header & 0x00000001_00000000 != 0; + let header_data = raw_header as u32; + + Ok(if occupied { + Self::Occupied(Order::from_raw(header_data)?) + } else { + Self::Free(Link::from_raw(header_data)) + }) + } + + /// Write out this header to memory. + /// + /// Returns an error if the `header_ptr` is out of bounds of the linear memory. + fn write_into(&self, memory: &mut M, header_ptr: u32) -> Result<(), FreeBumpError> { + let (header_data, occupied_mask) = match *self { + Self::Occupied(order) => (order.into_raw(), 0x00000001_00000000), + Self::Free(link) => (link.into_raw(), 0x00000000_00000000), + }; + let raw_header = header_data as u64 | occupied_mask; + memory.write_le_u64(header_ptr, raw_header)?; + Ok(()) + } + + /// Returns the order of the allocation if this is an occupied header. + fn into_occupied(self) -> Option { + match self { + Self::Occupied(order) => Some(order), + _ => None, + } + } + + /// Returns the link to the next element in the free list if this is a free header. + fn into_free(self) -> Option { + match self { + Self::Free(link) => Some(link), + _ => None, + } + } +} + +/// This struct represents a collection of intrusive linked lists for each order. +struct FreeLists { + heads: [Link; N_ORDERS], +} + +impl FreeLists { + /// Creates the free empty lists. + fn new() -> Self { + Self { heads: [Link::Nil; N_ORDERS] } + } + + /// Replaces a given link for the specified order and returns the old one. + fn replace(&mut self, order: Order, new: Link) -> Link { + let prev = self[order]; + self[order] = new; + prev + } +} + +impl Index for FreeLists { + type Output = Link; + fn index(&self, index: Order) -> &Link { + &self.heads[index.0 as usize] + } +} + +impl IndexMut for FreeLists { + fn index_mut(&mut self, index: Order) -> &mut Link { + &mut self.heads[index.0 as usize] + } +} + +/// Memory allocation stats gathered during the lifetime of the allocator. +#[derive(Clone, Debug, Default)] +#[non_exhaustive] +pub struct AllocationStats { + /// The current number of bytes allocated. + /// + /// This represents how many bytes are allocated *right now*. + pub bytes_allocated: u32, + + /// The peak number of bytes ever allocated. + /// + /// This is the maximum the `bytes_allocated` ever reached. + pub bytes_allocated_peak: u32, + + /// The sum of every allocation ever made. + /// + /// This increases every time a new allocation is made. + pub bytes_allocated_sum: u128, + + /// The amount of address space (in bytes) used by the allocator. + /// + /// This is calculated as the difference between the allocator's bumper + /// and the heap base. + /// + /// Currently the bumper's only ever incremented, so this is simultaneously + /// the current value as well as the peak value. + pub address_space_used: u32, +} + +/// Convert the given `size` in bytes into the number of pages. +/// +/// The returned number of pages is ensured to be big enough to hold memory with the given `size`. +/// +/// Returns `None` if the number of pages to not fit into `u32`. +// fn pages_from_size(size: u64) -> Option { +// u32::try_from((size + PAGE_SIZE as u64 - 1) / PAGE_SIZE as u64).ok() +// } + +/// An implementation of freeing bump allocator. +/// +/// Refer to the module-level documentation for further details. +pub struct FreeingBumpHeapAllocator { + original_heap_base: u32, + bumper: u32, + free_lists: FreeLists, + poisoned: bool, + last_observed_memory_size: u32, + stats: AllocationStats, +} + +impl Drop for FreeingBumpHeapAllocator { + fn drop(&mut self) { + log::debug!(target: LOG_TARGET, "allocator dropped: {:?}", self.stats) + } +} + +impl FreeingBumpHeapAllocator { + /// Creates a new allocation heap which follows a freeing-bump strategy. + /// + /// # Arguments + /// + /// - `heap_base` - the offset from the beginning of the linear memory where the heap starts. + pub fn new(heap_base: u32) -> Self { + let aligned_heap_base = (heap_base + ALIGNMENT - 1) / ALIGNMENT * ALIGNMENT; + + FreeingBumpHeapAllocator { + original_heap_base: aligned_heap_base, + bumper: aligned_heap_base, + free_lists: FreeLists::new(), + poisoned: false, + last_observed_memory_size: 0, + stats: AllocationStats::default(), + } + } + + /// Gets requested number of bytes to allocate and returns a pointer. + /// The maximum size which can be allocated at once is 32 MiB. + /// There is no minimum size, but whatever size is passed into + /// this function is rounded to the next power of two. If the requested + /// size is below 8 bytes it will be rounded up to 8 bytes. + /// + /// The identity or the type of the passed memory object does not matter. However, the size of + /// memory cannot shrink compared to the memory passed in previous invocations. + /// + /// NOTE: Once the allocator has returned an error all subsequent requests will return an error. + /// + /// # Arguments + /// + /// - `mem` - a slice representing the linear memory on which this allocator operates. + /// - `size` - size in bytes of the allocation request + pub fn allocate ( + &mut self, + mem: &mut M, + size: WordSize, + ) -> Result, FreeBumpError> { + if self.poisoned { + return Err(error("the allocator has been poisoned")) + } + + let bomb = PoisonBomb { poisoned: &mut self.poisoned }; + + Self::observe_memory_size(&mut self.last_observed_memory_size, mem)?; + let order = Order::from_size(size)?; + + let header_ptr: u32 = match self.free_lists[order] { + Link::Ptr(header_ptr) => { + assert!( + header_ptr + order.size() + HEADER_SIZE <= mem.size(), + "Pointer is looked up in list of free entries, into which + only valid values are inserted; qed" + ); + + // Remove this header from the free list. + let next_free = Header::read_from(mem, header_ptr)? + .into_free() + .ok_or_else(|| error("free list points to a occupied header"))?; + self.free_lists[order] = next_free; + + header_ptr + }, + Link::Nil => { + // Corresponding free list is empty. Allocate a new item. + Self::bump(&mut self.bumper, order.size() + HEADER_SIZE, mem.size())? + }, + }; + + // Write the order in the occupied header. + Header::Occupied(order).write_into(mem, header_ptr)?; + + self.stats.bytes_allocated += order.size() + HEADER_SIZE; + self.stats.bytes_allocated_sum += u128::from(order.size() + HEADER_SIZE); + self.stats.bytes_allocated_peak = + max(self.stats.bytes_allocated_peak, self.stats.bytes_allocated); + self.stats.address_space_used = self.bumper - self.original_heap_base; + + log::trace!(target: LOG_TARGET, "after allocation: {:?}", self.stats); + + bomb.disarm(); + Ok(Pointer::new(header_ptr + HEADER_SIZE)) + } + + /// Deallocates the space which was allocated for a pointer. + /// + /// The identity or the type of the passed memory object does not matter. However, the size of + /// memory cannot shrink compared to the memory passed in previous invocations. + /// + /// NOTE: Once the allocator has returned an error all subsequent requests will return an error. + /// + /// # Arguments + /// + /// - `mem` - a slice representing the linear memory on which this allocator operates. + /// - `ptr` - pointer to the allocated chunk + pub fn deallocate(&mut self, mem: &mut M, ptr: Pointer) -> Result<(), FreeBumpError> { + if self.poisoned { + return Err(error("the allocator has been poisoned")) + } + + let bomb = PoisonBomb { poisoned: &mut self.poisoned }; + + Self::observe_memory_size(&mut self.last_observed_memory_size, mem)?; + + let header_ptr = u32::from(ptr) + .checked_sub(HEADER_SIZE) + .ok_or_else(|| error("Invalid pointer for deallocation"))?; + + let order = Header::read_from(mem, header_ptr)? + .into_occupied() + .ok_or_else(|| error("the allocation points to an empty header"))?; + + // Update the just freed header and knit it back to the free list. + let prev_head = self.free_lists.replace(order, Link::Ptr(header_ptr)); + Header::Free(prev_head).write_into(mem, header_ptr)?; + + self.stats.bytes_allocated = self + .stats + .bytes_allocated + .checked_sub(order.size() + HEADER_SIZE) + .ok_or_else(|| error("underflow of the currently allocated bytes count"))?; + + log::trace!("after deallocation: {:?}", self.stats); + + bomb.disarm(); + Ok(()) + } + + /// Returns the allocation stats for this allocator. + pub fn stats(&self) -> AllocationStats { + self.stats.clone() + } + + /// Increases the `bumper` by `size`. + /// + /// Returns the `bumper` from before the increase. Returns an `Error::AllocatorOutOfSpace` if + /// the operation would exhaust the heap. + fn bump(bumper: &mut u32, size: u32, heap_end: u32) -> Result { + if *bumper + size > heap_end { + log::error!( + target: LOG_TARGET, + "running out of space with current bumper {}, mem size {}", + bumper, + heap_end + ); + return Err(FreeBumpError::AllocatorOutOfSpace) + } + + let res = *bumper; + *bumper += size; + Ok(res) + } + + fn observe_memory_size ( + last_observed_memory_size: &mut u32, + mem: &mut M, + ) -> Result<(), FreeBumpError> { + if mem.size() < *last_observed_memory_size { + return Err(FreeBumpError::MemoryShrinked) + } + *last_observed_memory_size = mem.size(); + Ok(()) + } +} + +/// A trait for abstraction of accesses to a wasm linear memory. Used to read or modify the +/// allocation prefixes. +/// +/// A wasm linear memory behaves similarly to a vector. The address space doesn't have holes and is +/// accessible up to the reported size. +/// +/// The linear memory can grow in size with the wasm page granularity (64KiB), but it cannot shrink. +pub trait Memory { + /// Read a u64 from the heap in LE form. Returns an error if any of the bytes read are out of + /// bounds. + fn read_le_u64(&self, ptr: u32) -> Result; + /// Write a u64 to the heap in LE form. Returns an error if any of the bytes written are out of + /// bounds. + fn write_le_u64(&mut self, ptr: u32, val: u64) -> Result<(), FreeBumpError>; + /// Returns the full size of the memory in bytes. + fn size(&self) -> u32; +} + +impl Memory for [u8] { + fn read_le_u64(&self, ptr: u32) -> Result { + let range = + heap_range(ptr, 8, self.len()).ok_or_else(|| error("read out of heap bounds"))?; + let bytes = self[range] + .try_into() + .expect("[u8] slice of length 8 must be convertible to [u8; 8]"); + Ok(u64::from_le_bytes(bytes)) + } + fn write_le_u64(&mut self, ptr: u32, val: u64) -> Result<(), FreeBumpError> { + let range = + heap_range(ptr, 8, self.len()).ok_or_else(|| error("write out of heap bounds"))?; + let bytes = val.to_le_bytes(); + self[range].copy_from_slice(&bytes[..]); + Ok(()) + } + fn size(&self) -> u32 { + u32::try_from(self.len()).expect("size of Wasm linear memory is <2^32; qed") + } +} + +fn heap_range(offset: u32, length: u32, heap_len: usize) -> Option> { + let start = offset as usize; + let end = offset.checked_add(length)? as usize; + if end <= heap_len { + Some(start..end) + } else { + None + } +} + +/// A guard that will raise the poisoned flag on drop unless disarmed. +struct PoisonBomb<'a> { + poisoned: &'a mut bool, +} + +impl<'a> PoisonBomb<'a> { + fn disarm(self) { + mem::forget(self) + } +} + +impl<'a> Drop for PoisonBomb<'a> { + fn drop(&mut self) { + *self.poisoned = true; + } +} diff --git a/node/runtime-interfaces/src/lib.rs b/node/runtime-interfaces/src/lib.rs new file mode 100644 index 000000000..85011581e --- /dev/null +++ b/node/runtime-interfaces/src/lib.rs @@ -0,0 +1,130 @@ +use sp_runtime_interface_macro::runtime_interface; +use sp_wasm_interface::{Result as SandboxResult }; +use cere_wasm_interface::Pointer; +pub type MemoryId = u32; +use std::{cell::RefCell, rc::Rc, str, sync::Arc}; +mod sandbox_util; +mod freeing_bump; +mod env; +mod util; +mod sandbox_interface; +mod wasmi_backend; +use crate::sandbox_util::Store; +use cere_wasm_interface::{Function, Value, WordSize}; +use wasmi::TableRef; +use wasmi::MemoryRef; +use crate::freeing_bump::FreeingBumpHeapAllocator; +use cere_wasm_interface::*; + +/// Something that provides access to the sandbox. +#[runtime_interface(wasm_only)] +pub trait Sandbox { + /// Get sandbox memory from the `memory_id` instance at `offset` into the given buffer. + fn memory_get( + &mut self, + memory_idx: u32, + offset: u32, + buf_ptr: Pointer, + buf_len: u32, + ) -> u32 { + // self.sandbox() + // .memory_get(memory_idx, offset, buf_ptr, buf_len) + // .expect("Failed to get memory with sandbox") + return 0; + } + /// Set sandbox memory from the given value. + fn memory_set( + &mut self, + memory_idx: u32, + offset: u32, + val_ptr: Pointer, + val_len: u32, + ) -> u32 { + // self.sandbox() + // .memory_set(memory_idx, offset, val_ptr, val_len) + // .expect("Failed to set memory with sandbox") + return 0; + } + /// Delete a memory instance. + fn memory_teardown(&mut self, memory_idx: u32) { + // self.sandbox() + // .memory_teardown(memory_idx) + // .expect("Failed to teardown memory with sandbox") + } + /// Create a new memory instance with the given `initial` size and the `maximum` size. + /// The size is given in wasm pages. + fn memory_new(&mut self, initial: u32, maximum: u32) -> u32 { + // self.sandbox() + // .memory_new(initial, maximum) + // .expect("Failed to create new memory with sandbox") + return 0; + } + /// Invoke an exported function by a name. + fn invoke( + &mut self, + instance_idx: u32, + function: &str, + args: &[u8], + return_val_ptr: Pointer, + return_val_len: u32, + state_ptr: Pointer, + ) -> u32 { + // self.sandbox() + // .invoke(instance_idx, function, args, return_val_ptr, return_val_len, state_ptr.into()) + // .expect("Failed to invoke function with sandbox") + return 0; + } + /// Delete a sandbox instance. + fn instance_teardown(&mut self, instance_idx: u32) { + // self.sandbox() + // .instance_teardown(instance_idx) + // .expect("Failed to teardown sandbox instance") + } + // FixMe: didn't find this function in this file in the removal PR. + // /// Create a new sandbox instance. + // fn instance_new( + // &mut self, + // dispatch_thunk_id: u32, + // wasm: &[u8], + // raw_env_def: &[u8], + // state: u32, + // ) -> SandboxResult { + // return Ok(0); + // } + /// Get the value from a global with the given `name`. The sandbox is determined by the + /// given `instance_idx` instance. + /// + /// Returns `Some(_)` when the requested global variable could be found. + fn get_global_val( + &mut self, + instance_idx: u32, + name: &str, + ) -> Option { + // self.sandbox() + // .get_global_val(instance_idx, name) + // .expect("Failed to get global from sandbox") + return Some(Value::I32(0)); + } + + /// Instantiate a new sandbox instance with the given `wasm_code`. + fn instantiate( + &mut self, + dispatch_thunk: u32, + wasm_code: &[u8], + env_def: &[u8], + state_ptr: Pointer, + ) -> u32 { + return 0; + } +} + +struct FunctionExecutor { + sandbox_store: Rc>>, + heap: RefCell, + memory: MemoryRef, + table: Option, + host_functions: Arc>, + allow_missing_func_imports: bool, + missing_functions: Arc>, + panic_message: Option, +} diff --git a/node/runtime-interfaces/src/sandbox_interface.rs b/node/runtime-interfaces/src/sandbox_interface.rs new file mode 100644 index 000000000..0b2ea15a0 --- /dev/null +++ b/node/runtime-interfaces/src/sandbox_interface.rs @@ -0,0 +1,372 @@ +use std::{cell::RefCell, rc::Rc, str, sync::Arc, result}; +use sp_wasm_interface::{Result as WResult}; +use cere_wasm_interface::{FunctionContext, Pointer, Function, Value, WordSize}; +use crate::FunctionExecutor; +use crate::env as sandbox_env; +use log::{debug, error, trace}; +use codec::{Decode, Encode}; +use crate::util::MemoryTransfer; +use wasmi::RuntimeValue; +use crate::sandbox_util as sandbox; +use crate::wasmi_backend::trap; +use sc_executor::error::{Error, Result}; +use cere_wasm_interface::Sandbox; + +/// Sandbox memory identifier. +pub type MemoryId = u32; + +// /// Result type used by traits in this crate. +// #[cfg(feature = "std")] +// pub type Result = result::Result; +// #[cfg(not(feature = "std"))] +// pub type Result = result::Result; + + +impl wasmi::Externals for FunctionExecutor { + fn invoke_index( + &mut self, + index: usize, + args: wasmi::RuntimeArgs, + ) -> std::result::Result, wasmi::Trap> { + let mut args = args.as_ref().iter().copied().map(|value| match value { + wasmi::RuntimeValue::I32(val) => Value::I32(val), + wasmi::RuntimeValue::I64(val) => Value::I64(val), + wasmi::RuntimeValue::F32(val) => Value::F32(val.into()), + wasmi::RuntimeValue::F64(val) => Value::F64(val.into()), + }); + + if let Some(function) = self.host_functions.clone().get(index) { + function + .execute(self, &mut args) + // .map_err(|msg| Error::FunctionExecution(function.name().to_string(), msg)) + .map_err(|_msg| trap("Function call failed")) + .map_err(wasmi::Trap::from) + .map(|v| { + v.map(|value| match value { + Value::I32(val) => RuntimeValue::I32(val), + Value::I64(val) => RuntimeValue::I64(val), + Value::F32(val) => RuntimeValue::F32(val.into()), + Value::F64(val) => RuntimeValue::F64(val.into()), + }) + }) + } else if self.allow_missing_func_imports && + index >= self.host_functions.len() && + index < self.host_functions.len() + self.missing_functions.len() + { + Err(trap("Could not find host function with index")) + } else { + Err(trap("Could not find host function with index")) + } + } +} + +struct SandboxContext<'a> { + executor: &'a mut FunctionExecutor, + dispatch_thunk: wasmi::FuncRef, +} + +impl<'a> sandbox::SandboxContext for SandboxContext<'a> { + fn invoke( + &mut self, + invoke_args_ptr: Pointer, + invoke_args_len: WordSize, + state: u32, + func_idx: sandbox::SupervisorFuncIndex, + ) -> Result { + let result = wasmi::FuncInstance::invoke( + &self.dispatch_thunk, + &[ + RuntimeValue::I32(u32::from(invoke_args_ptr) as i32), + RuntimeValue::I32(invoke_args_len as i32), + RuntimeValue::I32(state as i32), + RuntimeValue::I32(usize::from(func_idx) as i32), + ], + self.executor, + ); + + match result { + Ok(Some(RuntimeValue::I64(val))) => Ok(val), + Ok(_) => Err("Supervisor function returned unexpected result!".into()), + Err(err) => Err(Error::Other(err.to_string())), + } + } + + fn supervisor_context(&mut self) -> &mut dyn FunctionContext { + self.executor + } +} + +impl FunctionContext for FunctionExecutor { + fn read_memory_into(&self, address: Pointer, dest: &mut [u8]) -> WResult<()> { + self.memory.get_into(address.into(), dest).map_err(|e| e.to_string()) + } + + fn write_memory(&mut self, address: Pointer, data: &[u8]) -> WResult<()> { + self.memory.set(address.into(), data).map_err(|e| e.to_string()) + } + + fn allocate_memory(&mut self, size: WordSize) -> WResult> { + let heap = &mut self.heap.borrow_mut(); + self.memory + .with_direct_access_mut(|mem| heap.allocate(mem, size).map_err(|e| e.to_string())) + } + + fn deallocate_memory(&mut self, ptr: Pointer) -> WResult<()> { + let heap = &mut self.heap.borrow_mut(); + self.memory + .with_direct_access_mut(|mem| heap.deallocate(mem, ptr).map_err(|e| e.to_string())) + } + + fn sandbox(&mut self) -> &mut dyn Sandbox { + self + } + + fn register_panic_error_message(&mut self, message: &str) { + self.panic_message = Some(message.to_owned()); + } +} + +// /// Something that provides access to the sandbox. +// pub trait Sandbox { +// /// Get sandbox memory from the `memory_id` instance at `offset` into the given buffer. +// fn memory_get( +// &mut self, +// memory_id: MemoryId, +// offset: WordSize, +// buf_ptr: Pointer, +// buf_len: WordSize, +// ) -> WResult; +// /// Set sandbox memory from the given value. +// fn memory_set( +// &mut self, +// memory_id: MemoryId, +// offset: WordSize, +// val_ptr: Pointer, +// val_len: WordSize, +// ) -> WResult; +// /// Delete a memory instance. +// fn memory_teardown(&mut self, memory_id: MemoryId) -> WResult<()>; +// /// Create a new memory instance with the given `initial` size and the `maximum` size. +// /// The size is given in wasm pages. +// fn memory_new(&mut self, initial: u32, maximum: u32) -> WResult; +// /// Invoke an exported function by a name. +// fn invoke( +// &mut self, +// instance_id: u32, +// export_name: &str, +// args: &[u8], +// return_val: Pointer, +// return_val_len: WordSize, +// state: u32, +// ) -> WResult; +// /// Delete a sandbox instance. +// fn instance_teardown(&mut self, instance_id: u32) -> WResult<()>; +// /// Create a new sandbox instance. +// // fn instance_new( +// // &mut self, +// // dispatch_thunk_id: u32, +// // wasm: &[u8], +// // raw_env_def: &[u8], +// // state: u32, +// // ) -> Result; +// +// /// Get the value from a global with the given `name`. The sandbox is determined by the +// /// given `instance_idx` instance. +// /// +// /// Returns `Some(_)` when the requested global variable could be found. +// fn get_global_val(&self, instance_idx: u32, name: &str) -> WResult>; +// +// /// Instantiate a new sandbox instance with the given `wasm_code`. +// fn instantiate( +// &mut self, +// dispatch_thunk: u32, +// wasm_code: &[u8], +// env_def: &[u8], +// state_ptr: Pointer, +// ) -> u32; +// } + +impl Sandbox for FunctionExecutor { + fn memory_get( + &mut self, + memory_id: MemoryId, + offset: WordSize, + buf_ptr: Pointer, + buf_len: WordSize, + ) -> WResult { + let sandboxed_memory = + self.sandbox_store.borrow().memory(memory_id).map_err(|e| e.to_string())?; + + let len = buf_len as usize; + + let buffer = match sandboxed_memory.read(Pointer::new(offset as u32), len) { + Err(_) => return Ok(sandbox_env::ERR_OUT_OF_BOUNDS), + Ok(buffer) => buffer, + }; + + if self.memory.set(buf_ptr.into(), &buffer).is_err() { + return Ok(sandbox_env::ERR_OUT_OF_BOUNDS) + } + + Ok(sandbox_env::ERR_OK) + } + + fn memory_set( + &mut self, + memory_id: MemoryId, + offset: WordSize, + val_ptr: Pointer, + val_len: WordSize, + ) -> WResult { + let sandboxed_memory = + self.sandbox_store.borrow().memory(memory_id).map_err(|e| e.to_string())?; + + let len = val_len as usize; + + #[allow(deprecated)] + let buffer = match self.memory.get(val_ptr.into(), len) { + Err(_) => return Ok(sandbox_env::ERR_OUT_OF_BOUNDS), + Ok(buffer) => buffer, + }; + + if sandboxed_memory.write_from(Pointer::new(offset as u32), &buffer).is_err() { + return Ok(sandbox_env::ERR_OUT_OF_BOUNDS) + } + + Ok(sandbox_env::ERR_OK) + } + + fn memory_teardown(&mut self, memory_id: MemoryId) -> WResult<()> { + self.sandbox_store + .borrow_mut() + .memory_teardown(memory_id) + .map_err(|e| e.to_string()) + } + + fn memory_new(&mut self, initial: u32, maximum: u32) -> WResult { + self.sandbox_store + .borrow_mut() + .new_memory(initial, maximum) + .map_err(|e| e.to_string()) + } + + fn invoke( + &mut self, + instance_id: u32, + export_name: &str, + mut args: &[u8], + return_val: Pointer, + return_val_len: WordSize, + state: u32, + ) -> WResult { + trace!(target: "sp-sandbox", "invoke, instance_idx={}", instance_id); + + // Deserialize arguments and convert them into wasmi types. + let args = Vec::::decode(&mut args) + .map_err(|_| "Can't decode serialized arguments for the invocation")? + .into_iter() + .collect::>(); + + let instance = + self.sandbox_store.borrow().instance(instance_id).map_err(|e| e.to_string())?; + + let dispatch_thunk = self + .sandbox_store + .borrow() + .dispatch_thunk(instance_id) + .map_err(|e| e.to_string())?; + + match instance.invoke( + export_name, + &args, + state, + &mut SandboxContext { dispatch_thunk, executor: self }, + ) { + Ok(None) => Ok(sandbox_env::ERR_OK), + Ok(Some(val)) => { + // Serialize return value and write it back into the memory. + cere_wasm_interface::ReturnValue::Value(val).using_encoded(|val| { + if val.len() > return_val_len as usize { + return Err("Return value buffer is too small".into()) + } + self.write_memory(return_val, val).map_err(|_| "Return value buffer is OOB")?; + Ok(sandbox_env::ERR_OK) + }) + }, + Err(_) => Ok(sandbox_env::ERR_EXECUTION), + } + } + + fn instance_teardown(&mut self, instance_id: u32) -> WResult<()> { + self.sandbox_store + .borrow_mut() + .instance_teardown(instance_id) + .map_err(|e| e.to_string()) + } + + // fn instance_new( + // &mut self, + // dispatch_thunk_id: u32, + // wasm: &[u8], + // raw_env_def: &[u8], + // state: u32, + // ) -> WResult { + // // Extract a dispatch thunk from instance's table by the specified index. + // let dispatch_thunk = { + // let table = self + // .table + // .as_ref() + // .ok_or("Runtime doesn't have a table; sandbox is unavailable")?; + // table + // .get(dispatch_thunk_id) + // .map_err(|_| "dispatch_thunk_idx is out of the table bounds")? + // .ok_or("dispatch_thunk_idx points on an empty table entry")? + // }; + // + // let guest_env = + // match sandbox::GuestEnvironment::decode(&*self.sandbox_store.borrow(), raw_env_def) { + // Ok(guest_env) => guest_env, + // Err(_) => return Ok(sandbox_env::ERR_MODULE as u32), + // }; + // + // let store = self.sandbox_store.clone(); + // let result = store.borrow_mut().instantiate( + // wasm, + // guest_env, + // state, + // &mut SandboxContext { executor: self, dispatch_thunk: dispatch_thunk.clone() }, + // ); + // + // let instance_idx_or_err_code = + // match result.map(|i| i.register(&mut store.borrow_mut(), dispatch_thunk)) { + // Ok(instance_idx) => instance_idx, + // Err(sandbox::InstantiationError::StartTrapped) => sandbox_env::ERR_EXECUTION, + // Err(_) => sandbox_env::ERR_MODULE, + // }; + // + // Ok(instance_idx_or_err_code) + // } + + fn get_global_val( + &self, + instance_idx: u32, + name: &str, + ) -> WResult> { + self.sandbox_store + .borrow() + .instance(instance_idx) + .map(|i| i.get_global_val(name)) + .map_err(|e| e.to_string()) + } + + /// Instantiate a new sandbox instance with the given `wasm_code`. + fn instantiate( + &mut self, + dispatch_thunk: u32, + wasm_code: &[u8], + env_def: &[u8], + state_ptr: Pointer, + ) -> u32 { + return 0; + } +} diff --git a/node/runtime-interfaces/src/sandbox_util.rs b/node/runtime-interfaces/src/sandbox_util.rs new file mode 100644 index 000000000..5c654fc0d --- /dev/null +++ b/node/runtime-interfaces/src/sandbox_util.rs @@ -0,0 +1,585 @@ +// This file is part of Substrate. + +// Copyright (C) 2018-2022 Parity Technologies (UK) Ltd. +// 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 . + +//! This module implements sandboxing support in the runtime. +//! +//! Sandboxing is backed by wasmi and wasmer, depending on the configuration. + +#[cfg(feature = "wasmer-sandbox")] +mod wasmer_backend; + +use std::{collections::HashMap, rc::Rc}; + +use codec::Decode; +use crate::env as sandbox_env; +use cere_wasm_interface::{FunctionContext, Pointer, WordSize}; + +use crate::{ + util,wasmi_backend, +}; +use sc_executor::error::{Error, Result}; + + +#[cfg(feature = "wasmer-sandbox")] +use self::wasmer_backend::{ + get_global as wasmer_get_global, instantiate as wasmer_instantiate, invoke as wasmer_invoke, + new_memory as wasmer_new_memory, Backend as WasmerBackend, + MemoryWrapper as WasmerMemoryWrapper, +}; +use self::wasmi_backend::{ + get_global as wasmi_get_global, instantiate as wasmi_instantiate, invoke as wasmi_invoke, + new_memory as wasmi_new_memory, MemoryWrapper as WasmiMemoryWrapper, +}; + +/// Index of a function inside the supervisor. +/// +/// This is a typically an index in the default table of the supervisor, however +/// the exact meaning of this index is depends on the implementation of dispatch function. +#[derive(Copy, Clone, Debug, PartialEq)] +pub struct SupervisorFuncIndex(usize); + +impl From for usize { + fn from(index: SupervisorFuncIndex) -> Self { + index.0 + } +} + +/// Index of a function within guest index space. +/// +/// This index is supposed to be used as index for `Externals`. +#[derive(Copy, Clone, Debug, PartialEq)] +pub struct GuestFuncIndex(pub usize); + +/// This struct holds a mapping from guest index space to supervisor. +pub struct GuestToSupervisorFunctionMapping { + /// Position of elements in this vector are interpreted + /// as indices of guest functions and are mapped to + /// corresponding supervisor function indices. + funcs: Vec, +} + +impl GuestToSupervisorFunctionMapping { + /// Create an empty function mapping + fn new() -> GuestToSupervisorFunctionMapping { + GuestToSupervisorFunctionMapping { funcs: Vec::new() } + } + + /// Add a new supervisor function to the mapping. + /// Returns a newly assigned guest function index. + fn define(&mut self, supervisor_func: SupervisorFuncIndex) -> GuestFuncIndex { + let idx = self.funcs.len(); + self.funcs.push(supervisor_func); + GuestFuncIndex(idx) + } + + /// Find supervisor function index by its corresponding guest function index + pub fn func_by_guest_index(&self, guest_func_idx: GuestFuncIndex) -> Option { + self.funcs.get(guest_func_idx.0).cloned() + } +} + +/// Holds sandbox function and memory imports and performs name resolution +pub struct Imports { + /// Maps qualified function name to its guest function index + func_map: HashMap<(Vec, Vec), GuestFuncIndex>, + + /// Maps qualified field name to its memory reference + memories_map: HashMap<(Vec, Vec), Memory>, +} + +impl Imports { + pub fn func_by_name(&self, module_name: &str, func_name: &str) -> Option { + self.func_map + .get(&(module_name.as_bytes().to_owned(), func_name.as_bytes().to_owned())) + .cloned() + } + + pub fn memory_by_name(&self, module_name: &str, memory_name: &str) -> Option { + self.memories_map + .get(&(module_name.as_bytes().to_owned(), memory_name.as_bytes().to_owned())) + .cloned() + } +} + +/// The sandbox context used to execute sandboxed functions. +pub trait SandboxContext { + /// Invoke a function in the supervisor environment. + /// + /// This first invokes the dispatch thunk function, passing in the function index of the + /// desired function to call and serialized arguments. The thunk calls the desired function + /// with the deserialized arguments, then serializes the result into memory and returns + /// reference. The pointer to and length of the result in linear memory is encoded into an + /// `i64`, with the upper 32 bits representing the pointer and the lower 32 bits representing + /// the length. + /// + /// # Errors + /// + /// Returns `Err` if the dispatch_thunk function has an incorrect signature or traps during + /// execution. + fn invoke( + &mut self, + invoke_args_ptr: Pointer, + invoke_args_len: WordSize, + state: u32, + func_idx: SupervisorFuncIndex, + ) -> Result; + + /// Returns the supervisor context. + fn supervisor_context(&mut self) -> &mut dyn FunctionContext; +} + +/// Implementation of [`Externals`] that allows execution of guest module with +/// [externals][`Externals`] that might refer functions defined by supervisor. +/// +/// [`Externals`]: ../wasmi/trait.Externals.html +pub struct GuestExternals<'a> { + /// Instance of sandboxed module to be dispatched + pub sandbox_instance: &'a SandboxInstance, + + /// External state passed to guest environment, see the `instantiate` function + pub state: u32, +} + +/// Module instance in terms of selected backend +pub enum BackendInstance { + /// Wasmi module instance + Wasmi(wasmi::ModuleRef), + + /// Wasmer module instance + #[cfg(feature = "wasmer-sandbox")] + Wasmer(wasmer::Instance), +} + +/// Sandboxed instance of a wasm module. +/// +/// It's primary purpose is to [`invoke`] exported functions on it. +/// +/// All imports of this instance are specified at the creation time and +/// imports are implemented by the supervisor. +/// +/// Hence, in order to invoke an exported function on a sandboxed module instance, +/// it's required to provide supervisor externals: it will be used to execute +/// code in the supervisor context. +/// +/// This is generic over a supervisor function reference type. +/// +/// [`invoke`]: #method.invoke +pub struct SandboxInstance { + pub backend_instance: BackendInstance, + pub guest_to_supervisor_mapping: GuestToSupervisorFunctionMapping, +} + +impl SandboxInstance { + /// Invoke an exported function by a name. + /// + /// `supervisor_externals` is required to execute the implementations + /// of the syscalls that published to a sandboxed module instance. + /// + /// The `state` parameter can be used to provide custom data for + /// these syscall implementations. + pub fn invoke( + &self, + export_name: &str, + args: &[cere_wasm_interface::Value], + state: u32, + sandbox_context: &mut dyn SandboxContext, + ) -> std::result::Result, Error> { + match &self.backend_instance { + BackendInstance::Wasmi(wasmi_instance) => + wasmi_invoke(self, wasmi_instance, export_name, args, state, sandbox_context), + + #[cfg(feature = "wasmer-sandbox")] + BackendInstance::Wasmer(wasmer_instance) => + wasmer_invoke(wasmer_instance, export_name, args, state, sandbox_context), + } + } + + /// Get the value from a global with the given `name`. + /// + /// Returns `Some(_)` if the global could be found. + pub fn get_global_val(&self, name: &str) -> Option { + match &self.backend_instance { + BackendInstance::Wasmi(wasmi_instance) => wasmi_get_global(wasmi_instance, name), + + #[cfg(feature = "wasmer-sandbox")] + BackendInstance::Wasmer(wasmer_instance) => wasmer_get_global(wasmer_instance, name), + } + } +} + +/// Error occurred during instantiation of a sandboxed module. +pub enum InstantiationError { + /// Something wrong with the environment definition. It either can't + /// be decoded, have a reference to a non-existent or torn down memory instance. + EnvironmentDefinitionCorrupted, + /// Provided module isn't recognized as a valid webassembly binary. + ModuleDecoding, + /// Module is a well-formed webassembly binary but could not be instantiated. This could + /// happen because, e.g. the module imports entries not provided by the environment. + Instantiation, + /// Module is well-formed, instantiated and linked, but while executing the start function + /// a trap was generated. + StartTrapped, + /// The code was compiled with a CPU feature not available on the host. + CpuFeature, +} + +fn decode_environment_definition( + mut raw_env_def: &[u8], + memories: &[Option], +) -> std::result::Result<(Imports, GuestToSupervisorFunctionMapping), InstantiationError> { + let env_def = sandbox_env::EnvironmentDefinition::decode(&mut raw_env_def) + .map_err(|_| InstantiationError::EnvironmentDefinitionCorrupted)?; + + let mut func_map = HashMap::new(); + let mut memories_map = HashMap::new(); + let mut guest_to_supervisor_mapping = GuestToSupervisorFunctionMapping::new(); + + for entry in &env_def.entries { + let module = entry.module_name.clone(); + let field = entry.field_name.clone(); + + match entry.entity { + sandbox_env::ExternEntity::Function(func_idx) => { + let externals_idx = + guest_to_supervisor_mapping.define(SupervisorFuncIndex(func_idx as usize)); + func_map.insert((module, field), externals_idx); + }, + sandbox_env::ExternEntity::Memory(memory_idx) => { + let memory_ref = memories + .get(memory_idx as usize) + .cloned() + .ok_or(InstantiationError::EnvironmentDefinitionCorrupted)? + .ok_or(InstantiationError::EnvironmentDefinitionCorrupted)?; + memories_map.insert((module, field), memory_ref); + }, + } + } + + Ok((Imports { func_map, memories_map }, guest_to_supervisor_mapping)) +} + +/// An environment in which the guest module is instantiated. +pub struct GuestEnvironment { + /// Function and memory imports of the guest module + pub imports: Imports, + + /// Supervisor functinons mapped to guest index space + pub guest_to_supervisor_mapping: GuestToSupervisorFunctionMapping, +} + +impl GuestEnvironment { + /// Decodes an environment definition from the given raw bytes. + /// + /// Returns `Err` if the definition cannot be decoded. + pub fn decode
( + store: &Store
, + raw_env_def: &[u8], + ) -> std::result::Result { + let (imports, guest_to_supervisor_mapping) = + decode_environment_definition(raw_env_def, &store.memories)?; + Ok(Self { imports, guest_to_supervisor_mapping }) + } +} + +/// An unregistered sandboxed instance. +/// +/// To finish off the instantiation the user must call `register`. +#[must_use] +pub struct UnregisteredInstance { + sandbox_instance: Rc, +} + +impl UnregisteredInstance { + /// Finalizes instantiation of this module. + pub fn register
(self, store: &mut Store
, dispatch_thunk: DT) -> u32 { + // At last, register the instance. + store.register_sandbox_instance(self.sandbox_instance, dispatch_thunk) + } +} + +/// Sandbox backend to use +pub enum SandboxBackend { + /// Wasm interpreter + Wasmi, + + /// Wasmer environment + #[cfg(feature = "wasmer-sandbox")] + Wasmer, + + /// Use wasmer backend if available. Fall back to wasmi otherwise. + TryWasmer, +} + +/// Memory reference in terms of a selected backend +#[derive(Clone, Debug)] +pub enum Memory { + /// Wasmi memory reference + Wasmi(WasmiMemoryWrapper), + + /// Wasmer memory refernce + #[cfg(feature = "wasmer-sandbox")] + Wasmer(WasmerMemoryWrapper), +} + +impl Memory { + /// View as wasmi memory + pub fn as_wasmi(&self) -> Option { + match self { + Memory::Wasmi(memory) => Some(memory.clone()), + + #[cfg(feature = "wasmer-sandbox")] + Memory::Wasmer(_) => None, + } + } + + /// View as wasmer memory + #[cfg(feature = "wasmer-sandbox")] + pub fn as_wasmer(&self) -> Option { + match self { + Memory::Wasmer(memory) => Some(memory.clone()), + Memory::Wasmi(_) => None, + } + } +} + +impl util::MemoryTransfer for Memory { + fn read(&self, source_addr: Pointer, size: usize) -> Result> { + match self { + Memory::Wasmi(sandboxed_memory) => sandboxed_memory.read(source_addr, size), + + #[cfg(feature = "wasmer-sandbox")] + Memory::Wasmer(sandboxed_memory) => sandboxed_memory.read(source_addr, size), + } + } + + fn read_into(&self, source_addr: Pointer, destination: &mut [u8]) -> Result<()> { + match self { + Memory::Wasmi(sandboxed_memory) => sandboxed_memory.read_into(source_addr, destination), + + #[cfg(feature = "wasmer-sandbox")] + Memory::Wasmer(sandboxed_memory) => sandboxed_memory.read_into(source_addr, destination), + } + } + + fn write_from(&self, dest_addr: Pointer, source: &[u8]) -> Result<()> { + match self { + Memory::Wasmi(sandboxed_memory) => sandboxed_memory.write_from(dest_addr, source), + + #[cfg(feature = "wasmer-sandbox")] + Memory::Wasmer(sandboxed_memory) => sandboxed_memory.write_from(dest_addr, source), + } + } +} + +/// Information specific to a particular execution backend +enum BackendContext { + /// Wasmi specific context + Wasmi, + + /// Wasmer specific context + #[cfg(feature = "wasmer-sandbox")] + Wasmer(WasmerBackend), +} + +impl BackendContext { + pub fn new(backend: SandboxBackend) -> BackendContext { + match backend { + SandboxBackend::Wasmi => BackendContext::Wasmi, + + #[cfg(not(feature = "wasmer-sandbox"))] + SandboxBackend::TryWasmer => BackendContext::Wasmi, + + #[cfg(feature = "wasmer-sandbox")] + SandboxBackend::Wasmer | SandboxBackend::TryWasmer => + BackendContext::Wasmer(WasmerBackend::new()), + } + } +} + +/// This struct keeps track of all sandboxed components. +/// +/// This is generic over a supervisor function reference type. +pub struct Store
{ + /// Stores the instance and the dispatch thunk associated to per instance. + /// + /// Instances are `Some` until torn down. + instances: Vec, DT)>>, + /// Memories are `Some` until torn down. + memories: Vec>, + backend_context: BackendContext, +} + +impl Store
{ + /// Create a new empty sandbox store. + pub fn new(backend: SandboxBackend) -> Self { + Store { + instances: Vec::new(), + memories: Vec::new(), + backend_context: BackendContext::new(backend), + } + } + + /// Create a new memory instance and return it's index. + /// + /// # Errors + /// + /// Returns `Err` if the memory couldn't be created. + /// Typically happens if `initial` is more than `maximum`. + pub fn new_memory(&mut self, initial: u32, maximum: u32) -> Result { + let memories = &mut self.memories; + let backend_context = &self.backend_context; + + let maximum = match maximum { + sandbox_env::MEM_UNLIMITED => None, + specified_limit => Some(specified_limit), + }; + + let memory = match &backend_context { + BackendContext::Wasmi => wasmi_new_memory(initial, maximum)?, + + #[cfg(feature = "wasmer-sandbox")] + BackendContext::Wasmer(context) => wasmer_new_memory(context, initial, maximum)?, + }; + + let mem_idx = memories.len(); + memories.push(Some(memory)); + + Ok(mem_idx as u32) + } + + /// Returns `SandboxInstance` by `instance_idx`. + /// + /// # Errors + /// + /// Returns `Err` If `instance_idx` isn't a valid index of an instance or + /// instance is already torndown. + pub fn instance(&self, instance_idx: u32) -> Result> { + self.instances + .get(instance_idx as usize) + .ok_or("Trying to access a non-existent instance")? + .as_ref() + .map(|v| v.0.clone()) + .ok_or_else(|| "Trying to access a torndown instance".into()) + } + + /// Returns dispatch thunk by `instance_idx`. + /// + /// # Errors + /// + /// Returns `Err` If `instance_idx` isn't a valid index of an instance or + /// instance is already torndown. + pub fn dispatch_thunk(&self, instance_idx: u32) -> Result
{ + self.instances + .get(instance_idx as usize) + .as_ref() + .ok_or("Trying to access a non-existent instance")? + .as_ref() + .map(|v| v.1.clone()) + .ok_or_else(|| "Trying to access a torndown instance".into()) + } + + /// Returns reference to a memory instance by `memory_idx`. + /// + /// # Errors + /// + /// Returns `Err` If `memory_idx` isn't a valid index of an memory or + /// if memory has been torn down. + pub fn memory(&self, memory_idx: u32) -> Result { + self.memories + .get(memory_idx as usize) + .cloned() + .ok_or("Trying to access a non-existent sandboxed memory")? + .ok_or_else(|| "Trying to access a torndown sandboxed memory".into()) + } + + /// Tear down the memory at the specified index. + /// + /// # Errors + /// + /// Returns `Err` if `memory_idx` isn't a valid index of an memory or + /// if it has been torn down. + pub fn memory_teardown(&mut self, memory_idx: u32) -> Result<()> { + match self.memories.get_mut(memory_idx as usize) { + None => Err("Trying to teardown a non-existent sandboxed memory".into()), + Some(None) => Err("Double teardown of a sandboxed memory".into()), + Some(memory) => { + *memory = None; + Ok(()) + }, + } + } + + /// Tear down the instance at the specified index. + /// + /// # Errors + /// + /// Returns `Err` if `instance_idx` isn't a valid index of an instance or + /// if it has been torn down. + pub fn instance_teardown(&mut self, instance_idx: u32) -> Result<()> { + match self.instances.get_mut(instance_idx as usize) { + None => Err("Trying to teardown a non-existent instance".into()), + Some(None) => Err("Double teardown of an instance".into()), + Some(instance) => { + *instance = None; + Ok(()) + }, + } + } + + /// Instantiate a guest module and return it's index in the store. + /// + /// The guest module's code is specified in `wasm`. Environment that will be available to + /// guest module is specified in `guest_env`. A dispatch thunk is used as function that + /// handle calls from guests. `state` is an opaque pointer to caller's arbitrary context + /// normally created by `sp_sandbox::Instance` primitive. + /// + /// Note: Due to borrowing constraints dispatch thunk is now propagated using DTH + /// + /// Returns uninitialized sandboxed module instance or an instantiation error. + pub fn instantiate( + &mut self, + wasm: &[u8], + guest_env: GuestEnvironment, + state: u32, + sandbox_context: &mut dyn SandboxContext, + ) -> std::result::Result { + let sandbox_instance = match self.backend_context { + BackendContext::Wasmi => wasmi_instantiate(wasm, guest_env, state, sandbox_context)?, + + #[cfg(feature = "wasmer-sandbox")] + BackendContext::Wasmer(ref context) => + wasmer_instantiate(context, wasm, guest_env, state, sandbox_context)?, + }; + + Ok(UnregisteredInstance { sandbox_instance }) + } +} + +// Private routines +impl
Store
{ + fn register_sandbox_instance( + &mut self, + sandbox_instance: Rc, + dispatch_thunk: DT, + ) -> u32 { + let instance_idx = self.instances.len(); + self.instances.push(Some((sandbox_instance, dispatch_thunk))); + instance_idx as u32 + } +} diff --git a/node/runtime-interfaces/src/util.rs b/node/runtime-interfaces/src/util.rs new file mode 100644 index 000000000..3acacacc1 --- /dev/null +++ b/node/runtime-interfaces/src/util.rs @@ -0,0 +1,52 @@ +// This file is part of Substrate. + +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// 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 . + +//! Utilities used by all backends +use sc_executor::error::{Result}; + +use cere_wasm_interface::Pointer; +use std::ops::Range; + +/// Construct a range from an offset to a data length after the offset. +/// Returns None if the end of the range would exceed some maximum offset. +pub fn checked_range(offset: usize, len: usize, max: usize) -> Option> { + let end = offset.checked_add(len)?; + if end <= max { + Some(offset..end) + } else { + None + } +} + +/// Provides safe memory access interface using an external buffer +pub trait MemoryTransfer { + /// Read data from a slice of memory into a newly allocated buffer. + /// + /// Returns an error if the read would go out of the memory bounds. + fn read(&self, source_addr: Pointer, size: usize) -> Result>; + + /// Read data from a slice of memory into a destination buffer. + /// + /// Returns an error if the read would go out of the memory bounds. + fn read_into(&self, source_addr: Pointer, destination: &mut [u8]) -> Result<()>; + + /// Write data to a slice of memory. + /// + /// Returns an error if the write would go out of the memory bounds. + fn write_from(&self, dest_addr: Pointer, source: &[u8]) -> Result<()>; +} diff --git a/node/runtime-interfaces/src/wasmi_backend.rs b/node/runtime-interfaces/src/wasmi_backend.rs new file mode 100644 index 000000000..b79ce3b41 --- /dev/null +++ b/node/runtime-interfaces/src/wasmi_backend.rs @@ -0,0 +1,370 @@ +// This file is part of Substrate. + +// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// 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 . + +//! Wasmi specific impls for sandbox + +use std::{fmt, rc::Rc}; + +use codec::{Decode, Encode}; +use cere_wasm_interface::{ReturnValue, Value, WordSize}; +use cere_wasm_interface::{FunctionContext, Pointer}; +use wasmi::{ + memory_units::Pages, ImportResolver, MemoryInstance, Module, ModuleInstance, RuntimeArgs, + RuntimeValue, Trap, +}; + +use sc_executor::error::{Error, Result}; +use crate::{ + sandbox_util::{ + BackendInstance, GuestEnvironment, GuestExternals, GuestFuncIndex, Imports, + InstantiationError, Memory, SandboxContext, SandboxInstance, + }, + util::{checked_range, MemoryTransfer}, + env::HostError, +}; + +environmental::environmental!(SandboxContextStore: trait SandboxContext); + +#[derive(Debug)] +struct CustomHostError(String); + +impl fmt::Display for CustomHostError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "HostError: {}", self.0) + } +} + +impl wasmi::HostError for CustomHostError {} + +/// Construct trap error from specified message +pub fn trap(msg: &'static str) -> Trap { + Trap::host(CustomHostError(msg.into())) +} + +impl ImportResolver for Imports { + fn resolve_func( + &self, + module_name: &str, + field_name: &str, + signature: &wasmi::Signature, + ) -> std::result::Result { + let idx = self.func_by_name(module_name, field_name).ok_or_else(|| { + wasmi::Error::Instantiation(format!("Export {}:{} not found", module_name, field_name)) + })?; + + Ok(wasmi::FuncInstance::alloc_host(signature.clone(), idx.0)) + } + + fn resolve_memory( + &self, + module_name: &str, + field_name: &str, + _memory_type: &wasmi::MemoryDescriptor, + ) -> std::result::Result { + let mem = self.memory_by_name(module_name, field_name).ok_or_else(|| { + wasmi::Error::Instantiation(format!("Export {}:{} not found", module_name, field_name)) + })?; + + let wrapper = mem.as_wasmi().ok_or_else(|| { + wasmi::Error::Instantiation(format!( + "Unsupported non-wasmi export {}:{}", + module_name, field_name + )) + })?; + + // Here we use inner memory reference only to resolve the imports + // without accessing the memory contents. All subsequent memory accesses + // should happen through the wrapper, that enforces the memory access protocol. + let mem = wrapper.0; + + Ok(mem) + } + + fn resolve_global( + &self, + module_name: &str, + field_name: &str, + _global_type: &wasmi::GlobalDescriptor, + ) -> std::result::Result { + Err(wasmi::Error::Instantiation(format!("Export {}:{} not found", module_name, field_name))) + } + + fn resolve_table( + &self, + module_name: &str, + field_name: &str, + _table_type: &wasmi::TableDescriptor, + ) -> std::result::Result { + Err(wasmi::Error::Instantiation(format!("Export {}:{} not found", module_name, field_name))) + } +} + +/// Allocate new memory region +pub fn new_memory(initial: u32, maximum: Option) -> Result { + let memory = Memory::Wasmi(MemoryWrapper::new( + MemoryInstance::alloc(Pages(initial as usize), maximum.map(|m| Pages(m as usize))) + .map_err(|error| Error::Other(error.to_string()))?, + )); + + Ok(memory) +} + +/// Wasmi provides direct access to its memory using slices. +/// +/// This wrapper limits the scope where the slice can be taken to +#[derive(Debug, Clone)] +pub struct MemoryWrapper(wasmi::MemoryRef); + +impl MemoryWrapper { + /// Take ownership of the memory region and return a wrapper object + fn new(memory: wasmi::MemoryRef) -> Self { + Self(memory) + } +} + +impl MemoryTransfer for MemoryWrapper { + fn read(&self, source_addr: Pointer, size: usize) -> Result> { + self.0.with_direct_access(|source| { + let range = checked_range(source_addr.into(), size, source.len()) + .ok_or_else(|| Error::Other("memory read is out of bounds".into()))?; + + Ok(Vec::from(&source[range])) + }) + } + + fn read_into(&self, source_addr: Pointer, destination: &mut [u8]) -> Result<()> { + self.0.with_direct_access(|source| { + let range = checked_range(source_addr.into(), destination.len(), source.len()) + .ok_or_else(|| Error::Other("memory read is out of bounds".into()))?; + + destination.copy_from_slice(&source[range]); + Ok(()) + }) + } + + fn write_from(&self, dest_addr: Pointer, source: &[u8]) -> Result<()> { + self.0.with_direct_access_mut(|destination| { + let range = checked_range(dest_addr.into(), source.len(), destination.len()) + .ok_or_else(|| Error::Other("memory write is out of bounds".into()))?; + + destination[range].copy_from_slice(source); + Ok(()) + }) + } +} + +impl<'a> wasmi::Externals for GuestExternals<'a> { + fn invoke_index( + &mut self, + index: usize, + args: RuntimeArgs, + ) -> std::result::Result, Trap> { + SandboxContextStore::with(|sandbox_context| { + // Make `index` typesafe again. + let index = GuestFuncIndex(index); + + // Convert function index from guest to supervisor space + let func_idx = self.sandbox_instance + .guest_to_supervisor_mapping + .func_by_guest_index(index) + .expect( + "`invoke_index` is called with indexes registered via `FuncInstance::alloc_host`; + `FuncInstance::alloc_host` is called with indexes that were obtained from `guest_to_supervisor_mapping`; + `func_by_guest_index` called with `index` can't return `None`; + qed" + ); + + // Serialize arguments into a byte vector. + let invoke_args_data: Vec = args + .as_ref() + .iter() + .cloned() + .map(|value| match value { + wasmi::RuntimeValue::I32(val) => Value::I32(val), + wasmi::RuntimeValue::I64(val) => Value::I64(val), + wasmi::RuntimeValue::F32(val) => Value::F32(val.into()), + wasmi::RuntimeValue::F64(val) => Value::F64(val.into()), + }) + .collect::>() + .encode(); + + let state = self.state; + + // Move serialized arguments inside the memory, invoke dispatch thunk and + // then free allocated memory. + let invoke_args_len = invoke_args_data.len() as WordSize; + let invoke_args_ptr = sandbox_context + .supervisor_context() + .allocate_memory(invoke_args_len) + .map_err(|_| trap("Can't allocate memory in supervisor for the arguments"))?; + + let deallocate = |supervisor_context: &mut dyn FunctionContext, ptr, fail_msg| { + supervisor_context.deallocate_memory(ptr).map_err(|_| trap(fail_msg)) + }; + + if sandbox_context + .supervisor_context() + .write_memory(invoke_args_ptr, &invoke_args_data) + .is_err() + { + deallocate( + sandbox_context.supervisor_context(), + invoke_args_ptr, + "Failed dealloction after failed write of invoke arguments", + )?; + return Err(trap("Can't write invoke args into memory")) + } + + let result = sandbox_context.invoke( + invoke_args_ptr, + invoke_args_len, + state, + func_idx, + ); + + deallocate( + sandbox_context.supervisor_context(), + invoke_args_ptr, + "Can't deallocate memory for dispatch thunk's invoke arguments", + )?; + let result = result.map_err(|_| trap("Could not invoke sandbox"))?; + + // dispatch_thunk returns pointer to serialized arguments. + // Unpack pointer and len of the serialized result data. + let (serialized_result_val_ptr, serialized_result_val_len) = { + // Cast to u64 to use zero-extension. + let v = result as u64; + let ptr = (v as u64 >> 32) as u32; + let len = (v & 0xFFFFFFFF) as u32; + (Pointer::new(ptr), len) + }; + + let serialized_result_val = sandbox_context + .supervisor_context() + .read_memory(serialized_result_val_ptr, serialized_result_val_len) + .map_err(|_| trap("Can't read the serialized result from dispatch thunk")); + + deallocate( + sandbox_context.supervisor_context(), + serialized_result_val_ptr, + "Can't deallocate memory for dispatch thunk's result", + ) + .and(serialized_result_val) + .and_then(|serialized_result_val| { + let result_val = std::result::Result::::decode(&mut serialized_result_val.as_slice()) + .map_err(|_| trap("Decoding Result failed!"))?; + + match result_val { + Ok(return_value) => Ok(match return_value { + ReturnValue::Unit => None, + ReturnValue::Value(typed_value) => Some( match typed_value { + Value::I32(val) => RuntimeValue::I32(val), + Value::I64(val) => RuntimeValue::I64(val), + Value::F32(val) => RuntimeValue::F32(val.into()), + Value::F64(val) => RuntimeValue::F64(val.into()), + }), + }), + Err(HostError) => Err(trap("Supervisor function returned sandbox::HostError")), + } + }) + }).expect("SandboxContextStore is set when invoking sandboxed functions; qed") + } +} + +fn with_guest_externals(sandbox_instance: &SandboxInstance, state: u32, f: F) -> R + where + F: FnOnce(&mut GuestExternals) -> R, +{ + f(&mut GuestExternals { sandbox_instance, state }) +} + +/// Instantiate a module within a sandbox context +pub fn instantiate( + wasm: &[u8], + guest_env: GuestEnvironment, + state: u32, + sandbox_context: &mut dyn SandboxContext, +) -> std::result::Result, InstantiationError> { + let wasmi_module = Module::from_buffer(wasm).map_err(|_| InstantiationError::ModuleDecoding)?; + let wasmi_instance = ModuleInstance::new(&wasmi_module, &guest_env.imports) + .map_err(|_| InstantiationError::Instantiation)?; + + let sandbox_instance = Rc::new(SandboxInstance { + // In general, it's not a very good idea to use `.not_started_instance()` for + // anything but for extracting memory and tables. But in this particular case, we + // are extracting for the purpose of running `start` function which should be ok. + backend_instance: BackendInstance::Wasmi(wasmi_instance.not_started_instance().clone()), + guest_to_supervisor_mapping: guest_env.guest_to_supervisor_mapping, + }); + + with_guest_externals(&sandbox_instance, state, |guest_externals| { + SandboxContextStore::using(sandbox_context, || { + wasmi_instance + .run_start(guest_externals) + .map_err(|_| InstantiationError::StartTrapped) + }) + })?; + + Ok(sandbox_instance) +} + +/// Invoke a function within a sandboxed module +pub fn invoke( + instance: &SandboxInstance, + module: &wasmi::ModuleRef, + export_name: &str, + args: &[Value], + state: u32, + sandbox_context: &mut dyn SandboxContext, +) -> std::result::Result, Error> { + with_guest_externals(instance, state, |guest_externals| { + SandboxContextStore::using(sandbox_context, || { + let args = args.iter().cloned().map(|value| match value { + Value::I32(val) => RuntimeValue::I32(val), + Value::I64(val) => RuntimeValue::I64(val), + Value::F32(val) => RuntimeValue::F32(val.into()), + Value::F64(val) => RuntimeValue::F64(val.into()), + }) + .collect::>(); + + module + .invoke_export(export_name, &args, guest_externals) + .map(|result| { result.map(|value| match value { + wasmi::RuntimeValue::I32(val) => Value::I32(val), + wasmi::RuntimeValue::I64(val) => Value::I64(val), + wasmi::RuntimeValue::F32(val) => Value::F32(val.into()), + wasmi::RuntimeValue::F64(val) => Value::F64(val.into()), + }) + }) + .map_err(|error| Error::Other(error.to_string())) + }) + }) +} + +/// Get global value by name +pub fn get_global(instance: &wasmi::ModuleRef, name: &str) -> Option { + let value = instance.export_by_name(name)?.as_global()?.get(); + let val = match value { + wasmi::RuntimeValue::I32(val) => Value::I32(val), + wasmi::RuntimeValue::I64(val) => Value::I64(val), + wasmi::RuntimeValue::F32(val) => Value::F32(val.into()), + wasmi::RuntimeValue::F64(val) => Value::F64(val.into()), + }; + + Some(val) +}