mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-06-13 09:21:05 +00:00
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:
committed by
Gavin Wood
parent
f000392cc0
commit
86b6ac5571
@@ -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 }
|
||||
|
||||
@@ -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>,
|
||||
}
|
||||
|
||||
|
||||
@@ -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>,
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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};
|
||||
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user