Offchain execution extensions (#4145)

* Pass Extensions instead of individual objects.

* Move TransactionPool to a separate ExternalitiesExtension.

* Fix compilation.?

* Clean up.

* Refactor testing utilities.

* Add docs, fix tests.

* Fix doctest.

* Fix formatting and add some logs.

* Add some docs.

* Remove unused files.
This commit is contained in:
Tomasz Drwięga
2019-11-22 17:10:23 +01:00
committed by Gavin Wood
parent f000392cc0
commit 86b6ac5571
39 changed files with 554 additions and 360 deletions
+1
View File
@@ -10,6 +10,7 @@ codec = { package = "parity-scale-codec", version = "1.0.0", default-features =
consensus = { package = "substrate-consensus-common", path = "../../primitives/consensus/common" }
derive_more = { version = "0.15.0" }
executor = { package = "substrate-executor", path = "../executor" }
externalities = { package = "substrate-externalities", path = "../../primitives/externalities" }
fnv = { version = "1.0.6" }
futures = { version = "0.3.1" }
hash-db = { version = "0.15.2", default-features = false }
+14 -1
View File
@@ -19,6 +19,7 @@
use std::sync::Arc;
use std::collections::HashMap;
use primitives::ChangesTrieConfiguration;
use primitives::offchain::OffchainStorage;
use sr_primitives::{generic::BlockId, Justification, StorageOverlay, ChildrenStorageOverlay};
use sr_primitives::traits::{Block as BlockT, NumberFor};
use state_machine::backend::Backend as StateBackend;
@@ -27,7 +28,6 @@ use crate::{
blockchain::{
Backend as BlockchainBackend, well_known_cache_keys
},
offchain::OffchainStorage,
error,
light::RemoteBlockchain,
};
@@ -41,12 +41,22 @@ pub type StorageCollection = Vec<(Vec<u8>, Option<Vec<u8>>)>;
/// In memory arrays of storage values for multiple child tries.
pub type ChildStorageCollection = Vec<(Vec<u8>, StorageCollection)>;
/// Import operation summary.
///
/// Contains information about the block that just got imported,
/// including storage changes, reorged blocks, etc.
pub struct ImportSummary<Block: BlockT> {
/// Block hash of the imported block.
pub hash: Block::Hash,
/// Import origin.
pub origin: BlockOrigin,
/// Header of the imported block.
pub header: Block::Header,
/// Is this block a new best block.
pub is_new_best: bool,
/// Optional storage changes.
pub storage_changes: Option<(StorageCollection, ChildStorageCollection)>,
/// Blocks that got retracted because of this one got imported.
pub retracted: Vec<Block::Hash>,
}
@@ -56,8 +66,11 @@ pub struct ClientImportOperation<
H: Hasher<Out=Block::Hash>,
B: Backend<Block, H>,
> {
/// DB Operation.
pub op: B::BlockImportOperation,
/// Summary of imported block.
pub notify_imported: Option<ImportSummary<Block>>,
/// A list of hashes of blocks that got finalized.
pub notify_finalized: Vec<Block::Hash>,
}
+7 -5
View File
@@ -14,6 +14,8 @@
// You should have received a copy of the GNU General Public License
// along with Substrate. If not, see <http://www.gnu.org/licenses/>.
//! A method call executor interface.
use std::{cmp::Ord, panic::UnwindSafe, result, cell::RefCell};
use codec::{Encode, Decode};
use sr_primitives::{
@@ -24,8 +26,9 @@ use state_machine::{
ChangesTrieTransaction, StorageProof,
};
use executor::{RuntimeVersion, NativeVersion};
use externalities::Extensions;
use hash_db::Hasher;
use primitives::{offchain::OffchainExt, Blake2Hasher, NativeOrEncoded};
use primitives::{Blake2Hasher, NativeOrEncoded};
use sr_api::{ProofRecorder, InitializeBlock};
use crate::error;
@@ -49,7 +52,7 @@ where
method: &str,
call_data: &[u8],
strategy: ExecutionStrategy,
side_effects_handler: Option<OffchainExt>,
extensions: Option<Extensions>,
) -> Result<Vec<u8>, error::Error>;
/// Execute a contextual call on top of state in a block of a given hash.
@@ -76,9 +79,8 @@ where
initialize_block: InitializeBlock<'a, B>,
execution_manager: ExecutionManager<EM>,
native_call: Option<NC>,
side_effects_handler: Option<OffchainExt>,
proof_recorder: &Option<ProofRecorder<B>>,
enable_keystore: bool,
extensions: Option<Extensions>,
) -> error::Result<NativeOrEncoded<R>> where ExecutionManager<EM>: Clone;
/// Extract RuntimeVersion of given block
@@ -104,7 +106,7 @@ where
call_data: &[u8],
manager: ExecutionManager<F>,
native_call: Option<NC>,
side_effects_handler: Option<OffchainExt>,
extensions: Option<Extensions>,
) -> Result<
(
NativeOrEncoded<R>,
+2 -28
View File
@@ -14,10 +14,11 @@
// You should have received a copy of the GNU General Public License
// along with Substrate. If not, see <http://www.gnu.org/licenses/>.
//! A set of APIs supported by the client along with their primitives.
use std::collections::HashMap;
use futures::channel::mpsc;
use primitives::storage::StorageKey;
use state_machine::ExecutionStrategy;
use sr_primitives::{
traits::{Block as BlockT, NumberFor},
generic::BlockId
@@ -39,33 +40,6 @@ pub type FinalityNotifications<Block> = mpsc::UnboundedReceiver<FinalityNotifica
/// This may be used as chain spec extension to filter out known, unwanted forks.
pub type ForkBlocks<Block> = Option<HashMap<NumberFor<Block>, <Block as BlockT>::Hash>>;
/// Execution strategies settings.
#[derive(Debug, Clone)]
pub struct ExecutionStrategies {
/// Execution strategy used when syncing.
pub syncing: ExecutionStrategy,
/// Execution strategy used when importing blocks.
pub importing: ExecutionStrategy,
/// Execution strategy used when constructing blocks.
pub block_construction: ExecutionStrategy,
/// Execution strategy used for offchain workers.
pub offchain_worker: ExecutionStrategy,
/// Execution strategy used in other cases.
pub other: ExecutionStrategy,
}
impl Default for ExecutionStrategies {
fn default() -> ExecutionStrategies {
ExecutionStrategies {
syncing: ExecutionStrategy::NativeElseWasm,
importing: ExecutionStrategy::NativeElseWasm,
block_construction: ExecutionStrategy::AlwaysWasm,
offchain_worker: ExecutionStrategy::NativeWhenPossible,
other: ExecutionStrategy::NativeElseWasm,
}
}
}
/// Figure out the block type for a given type (for now, just a `Client`).
pub trait BlockOf {
/// The type of the block.
@@ -0,0 +1,184 @@
// Copyright 2019 Parity Technologies (UK) Ltd.
// This file is part of Substrate.
// Substrate 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.
// Substrate 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 Substrate. If not, see <http://www.gnu.org/licenses/>.
//! Execution extensions for runtime calls.
//!
//! This module is responsible for defining the execution
//! strategy for the runtime calls and provide the right `Externalities`
//! extensions to support APIs for particular execution context & capabilities.
use std::sync::{Weak, Arc};
use codec::Decode;
use primitives::{
ExecutionContext,
offchain::{self, OffchainExt, TransactionPoolExt},
traits::{BareCryptoStorePtr, KeystoreExt},
};
use sr_primitives::{
generic::BlockId,
traits,
offchain::{TransactionPool},
};
use state_machine::{ExecutionStrategy, ExecutionManager, DefaultHandler};
use externalities::Extensions;
use parking_lot::RwLock;
/// Execution strategies settings.
#[derive(Debug, Clone)]
pub struct ExecutionStrategies {
/// Execution strategy used when syncing.
pub syncing: ExecutionStrategy,
/// Execution strategy used when importing blocks.
pub importing: ExecutionStrategy,
/// Execution strategy used when constructing blocks.
pub block_construction: ExecutionStrategy,
/// Execution strategy used for offchain workers.
pub offchain_worker: ExecutionStrategy,
/// Execution strategy used in other cases.
pub other: ExecutionStrategy,
}
impl Default for ExecutionStrategies {
fn default() -> ExecutionStrategies {
ExecutionStrategies {
syncing: ExecutionStrategy::NativeElseWasm,
importing: ExecutionStrategy::NativeElseWasm,
block_construction: ExecutionStrategy::AlwaysWasm,
offchain_worker: ExecutionStrategy::NativeWhenPossible,
other: ExecutionStrategy::NativeElseWasm,
}
}
}
/// A producer of execution extensions for offchain calls.
///
/// This crate aggregates extensions available for the offchain calls
/// and is responsbile to produce a right `Extensions` object
/// for each call, based on required `Capabilities`.
pub struct ExecutionExtensions<Block: traits::Block> {
strategies: ExecutionStrategies,
keystore: Option<BareCryptoStorePtr>,
transaction_pool: RwLock<Option<Weak<dyn TransactionPool<Block>>>>,
}
impl<Block: traits::Block> Default for ExecutionExtensions<Block> {
fn default() -> Self {
Self {
strategies: Default::default(),
keystore: None,
transaction_pool: RwLock::new(None),
}
}
}
impl<Block: traits::Block> ExecutionExtensions<Block> {
/// Create new `ExecutionExtensions` given a `keystore` and `ExecutionStrategies`.
pub fn new(
strategies: ExecutionStrategies,
keystore: Option<BareCryptoStorePtr>,
) -> Self {
let transaction_pool = RwLock::new(None);
Self { strategies, keystore, transaction_pool }
}
/// Get a reference to the execution strategies.
pub fn strategies(&self) -> &ExecutionStrategies {
&self.strategies
}
/// Register transaction pool extension.
///
/// To break retain cycle between `Client` and `TransactionPool` we require this
/// extension to be a `Weak` reference.
/// That's also the reason why it's being registered lazily instead of
/// during initialisation.
pub fn register_transaction_pool(&self, pool: Weak<dyn TransactionPool<Block>>) {
*self.transaction_pool.write() = Some(pool);
}
/// Create `ExecutionManager` and `Extensions` for given offchain call.
///
/// Based on the execution context and capabilities it produces
/// the right manager and extensions object to support desired set of APIs.
pub fn manager_and_extensions<E: std::fmt::Debug, R: codec::Codec>(
&self,
at: &BlockId<Block>,
context: ExecutionContext,
) -> (
ExecutionManager<DefaultHandler<R, E>>,
Extensions,
) {
let manager = match context {
ExecutionContext::BlockConstruction =>
self.strategies.block_construction.get_manager(),
ExecutionContext::Syncing =>
self.strategies.syncing.get_manager(),
ExecutionContext::Importing =>
self.strategies.importing.get_manager(),
ExecutionContext::OffchainCall(Some((_, capabilities))) if capabilities.has_all() =>
self.strategies.offchain_worker.get_manager(),
ExecutionContext::OffchainCall(_) =>
self.strategies.other.get_manager(),
};
let capabilities = context.capabilities();
let mut extensions = Extensions::new();
if capabilities.has(offchain::Capability::Keystore) {
if let Some(keystore) = self.keystore.as_ref() {
extensions.register(KeystoreExt(keystore.clone()));
}
}
if capabilities.has(offchain::Capability::TransactionPool) {
if let Some(pool) = self.transaction_pool.read().as_ref().and_then(|x| x.upgrade()) {
extensions.register(TransactionPoolExt(Box::new(TransactionPoolAdapter {
at: *at,
pool,
}) as _));
}
}
if let ExecutionContext::OffchainCall(Some(ext)) = context {
extensions.register(
OffchainExt::new(offchain::LimitedExternalities::new(capabilities, ext.0))
)
}
(manager, extensions)
}
}
/// A wrapper type to pass `BlockId` to the actual transaction pool.
struct TransactionPoolAdapter<Block: traits::Block> {
at: BlockId<Block>,
pool: Arc<dyn TransactionPool<Block>>,
}
impl<Block: traits::Block> offchain::TransactionPool for TransactionPoolAdapter<Block> {
fn submit_transaction(&mut self, data: Vec<u8>) -> Result<(), ()> {
let xt = match Block::Extrinsic::decode(&mut &*data) {
Ok(xt) => xt,
Err(e) => {
log::warn!("Unable to decode extrinsic: {:?}: {}", data, e.what());
return Err(());
},
};
self.pool.submit_at(&self.at, xt)
}
}
+9 -9
View File
@@ -15,25 +15,25 @@
// along with Substrate. If not, see <http://www.gnu.org/licenses/>.
//! Substrate client interfaces.
#![warn(missing_docs)]
// TODO: make internal
pub mod error;
pub mod backend;
pub mod blockchain;
pub mod light;
pub mod notifications;
pub mod call_executor;
pub mod client;
pub mod offchain;
pub mod error;
pub mod execution_extensions;
pub mod light;
pub mod notifications;
pub use error::*;
// TODO: avoid re-exports
pub use backend::*;
pub use blockchain::*;
pub use call_executor::*;
pub use client::*;
pub use error::*;
pub use light::*;
pub use notifications::*;
pub use call_executor::*;
pub use offchain::*;
pub use client::*;
pub use state_machine::{StorageProof, ExecutionStrategy};
-77
View File
@@ -1,77 +0,0 @@
// Copyright 2019 Parity Technologies (UK) Ltd.
// This file is part of Substrate.
// Substrate 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.
// Substrate 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 Substrate. If not, see <http://www.gnu.org/licenses/>.
use std::collections::hash_map::{HashMap, Entry};
/// Offchain workers local storage.
pub trait OffchainStorage: Clone + Send + Sync {
/// Persist a value in storage under given key and prefix.
fn set(&mut self, prefix: &[u8], key: &[u8], value: &[u8]);
/// Retrieve a value from storage under given key and prefix.
fn get(&self, prefix: &[u8], key: &[u8]) -> Option<Vec<u8>>;
/// Replace the value in storage if given old_value matches the current one.
///
/// Returns `true` if the value has been set and false otherwise.
fn compare_and_set(
&mut self,
prefix: &[u8],
key: &[u8],
old_value: Option<&[u8]>,
new_value: &[u8],
) -> bool;
}
/// In-memory storage for offchain workers.
#[derive(Debug, Clone, Default)]
pub struct InMemOffchainStorage {
storage: HashMap<Vec<u8>, Vec<u8>>,
}
impl OffchainStorage for InMemOffchainStorage {
fn set(&mut self, prefix: &[u8], key: &[u8], value: &[u8]) {
let key = prefix.iter().chain(key).cloned().collect();
self.storage.insert(key, value.to_vec());
}
fn get(&self, prefix: &[u8], key: &[u8]) -> Option<Vec<u8>> {
let key: Vec<u8> = prefix.iter().chain(key).cloned().collect();
self.storage.get(&key).cloned()
}
fn compare_and_set(
&mut self,
prefix: &[u8],
key: &[u8],
old_value: Option<&[u8]>,
new_value: &[u8],
) -> bool {
let key = prefix.iter().chain(key).cloned().collect();
match self.storage.entry(key) {
Entry::Vacant(entry) => if old_value.is_none() {
entry.insert(new_value.to_vec());
true
} else { false },
Entry::Occupied(ref mut entry) if Some(entry.get().as_slice()) == old_value => {
entry.insert(new_value.to_vec());
true
},
_ => false,
}
}
}