mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-06-14 07:31:08 +00:00
Introduce capabilities filtering for off-chain runtime calls. (#3454)
* Introduce capabilities filtering for calls. * Bump impl version. * Allow RichOffchainCall to only read offchain db. * Fix code. * Panic on invalid calls. * Merge execution contexts and expose capabilities. * Fix repr * Re-enable keystore for offchain calls.
This commit is contained in:
committed by
Bastian Köcher
parent
d81df14391
commit
0128d0db84
Generated
+1
@@ -4946,6 +4946,7 @@ dependencies = [
|
|||||||
"hex-literal 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
"hex-literal 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"impl-serde 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
"impl-serde 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
"lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"log 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"num-traits 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
"num-traits 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"parity-scale-codec 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
"parity-scale-codec 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"parking_lot 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
"parking_lot 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
|||||||
@@ -27,8 +27,9 @@ use codec::{Encode, Decode};
|
|||||||
use hash_db::{Hasher, Prefix};
|
use hash_db::{Hasher, Prefix};
|
||||||
use primitives::{
|
use primitives::{
|
||||||
Blake2Hasher, H256, ChangesTrieConfiguration, convert_hash,
|
Blake2Hasher, H256, ChangesTrieConfiguration, convert_hash,
|
||||||
NeverNativeValue, ExecutionContext,
|
NeverNativeValue, ExecutionContext, NativeOrEncoded,
|
||||||
storage::{StorageKey, StorageData, well_known_keys}, NativeOrEncoded
|
storage::{StorageKey, StorageData, well_known_keys},
|
||||||
|
offchain,
|
||||||
};
|
};
|
||||||
use substrate_telemetry::{telemetry, SUBSTRATE_INFO};
|
use substrate_telemetry::{telemetry, SUBSTRATE_INFO};
|
||||||
use sr_primitives::{
|
use sr_primitives::{
|
||||||
@@ -1471,8 +1472,6 @@ impl<B, E, Block, RA> CallRuntimeAt<Block> for Client<B, E, Block, RA> where
|
|||||||
context: ExecutionContext,
|
context: ExecutionContext,
|
||||||
recorder: &Option<Rc<RefCell<ProofRecorder<Block>>>>,
|
recorder: &Option<Rc<RefCell<ProofRecorder<Block>>>>,
|
||||||
) -> error::Result<NativeOrEncoded<R>> {
|
) -> error::Result<NativeOrEncoded<R>> {
|
||||||
let enable_keystore = context.enable_keystore();
|
|
||||||
|
|
||||||
let manager = match context {
|
let manager = match context {
|
||||||
ExecutionContext::BlockConstruction =>
|
ExecutionContext::BlockConstruction =>
|
||||||
self.execution_strategies.block_construction.get_manager(),
|
self.execution_strategies.block_construction.get_manager(),
|
||||||
@@ -1480,16 +1479,17 @@ impl<B, E, Block, RA> CallRuntimeAt<Block> for Client<B, E, Block, RA> where
|
|||||||
self.execution_strategies.syncing.get_manager(),
|
self.execution_strategies.syncing.get_manager(),
|
||||||
ExecutionContext::Importing =>
|
ExecutionContext::Importing =>
|
||||||
self.execution_strategies.importing.get_manager(),
|
self.execution_strategies.importing.get_manager(),
|
||||||
ExecutionContext::OffchainWorker(_) =>
|
ExecutionContext::OffchainCall(Some((_, capabilities))) if capabilities.has_all() =>
|
||||||
self.execution_strategies.offchain_worker.get_manager(),
|
self.execution_strategies.offchain_worker.get_manager(),
|
||||||
ExecutionContext::Other =>
|
ExecutionContext::OffchainCall(_) =>
|
||||||
self.execution_strategies.other.get_manager(),
|
self.execution_strategies.other.get_manager(),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let capabilities = context.capabilities();
|
||||||
let mut offchain_extensions = match context {
|
let mut offchain_extensions = match context {
|
||||||
ExecutionContext::OffchainWorker(ext) => Some(ext),
|
ExecutionContext::OffchainCall(ext) => ext.map(|x| x.0),
|
||||||
_ => None,
|
_ => None,
|
||||||
};
|
}.map(|ext| offchain::LimitedExternalities::new(capabilities, ext));
|
||||||
|
|
||||||
self.executor.contextual_call::<_, _, fn(_,_) -> _,_,_>(
|
self.executor.contextual_call::<_, _, fn(_,_) -> _,_,_>(
|
||||||
|| core_api.initialize_block(at, &self.prepare_environment_block(at)?),
|
|| core_api.initialize_block(at, &self.prepare_environment_block(at)?),
|
||||||
@@ -1502,7 +1502,7 @@ impl<B, E, Block, RA> CallRuntimeAt<Block> for Client<B, E, Block, RA> where
|
|||||||
native_call,
|
native_call,
|
||||||
offchain_extensions.as_mut(),
|
offchain_extensions.as_mut(),
|
||||||
recorder,
|
recorder,
|
||||||
enable_keystore,
|
capabilities.has(offchain::Capability::Keystore),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ use client::runtime_api::ApiExt;
|
|||||||
use futures::future::Future;
|
use futures::future::Future;
|
||||||
use log::{debug, warn};
|
use log::{debug, warn};
|
||||||
use network::NetworkStateInfo;
|
use network::NetworkStateInfo;
|
||||||
use primitives::ExecutionContext;
|
use primitives::{offchain, ExecutionContext};
|
||||||
use sr_primitives::{generic::BlockId, traits::{self, ProvideRuntimeApi}};
|
use sr_primitives::{generic::BlockId, traits::{self, ProvideRuntimeApi}};
|
||||||
use transaction_pool::txpool::{Pool, ChainApi};
|
use transaction_pool::txpool::{Pool, ChainApi};
|
||||||
|
|
||||||
@@ -122,7 +122,7 @@ impl<Client, Storage, Block> OffchainWorkers<
|
|||||||
debug!("Running offchain workers at {:?}", at);
|
debug!("Running offchain workers at {:?}", at);
|
||||||
let run = runtime.offchain_worker_with_context(
|
let run = runtime.offchain_worker_with_context(
|
||||||
&at,
|
&at,
|
||||||
ExecutionContext::OffchainWorker(api),
|
ExecutionContext::OffchainCall(Some((api, offchain::Capabilities::all()))),
|
||||||
number,
|
number,
|
||||||
);
|
);
|
||||||
if let Err(e) = run {
|
if let Err(e) = run {
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ twox-hash = { version = "1.2.0", optional = true }
|
|||||||
byteorder = { version = "1.3.1", default-features = false }
|
byteorder = { version = "1.3.1", default-features = false }
|
||||||
primitive-types = { version = "0.5.0", default-features = false, features = ["codec"] }
|
primitive-types = { version = "0.5.0", default-features = false, features = ["codec"] }
|
||||||
impl-serde = { version = "0.1", optional = true }
|
impl-serde = { version = "0.1", optional = true }
|
||||||
|
log = { version = "0.4", optional = true }
|
||||||
wasmi = { version = "0.5.0", optional = true }
|
wasmi = { version = "0.5.0", optional = true }
|
||||||
hash-db = { version = "0.15.2", default-features = false }
|
hash-db = { version = "0.15.2", default-features = false }
|
||||||
hash256-std-hasher = { version = "0.15.2", default-features = false }
|
hash256-std-hasher = { version = "0.15.2", default-features = false }
|
||||||
@@ -48,6 +49,7 @@ bench = false
|
|||||||
[features]
|
[features]
|
||||||
default = ["std"]
|
default = ["std"]
|
||||||
std = [
|
std = [
|
||||||
|
"log",
|
||||||
"wasmi",
|
"wasmi",
|
||||||
"lazy_static",
|
"lazy_static",
|
||||||
"parking_lot",
|
"parking_lot",
|
||||||
|
|||||||
@@ -88,19 +88,23 @@ pub enum ExecutionContext {
|
|||||||
Syncing,
|
Syncing,
|
||||||
/// Context used for block construction.
|
/// Context used for block construction.
|
||||||
BlockConstruction,
|
BlockConstruction,
|
||||||
/// Offchain worker context.
|
/// Context used for offchain calls.
|
||||||
OffchainWorker(Box<dyn offchain::Externalities>),
|
///
|
||||||
/// Context used for other calls.
|
/// This allows passing offchain extension and customizing available capabilities.
|
||||||
Other,
|
OffchainCall(Option<(Box<dyn offchain::Externalities>, offchain::Capabilities)>),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ExecutionContext {
|
impl ExecutionContext {
|
||||||
/// Returns if the keystore should be enabled for the current context.
|
/// Returns the capabilities of particular context.
|
||||||
pub fn enable_keystore(&self) -> bool {
|
pub fn capabilities(&self) -> offchain::Capabilities {
|
||||||
use ExecutionContext::*;
|
use ExecutionContext::*;
|
||||||
|
|
||||||
match self {
|
match self {
|
||||||
Importing | Syncing | BlockConstruction => false,
|
Importing | Syncing | BlockConstruction =>
|
||||||
OffchainWorker(_) | Other => true,
|
offchain::Capabilities::none(),
|
||||||
|
// Enable keystore by default for offchain calls. CC @bkchr
|
||||||
|
OffchainCall(None) => [offchain::Capability::Keystore][..].into(),
|
||||||
|
OffchainCall(Some((_, capabilities))) => *capabilities,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -232,6 +232,70 @@ impl Timestamp {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Execution context extra capabilities.
|
||||||
|
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
|
||||||
|
#[repr(u8)]
|
||||||
|
pub enum Capability {
|
||||||
|
/// Access to transaction pool.
|
||||||
|
TransactionPool = 1,
|
||||||
|
/// External http calls.
|
||||||
|
Http = 2,
|
||||||
|
/// Keystore access.
|
||||||
|
Keystore = 4,
|
||||||
|
/// Randomness source.
|
||||||
|
Randomness = 8,
|
||||||
|
/// Access to opaque network state.
|
||||||
|
NetworkState = 16,
|
||||||
|
/// Access to offchain worker DB (read only).
|
||||||
|
OffchainWorkerDbRead = 32,
|
||||||
|
/// Access to offchain worker DB (writes).
|
||||||
|
OffchainWorkerDbWrite = 64,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A set of capabilities
|
||||||
|
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
|
||||||
|
pub struct Capabilities(u8);
|
||||||
|
|
||||||
|
impl Capabilities {
|
||||||
|
/// Return an object representing an empty set of capabilities.
|
||||||
|
pub fn none() -> Self {
|
||||||
|
Self(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return an object representing all capabilities enabled.
|
||||||
|
pub fn all() -> Self {
|
||||||
|
Self(u8::max_value())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return capabilities for rich offchain calls.
|
||||||
|
///
|
||||||
|
/// Those calls should be allowed to sign and submit transactions
|
||||||
|
/// and access offchain workers database (but read only!).
|
||||||
|
pub fn rich_offchain_call() -> Self {
|
||||||
|
[
|
||||||
|
Capability::TransactionPool,
|
||||||
|
Capability::Keystore,
|
||||||
|
Capability::OffchainWorkerDbRead,
|
||||||
|
][..].into()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Check if particular capability is enabled.
|
||||||
|
pub fn has(&self, capability: Capability) -> bool {
|
||||||
|
self.0 & capability as u8 != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Check if this capability object represents all capabilities.
|
||||||
|
pub fn has_all(&self) -> bool {
|
||||||
|
self == &Capabilities::all()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> From<&'a [Capability]> for Capabilities {
|
||||||
|
fn from(list: &'a [Capability]) -> Self {
|
||||||
|
Capabilities(list.iter().fold(0_u8, |a, b| a | *b as u8))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// An extended externalities for offchain workers.
|
/// An extended externalities for offchain workers.
|
||||||
pub trait Externalities {
|
pub trait Externalities {
|
||||||
/// Returns if the local node is a potential validator.
|
/// Returns if the local node is a potential validator.
|
||||||
@@ -481,6 +545,123 @@ impl<T: Externalities + ?Sized> Externalities for Box<T> {
|
|||||||
(&mut **self).http_response_read_body(request_id, buffer, deadline)
|
(&mut **self).http_response_read_body(request_id, buffer, deadline)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
/// An `OffchainExternalities` implementation with limited capabilities.
|
||||||
|
pub struct LimitedExternalities<T> {
|
||||||
|
capabilities: Capabilities,
|
||||||
|
externalities: T,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> LimitedExternalities<T> {
|
||||||
|
/// Create new externalities limited to given `capabilities`.
|
||||||
|
pub fn new(capabilities: Capabilities, externalities: T) -> Self {
|
||||||
|
Self {
|
||||||
|
capabilities,
|
||||||
|
externalities,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Check if given capability is allowed.
|
||||||
|
///
|
||||||
|
/// Panics in case it is not.
|
||||||
|
fn check(&self, capability: Capability, name: &'static str) {
|
||||||
|
if !self.capabilities.has(capability) {
|
||||||
|
panic!("Accessing a forbidden API: {}. No: {:?} capability.", name, capability);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Externalities> Externalities for LimitedExternalities<T> {
|
||||||
|
fn is_validator(&self) -> bool {
|
||||||
|
self.check(Capability::Keystore, "is_validator");
|
||||||
|
self.externalities.is_validator()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn submit_transaction(&mut self, ex: Vec<u8>) -> Result<(), ()> {
|
||||||
|
self.check(Capability::TransactionPool, "submit_transaction");
|
||||||
|
self.externalities.submit_transaction(ex)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn network_state(&self) -> Result<OpaqueNetworkState, ()> {
|
||||||
|
self.check(Capability::NetworkState, "network_state");
|
||||||
|
self.externalities.network_state()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn timestamp(&mut self) -> Timestamp {
|
||||||
|
self.check(Capability::Http, "timestamp");
|
||||||
|
self.externalities.timestamp()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn sleep_until(&mut self, deadline: Timestamp) {
|
||||||
|
self.check(Capability::Http, "sleep_until");
|
||||||
|
self.externalities.sleep_until(deadline)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn random_seed(&mut self) -> [u8; 32] {
|
||||||
|
self.check(Capability::Randomness, "random_seed");
|
||||||
|
self.externalities.random_seed()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn local_storage_set(&mut self, kind: StorageKind, key: &[u8], value: &[u8]) {
|
||||||
|
self.check(Capability::OffchainWorkerDbWrite, "local_storage_set");
|
||||||
|
self.externalities.local_storage_set(kind, key, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn local_storage_compare_and_set(
|
||||||
|
&mut self,
|
||||||
|
kind: StorageKind,
|
||||||
|
key: &[u8],
|
||||||
|
old_value: Option<&[u8]>,
|
||||||
|
new_value: &[u8],
|
||||||
|
) -> bool {
|
||||||
|
self.check(Capability::OffchainWorkerDbWrite, "local_storage_compare_and_set");
|
||||||
|
self.externalities.local_storage_compare_and_set(kind, key, old_value, new_value)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn local_storage_get(&mut self, kind: StorageKind, key: &[u8]) -> Option<Vec<u8>> {
|
||||||
|
self.check(Capability::OffchainWorkerDbRead, "local_storage_get");
|
||||||
|
self.externalities.local_storage_get(kind, key)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn http_request_start(&mut self, method: &str, uri: &str, meta: &[u8]) -> Result<HttpRequestId, ()> {
|
||||||
|
self.check(Capability::Http, "http_request_start");
|
||||||
|
self.externalities.http_request_start(method, uri, meta)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn http_request_add_header(&mut self, request_id: HttpRequestId, name: &str, value: &str) -> Result<(), ()> {
|
||||||
|
self.check(Capability::Http, "http_request_add_header");
|
||||||
|
self.externalities.http_request_add_header(request_id, name, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn http_request_write_body(
|
||||||
|
&mut self,
|
||||||
|
request_id: HttpRequestId,
|
||||||
|
chunk: &[u8],
|
||||||
|
deadline: Option<Timestamp>
|
||||||
|
) -> Result<(), HttpError> {
|
||||||
|
self.check(Capability::Http, "http_request_write_body");
|
||||||
|
self.externalities.http_request_write_body(request_id, chunk, deadline)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn http_response_wait(&mut self, ids: &[HttpRequestId], deadline: Option<Timestamp>) -> Vec<HttpRequestStatus> {
|
||||||
|
self.check(Capability::Http, "http_response_wait");
|
||||||
|
self.externalities.http_response_wait(ids, deadline)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn http_response_headers(&mut self, request_id: HttpRequestId) -> Vec<(Vec<u8>, Vec<u8>)> {
|
||||||
|
self.check(Capability::Http, "http_response_headers");
|
||||||
|
self.externalities.http_response_headers(request_id)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn http_response_read_body(
|
||||||
|
&mut self,
|
||||||
|
request_id: HttpRequestId,
|
||||||
|
buffer: &mut [u8],
|
||||||
|
deadline: Option<Timestamp>
|
||||||
|
) -> Result<usize, HttpError> {
|
||||||
|
self.check(Capability::Http, "http_response_read_body");
|
||||||
|
self.externalities.http_response_read_body(request_id, buffer, deadline)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
@@ -494,4 +675,18 @@ mod tests {
|
|||||||
assert_eq!(t.sub(Duration::from_millis(10)), Timestamp(0));
|
assert_eq!(t.sub(Duration::from_millis(10)), Timestamp(0));
|
||||||
assert_eq!(t.diff(&Timestamp(3)), Duration(2));
|
assert_eq!(t.diff(&Timestamp(3)), Duration(2));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn capabilities() {
|
||||||
|
let none = Capabilities::none();
|
||||||
|
let all = Capabilities::all();
|
||||||
|
let some = Capabilities::from(&[Capability::Keystore, Capability::Randomness][..]);
|
||||||
|
|
||||||
|
assert!(!none.has(Capability::Keystore));
|
||||||
|
assert!(all.has(Capability::Keystore));
|
||||||
|
assert!(some.has(Capability::Keystore));
|
||||||
|
assert!(!none.has(Capability::TransactionPool));
|
||||||
|
assert!(all.has(Capability::TransactionPool));
|
||||||
|
assert!(!some.has(Capability::TransactionPool));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -552,9 +552,9 @@ impl<'a> ToClientSideDecl<'a> {
|
|||||||
fn fold_trait_item_method(&mut self, method: TraitItemMethod)
|
fn fold_trait_item_method(&mut self, method: TraitItemMethod)
|
||||||
-> (TraitItemMethod, Option<TraitItemMethod>, TraitItemMethod) {
|
-> (TraitItemMethod, Option<TraitItemMethod>, TraitItemMethod) {
|
||||||
let crate_ = self.crate_;
|
let crate_ = self.crate_;
|
||||||
let context_other = quote!( #crate_::runtime_api::ExecutionContext::Other );
|
let context = quote!( #crate_::runtime_api::ExecutionContext::OffchainCall(None) );
|
||||||
let fn_impl = self.create_method_runtime_api_impl(method.clone());
|
let fn_impl = self.create_method_runtime_api_impl(method.clone());
|
||||||
let fn_decl = self.create_method_decl(method.clone(), context_other);
|
let fn_decl = self.create_method_decl(method.clone(), context);
|
||||||
let fn_decl_ctx = self.create_method_decl_with_context(method);
|
let fn_decl_ctx = self.create_method_decl_with_context(method);
|
||||||
|
|
||||||
(fn_decl, fn_impl, fn_decl_ctx)
|
(fn_decl, fn_impl, fn_decl_ctx)
|
||||||
|
|||||||
@@ -80,7 +80,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion {
|
|||||||
// implementation changes and behavior does not, then leave spec_version as
|
// implementation changes and behavior does not, then leave spec_version as
|
||||||
// is and increment impl_version.
|
// is and increment impl_version.
|
||||||
spec_version: 151,
|
spec_version: 151,
|
||||||
impl_version: 152,
|
impl_version: 153,
|
||||||
apis: RUNTIME_API_VERSIONS,
|
apis: RUNTIME_API_VERSIONS,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user