Skip to content
This repository was archived by the owner on Nov 15, 2023. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.lock

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

20 changes: 12 additions & 8 deletions core/client/src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,9 @@ use codec::{Encode, Decode};
use hash_db::{Hasher, Prefix};
use primitives::{
Blake2Hasher, H256, ChangesTrieConfiguration, convert_hash,
NeverNativeValue, ExecutionContext,
storage::{StorageKey, StorageData, well_known_keys}, NativeOrEncoded
NeverNativeValue, ExecutionContext, NativeOrEncoded,
storage::{StorageKey, StorageData, well_known_keys},
offchain,
};
use substrate_telemetry::{telemetry, SUBSTRATE_INFO};
use sr_primitives::{
Expand Down Expand Up @@ -1471,7 +1472,7 @@ impl<B, E, Block, RA> CallRuntimeAt<Block> for Client<B, E, Block, RA> where
context: ExecutionContext,
recorder: &Option<Rc<RefCell<ProofRecorder<Block>>>>,
) -> error::Result<NativeOrEncoded<R>> {
let enable_keystore = context.enable_keystore();
let capabilities = context.capabilities();

let manager = match context {
ExecutionContext::BlockConstruction =>
Expand All @@ -1480,16 +1481,19 @@ impl<B, E, Block, RA> CallRuntimeAt<Block> for Client<B, E, Block, RA> where
self.execution_strategies.syncing.get_manager(),
ExecutionContext::Importing =>
self.execution_strategies.importing.get_manager(),
ExecutionContext::OffchainCall =>
self.execution_strategies.other.get_manager(),
ExecutionContext::RichOffchainCall(_) =>
self.execution_strategies.other.get_manager(),
ExecutionContext::OffchainWorker(_) =>
self.execution_strategies.offchain_worker.get_manager(),
ExecutionContext::Other =>
self.execution_strategies.other.get_manager(),
};

let mut offchain_extensions = match context {
ExecutionContext::OffchainWorker(ext) => Some(ext),
ExecutionContext::OffchainWorker(ext)
| ExecutionContext::RichOffchainCall(ext) => Some(ext),
_ => None,
};
}.map(|ext| offchain::LimitedExternalities::new(capabilities, ext));

self.executor.contextual_call::<_, _, fn(_,_) -> _,_,_>(
|| core_api.initialize_block(at, &self.prepare_environment_block(at)?),
Expand All @@ -1502,7 +1506,7 @@ impl<B, E, Block, RA> CallRuntimeAt<Block> for Client<B, E, Block, RA> where
native_call,
offchain_extensions.as_mut(),
recorder,
enable_keystore,
capabilities.has(offchain::Capability::Keystore),
)
}

Expand Down
2 changes: 2 additions & 0 deletions core/primitives/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ twox-hash = { version = "1.2.0", optional = true }
byteorder = { version = "1.3.1", default-features = false }
primitive-types = { version = "0.5.0", default-features = false, features = ["codec"] }
impl-serde = { version = "0.1", optional = true }
log = { version = "0.4", optional = true }
wasmi = { version = "0.5.0", optional = true }
hash-db = { version = "0.15.2", default-features = false }
hash256-std-hasher = { version = "0.15.2", default-features = false }
Expand Down Expand Up @@ -48,6 +49,7 @@ bench = false
[features]
default = ["std"]
std = [
"log",
"wasmi",
"lazy_static",
"parking_lot",
Expand Down
31 changes: 24 additions & 7 deletions core/primitives/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -88,19 +88,36 @@ pub enum ExecutionContext {
Syncing,
/// Context used for block construction.
BlockConstruction,
/// Offchain worker context.
/// Context used for offchain calls.
///
/// Includes keystore capabilities.
OffchainCall,
/// Context used for rich offchain calls.
///
/// This includes access to offchain worker db (read / write)
/// and transaction pool.
RichOffchainCall(Box<dyn offchain::Externalities>),
/// A context used for offchain workers.
///
/// This should include all capabilities of `offchain::Externalities`.
OffchainWorker(Box<dyn offchain::Externalities>),
/// Context used for other calls.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are all of these exclusive?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes.

Other,
}

impl ExecutionContext {
/// Returns if the keystore should be enabled for the current context.
pub fn enable_keystore(&self) -> bool {
/// Returns the capabilities of particular context.
pub fn capabilities(&self) -> offchain::Capabilities {
use ExecutionContext::*;
use crate::offchain::Capability::*;
use crate::offchain::Capabilities;
match self {
Importing | Syncing | BlockConstruction => false,
OffchainWorker(_) | Other => true,
Importing | Syncing | BlockConstruction => Capabilities::none(),
OffchainCall => [Keystore][..].into(),
RichOffchainCall(_) => [
TransactionPool,
Keystore,
OffchainWorkerDb,
][..].into(),
OffchainWorker(_) => Capabilities::all(),
}
}
}
Expand Down
200 changes: 200 additions & 0 deletions core/primitives/src/offchain.rs
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,51 @@ impl Timestamp {
}
}

/// Execution context extra capabilities.
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
#[repr(C)]
pub enum Capability {
/// Access to transaction pool.
TransactionPool = 1,
/// Access to offchain worker DB.
OffchainWorkerDb = 2,
/// External http calls.
Http = 4,
/// Keystore access.
Keystore = 8,
/// Randomness source.
Randomness = 16,
/// Access to opaque network state.
NetworkState = 32,
}

/// 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())
}

/// Check if particular capability is enabled.
pub fn has(&self, capability: Capability) -> bool {
self.0 & capability as u8 != 0
}
}

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.
pub trait Externalities {
/// Returns if the local node is a potential validator.
Expand Down Expand Up @@ -430,6 +475,147 @@ impl<T: Externalities + ?Sized> Externalities for Box<T> {
(&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.
///
/// Returns `Ok` in case it is, `Err` otherwise.
fn check(&self, capability: Capability) -> Result<(), ()> {
if self.capabilities.has(capability) {
Ok(())
} else {
#[cfg(feature = "log")]
log::warn!("Accessing a forbidden API. No: {:?} capability.", capability);
Err(())
}
}
}

impl<T: Externalities> Externalities for LimitedExternalities<T> {
fn is_validator(&self) -> bool {
if self.check(Capability::Keystore).is_ok() {
self.externalities.is_validator()
} else {
false
}
}

fn submit_transaction(&mut self, ex: Vec<u8>) -> Result<(), ()> {
self.check(Capability::TransactionPool)?;
self.externalities.submit_transaction(ex)
}

fn network_state(&self) -> Result<OpaqueNetworkState, ()> {
self.check(Capability::NetworkState)?;
self.externalities.network_state()
}

fn timestamp(&mut self) -> Timestamp {
if self.check(Capability::Http).is_ok() {
self.externalities.timestamp()
} else {
Timestamp::from_unix_millis(0)
}
}

fn sleep_until(&mut self, deadline: Timestamp) {
if self.check(Capability::Http).is_ok() {
self.externalities.sleep_until(deadline)
}
}

fn random_seed(&mut self) -> [u8; 32] {
if self.check(Capability::Randomness).is_ok() {
self.externalities.random_seed()
} else {
[0_u8; 32]
}
}

fn local_storage_set(&mut self, kind: StorageKind, key: &[u8], value: &[u8]) {
if self.check(Capability::OffchainWorkerDb).is_ok() {
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 {
if self.check(Capability::OffchainWorkerDb).is_ok() {
self.externalities.local_storage_compare_and_set(kind, key, old_value, new_value)
} else {
true
}
}

fn local_storage_get(&mut self, kind: StorageKind, key: &[u8]) -> Option<Vec<u8>> {
self.check(Capability::OffchainWorkerDb).ok()?;
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)?;
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)?;
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).map_err(|_| HttpError::IoError)?;
self.externalities.http_request_write_body(request_id, chunk, deadline)
}

fn http_response_wait(&mut self, ids: &[HttpRequestId], deadline: Option<Timestamp>) -> Vec<HttpRequestStatus> {
if self.check(Capability::Http).is_ok() {
self.externalities.http_response_wait(ids, deadline)
} else {
ids.iter().map(|_| HttpRequestStatus::Unknown).collect()
}
}

fn http_response_headers(&mut self, request_id: HttpRequestId) -> Vec<(Vec<u8>, Vec<u8>)> {
if self.check(Capability::Http).is_ok() {
self.externalities.http_response_headers(request_id)
} else {
Default::default()
}
}

fn http_response_read_body(
&mut self,
request_id: HttpRequestId,
buffer: &mut [u8],
deadline: Option<Timestamp>
) -> Result<usize, HttpError> {
self.check(Capability::Http).map_err(|_| HttpError::IoError)?;
self.externalities.http_response_read_body(request_id, buffer, deadline)
}
}


#[cfg(test)]
Expand All @@ -443,4 +629,18 @@ mod tests {
assert_eq!(t.sub(Duration::from_millis(10)), Timestamp(0));
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));
}
}
2 changes: 1 addition & 1 deletion core/sr-api-macros/src/decl_runtime_apis.rs
Original file line number Diff line number Diff line change
Expand Up @@ -552,7 +552,7 @@ impl<'a> ToClientSideDecl<'a> {
fn fold_trait_item_method(&mut self, method: TraitItemMethod)
-> (TraitItemMethod, Option<TraitItemMethod>, TraitItemMethod) {
let crate_ = self.crate_;
let context_other = quote!( #crate_::runtime_api::ExecutionContext::Other );
let context_other = quote!( #crate_::runtime_api::ExecutionContext::OffchainCall );
let fn_impl = self.create_method_runtime_api_impl(method.clone());
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe rename context_other.

let fn_decl = self.create_method_decl(method.clone(), context_other);
let fn_decl_ctx = self.create_method_decl_with_context(method);
Expand Down
2 changes: 1 addition & 1 deletion node/runtime/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion {
// implementation changes and behavior does not, then leave spec_version as
// is and increment impl_version.
spec_version: 148,
impl_version: 148,
impl_version: 149,
apis: RUNTIME_API_VERSIONS,
};

Expand Down