mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-04-30 06:08:00 +00:00
Initial: Offchain Workers (#1942)
* Refactor state-machine stuff. * Fix tests. * WiP * WiP2 * Service support for offchain workers. * Service support for offchain workers. * Testing offchain worker. * Initial version working. * Pass side effects in call. * Pass OffchainExt in context. * Submit extrinsics to the pool. * Support inherents. * Insert to inherents pool. * Inserting to the pool asynchronously. * Add test to offchain worker. * Implement convenience syntax for modules. * Dispatching offchain worker through executive. * Fix offchain test. * Remove offchain worker from timestamp. * Update Cargo.lock. * Address review comments. * Use latest patch version for futures. * Add CLI parameter for offchain worker. * Fix compilation. * Fix test. * Fix extrinsics format for tests. * Fix RPC test. * Bump spec version. * Fix executive. * Fix support macro. * Address grumbles. * Bump runtime
This commit is contained in:
Generated
+32
@@ -1974,6 +1974,7 @@ dependencies = [
|
||||
"substrate-client 0.1.0",
|
||||
"substrate-consensus-aura-primitives 0.1.0",
|
||||
"substrate-keyring 0.1.0",
|
||||
"substrate-offchain-primitives 0.1.0",
|
||||
"substrate-primitives 0.1.0",
|
||||
]
|
||||
|
||||
@@ -2029,6 +2030,7 @@ dependencies = [
|
||||
"srml-timestamp 0.1.0",
|
||||
"substrate-client 0.1.0",
|
||||
"substrate-consensus-aura-primitives 0.1.0",
|
||||
"substrate-offchain-primitives 0.1.0",
|
||||
"substrate-primitives 0.1.0",
|
||||
]
|
||||
|
||||
@@ -3978,6 +3980,33 @@ dependencies = [
|
||||
"void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "substrate-offchain"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"env_logger 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"futures 0.1.25 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"parity-codec 3.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"sr-primitives 0.1.0",
|
||||
"substrate-client 0.1.0",
|
||||
"substrate-consensus-common 0.1.0",
|
||||
"substrate-inherents 0.1.0",
|
||||
"substrate-offchain-primitives 0.1.0",
|
||||
"substrate-primitives 0.1.0",
|
||||
"substrate-test-client 0.1.0",
|
||||
"substrate-transaction-pool 0.1.0",
|
||||
"tokio 0.1.15 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "substrate-offchain-primitives"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"sr-primitives 0.1.0",
|
||||
"substrate-client 0.1.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "substrate-panic-handler"
|
||||
version = "0.1.0"
|
||||
@@ -4110,8 +4139,10 @@ dependencies = [
|
||||
"substrate-client-db 0.1.0",
|
||||
"substrate-consensus-common 0.1.0",
|
||||
"substrate-executor 0.1.0",
|
||||
"substrate-inherents 0.1.0",
|
||||
"substrate-keystore 0.1.0",
|
||||
"substrate-network 0.1.0",
|
||||
"substrate-offchain 0.1.0",
|
||||
"substrate-primitives 0.1.0",
|
||||
"substrate-rpc-servers 0.1.0",
|
||||
"substrate-telemetry 0.3.1",
|
||||
@@ -4222,6 +4253,7 @@ dependencies = [
|
||||
"substrate-executor 0.1.0",
|
||||
"substrate-inherents 0.1.0",
|
||||
"substrate-keyring 0.1.0",
|
||||
"substrate-offchain-primitives 0.1.0",
|
||||
"substrate-primitives 0.1.0",
|
||||
"substrate-test-client 0.1.0",
|
||||
]
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
//
|
||||
use std::{self, time, sync::Arc};
|
||||
|
||||
use log::{info, debug};
|
||||
use log::{info, debug, warn};
|
||||
|
||||
use client::{
|
||||
self, error, Client as SubstrateClient, CallExecutor,
|
||||
@@ -28,15 +28,14 @@ use client::{
|
||||
};
|
||||
use codec::Decode;
|
||||
use consensus_common::{self, evaluation};
|
||||
use primitives::{H256, Blake2Hasher};
|
||||
use primitives::{H256, Blake2Hasher, ExecutionContext};
|
||||
use runtime_primitives::traits::{
|
||||
Block as BlockT, Hash as HashT, Header as HeaderT, ProvideRuntimeApi, AuthorityIdFor
|
||||
};
|
||||
use runtime_primitives::ExecutionContext;
|
||||
use runtime_primitives::generic::BlockId;
|
||||
use runtime_primitives::ApplyError;
|
||||
use transaction_pool::txpool::{self, Pool as TransactionPool};
|
||||
use inherents::InherentData;
|
||||
use inherents::{InherentData, pool::InherentsPool};
|
||||
use substrate_telemetry::{telemetry, CONSENSUS_INFO};
|
||||
|
||||
/// Build new blocks.
|
||||
@@ -115,6 +114,8 @@ pub struct ProposerFactory<C, A> where A: txpool::ChainApi {
|
||||
pub client: Arc<C>,
|
||||
/// The transaction pool.
|
||||
pub transaction_pool: Arc<TransactionPool<A>>,
|
||||
/// The inherents pool
|
||||
pub inherents_pool: Arc<InherentsPool<<A::Block as BlockT>::Extrinsic>>,
|
||||
}
|
||||
|
||||
impl<C, A> consensus_common::Environment<<C as AuthoringApi>::Block> for ProposerFactory<C, A> where
|
||||
@@ -144,6 +145,7 @@ impl<C, A> consensus_common::Environment<<C as AuthoringApi>::Block> for Propose
|
||||
parent_id: id,
|
||||
parent_number: *parent_header.number(),
|
||||
transaction_pool: self.transaction_pool.clone(),
|
||||
inherents_pool: self.inherents_pool.clone(),
|
||||
now: Box::new(time::Instant::now),
|
||||
};
|
||||
|
||||
@@ -158,6 +160,7 @@ pub struct Proposer<Block: BlockT, C, A: txpool::ChainApi> {
|
||||
parent_id: BlockId<Block>,
|
||||
parent_number: <<Block as BlockT>::Header as HeaderT>::Number,
|
||||
transaction_pool: Arc<TransactionPool<A>>,
|
||||
inherents_pool: Arc<InherentsPool<<Block as BlockT>::Extrinsic>>,
|
||||
now: Box<Fn() -> time::Instant>,
|
||||
}
|
||||
|
||||
@@ -201,11 +204,23 @@ impl<Block, C, A> Proposer<Block, C, A> where
|
||||
&self.parent_id,
|
||||
inherent_data,
|
||||
|block_builder| {
|
||||
// Add inherents from the internal pool
|
||||
|
||||
let inherents = self.inherents_pool.drain();
|
||||
debug!("Pushing {} queued inherents.", inherents.len());
|
||||
for i in inherents {
|
||||
if let Err(e) = block_builder.push_extrinsic(i) {
|
||||
warn!("Error while pushing inherent extrinsic from the pool: {:?}", e);
|
||||
}
|
||||
}
|
||||
|
||||
// proceed with transactions
|
||||
let mut is_first = true;
|
||||
let mut skipped = 0;
|
||||
let mut unqueue_invalid = Vec::new();
|
||||
let pending_iterator = self.transaction_pool.ready();
|
||||
|
||||
debug!("Attempting to push transactions from the pool.");
|
||||
for pending in pending_iterator {
|
||||
if (self.now)() > deadline {
|
||||
debug!("Consensus deadline reached when pushing block transactions, proceeding with proposing.");
|
||||
@@ -303,6 +318,7 @@ mod tests {
|
||||
let proposer_factory = ProposerFactory {
|
||||
client: client.clone(),
|
||||
transaction_pool: txpool.clone(),
|
||||
inherents_pool: Default::default(),
|
||||
};
|
||||
|
||||
let mut proposer = proposer_factory.init(
|
||||
@@ -325,4 +341,32 @@ mod tests {
|
||||
assert_eq!(txpool.ready().count(), 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_include_inherents_from_the_pool() {
|
||||
// given
|
||||
let client = Arc::new(test_client::new());
|
||||
let chain_api = transaction_pool::ChainApi::new(client.clone());
|
||||
let txpool = Arc::new(TransactionPool::new(Default::default(), chain_api));
|
||||
let inpool = Arc::new(InherentsPool::default());
|
||||
|
||||
let proposer_factory = ProposerFactory {
|
||||
client: client.clone(),
|
||||
transaction_pool: txpool.clone(),
|
||||
inherents_pool: inpool.clone(),
|
||||
};
|
||||
|
||||
inpool.add(extrinsic(0));
|
||||
|
||||
let proposer = proposer_factory.init(
|
||||
&client.header(&BlockId::number(0)).unwrap().unwrap(),
|
||||
&[]
|
||||
).unwrap();
|
||||
|
||||
// when
|
||||
let deadline = time::Duration::from_secs(3);
|
||||
let block = proposer.propose(Default::default(), deadline).unwrap();
|
||||
|
||||
// then
|
||||
assert_eq!(block.extrinsics().len(), 1);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -419,11 +419,20 @@ where
|
||||
service::Roles::FULL
|
||||
};
|
||||
|
||||
let exec = cli.execution_strategies;
|
||||
config.execution_strategies = ExecutionStrategies {
|
||||
syncing: cli.syncing_execution.into(),
|
||||
importing: cli.importing_execution.into(),
|
||||
block_construction: cli.block_construction_execution.into(),
|
||||
other: cli.other_execution.into(),
|
||||
syncing: exec.syncing_execution.into(),
|
||||
importing: exec.importing_execution.into(),
|
||||
block_construction: exec.block_construction_execution.into(),
|
||||
offchain_worker: exec.offchain_worker_execution.into(),
|
||||
other: exec.other_execution.into(),
|
||||
};
|
||||
|
||||
config.offchain_worker = match (cli.offchain_worker, role) {
|
||||
(params::OffchainWorkerEnabled::WhenValidating, service::Roles::AUTHORITY) => true,
|
||||
(params::OffchainWorkerEnabled::Always, _) => true,
|
||||
(params::OffchainWorkerEnabled::Never, _) => false,
|
||||
(params::OffchainWorkerEnabled::WhenValidating, _) => false,
|
||||
};
|
||||
|
||||
config.roles = role;
|
||||
|
||||
@@ -53,6 +53,16 @@ impl Into<client::ExecutionStrategy> for ExecutionStrategy {
|
||||
}
|
||||
}
|
||||
|
||||
arg_enum! {
|
||||
/// How to execute blocks
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum OffchainWorkerEnabled {
|
||||
Always,
|
||||
Never,
|
||||
WhenValidating,
|
||||
}
|
||||
}
|
||||
|
||||
/// Shared parameters used by all `CoreParams`.
|
||||
#[derive(Debug, StructOpt, Clone)]
|
||||
pub struct SharedParams {
|
||||
@@ -205,6 +215,70 @@ pub struct TransactionPoolParams {
|
||||
pub pool_kbytes: usize,
|
||||
}
|
||||
|
||||
/// Execution strategies parameters.
|
||||
#[derive(Debug, StructOpt, Clone)]
|
||||
pub struct ExecutionStrategies {
|
||||
/// The means of execution used when calling into the runtime while syncing blocks.
|
||||
#[structopt(
|
||||
long = "syncing-execution",
|
||||
value_name = "STRATEGY",
|
||||
raw(
|
||||
possible_values = "&ExecutionStrategy::variants()",
|
||||
case_insensitive = "true",
|
||||
default_value = r#""NativeElseWasm""#
|
||||
)
|
||||
)]
|
||||
pub syncing_execution: ExecutionStrategy,
|
||||
|
||||
/// The means of execution used when calling into the runtime while importing blocks.
|
||||
#[structopt(
|
||||
long = "importing-execution",
|
||||
value_name = "STRATEGY",
|
||||
raw(
|
||||
possible_values = "&ExecutionStrategy::variants()",
|
||||
case_insensitive = "true",
|
||||
default_value = r#""NativeElseWasm""#
|
||||
)
|
||||
)]
|
||||
pub importing_execution: ExecutionStrategy,
|
||||
|
||||
/// The means of execution used when calling into the runtime while constructing blocks.
|
||||
#[structopt(
|
||||
long = "block-construction-execution",
|
||||
value_name = "STRATEGY",
|
||||
raw(
|
||||
possible_values = "&ExecutionStrategy::variants()",
|
||||
case_insensitive = "true",
|
||||
default_value = r#""Wasm""#
|
||||
)
|
||||
)]
|
||||
pub block_construction_execution: ExecutionStrategy,
|
||||
|
||||
/// The means of execution used when calling into the runtime while constructing blocks.
|
||||
#[structopt(
|
||||
long = "offchain-worker-execution",
|
||||
value_name = "STRATEGY",
|
||||
raw(
|
||||
possible_values = "&ExecutionStrategy::variants()",
|
||||
case_insensitive = "true",
|
||||
default_value = r#""NativeWhenPossible""#
|
||||
)
|
||||
)]
|
||||
pub offchain_worker_execution: ExecutionStrategy,
|
||||
|
||||
/// The means of execution used when calling into the runtime while not syncing, importing or constructing blocks.
|
||||
#[structopt(
|
||||
long = "other-execution",
|
||||
value_name = "STRATEGY",
|
||||
raw(
|
||||
possible_values = "&ExecutionStrategy::variants()",
|
||||
case_insensitive = "true",
|
||||
default_value = r#""Wasm""#
|
||||
)
|
||||
)]
|
||||
pub other_execution: ExecutionStrategy,
|
||||
}
|
||||
|
||||
/// The `run` command used to run a node.
|
||||
#[derive(Debug, StructOpt, Clone)]
|
||||
pub struct RunCmd {
|
||||
@@ -266,53 +340,22 @@ pub struct RunCmd {
|
||||
#[structopt(long = "telemetry-url", value_name = "URL VERBOSITY", parse(try_from_str = "parse_telemetry_endpoints"))]
|
||||
pub telemetry_endpoints: Vec<(String, u8)>,
|
||||
|
||||
/// The means of execution used when calling into the runtime while syncing blocks.
|
||||
/// Should execute offchain workers on every block. By default it's only enabled for nodes that are authoring new
|
||||
/// blocks.
|
||||
#[structopt(
|
||||
long = "syncing-execution",
|
||||
value_name = "STRATEGY",
|
||||
long = "offchain-worker",
|
||||
value_name = "ENABLED",
|
||||
raw(
|
||||
possible_values = "&ExecutionStrategy::variants()",
|
||||
possible_values = "&OffchainWorkerEnabled::variants()",
|
||||
case_insensitive = "true",
|
||||
default_value = r#""NativeElseWasm""#
|
||||
default_value = r#""WhenValidating""#
|
||||
)
|
||||
)]
|
||||
pub syncing_execution: ExecutionStrategy,
|
||||
pub offchain_worker: OffchainWorkerEnabled,
|
||||
|
||||
/// The means of execution used when calling into the runtime while importing blocks.
|
||||
#[structopt(
|
||||
long = "importing-execution",
|
||||
value_name = "STRATEGY",
|
||||
raw(
|
||||
possible_values = "&ExecutionStrategy::variants()",
|
||||
case_insensitive = "true",
|
||||
default_value = r#""NativeElseWasm""#
|
||||
)
|
||||
)]
|
||||
pub importing_execution: ExecutionStrategy,
|
||||
|
||||
/// The means of execution used when calling into the runtime while constructing blocks.
|
||||
#[structopt(
|
||||
long = "block-construction-execution",
|
||||
value_name = "STRATEGY",
|
||||
raw(
|
||||
possible_values = "&ExecutionStrategy::variants()",
|
||||
case_insensitive = "true",
|
||||
default_value = r#""Wasm""#
|
||||
)
|
||||
)]
|
||||
pub block_construction_execution: ExecutionStrategy,
|
||||
|
||||
/// The means of execution used when calling into the runtime while not syncing, importing or constructing blocks.
|
||||
#[structopt(
|
||||
long = "other-execution",
|
||||
value_name = "STRATEGY",
|
||||
raw(
|
||||
possible_values = "&ExecutionStrategy::variants()",
|
||||
case_insensitive = "true",
|
||||
default_value = r#""Wasm""#
|
||||
)
|
||||
)]
|
||||
pub other_execution: ExecutionStrategy,
|
||||
#[allow(missing_docs)]
|
||||
#[structopt(flatten)]
|
||||
pub execution_strategies: ExecutionStrategies,
|
||||
|
||||
#[allow(missing_docs)]
|
||||
#[structopt(flatten)]
|
||||
|
||||
@@ -17,15 +17,15 @@
|
||||
use super::api::BlockBuilder as BlockBuilderApi;
|
||||
use std::vec::Vec;
|
||||
use parity_codec::Encode;
|
||||
use crate::blockchain::HeaderBackend;
|
||||
use runtime_primitives::ApplyOutcome;
|
||||
use runtime_primitives::generic::BlockId;
|
||||
use runtime_primitives::traits::{
|
||||
Header as HeaderT, Hash, Block as BlockT, One, HashFor, ProvideRuntimeApi, ApiRef
|
||||
};
|
||||
use primitives::H256;
|
||||
use runtime_primitives::generic::BlockId;
|
||||
use primitives::{H256, ExecutionContext};
|
||||
use crate::blockchain::HeaderBackend;
|
||||
use crate::runtime_api::Core;
|
||||
use crate::error;
|
||||
use runtime_primitives::{ApplyOutcome, ExecutionContext};
|
||||
|
||||
|
||||
/// Utility for building new (valid) blocks from a stream of extrinsics.
|
||||
|
||||
@@ -19,12 +19,12 @@ use parity_codec::{Encode, Decode};
|
||||
use runtime_primitives::generic::BlockId;
|
||||
use runtime_primitives::traits::Block as BlockT;
|
||||
use state_machine::{
|
||||
self, OverlayedChanges, Ext, CodeExecutor, ExecutionManager, ExecutionStrategy
|
||||
self, OverlayedChanges, Ext, CodeExecutor, ExecutionManager, ExecutionStrategy, NeverOffchainExt,
|
||||
};
|
||||
use executor::{RuntimeVersion, RuntimeInfo, NativeVersion};
|
||||
use hash_db::Hasher;
|
||||
use trie::MemoryDB;
|
||||
use primitives::{H256, Blake2Hasher, NativeOrEncoded, NeverNativeValue};
|
||||
use primitives::{H256, Blake2Hasher, NativeOrEncoded, NeverNativeValue, OffchainExt};
|
||||
|
||||
use crate::backend;
|
||||
use crate::error;
|
||||
@@ -42,12 +42,15 @@ where
|
||||
/// Execute a call to a contract on top of state in a block of given hash.
|
||||
///
|
||||
/// No changes are made.
|
||||
fn call(
|
||||
fn call<
|
||||
O: OffchainExt,
|
||||
>(
|
||||
&self,
|
||||
id: &BlockId<B>,
|
||||
method: &str,
|
||||
call_data: &[u8],
|
||||
strategy: ExecutionStrategy,
|
||||
side_effects_handler: Option<&mut O>,
|
||||
) -> Result<Vec<u8>, error::Error>;
|
||||
|
||||
/// Execute a contextual call on top of state in a block of a given hash.
|
||||
@@ -56,6 +59,7 @@ where
|
||||
/// Before executing the method, passed header is installed as the current header
|
||||
/// of the execution context.
|
||||
fn contextual_call<
|
||||
O: OffchainExt,
|
||||
PB: Fn() -> error::Result<B::Header>,
|
||||
EM: Fn(
|
||||
Result<NativeOrEncoded<R>, Self::Error>,
|
||||
@@ -73,6 +77,7 @@ where
|
||||
prepare_environment_block: PB,
|
||||
execution_manager: ExecutionManager<EM>,
|
||||
native_call: Option<NC>,
|
||||
side_effects_handler: Option<&mut O>,
|
||||
) -> error::Result<NativeOrEncoded<R>> where ExecutionManager<EM>: Clone;
|
||||
|
||||
/// Extract RuntimeVersion of given block
|
||||
@@ -84,6 +89,7 @@ where
|
||||
///
|
||||
/// No changes are made.
|
||||
fn call_at_state<
|
||||
O: OffchainExt,
|
||||
S: state_machine::Backend<H>,
|
||||
F: FnOnce(
|
||||
Result<NativeOrEncoded<R>, Self::Error>,
|
||||
@@ -98,6 +104,7 @@ where
|
||||
call_data: &[u8],
|
||||
manager: ExecutionManager<F>,
|
||||
native_call: Option<NC>,
|
||||
side_effects_handler: Option<&mut O>,
|
||||
) -> Result<(NativeOrEncoded<R>, S::Transaction, Option<MemoryDB<H>>), error::Error>;
|
||||
|
||||
/// Execute a call to a contract on top of given state, gathering execution proof.
|
||||
@@ -140,7 +147,10 @@ pub struct LocalCallExecutor<B, E> {
|
||||
impl<B, E> LocalCallExecutor<B, E> {
|
||||
/// Creates new instance of local call executor.
|
||||
pub fn new(backend: Arc<B>, executor: E) -> Self {
|
||||
LocalCallExecutor { backend, executor }
|
||||
LocalCallExecutor {
|
||||
backend,
|
||||
executor,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -161,17 +171,19 @@ where
|
||||
{
|
||||
type Error = E::Error;
|
||||
|
||||
fn call(&self,
|
||||
fn call<O: OffchainExt>(&self,
|
||||
id: &BlockId<Block>,
|
||||
method: &str,
|
||||
call_data: &[u8],
|
||||
strategy: ExecutionStrategy
|
||||
strategy: ExecutionStrategy,
|
||||
side_effects_handler: Option<&mut O>,
|
||||
) -> error::Result<Vec<u8>> {
|
||||
let mut changes = OverlayedChanges::default();
|
||||
let state = self.backend.state_at(*id)?;
|
||||
let return_data = state_machine::new(
|
||||
&state,
|
||||
self.backend.changes_trie_storage(),
|
||||
side_effects_handler,
|
||||
&mut changes,
|
||||
&self.executor,
|
||||
method,
|
||||
@@ -187,6 +199,7 @@ where
|
||||
}
|
||||
|
||||
fn contextual_call<
|
||||
O: OffchainExt,
|
||||
PB: Fn() -> error::Result<Block::Header>,
|
||||
EM: Fn(
|
||||
Result<NativeOrEncoded<R>, Self::Error>,
|
||||
@@ -204,6 +217,7 @@ where
|
||||
prepare_environment_block: PB,
|
||||
execution_manager: ExecutionManager<EM>,
|
||||
native_call: Option<NC>,
|
||||
mut side_effects_handler: Option<&mut O>,
|
||||
) -> Result<NativeOrEncoded<R>, error::Error> where ExecutionManager<EM>: Clone {
|
||||
let state = self.backend.state_at(*at)?;
|
||||
if method != "Core_initialise_block" && initialised_block.map(|id| id != *at).unwrap_or(true) {
|
||||
@@ -211,6 +225,7 @@ where
|
||||
state_machine::new(
|
||||
&state,
|
||||
self.backend.changes_trie_storage(),
|
||||
side_effects_handler.as_mut().map(|x| &mut **x),
|
||||
changes,
|
||||
&self.executor,
|
||||
"Core_initialise_block",
|
||||
@@ -226,6 +241,7 @@ where
|
||||
let result = state_machine::new(
|
||||
&state,
|
||||
self.backend.changes_trie_storage(),
|
||||
side_effects_handler,
|
||||
changes,
|
||||
&self.executor,
|
||||
method,
|
||||
@@ -248,12 +264,13 @@ where
|
||||
fn runtime_version(&self, id: &BlockId<Block>) -> error::Result<RuntimeVersion> {
|
||||
let mut overlay = OverlayedChanges::default();
|
||||
let state = self.backend.state_at(*id)?;
|
||||
let mut ext = Ext::new(&mut overlay, &state, self.backend.changes_trie_storage());
|
||||
let mut ext = Ext::new(&mut overlay, &state, self.backend.changes_trie_storage(), NeverOffchainExt::new());
|
||||
self.executor.runtime_version(&mut ext)
|
||||
.ok_or(error::ErrorKind::VersionInvalid.into())
|
||||
}
|
||||
|
||||
fn call_at_state<
|
||||
O: OffchainExt,
|
||||
S: state_machine::Backend<Blake2Hasher>,
|
||||
F: FnOnce(
|
||||
Result<NativeOrEncoded<R>, Self::Error>,
|
||||
@@ -268,10 +285,12 @@ where
|
||||
call_data: &[u8],
|
||||
manager: ExecutionManager<F>,
|
||||
native_call: Option<NC>,
|
||||
side_effects_handler: Option<&mut O>,
|
||||
) -> error::Result<(NativeOrEncoded<R>, S::Transaction, Option<MemoryDB<Blake2Hasher>>)> {
|
||||
state_machine::new(
|
||||
state,
|
||||
self.backend.changes_trie_storage(),
|
||||
side_effects_handler,
|
||||
changes,
|
||||
&self.executor,
|
||||
method,
|
||||
|
||||
@@ -33,9 +33,9 @@ use runtime_primitives::traits::{
|
||||
Block as BlockT, Header as HeaderT, Zero, As, NumberFor, CurrentHeight, BlockNumberToHash,
|
||||
ApiRef, ProvideRuntimeApi, Digest, DigestItem, AuthorityIdFor
|
||||
};
|
||||
use runtime_primitives::{BuildStorage, ExecutionContext};
|
||||
use runtime_primitives::BuildStorage;
|
||||
use crate::runtime_api::{CallRuntimeAt, ConstructRuntimeApi};
|
||||
use primitives::{Blake2Hasher, H256, ChangesTrieConfiguration, convert_hash, NeverNativeValue};
|
||||
use primitives::{Blake2Hasher, H256, ChangesTrieConfiguration, convert_hash, NeverNativeValue, ExecutionContext};
|
||||
use primitives::storage::{StorageKey, StorageData};
|
||||
use primitives::storage::well_known_keys;
|
||||
use parity_codec::{Encode, Decode};
|
||||
@@ -43,7 +43,7 @@ use state_machine::{
|
||||
DBValue, Backend as StateBackend, CodeExecutor, ChangesTrieAnchorBlockId,
|
||||
ExecutionStrategy, ExecutionManager, prove_read,
|
||||
ChangesTrieRootsStorage, ChangesTrieStorage,
|
||||
key_changes, key_changes_proof, OverlayedChanges,
|
||||
key_changes, key_changes_proof, OverlayedChanges, NeverOffchainExt,
|
||||
};
|
||||
use hash_db::Hasher;
|
||||
|
||||
@@ -84,6 +84,8 @@ pub struct ExecutionStrategies {
|
||||
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,
|
||||
}
|
||||
@@ -94,6 +96,7 @@ impl Default for ExecutionStrategies {
|
||||
syncing: ExecutionStrategy::NativeElseWasm,
|
||||
importing: ExecutionStrategy::NativeElseWasm,
|
||||
block_construction: ExecutionStrategy::AlwaysWasm,
|
||||
offchain_worker: ExecutionStrategy::NativeWhenPossible,
|
||||
other: ExecutionStrategy::NativeElseWasm,
|
||||
}
|
||||
}
|
||||
@@ -343,7 +346,7 @@ impl<B, E, Block, RA> Client<B, E, Block, RA> where
|
||||
pub fn authorities_at(&self, id: &BlockId<Block>) -> error::Result<Vec<AuthorityIdFor<Block>>> {
|
||||
match self.backend.blockchain().cache().and_then(|cache| cache.authorities_at(*id)) {
|
||||
Some(cached_value) => Ok(cached_value),
|
||||
None => self.executor.call(id, "Core_authorities", &[], ExecutionStrategy::NativeElseWasm)
|
||||
None => self.executor.call(id, "Core_authorities", &[], ExecutionStrategy::NativeElseWasm, NeverOffchainExt::new())
|
||||
.and_then(|r| Vec::<AuthorityIdFor<Block>>::decode(&mut &r[..])
|
||||
.ok_or_else(|| error::ErrorKind::InvalidAuthoritiesSet.into()))
|
||||
}
|
||||
@@ -871,7 +874,7 @@ impl<B, E, Block, RA> Client<B, E, Block, RA> where
|
||||
}),
|
||||
}
|
||||
};
|
||||
let (_, storage_update, changes_update) = self.executor.call_at_state::<_, _, NeverNativeValue, fn() -> _>(
|
||||
let (_, storage_update, changes_update) = self.executor.call_at_state::<_, _, _, NeverNativeValue, fn() -> _>(
|
||||
transaction_state,
|
||||
&mut overlay,
|
||||
"Core_execute_block",
|
||||
@@ -881,6 +884,7 @@ impl<B, E, Block, RA> Client<B, E, Block, RA> where
|
||||
_ => get_execution_manager(self.execution_strategies().importing),
|
||||
},
|
||||
None,
|
||||
NeverOffchainExt::new(),
|
||||
)?;
|
||||
|
||||
overlay.commit_prospective();
|
||||
@@ -1339,7 +1343,8 @@ impl<B, E, Block, RA> CallRuntimeAt<Block> for Client<B, E, Block, RA> where
|
||||
Block: BlockT<Hash=H256>
|
||||
{
|
||||
fn call_api_at<
|
||||
R: Encode + Decode + PartialEq, NC: FnOnce() -> result::Result<R, &'static str> + UnwindSafe
|
||||
R: Encode + Decode + PartialEq,
|
||||
NC: FnOnce() -> result::Result<R, &'static str> + UnwindSafe,
|
||||
>(
|
||||
&self,
|
||||
at: &BlockId<Block>,
|
||||
@@ -1348,15 +1353,22 @@ impl<B, E, Block, RA> CallRuntimeAt<Block> for Client<B, E, Block, RA> where
|
||||
changes: &mut OverlayedChanges,
|
||||
initialised_block: &mut Option<BlockId<Block>>,
|
||||
native_call: Option<NC>,
|
||||
context: ExecutionContext
|
||||
context: ExecutionContext,
|
||||
) -> error::Result<NativeOrEncoded<R>> {
|
||||
let manager = match context {
|
||||
ExecutionContext::BlockConstruction => self.execution_strategies.block_construction.get_manager(),
|
||||
ExecutionContext::Syncing => self.execution_strategies.syncing.get_manager(),
|
||||
ExecutionContext::Importing => self.execution_strategies.importing.get_manager(),
|
||||
ExecutionContext::OffchainWorker(_) => self.execution_strategies.offchain_worker.get_manager(),
|
||||
ExecutionContext::Other => self.execution_strategies.other.get_manager(),
|
||||
};
|
||||
self.executor.contextual_call::<_, fn(_,_) -> _,_,_>(
|
||||
|
||||
let mut offchain_extensions = match context {
|
||||
ExecutionContext::OffchainWorker(ext) => Some(ext),
|
||||
_ => None,
|
||||
};
|
||||
|
||||
self.executor.contextual_call::<_, _, fn(_,_) -> _,_,_>(
|
||||
at,
|
||||
function,
|
||||
&args,
|
||||
@@ -1365,6 +1377,7 @@ impl<B, E, Block, RA> CallRuntimeAt<Block> for Client<B, E, Block, RA> where
|
||||
|| self.prepare_environment_block(at),
|
||||
manager,
|
||||
native_call,
|
||||
offchain_extensions.as_mut(),
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -90,6 +90,7 @@ mod tests {
|
||||
state_machine::new(
|
||||
backend,
|
||||
Some(&InMemoryChangesTrieStorage::new()),
|
||||
state_machine::NeverOffchainExt::new(),
|
||||
&mut overlay,
|
||||
&executor(),
|
||||
"Core_initialise_block",
|
||||
@@ -102,6 +103,7 @@ mod tests {
|
||||
state_machine::new(
|
||||
backend,
|
||||
Some(&InMemoryChangesTrieStorage::new()),
|
||||
state_machine::NeverOffchainExt::new(),
|
||||
&mut overlay,
|
||||
&executor(),
|
||||
"BlockBuilder_apply_extrinsic",
|
||||
@@ -114,6 +116,7 @@ mod tests {
|
||||
let (ret_data, _, _) = state_machine::new(
|
||||
backend,
|
||||
Some(&InMemoryChangesTrieStorage::new()),
|
||||
state_machine::NeverOffchainExt::new(),
|
||||
&mut overlay,
|
||||
&executor(),
|
||||
"BlockBuilder_finalise_block",
|
||||
@@ -160,6 +163,7 @@ mod tests {
|
||||
let _ = state_machine::new(
|
||||
&backend,
|
||||
Some(&InMemoryChangesTrieStorage::new()),
|
||||
state_machine::NeverOffchainExt::new(),
|
||||
&mut overlay,
|
||||
&executor(),
|
||||
"Core_execute_block",
|
||||
@@ -188,6 +192,7 @@ mod tests {
|
||||
let _ = state_machine::new(
|
||||
&backend,
|
||||
Some(&InMemoryChangesTrieStorage::new()),
|
||||
state_machine::NeverOffchainExt::new(),
|
||||
&mut overlay,
|
||||
&executor(),
|
||||
"Core_execute_block",
|
||||
@@ -216,6 +221,7 @@ mod tests {
|
||||
let r = state_machine::new(
|
||||
&backend,
|
||||
Some(&InMemoryChangesTrieStorage::new()),
|
||||
state_machine::NeverOffchainExt::new(),
|
||||
&mut overlay,
|
||||
&Executor::new(None),
|
||||
"Core_execute_block",
|
||||
|
||||
@@ -21,11 +21,11 @@ use std::{collections::HashSet, sync::Arc, panic::UnwindSafe, result, marker::Ph
|
||||
use futures::{IntoFuture, Future};
|
||||
|
||||
use parity_codec::{Encode, Decode};
|
||||
use primitives::{H256, Blake2Hasher, convert_hash, NativeOrEncoded};
|
||||
use primitives::{H256, Blake2Hasher, convert_hash, NativeOrEncoded, OffchainExt};
|
||||
use runtime_primitives::generic::BlockId;
|
||||
use runtime_primitives::traits::{As, Block as BlockT, Header as HeaderT};
|
||||
use state_machine::{self, Backend as StateBackend, CodeExecutor, OverlayedChanges, ExecutionStrategy,
|
||||
create_proof_check_backend, execution_proof_check_on_trie_backend, ExecutionManager};
|
||||
create_proof_check_backend, execution_proof_check_on_trie_backend, ExecutionManager, NeverOffchainExt};
|
||||
use hash_db::Hasher;
|
||||
|
||||
use crate::backend::RemoteBackend;
|
||||
@@ -80,7 +80,16 @@ where
|
||||
{
|
||||
type Error = ClientError;
|
||||
|
||||
fn call(&self, id: &BlockId<Block>, method: &str, call_data: &[u8], _strategy: ExecutionStrategy)
|
||||
fn call<
|
||||
O: OffchainExt,
|
||||
>(
|
||||
&self,
|
||||
id: &BlockId<Block>,
|
||||
method: &str,
|
||||
call_data: &[u8],
|
||||
_strategy: ExecutionStrategy,
|
||||
_side_effects_handler: Option<&mut O>,
|
||||
)
|
||||
-> ClientResult<Vec<u8>> {
|
||||
let block_hash = self.blockchain.expect_block_hash_from_id(id)?;
|
||||
let block_header = self.blockchain.expect_header(id.clone())?;
|
||||
@@ -95,6 +104,7 @@ where
|
||||
}
|
||||
|
||||
fn contextual_call<
|
||||
O: OffchainExt,
|
||||
PB: Fn() -> ClientResult<Block::Header>,
|
||||
EM: Fn(
|
||||
Result<NativeOrEncoded<R>, Self::Error>,
|
||||
@@ -112,22 +122,24 @@ where
|
||||
_prepare_environment_block: PB,
|
||||
execution_manager: ExecutionManager<EM>,
|
||||
_native_call: Option<NC>,
|
||||
side_effects_handler: Option<&mut O>,
|
||||
) -> ClientResult<NativeOrEncoded<R>> where ExecutionManager<EM>: Clone {
|
||||
// it is only possible to execute contextual call if changes are empty
|
||||
if !changes.is_empty() || initialised_block.is_some() {
|
||||
return Err(ClientErrorKind::NotAvailableOnLightClient.into());
|
||||
}
|
||||
|
||||
self.call(at, method, call_data, (&execution_manager).into()).map(NativeOrEncoded::Encoded)
|
||||
self.call(at, method, call_data, (&execution_manager).into(), side_effects_handler).map(NativeOrEncoded::Encoded)
|
||||
}
|
||||
|
||||
fn runtime_version(&self, id: &BlockId<Block>) -> ClientResult<RuntimeVersion> {
|
||||
let call_result = self.call(id, "version", &[], ExecutionStrategy::NativeElseWasm)?;
|
||||
let call_result = self.call(id, "version", &[], ExecutionStrategy::NativeElseWasm, NeverOffchainExt::new())?;
|
||||
RuntimeVersion::decode(&mut call_result.as_slice())
|
||||
.ok_or_else(|| ClientErrorKind::VersionInvalid.into())
|
||||
}
|
||||
|
||||
fn call_at_state<
|
||||
O: OffchainExt,
|
||||
S: StateBackend<Blake2Hasher>,
|
||||
FF: FnOnce(
|
||||
Result<NativeOrEncoded<R>, Self::Error>,
|
||||
@@ -142,6 +154,7 @@ where
|
||||
_call_data: &[u8],
|
||||
_m: ExecutionManager<FF>,
|
||||
_native_call: Option<NC>,
|
||||
_side_effects_handler: Option<&mut O>,
|
||||
) -> ClientResult<(NativeOrEncoded<R>, S::Transaction, Option<MemoryDB<Blake2Hasher>>)> {
|
||||
Err(ClientErrorKind::NotAvailableOnLightClient.into())
|
||||
}
|
||||
@@ -201,15 +214,24 @@ impl<Block, B, Remote, Local> CallExecutor<Block, Blake2Hasher> for
|
||||
{
|
||||
type Error = ClientError;
|
||||
|
||||
fn call(&self, id: &BlockId<Block>, method: &str, call_data: &[u8], strategy: ExecutionStrategy)
|
||||
-> ClientResult<Vec<u8>> {
|
||||
fn call<
|
||||
O: OffchainExt,
|
||||
>(
|
||||
&self,
|
||||
id: &BlockId<Block>,
|
||||
method: &str,
|
||||
call_data: &[u8],
|
||||
strategy: ExecutionStrategy,
|
||||
side_effects_handler: Option<&mut O>,
|
||||
) -> ClientResult<Vec<u8>> {
|
||||
match self.backend.is_local_state_available(id) {
|
||||
true => self.local.call(id, method, call_data, strategy),
|
||||
false => self.remote.call(id, method, call_data, strategy),
|
||||
true => self.local.call(id, method, call_data, strategy, side_effects_handler),
|
||||
false => self.remote.call(id, method, call_data, strategy, side_effects_handler),
|
||||
}
|
||||
}
|
||||
|
||||
fn contextual_call<
|
||||
O: OffchainExt,
|
||||
PB: Fn() -> ClientResult<Block::Header>,
|
||||
EM: Fn(
|
||||
Result<NativeOrEncoded<R>, Self::Error>,
|
||||
@@ -227,12 +249,14 @@ impl<Block, B, Remote, Local> CallExecutor<Block, Blake2Hasher> for
|
||||
prepare_environment_block: PB,
|
||||
_manager: ExecutionManager<EM>,
|
||||
native_call: Option<NC>,
|
||||
side_effects_handler: Option<&mut O>,
|
||||
) -> ClientResult<NativeOrEncoded<R>> where ExecutionManager<EM>: Clone {
|
||||
// there's no actual way/need to specify native/wasm execution strategy on light node
|
||||
// => we can safely ignore passed values
|
||||
|
||||
match self.backend.is_local_state_available(at) {
|
||||
true => CallExecutor::contextual_call::<
|
||||
_,
|
||||
_,
|
||||
fn(
|
||||
Result<NativeOrEncoded<R>, Local::Error>,
|
||||
@@ -250,8 +274,10 @@ impl<Block, B, Remote, Local> CallExecutor<Block, Blake2Hasher> for
|
||||
prepare_environment_block,
|
||||
ExecutionManager::NativeWhenPossible,
|
||||
native_call,
|
||||
side_effects_handler,
|
||||
).map_err(|e| ClientErrorKind::Execution(Box::new(e.to_string())).into()),
|
||||
false => CallExecutor::contextual_call::<
|
||||
_,
|
||||
_,
|
||||
fn(
|
||||
Result<NativeOrEncoded<R>, Remote::Error>,
|
||||
@@ -269,6 +295,7 @@ impl<Block, B, Remote, Local> CallExecutor<Block, Blake2Hasher> for
|
||||
prepare_environment_block,
|
||||
ExecutionManager::NativeWhenPossible,
|
||||
native_call,
|
||||
side_effects_handler,
|
||||
).map_err(|e| ClientErrorKind::Execution(Box::new(e.to_string())).into()),
|
||||
}
|
||||
}
|
||||
@@ -281,6 +308,7 @@ impl<Block, B, Remote, Local> CallExecutor<Block, Blake2Hasher> for
|
||||
}
|
||||
|
||||
fn call_at_state<
|
||||
O: OffchainExt,
|
||||
S: StateBackend<Blake2Hasher>,
|
||||
FF: FnOnce(
|
||||
Result<NativeOrEncoded<R>, Self::Error>,
|
||||
@@ -295,11 +323,13 @@ impl<Block, B, Remote, Local> CallExecutor<Block, Blake2Hasher> for
|
||||
call_data: &[u8],
|
||||
_manager: ExecutionManager<FF>,
|
||||
native_call: Option<NC>,
|
||||
side_effects_handler: Option<&mut O>,
|
||||
) -> ClientResult<(NativeOrEncoded<R>, S::Transaction, Option<MemoryDB<Blake2Hasher>>)> {
|
||||
// there's no actual way/need to specify native/wasm execution strategy on light node
|
||||
// => we can safely ignore passed values
|
||||
|
||||
CallExecutor::call_at_state::<
|
||||
_,
|
||||
_,
|
||||
fn(
|
||||
Result<NativeOrEncoded<R>, Remote::Error>,
|
||||
@@ -315,6 +345,7 @@ impl<Block, B, Remote, Local> CallExecutor<Block, Blake2Hasher> for
|
||||
call_data,
|
||||
ExecutionManager::NativeWhenPossible,
|
||||
native_call,
|
||||
side_effects_handler,
|
||||
).map_err(|e| ClientErrorKind::Execution(Box::new(e.to_string())).into())
|
||||
}
|
||||
|
||||
@@ -509,7 +540,7 @@ mod tests {
|
||||
let local_executor = RemoteCallExecutor::new(Arc::new(backend.blockchain().clone()), Arc::new(OkCallFetcher::new(vec![1])));
|
||||
let remote_executor = RemoteCallExecutor::new(Arc::new(backend.blockchain().clone()), Arc::new(OkCallFetcher::new(vec![2])));
|
||||
let remote_or_local = RemoteOrLocalCallExecutor::new(backend, remote_executor, local_executor);
|
||||
assert_eq!(remote_or_local.call(&BlockId::Number(0), "test_method", &[], ExecutionStrategy::NativeElseWasm).unwrap(), vec![1]);
|
||||
assert_eq!(remote_or_local.call(&BlockId::Number(1), "test_method", &[], ExecutionStrategy::NativeElseWasm).unwrap(), vec![2]);
|
||||
assert_eq!(remote_or_local.call(&BlockId::Number(0), "test_method", &[], ExecutionStrategy::NativeElseWasm, NeverOffchainExt::new()).unwrap(), vec![1]);
|
||||
assert_eq!(remote_or_local.call(&BlockId::Number(1), "test_method", &[], ExecutionStrategy::NativeElseWasm, NeverOffchainExt::new()).unwrap(), vec![2]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,10 +24,12 @@ pub use state_machine::OverlayedChanges;
|
||||
pub use primitives::NativeOrEncoded;
|
||||
#[doc(hidden)]
|
||||
pub use runtime_primitives::{
|
||||
traits::{AuthorityIdFor, Block as BlockT, GetNodeBlockType, GetRuntimeBlockType, ApiRef, RuntimeApiInfo},
|
||||
generic::BlockId, transaction_validity::TransactionValidity, ExecutionContext,
|
||||
traits::{AuthorityIdFor, Block as BlockT, GetNodeBlockType, GetRuntimeBlockType, Header as HeaderT, ApiRef, RuntimeApiInfo},
|
||||
generic::BlockId, transaction_validity::TransactionValidity,
|
||||
};
|
||||
#[doc(hidden)]
|
||||
pub use primitives::{ExecutionContext, OffchainExt};
|
||||
#[doc(hidden)]
|
||||
pub use runtime_version::{ApiId, RuntimeVersion, ApisVec, create_apis_vec};
|
||||
#[doc(hidden)]
|
||||
pub use rstd::{slice, mem};
|
||||
@@ -91,7 +93,10 @@ pub trait ApiExt<Block: BlockT> {
|
||||
pub trait CallRuntimeAt<Block: BlockT> {
|
||||
/// Calls the given api function with the given encoded arguments at the given block
|
||||
/// and returns the encoded result.
|
||||
fn call_api_at<R: Encode + Decode + PartialEq, NC: FnOnce() -> result::Result<R, &'static str> + UnwindSafe>(
|
||||
fn call_api_at<
|
||||
R: Encode + Decode + PartialEq,
|
||||
NC: FnOnce() -> result::Result<R, &'static str> + UnwindSafe,
|
||||
>(
|
||||
&self,
|
||||
at: &BlockId<Block>,
|
||||
function: &'static str,
|
||||
@@ -99,7 +104,7 @@ pub trait CallRuntimeAt<Block: BlockT> {
|
||||
changes: &mut OverlayedChanges,
|
||||
initialised_block: &mut Option<BlockId<Block>>,
|
||||
native_call: Option<NC>,
|
||||
context: ExecutionContext
|
||||
context: ExecutionContext,
|
||||
) -> error::Result<NativeOrEncoded<R>>;
|
||||
|
||||
/// Returns the runtime version at the given block.
|
||||
@@ -132,3 +137,4 @@ decl_runtime_apis! {
|
||||
fn validate_transaction(tx: <Block as BlockT>::Extrinsic) -> TransactionValidity;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -520,6 +520,15 @@ impl_function_executor!(this: FunctionExecutor<'e, E>,
|
||||
|
||||
Ok(0)
|
||||
},
|
||||
ext_submit_extrinsic(msg_data: *const u8, len: u32) => {
|
||||
let extrinsic = this.memory.get(msg_data, len as usize)
|
||||
.map_err(|_| UserError("OOB while ext_submit_extrinsic: wasm"))?;
|
||||
|
||||
this.ext.submit_extrinsic(extrinsic)
|
||||
.map_err(|_| UserError("Calling unavailable API ext_submit_extrinsic: wasm"))?;
|
||||
|
||||
Ok(())
|
||||
},
|
||||
ext_sandbox_instantiate(
|
||||
dispatch_thunk_idx: usize,
|
||||
wasm_ptr: *const u8,
|
||||
|
||||
@@ -35,8 +35,7 @@ use std::collections::{HashMap, HashSet};
|
||||
use std::result;
|
||||
use runtime_primitives::traits::{ApiRef, ProvideRuntimeApi};
|
||||
use runtime_primitives::generic::BlockId;
|
||||
use runtime_primitives::ExecutionContext;
|
||||
use substrate_primitives::NativeOrEncoded;
|
||||
use substrate_primitives::{NativeOrEncoded, ExecutionContext};
|
||||
|
||||
use authorities::AuthoritySet;
|
||||
use consensus_changes::ConsensusChanges;
|
||||
|
||||
@@ -31,6 +31,7 @@
|
||||
//! information on how that is done.
|
||||
|
||||
#![cfg_attr(not(feature = "std"), no_std)]
|
||||
#![warn(missing_docs)]
|
||||
|
||||
use parity_codec as codec;
|
||||
use codec::{Encode, Decode};
|
||||
@@ -43,6 +44,9 @@ use parking_lot::RwLock;
|
||||
#[cfg(feature = "std")]
|
||||
use std::{sync::Arc, format};
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
pub mod pool;
|
||||
|
||||
pub use runtime_primitives::RuntimeString;
|
||||
|
||||
/// An identifier for an inherent.
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
// 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/>.
|
||||
|
||||
//! Inherents Pool
|
||||
|
||||
use std::{fmt, mem, vec};
|
||||
use parking_lot::Mutex;
|
||||
|
||||
/// Inherents Pool
|
||||
///
|
||||
/// The pool is responsible to collect inherents asynchronously generated
|
||||
/// by some other parts of the code and make them ready for the next block production.
|
||||
pub struct InherentsPool<T> {
|
||||
data: Mutex<Vec<T>>,
|
||||
}
|
||||
|
||||
impl<T> Default for InherentsPool<T> {
|
||||
fn default() -> Self {
|
||||
InherentsPool {
|
||||
data: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: fmt::Debug> fmt::Debug for InherentsPool<T> {
|
||||
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
|
||||
let mut builder = fmt.debug_struct("InherentsPool");
|
||||
if let Some(data) = self.data.try_lock() {
|
||||
builder.field("data", &*data);
|
||||
}
|
||||
builder.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> InherentsPool<T> {
|
||||
/// Add inherent extrinsic to the pool.
|
||||
///
|
||||
/// This inherent will be appended to the next produced block.
|
||||
pub fn add(&self, extrinsic: T) {
|
||||
self.data.lock().push(extrinsic);
|
||||
}
|
||||
|
||||
/// Drain all currently queued inherents.
|
||||
pub fn drain(&self) -> Vec<T> {
|
||||
mem::replace(&mut *self.data.lock(), vec![])
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn should_drain_inherents_to_given_data() {
|
||||
let pool = InherentsPool::default();
|
||||
pool.add(5);
|
||||
pool.add(7);
|
||||
|
||||
assert_eq!(pool.drain(), vec![5, 7]);
|
||||
assert_eq!(pool.drain(), vec![]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
[package]
|
||||
description = "Substrate offchain workers"
|
||||
name = "substrate-offchain"
|
||||
version = "0.1.0"
|
||||
license = "GPL-3.0"
|
||||
authors = ["Parity Technologies <admin@parity.io>"]
|
||||
edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
client = { package = "substrate-client", path = "../../core/client" }
|
||||
consensus = { package = "substrate-consensus-common", path = "../../core/consensus/common" }
|
||||
futures = "0.1.25"
|
||||
inherents = { package = "substrate-inherents", path = "../../core/inherents" }
|
||||
log = "0.4"
|
||||
offchain-primitives = { package = "substrate-offchain-primitives", path = "./primitives" }
|
||||
parity-codec = { version = "3.1", features = ["derive"] }
|
||||
primitives = { package = "substrate-primitives", path = "../../core/primitives" }
|
||||
runtime_primitives = { package = "sr-primitives", path = "../../core/sr-primitives" }
|
||||
tokio = "0.1.7"
|
||||
transaction_pool = { package = "substrate-transaction-pool", path = "../../core/transaction-pool" }
|
||||
|
||||
[dev-dependencies]
|
||||
env_logger = "0.6"
|
||||
test_client = { package = "substrate-test-client", path = "../../core/test-client" }
|
||||
|
||||
[features]
|
||||
default = []
|
||||
@@ -0,0 +1,18 @@
|
||||
[package]
|
||||
description = "Substrate offchain workers primitives"
|
||||
name = "substrate-offchain-primitives"
|
||||
version = "0.1.0"
|
||||
license = "GPL-3.0"
|
||||
authors = ["Parity Technologies <admin@parity.io>"]
|
||||
edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
client = { package = "substrate-client", path = "../../client", default-features = false }
|
||||
runtime_primitives = { package = "sr-primitives", path = "../../sr-primitives", default-features = false }
|
||||
|
||||
[features]
|
||||
default = ["std"]
|
||||
std = [
|
||||
"client/std",
|
||||
"runtime_primitives/std"
|
||||
]
|
||||
@@ -0,0 +1,31 @@
|
||||
// Copyright 2018 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/>.
|
||||
|
||||
//! The Offchain Worker runtime api primitives.
|
||||
|
||||
#![cfg_attr(not(feature = "std"), no_std)]
|
||||
#![warn(missing_docs)]
|
||||
|
||||
use client::decl_runtime_apis;
|
||||
use runtime_primitives::traits::NumberFor;
|
||||
|
||||
decl_runtime_apis! {
|
||||
/// The offchain worker api.
|
||||
pub trait OffchainWorkerApi {
|
||||
/// Starts the off-chain task for given block number.
|
||||
fn offchain_worker(number: NumberFor<Block>);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
// 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::sync::Arc;
|
||||
use futures::{Stream, Future, sync::mpsc};
|
||||
use inherents::pool::InherentsPool;
|
||||
use log::{info, debug, warn};
|
||||
use parity_codec::Decode;
|
||||
use primitives::OffchainExt;
|
||||
use runtime_primitives::{
|
||||
generic::BlockId,
|
||||
traits::{self, Extrinsic},
|
||||
};
|
||||
use transaction_pool::txpool::{Pool, ChainApi};
|
||||
|
||||
/// A message between the offchain extension and the processing thread.
|
||||
enum ExtMessage {
|
||||
SubmitExtrinsic(Vec<u8>),
|
||||
}
|
||||
|
||||
/// Asynchronous offchain API.
|
||||
///
|
||||
/// NOTE this is done to prevent recursive calls into the runtime (which are not supported currently).
|
||||
pub(crate) struct AsyncApi(mpsc::UnboundedSender<ExtMessage>);
|
||||
|
||||
impl OffchainExt for AsyncApi {
|
||||
fn submit_extrinsic(&mut self, ext: Vec<u8>) {
|
||||
let _ = self.0.unbounded_send(ExtMessage::SubmitExtrinsic(ext));
|
||||
}
|
||||
}
|
||||
|
||||
/// Offchain extensions implementation API
|
||||
pub(crate) struct Api<A: ChainApi> {
|
||||
receiver: Option<mpsc::UnboundedReceiver<ExtMessage>>,
|
||||
transaction_pool: Arc<Pool<A>>,
|
||||
inherents_pool: Arc<InherentsPool<<A::Block as traits::Block>::Extrinsic>>,
|
||||
at: BlockId<A::Block>,
|
||||
}
|
||||
|
||||
impl<A: ChainApi> Api<A> {
|
||||
pub fn new(
|
||||
transaction_pool: Arc<Pool<A>>,
|
||||
inherents_pool: Arc<InherentsPool<<A::Block as traits::Block>::Extrinsic>>,
|
||||
at: BlockId<A::Block>,
|
||||
) -> (AsyncApi, Self) {
|
||||
let (tx, rx) = mpsc::unbounded();
|
||||
let api = Self {
|
||||
receiver: Some(rx),
|
||||
transaction_pool,
|
||||
inherents_pool,
|
||||
at,
|
||||
};
|
||||
(AsyncApi(tx), api)
|
||||
}
|
||||
|
||||
/// Run a processing task for the API
|
||||
pub fn process(mut self) -> impl Future<Item=(), Error=()> {
|
||||
let receiver = self.receiver.take().expect("Take invoked only once.");
|
||||
|
||||
receiver.for_each(move |msg| {
|
||||
match msg {
|
||||
ExtMessage::SubmitExtrinsic(ext) => self.submit_extrinsic(ext),
|
||||
}
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
|
||||
fn submit_extrinsic(&mut self, ext: Vec<u8>) {
|
||||
let xt = match <A::Block as traits::Block>::Extrinsic::decode(&mut &*ext) {
|
||||
Some(xt) => xt,
|
||||
None => {
|
||||
warn!("Unable to decode extrinsic: {:?}", ext);
|
||||
return
|
||||
},
|
||||
};
|
||||
|
||||
info!("Submitting to the pool: {:?} (isSigned: {:?})", xt, xt.is_signed());
|
||||
match self.transaction_pool.submit_one(&self.at, xt.clone()) {
|
||||
Ok(hash) => debug!("[{:?}] Offchain transaction added to the pool.", hash),
|
||||
Err(_) => {
|
||||
debug!("Offchain inherent added to the pool.");
|
||||
self.inherents_pool.add(xt);
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,132 @@
|
||||
// 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/>.
|
||||
|
||||
//! Substrate offchain workers.
|
||||
//!
|
||||
//! The offchain workers is a special function of the runtime that
|
||||
//! gets executed after block is imported. During execution
|
||||
//! it's able to asynchronously submit extrinsics that will either
|
||||
//! be propagated to other nodes (transactions) or will be
|
||||
//! added to the next block produced by the node as inherents.
|
||||
//!
|
||||
//! Offchain workers can be used for computation-heavy tasks
|
||||
//! that are not feasible for execution during regular block processing.
|
||||
//! It can either be tasks that no consensus is required for,
|
||||
//! or some form of consensus over the data can be built on-chain
|
||||
//! for instance via:
|
||||
//! 1. Challenge period for incorrect computations
|
||||
//! 2. Majority voting for results
|
||||
//! 3. etc
|
||||
|
||||
#![warn(missing_docs)]
|
||||
|
||||
use std::{
|
||||
marker::PhantomData,
|
||||
sync::Arc,
|
||||
};
|
||||
|
||||
use client::runtime_api::ApiExt;
|
||||
use inherents::pool::InherentsPool;
|
||||
use log::{debug, warn};
|
||||
use primitives::ExecutionContext;
|
||||
use runtime_primitives::{
|
||||
generic::BlockId,
|
||||
traits::{self, ProvideRuntimeApi},
|
||||
};
|
||||
use tokio::runtime::TaskExecutor;
|
||||
use transaction_pool::txpool::{Pool, ChainApi};
|
||||
|
||||
mod api;
|
||||
|
||||
pub use offchain_primitives::OffchainWorkerApi;
|
||||
|
||||
/// An offchain workers manager.
|
||||
#[derive(Debug)]
|
||||
pub struct OffchainWorkers<C, Block: traits::Block> {
|
||||
client: Arc<C>,
|
||||
inherents_pool: Arc<InherentsPool<<Block as traits::Block>::Extrinsic>>,
|
||||
executor: TaskExecutor,
|
||||
_block: PhantomData<Block>,
|
||||
}
|
||||
|
||||
impl<C, Block: traits::Block> OffchainWorkers<C, Block> {
|
||||
/// Creates new `OffchainWorkers`.
|
||||
pub fn new(
|
||||
client: Arc<C>,
|
||||
inherents_pool: Arc<InherentsPool<<Block as traits::Block>::Extrinsic>>,
|
||||
executor: TaskExecutor,
|
||||
) -> Self {
|
||||
Self {
|
||||
client,
|
||||
inherents_pool,
|
||||
executor,
|
||||
_block: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<C, Block> OffchainWorkers<C, Block> where
|
||||
Block: traits::Block,
|
||||
C: ProvideRuntimeApi,
|
||||
C::Api: OffchainWorkerApi<Block>,
|
||||
{
|
||||
/// Start the offchain workers after given block.
|
||||
pub fn on_block_imported<A>(
|
||||
&self,
|
||||
number: &<Block::Header as traits::Header>::Number,
|
||||
pool: &Arc<Pool<A>>,
|
||||
) where
|
||||
A: ChainApi<Block=Block> + 'static,
|
||||
{
|
||||
let runtime = self.client.runtime_api();
|
||||
let at = BlockId::number(*number);
|
||||
let has_api = runtime.has_api::<OffchainWorkerApi<Block>>(&at);
|
||||
debug!("Checking offchain workers at {:?}: {:?}", at, has_api);
|
||||
|
||||
if has_api.unwrap_or(false) {
|
||||
let (api, runner) = api::Api::new(pool.clone(), self.inherents_pool.clone(), at.clone());
|
||||
self.executor.spawn(runner.process());
|
||||
|
||||
debug!("Running offchain workers at {:?}", at);
|
||||
let api = Box::new(api);
|
||||
runtime.offchain_worker_with_context(&at, ExecutionContext::OffchainWorker(api), *number).unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use futures::Future;
|
||||
|
||||
#[test]
|
||||
fn should_call_into_runtime_and_produce_extrinsic() {
|
||||
// given
|
||||
let _ = env_logger::try_init();
|
||||
let runtime = tokio::runtime::Runtime::new().unwrap();
|
||||
let client = Arc::new(test_client::new());
|
||||
let pool = Arc::new(Pool::new(Default::default(), ::transaction_pool::ChainApi::new(client.clone())));
|
||||
let inherents = Arc::new(InherentsPool::default());
|
||||
|
||||
// when
|
||||
let offchain = OffchainWorkers::new(client, inherents.clone(), runtime.executor());
|
||||
offchain.on_block_imported(&0u64, &pool);
|
||||
|
||||
// then
|
||||
runtime.shutdown_on_idle().wait().unwrap();
|
||||
assert_eq!(inherents.drain().len(), 1);
|
||||
}
|
||||
}
|
||||
@@ -76,6 +76,35 @@ pub use hash_db::Hasher;
|
||||
// pub use self::hasher::blake::BlakeHasher;
|
||||
pub use self::hasher::blake2::Blake2Hasher;
|
||||
|
||||
/// Context for executing a call into the runtime.
|
||||
#[repr(u8)]
|
||||
pub enum ExecutionContext {
|
||||
/// Context for general importing (including own blocks).
|
||||
Importing,
|
||||
/// Context used when syncing the blockchain.
|
||||
Syncing,
|
||||
/// Context used for block construction.
|
||||
BlockConstruction,
|
||||
/// Offchain worker context.
|
||||
OffchainWorker(Box<OffchainExt>),
|
||||
/// Context used for other calls.
|
||||
Other,
|
||||
}
|
||||
|
||||
/// An extended externalities for offchain workers.
|
||||
pub trait OffchainExt {
|
||||
/// Submits an extrinsics.
|
||||
///
|
||||
/// The extrinsic will either go to the pool (signed)
|
||||
/// or to the next produced block (inherent).
|
||||
fn submit_extrinsic(&mut self, extrinsic: Vec<u8>);
|
||||
}
|
||||
impl<T: OffchainExt + ?Sized> OffchainExt for Box<T> {
|
||||
fn submit_extrinsic(&mut self, ex: Vec<u8>) {
|
||||
(&mut **self).submit_extrinsic(ex)
|
||||
}
|
||||
}
|
||||
|
||||
/// Hex-serialised shim for `Vec<u8>`.
|
||||
#[derive(PartialEq, Eq, Clone)]
|
||||
#[cfg_attr(feature = "std", derive(Serialize, Deserialize, Debug, Hash, PartialOrd, Ord))]
|
||||
|
||||
@@ -35,7 +35,7 @@ use crate::rpc::futures::{stream, Future, Sink, Stream};
|
||||
use runtime_primitives::generic::BlockId;
|
||||
use runtime_primitives::traits::{Block as BlockT, Header, ProvideRuntimeApi, As, NumberFor};
|
||||
use runtime_version::RuntimeVersion;
|
||||
use state_machine::ExecutionStrategy;
|
||||
use state_machine::{self, ExecutionStrategy};
|
||||
|
||||
use crate::subscriptions::Subscriptions;
|
||||
|
||||
@@ -298,7 +298,7 @@ impl<B, E, Block, RA> StateApi<Block::Hash> for State<B, E, Block, RA> where
|
||||
.executor()
|
||||
.call(
|
||||
&BlockId::Hash(block),
|
||||
&method, &data.0, ExecutionStrategy::NativeElseWasm
|
||||
&method, &data.0, ExecutionStrategy::NativeElseWasm, state_machine::NeverOffchainExt::new(),
|
||||
)?;
|
||||
Ok(Bytes(return_data))
|
||||
}
|
||||
|
||||
@@ -221,7 +221,7 @@ fn should_return_runtime_version() {
|
||||
|
||||
assert_eq!(
|
||||
::serde_json::to_string(&api.runtime_version(None.into()).unwrap()).unwrap(),
|
||||
r#"{"specName":"test","implName":"parity-test","authoringVersion":1,"specVersion":1,"implVersion":1,"apis":[["0xdf6acb689907609b",1],["0x37e397fc7c91f5e4",1],["0xd2bc9897eed08f15",1],["0x40fe3ad401f8959a",2],["0xc6e9a76309f39b09",1],["0xdd718d5cc53262d4",1]]}"#
|
||||
r#"{"specName":"test","implName":"parity-test","authoringVersion":1,"specVersion":1,"implVersion":1,"apis":[["0xdf6acb689907609b",1],["0x37e397fc7c91f5e4",1],["0xd2bc9897eed08f15",1],["0x40fe3ad401f8959a",2],["0xc6e9a76309f39b09",1],["0xdd718d5cc53262d4",1],["0xf78b278be53f454c",1]]}"#
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@ serde = "1.0"
|
||||
serde_json = "1.0"
|
||||
serde_derive = "1.0"
|
||||
target_info = "0.1"
|
||||
inherents = { package = "substrate-inherents", path = "../../core/inherents" }
|
||||
keystore = { package = "substrate-keystore", path = "../../core/keystore" }
|
||||
sr-io = { path = "../../core/sr-io" }
|
||||
runtime_primitives = { package = "sr-primitives", path = "../../core/sr-primitives" }
|
||||
@@ -30,6 +31,7 @@ substrate-executor = { path = "../../core/executor" }
|
||||
transaction_pool = { package = "substrate-transaction-pool", path = "../../core/transaction-pool" }
|
||||
rpc = { package = "substrate-rpc-servers", path = "../../core/rpc-servers" }
|
||||
tel = { package = "substrate-telemetry", path = "../../core/telemetry" }
|
||||
offchain = { package = "substrate-offchain", path = "../../core/offchain" }
|
||||
|
||||
[dev-dependencies]
|
||||
substrate-test-client = { path = "../test-client" }
|
||||
|
||||
@@ -116,35 +116,43 @@ impl<G: RuntimeGenesis> Clone for ChainSpec<G> {
|
||||
}
|
||||
|
||||
impl<G: RuntimeGenesis> ChainSpec<G> {
|
||||
/// A list of bootnode addresses.
|
||||
pub fn boot_nodes(&self) -> &[String] {
|
||||
&self.spec.boot_nodes
|
||||
}
|
||||
|
||||
/// Spec name.
|
||||
pub fn name(&self) -> &str {
|
||||
&self.spec.name
|
||||
}
|
||||
|
||||
/// Spec id.
|
||||
pub fn id(&self) -> &str {
|
||||
&self.spec.id
|
||||
}
|
||||
|
||||
/// Telemetry endpoints (if any)
|
||||
pub fn telemetry_endpoints(&self) -> &Option<TelemetryEndpoints> {
|
||||
&self.spec.telemetry_endpoints
|
||||
}
|
||||
|
||||
/// Network protocol id.
|
||||
pub fn protocol_id(&self) -> Option<&str> {
|
||||
self.spec.protocol_id.as_ref().map(String::as_str)
|
||||
}
|
||||
|
||||
/// Name of the consensus engine.
|
||||
pub fn consensus_engine(&self) -> Option<&str> {
|
||||
self.spec.consensus_engine.as_ref().map(String::as_str)
|
||||
}
|
||||
|
||||
/// Additional loosly-typed properties of the chain.
|
||||
pub fn properties(&self) -> Properties {
|
||||
// Return an empty JSON object if 'properties' not defined in config
|
||||
self.spec.properties.as_ref().unwrap_or(&json::map::Map::new()).clone()
|
||||
}
|
||||
|
||||
/// Add a bootnode to the list.
|
||||
pub fn add_boot_node(&mut self, addr: Multiaddr) {
|
||||
self.spec.boot_nodes.push(addr.to_string())
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ use serde::{Serialize, de::DeserializeOwned};
|
||||
use tokio::runtime::TaskExecutor;
|
||||
use crate::chain_spec::ChainSpec;
|
||||
use client_db;
|
||||
use client::{self, Client, runtime_api::{Metadata, TaggedTransactionQueue}};
|
||||
use client::{self, Client, runtime_api};
|
||||
use crate::{error, Service, maybe_start_server};
|
||||
use consensus_common::import_queue::ImportQueue;
|
||||
use network::{self, OnDemand};
|
||||
@@ -150,7 +150,7 @@ pub trait StartRPC<C: Components> {
|
||||
|
||||
impl<C: Components> StartRPC<Self> for C where
|
||||
ComponentClient<C>: ProvideRuntimeApi,
|
||||
<ComponentClient<C> as ProvideRuntimeApi>::Api: Metadata<ComponentBlock<C>>,
|
||||
<ComponentClient<C> as ProvideRuntimeApi>::Api: runtime_api::Metadata<ComponentBlock<C>>,
|
||||
{
|
||||
type ServersHandle = (Option<rpc::HttpServer>, Option<Mutex<rpc::WsServer>>);
|
||||
|
||||
@@ -192,14 +192,14 @@ impl<C: Components> StartRPC<Self> for C where
|
||||
|
||||
/// Something that can maintain transaction pool on every imported block.
|
||||
pub trait MaintainTransactionPool<C: Components> {
|
||||
fn on_block_imported(
|
||||
fn maintain_transaction_pool(
|
||||
id: &BlockId<ComponentBlock<C>>,
|
||||
client: &ComponentClient<C>,
|
||||
transaction_pool: &TransactionPool<C::TransactionPoolApi>,
|
||||
) -> error::Result<()>;
|
||||
}
|
||||
|
||||
fn on_block_imported<Api, Backend, Block, Executor, PoolApi>(
|
||||
fn maintain_transaction_pool<Api, Backend, Block, Executor, PoolApi>(
|
||||
id: &BlockId<Block>,
|
||||
client: &Client<Backend, Executor, Block, Api>,
|
||||
transaction_pool: &TransactionPool<PoolApi>,
|
||||
@@ -207,7 +207,7 @@ fn on_block_imported<Api, Backend, Block, Executor, PoolApi>(
|
||||
Block: BlockT<Hash = <Blake2Hasher as ::primitives::Hasher>::Out>,
|
||||
Backend: client::backend::Backend<Block, Blake2Hasher>,
|
||||
Client<Backend, Executor, Block, Api>: ProvideRuntimeApi,
|
||||
<Client<Backend, Executor, Block, Api> as ProvideRuntimeApi>::Api: TaggedTransactionQueue<Block>,
|
||||
<Client<Backend, Executor, Block, Api> as ProvideRuntimeApi>::Api: runtime_api::TaggedTransactionQueue<Block>,
|
||||
Executor: client::CallExecutor<Block, Blake2Hasher>,
|
||||
PoolApi: txpool::ChainApi<Hash = Block::Hash, Block = Block>,
|
||||
{
|
||||
@@ -227,14 +227,35 @@ fn on_block_imported<Api, Backend, Block, Executor, PoolApi>(
|
||||
|
||||
impl<C: Components> MaintainTransactionPool<Self> for C where
|
||||
ComponentClient<C>: ProvideRuntimeApi,
|
||||
<ComponentClient<C> as ProvideRuntimeApi>::Api: TaggedTransactionQueue<ComponentBlock<C>>,
|
||||
<ComponentClient<C> as ProvideRuntimeApi>::Api: runtime_api::TaggedTransactionQueue<ComponentBlock<C>>,
|
||||
{
|
||||
fn on_block_imported(
|
||||
fn maintain_transaction_pool(
|
||||
id: &BlockId<ComponentBlock<C>>,
|
||||
client: &ComponentClient<C>,
|
||||
transaction_pool: &TransactionPool<C::TransactionPoolApi>,
|
||||
) -> error::Result<()> {
|
||||
on_block_imported(id, client, transaction_pool)
|
||||
maintain_transaction_pool(id, client, transaction_pool)
|
||||
}
|
||||
}
|
||||
|
||||
pub trait OffchainWorker<C: Components> {
|
||||
fn offchain_workers(
|
||||
number: &FactoryBlockNumber<C::Factory>,
|
||||
offchain: &offchain::OffchainWorkers<ComponentClient<C>, ComponentBlock<C>>,
|
||||
pool: &Arc<TransactionPool<C::TransactionPoolApi>>,
|
||||
) -> error::Result<()>;
|
||||
}
|
||||
|
||||
impl<C: Components> OffchainWorker<Self> for C where
|
||||
ComponentClient<C>: ProvideRuntimeApi,
|
||||
<ComponentClient<C> as ProvideRuntimeApi>::Api: offchain::OffchainWorkerApi<ComponentBlock<C>>,
|
||||
{
|
||||
fn offchain_workers(
|
||||
number: &FactoryBlockNumber<C::Factory>,
|
||||
offchain: &offchain::OffchainWorkers<ComponentClient<C>, ComponentBlock<C>>,
|
||||
pool: &Arc<TransactionPool<C::TransactionPoolApi>>,
|
||||
) -> error::Result<()> {
|
||||
Ok(offchain.on_block_imported(number, pool))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -246,9 +267,16 @@ pub trait ServiceTrait<C: Components>:
|
||||
+ 'static
|
||||
+ StartRPC<C>
|
||||
+ MaintainTransactionPool<C>
|
||||
+ OffchainWorker<C>
|
||||
{}
|
||||
impl<C: Components, T> ServiceTrait<C> for T where
|
||||
T: Deref<Target = Service<C>> + Send + Sync + 'static + StartRPC<C> + MaintainTransactionPool<C>
|
||||
T: Deref<Target = Service<C>>
|
||||
+ Send
|
||||
+ Sync
|
||||
+ 'static
|
||||
+ StartRPC<C>
|
||||
+ MaintainTransactionPool<C>
|
||||
+ OffchainWorker<C>
|
||||
{}
|
||||
|
||||
/// A collection of types and methods to build a service on top of the substrate service.
|
||||
@@ -338,17 +366,14 @@ pub trait Components: Sized + 'static {
|
||||
type Executor: 'static + client::CallExecutor<FactoryBlock<Self::Factory>, Blake2Hasher> + Send + Sync + Clone;
|
||||
/// The type that implements the runtime API.
|
||||
type RuntimeApi: Send + Sync;
|
||||
/// A type that can start the RPC.
|
||||
type RPC: StartRPC<Self>;
|
||||
/// A type that can start all runtime-dependent services.
|
||||
type RuntimeServices: ServiceTrait<Self>;
|
||||
// TODO: Traitify transaction pool and allow people to implement their own. (#1242)
|
||||
/// A type that can maintain transaction pool.
|
||||
type TransactionPool: MaintainTransactionPool<Self>;
|
||||
/// Extrinsic pool type.
|
||||
type TransactionPoolApi: 'static + txpool::ChainApi<
|
||||
Hash = <FactoryBlock<Self::Factory> as BlockT>::Hash,
|
||||
Block = FactoryBlock<Self::Factory>
|
||||
>;
|
||||
|
||||
/// Our Import Queue
|
||||
type ImportQueue: ImportQueue<FactoryBlock<Self::Factory>> + 'static;
|
||||
|
||||
@@ -382,6 +407,7 @@ pub struct FullComponents<Factory: ServiceFactory> {
|
||||
}
|
||||
|
||||
impl<Factory: ServiceFactory> FullComponents<Factory> {
|
||||
/// Create new `FullComponents`
|
||||
pub fn new(
|
||||
config: FactoryFullConfiguration<Factory>,
|
||||
task_executor: TaskExecutor
|
||||
@@ -416,8 +442,7 @@ impl<Factory: ServiceFactory> Components for FullComponents<Factory> {
|
||||
type TransactionPoolApi = <Factory as ServiceFactory>::FullTransactionPoolApi;
|
||||
type ImportQueue = Factory::FullImportQueue;
|
||||
type RuntimeApi = Factory::RuntimeApi;
|
||||
type RPC = Factory::FullService;
|
||||
type TransactionPool = Factory::FullService;
|
||||
type RuntimeServices = Factory::FullService;
|
||||
|
||||
fn build_client(
|
||||
config: &FactoryFullConfiguration<Factory>,
|
||||
@@ -462,6 +487,7 @@ pub struct LightComponents<Factory: ServiceFactory> {
|
||||
}
|
||||
|
||||
impl<Factory: ServiceFactory> LightComponents<Factory> {
|
||||
/// Create new `LightComponents`
|
||||
pub fn new(
|
||||
config: FactoryFullConfiguration<Factory>,
|
||||
task_executor: TaskExecutor
|
||||
@@ -490,8 +516,7 @@ impl<Factory: ServiceFactory> Components for LightComponents<Factory> {
|
||||
type TransactionPoolApi = <Factory as ServiceFactory>::LightTransactionPoolApi;
|
||||
type ImportQueue = <Factory as ServiceFactory>::LightImportQueue;
|
||||
type RuntimeApi = Factory::RuntimeApi;
|
||||
type RPC = Factory::LightService;
|
||||
type TransactionPool = Factory::LightService;
|
||||
type RuntimeServices = Factory::LightService;
|
||||
|
||||
fn build_client(
|
||||
config: &FactoryFullConfiguration<Factory>,
|
||||
@@ -564,7 +589,7 @@ mod tests {
|
||||
|
||||
// fire notification - this should clean up the queue
|
||||
assert_eq!(pool.status().ready, 1);
|
||||
on_block_imported(
|
||||
maintain_transaction_pool(
|
||||
&id,
|
||||
&client,
|
||||
&pool,
|
||||
|
||||
@@ -68,6 +68,8 @@ pub struct Configuration<C, G: Serialize + DeserializeOwned + BuildStorage> {
|
||||
pub telemetry_endpoints: Option<TelemetryEndpoints>,
|
||||
/// The default number of 64KB pages to allocate for Wasm execution
|
||||
pub default_heap_pages: Option<u64>,
|
||||
/// Should offchain workers be executed.
|
||||
pub offchain_worker: bool,
|
||||
/// Enable authoring even when offline.
|
||||
pub force_authoring: bool,
|
||||
/// Disable GRANDPA when running in validator mode
|
||||
@@ -97,6 +99,7 @@ impl<C: Default, G: Serialize + DeserializeOwned + BuildStorage> Configuration<C
|
||||
rpc_ws: None,
|
||||
telemetry_endpoints: None,
|
||||
default_heap_pages: None,
|
||||
offchain_worker: Default::default(),
|
||||
force_authoring: false,
|
||||
disable_grandpa: false,
|
||||
};
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
//! Substrate service. Starts a thread that spins up the network, client, and extrinsic pool.
|
||||
//! Manages communication between them.
|
||||
|
||||
#![warn(unused_extern_crates)]
|
||||
#![warn(missing_docs)]
|
||||
|
||||
mod components;
|
||||
mod error;
|
||||
@@ -28,20 +28,18 @@ pub mod chain_ops;
|
||||
use std::io;
|
||||
use std::net::SocketAddr;
|
||||
use std::collections::HashMap;
|
||||
#[doc(hidden)]
|
||||
pub use std::{ops::Deref, result::Result, sync::Arc};
|
||||
use log::{info, warn, debug};
|
||||
use futures::prelude::*;
|
||||
use keystore::Store as Keystore;
|
||||
|
||||
use client::BlockchainEvents;
|
||||
use exit_future::Signal;
|
||||
use futures::prelude::*;
|
||||
use inherents::pool::InherentsPool;
|
||||
use keystore::Store as Keystore;
|
||||
use log::{info, warn, debug};
|
||||
use parity_codec::{Encode, Decode};
|
||||
use primitives::Pair;
|
||||
use runtime_primitives::generic::BlockId;
|
||||
use runtime_primitives::traits::{Header, As};
|
||||
use exit_future::Signal;
|
||||
#[doc(hidden)]
|
||||
pub use tokio::runtime::TaskExecutor;
|
||||
use substrate_executor::NativeExecutor;
|
||||
use parity_codec::{Encode, Decode};
|
||||
use tel::{telemetry, SUBSTRATE_INFO};
|
||||
|
||||
pub use self::error::{ErrorKind, Error};
|
||||
@@ -59,9 +57,13 @@ pub use components::{ServiceFactory, FullBackend, FullExecutor, LightBackend,
|
||||
FactoryFullConfiguration, RuntimeGenesis, FactoryGenesis,
|
||||
ComponentExHash, ComponentExtrinsic, FactoryExtrinsic
|
||||
};
|
||||
use components::{StartRPC, MaintainTransactionPool};
|
||||
use components::{StartRPC, MaintainTransactionPool, OffchainWorker};
|
||||
#[doc(hidden)]
|
||||
pub use std::{ops::Deref, result::Result, sync::Arc};
|
||||
#[doc(hidden)]
|
||||
pub use network::OnDemand;
|
||||
#[doc(hidden)]
|
||||
pub use tokio::runtime::TaskExecutor;
|
||||
|
||||
const DEFAULT_PROTOCOL_ID: &str = "sup";
|
||||
|
||||
@@ -70,6 +72,7 @@ pub struct Service<Components: components::Components> {
|
||||
client: Arc<ComponentClient<Components>>,
|
||||
network: Option<Arc<components::NetworkService<Components::Factory>>>,
|
||||
transaction_pool: Arc<TransactionPool<Components::TransactionPoolApi>>,
|
||||
inherents_pool: Arc<InherentsPool<ComponentExtrinsic<Components>>>,
|
||||
keystore: Keystore,
|
||||
exit: ::exit_future::Exit,
|
||||
signal: Option<Signal>,
|
||||
@@ -77,6 +80,7 @@ pub struct Service<Components: components::Components> {
|
||||
pub config: FactoryFullConfiguration<Components::Factory>,
|
||||
_rpc: Box<::std::any::Any + Send + Sync>,
|
||||
_telemetry: Option<Arc<tel::Telemetry>>,
|
||||
_offchain_workers: Option<Arc<offchain::OffchainWorkers<ComponentClient<Components>, ComponentBlock<Components>>>>,
|
||||
}
|
||||
|
||||
/// Creates bare client without any networking.
|
||||
@@ -96,9 +100,7 @@ impl<Components: components::Components> Service<Components> {
|
||||
pub fn new(
|
||||
mut config: FactoryFullConfiguration<Components::Factory>,
|
||||
task_executor: TaskExecutor,
|
||||
)
|
||||
-> Result<Self, error::Error>
|
||||
{
|
||||
) -> Result<Self, error::Error> {
|
||||
let (signal, exit) = ::exit_future::signal();
|
||||
|
||||
// Create client
|
||||
@@ -169,24 +171,48 @@ impl<Components: components::Components> Service<Components> {
|
||||
)?;
|
||||
on_demand.map(|on_demand| on_demand.set_network_sender(network_chan));
|
||||
|
||||
let inherents_pool = Arc::new(InherentsPool::default());
|
||||
let offchain_workers = if config.offchain_worker {
|
||||
Some(Arc::new(offchain::OffchainWorkers::new(
|
||||
client.clone(),
|
||||
inherents_pool.clone(),
|
||||
task_executor.clone(),
|
||||
)))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
{
|
||||
// block notifications
|
||||
let network = Arc::downgrade(&network);
|
||||
let txpool = Arc::downgrade(&transaction_pool);
|
||||
let wclient = Arc::downgrade(&client);
|
||||
let offchain = offchain_workers.as_ref().map(Arc::downgrade);
|
||||
|
||||
let events = client.import_notification_stream()
|
||||
.for_each(move |notification| {
|
||||
let number = *notification.header.number();
|
||||
|
||||
if let Some(network) = network.upgrade() {
|
||||
network.on_block_imported(notification.hash, notification.header);
|
||||
}
|
||||
|
||||
if let (Some(txpool), Some(client)) = (txpool.upgrade(), wclient.upgrade()) {
|
||||
Components::TransactionPool::on_block_imported(
|
||||
Components::RuntimeServices::maintain_transaction_pool(
|
||||
&BlockId::hash(notification.hash),
|
||||
&*client,
|
||||
&*txpool,
|
||||
).map_err(|e| warn!("Pool error processing new block: {:?}", e))?;
|
||||
}
|
||||
|
||||
if let (Some(txpool), Some(offchain)) = (txpool.upgrade(), offchain.as_ref().and_then(|o| o.upgrade())) {
|
||||
Components::RuntimeServices::offchain_workers(
|
||||
&number,
|
||||
&offchain,
|
||||
&txpool,
|
||||
).map_err(|e| warn!("Offchain workers error processing new block: {:?}", e))?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
})
|
||||
.select(exit.clone())
|
||||
@@ -264,7 +290,7 @@ impl<Components: components::Components> Service<Components> {
|
||||
impl_version: config.impl_version.into(),
|
||||
properties: config.chain_spec.properties(),
|
||||
};
|
||||
let rpc = Components::RPC::start_rpc(
|
||||
let rpc = Components::RuntimeServices::start_rpc(
|
||||
client.clone(), network.clone(), has_bootnodes, system_info, config.rpc_http,
|
||||
config.rpc_ws, task_executor.clone(), transaction_pool.clone(),
|
||||
)?;
|
||||
@@ -299,12 +325,14 @@ impl<Components: components::Components> Service<Components> {
|
||||
client,
|
||||
network: Some(network),
|
||||
transaction_pool,
|
||||
inherents_pool,
|
||||
signal: Some(signal),
|
||||
keystore,
|
||||
config,
|
||||
exit,
|
||||
_rpc: Box::new(rpc),
|
||||
_telemetry: telemetry,
|
||||
_offchain_workers: offchain_workers,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -321,6 +349,7 @@ impl<Components: components::Components> Service<Components> {
|
||||
}
|
||||
}
|
||||
|
||||
/// return a shared instance of Telemtry (if enabled)
|
||||
pub fn telemetry(&self) -> Option<Arc<tel::Telemetry>> {
|
||||
self._telemetry.as_ref().map(|t| t.clone())
|
||||
}
|
||||
@@ -337,11 +366,16 @@ impl<Components> Service<Components> where Components: components::Components {
|
||||
self.network.as_ref().expect("self.network always Some").clone()
|
||||
}
|
||||
|
||||
/// Get shared extrinsic pool instance.
|
||||
/// Get shared transaction pool instance.
|
||||
pub fn transaction_pool(&self) -> Arc<TransactionPool<Components::TransactionPoolApi>> {
|
||||
self.transaction_pool.clone()
|
||||
}
|
||||
|
||||
/// Get shared inherents pool instance.
|
||||
pub fn inherents_pool(&self) -> Arc<InherentsPool<ComponentExtrinsic<Components>>> {
|
||||
self.inherents_pool.clone()
|
||||
}
|
||||
|
||||
/// Get shared keystore.
|
||||
pub fn keystore(&self) -> &Keystore {
|
||||
&self.keystore
|
||||
|
||||
@@ -120,6 +120,7 @@ fn node_config<F: ServiceFactory> (
|
||||
rpc_ws: None,
|
||||
telemetry_endpoints: None,
|
||||
default_heap_pages: None,
|
||||
offchain_worker: false,
|
||||
force_authoring: false,
|
||||
disable_grandpa: false,
|
||||
}
|
||||
|
||||
@@ -469,7 +469,7 @@ impl<'a> Fold for ApiRuntimeImplToApiRuntimeApiImpl<'a> {
|
||||
};
|
||||
|
||||
let context_arg: syn::FnArg = parse_quote!( context: #crate_::runtime_api::ExecutionContext );
|
||||
|
||||
|
||||
// Rewrite the input parameters.
|
||||
input.sig.decl.inputs = parse_quote! {
|
||||
&self, at: &#block_id, #context_arg, params: Option<( #( #param_types ),* )>, params_encoded: Vec<u8>
|
||||
@@ -615,7 +615,7 @@ fn generate_runtime_api_versions(impls: &[ItemImpl]) -> Result<TokenStream> {
|
||||
pub fn impl_runtime_apis_impl(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
|
||||
// Parse all impl blocks
|
||||
let RuntimeApiImpls { impls: api_impls } = parse_macro_input!(input as RuntimeApiImpls);
|
||||
|
||||
|
||||
let dispatch_impl = unwrap_or_error(generate_dispatch_function(&api_impls));
|
||||
let api_impls_for_runtime = unwrap_or_error(generate_api_impl_for_runtime(&api_impls));
|
||||
let base_runtime_api = unwrap_or_error(generate_runtime_api_base_structures(&api_impls));
|
||||
|
||||
@@ -218,6 +218,14 @@ pub fn secp256k1_ecdsa_recover(sig: &[u8; 65], msg: &[u8; 32]) -> Result<[u8; 64
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
/// Submit extrinsic.
|
||||
pub fn submit_extrinsic<T: codec::Encode>(data: &T) {
|
||||
ext::with(|ext| ext
|
||||
.submit_extrinsic(codec::Encode::encode(data))
|
||||
.expect("submit_extrinsic can be called only in offchain worker context")
|
||||
).expect("submit_extrinsic cannot be called outside of an Externalities-provided environment.")
|
||||
}
|
||||
|
||||
/// Execute the given closure with global function available whose functionality routes into the
|
||||
/// externalities `ext`. Forwards the value that the closure returns.
|
||||
// NOTE: need a concrete hasher here due to limitations of the `environmental!` macro, otherwise a type param would have been fine I think.
|
||||
|
||||
@@ -283,6 +283,13 @@ extern_functions! {
|
||||
fn ext_sr25519_verify(msg_data: *const u8, msg_len: u32, sig_data: *const u8, pubkey_data: *const u8) -> u32;
|
||||
/// Note: ext_secp256k1_ecdsa_recover returns 0 if the signature is correct, nonzero otherwise.
|
||||
fn ext_secp256k1_ecdsa_recover(msg_data: *const u8, sig_data: *const u8, pubkey_data: *mut u8) -> u32;
|
||||
|
||||
//================================
|
||||
// Offchain-worker Context
|
||||
//================================
|
||||
|
||||
/// Submit extrinsic.
|
||||
fn ext_submit_extrinsic(data: *const u8, len: u32);
|
||||
}
|
||||
|
||||
/// Ensures we use the right crypto when calling into native
|
||||
@@ -594,6 +601,18 @@ pub fn secp256k1_ecdsa_recover(sig: &[u8; 65], msg: &[u8; 32]) -> Result<[u8; 64
|
||||
}
|
||||
}
|
||||
|
||||
/// Submit extrinsic from the runtime.
|
||||
///
|
||||
/// Depending on the kind of extrinsic it will either be:
|
||||
/// 1. scheduled to be included in the next produced block (inherent)
|
||||
/// 2. added to the pool and propagated (transaction)
|
||||
pub fn submit_extrinsic<T: codec::Encode>(data: &T) {
|
||||
let encoded_data = codec::Encode::encode(data);
|
||||
unsafe {
|
||||
ext_submit_extrinsic.get()(encoded_data.as_ptr(), encoded_data.len() as u32)
|
||||
}
|
||||
}
|
||||
|
||||
/// Trait for things which can be printed.
|
||||
pub trait Printable {
|
||||
fn print(self);
|
||||
|
||||
@@ -393,21 +393,6 @@ impl From<sr25519::Signature> for AnySignature {
|
||||
}
|
||||
}
|
||||
|
||||
/// Context for executing a call into the runtime.
|
||||
#[derive(Copy, Clone, Eq, PartialEq, Encode, Decode)]
|
||||
#[cfg_attr(feature = "std", derive(Debug, Serialize))]
|
||||
#[repr(u8)]
|
||||
pub enum ExecutionContext {
|
||||
/// Context for general importing (including own blocks).
|
||||
Importing,
|
||||
/// Context used when syncing the blockchain.
|
||||
Syncing,
|
||||
/// Context used for block construction.
|
||||
BlockConstruction,
|
||||
/// Context used for other calls.
|
||||
Other,
|
||||
}
|
||||
|
||||
#[derive(Eq, PartialEq, Clone, Copy, Decode)]
|
||||
#[cfg_attr(feature = "std", derive(Debug, Serialize))]
|
||||
#[repr(u8)]
|
||||
|
||||
@@ -270,6 +270,24 @@ pub trait OnInitialise<BlockNumber> {
|
||||
|
||||
impl<N> OnInitialise<N> for () {}
|
||||
|
||||
/// Off-chain computation trait.
|
||||
///
|
||||
/// Implementing this trait on a module allows you to perform a long-running tasks
|
||||
/// that make validators generate extrinsics (either transactions or inherents)
|
||||
/// with results of those long-running computations.
|
||||
///
|
||||
/// NOTE: This function runs off-chain, so it can access the block state,
|
||||
/// but cannot preform any alterations.
|
||||
pub trait OffchainWorker<BlockNumber> {
|
||||
/// This function is being called on every block.
|
||||
///
|
||||
/// Implement this and use special `extern`s to generate transactions or inherents.
|
||||
/// Any state alterations are lost and are not persisted.
|
||||
fn generate_extrinsics(_n: BlockNumber) {}
|
||||
}
|
||||
|
||||
impl<N> OffchainWorker<N> for () {}
|
||||
|
||||
macro_rules! tuple_impl {
|
||||
($one:ident,) => {
|
||||
impl<Number: Copy, $one: OnFinalise<Number>> OnFinalise<Number> for ($one,) {
|
||||
@@ -282,6 +300,11 @@ macro_rules! tuple_impl {
|
||||
$one::on_initialise(n);
|
||||
}
|
||||
}
|
||||
impl<Number: Copy, $one: OffchainWorker<Number>> OffchainWorker<Number> for ($one,) {
|
||||
fn generate_extrinsics(n: Number) {
|
||||
$one::generate_extrinsics(n);
|
||||
}
|
||||
}
|
||||
};
|
||||
($first:ident, $($rest:ident,)+) => {
|
||||
impl<
|
||||
@@ -304,6 +327,16 @@ macro_rules! tuple_impl {
|
||||
$($rest::on_initialise(n);)+
|
||||
}
|
||||
}
|
||||
impl<
|
||||
Number: Copy,
|
||||
$first: OffchainWorker<Number>,
|
||||
$($rest: OffchainWorker<Number>),+
|
||||
> OffchainWorker<Number> for ($first, $($rest),+) {
|
||||
fn generate_extrinsics(n: Number) {
|
||||
$first::generate_extrinsics(n);
|
||||
$($rest::generate_extrinsics(n);)+
|
||||
}
|
||||
}
|
||||
tuple_impl!($($rest,)+);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@ use trie::trie_root;
|
||||
use primitives::storage::well_known_keys::{CHANGES_TRIE_CONFIG, CODE, HEAP_PAGES};
|
||||
use parity_codec::Encode;
|
||||
use super::{Externalities, OverlayedChanges};
|
||||
use log::warn;
|
||||
|
||||
/// Simple HashMap-based Externalities impl.
|
||||
pub struct BasicExternalities {
|
||||
@@ -151,6 +152,11 @@ impl<H: Hasher> Externalities<H> for BasicExternalities where H::Out: Ord + Heap
|
||||
fn storage_changes_root(&mut self, _parent: H::Out, _parent_num: u64) -> Option<H::Out> {
|
||||
None
|
||||
}
|
||||
|
||||
fn submit_extrinsic(&mut self, _extrinsic: Vec<u8>) -> Result<(), ()> {
|
||||
warn!("Call to submit_extrinsic without offchain externalities set.");
|
||||
Err(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
@@ -20,7 +20,7 @@ use std::{error, fmt, cmp::Ord};
|
||||
use log::warn;
|
||||
use crate::backend::{Backend, Consolidate};
|
||||
use crate::changes_trie::{AnchorBlockId, Storage as ChangesTrieStorage, compute_changes_trie_root};
|
||||
use crate::{Externalities, OverlayedChanges};
|
||||
use crate::{Externalities, OverlayedChanges, OffchainExt};
|
||||
use hash_db::Hasher;
|
||||
use primitives::storage::well_known_keys::is_child_storage_key;
|
||||
use trie::{MemoryDB, TrieDBMut, TrieMut, default_child_trie_root, is_child_trie_key_valid};
|
||||
@@ -58,12 +58,11 @@ impl<B: error::Error, E: error::Error> error::Error for Error<B, E> {
|
||||
}
|
||||
|
||||
/// Wraps a read-only backend, call executor, and current overlayed changes.
|
||||
pub struct Ext<'a, H, B, T>
|
||||
pub struct Ext<'a, H, B, T, O>
|
||||
where
|
||||
H: Hasher,
|
||||
|
||||
B: 'a + Backend<H>,
|
||||
T: 'a + ChangesTrieStorage<H>,
|
||||
{
|
||||
/// The overlayed changes to write to.
|
||||
overlay: &'a mut OverlayedChanges,
|
||||
@@ -81,23 +80,34 @@ where
|
||||
/// `storage_changes_root` is called matters + we need to remember additional
|
||||
/// data at this moment (block number).
|
||||
changes_trie_transaction: Option<(u64, MemoryDB<H>, H::Out)>,
|
||||
/// Additional externalities for offchain workers.
|
||||
///
|
||||
/// If None, some methods from the trait might not supported.
|
||||
offchain_externalities: Option<&'a mut O>,
|
||||
}
|
||||
|
||||
impl<'a, H, B, T> Ext<'a, H, B, T>
|
||||
impl<'a, H, B, T, O> Ext<'a, H, B, T, O>
|
||||
where
|
||||
H: Hasher,
|
||||
B: 'a + Backend<H>,
|
||||
T: 'a + ChangesTrieStorage<H>,
|
||||
O: 'a + OffchainExt,
|
||||
H::Out: Ord + HeapSizeOf,
|
||||
{
|
||||
/// Create a new `Ext` from overlayed changes and read-only backend
|
||||
pub fn new(overlay: &'a mut OverlayedChanges, backend: &'a B, changes_trie_storage: Option<&'a T>) -> Self {
|
||||
pub fn new(
|
||||
overlay: &'a mut OverlayedChanges,
|
||||
backend: &'a B,
|
||||
changes_trie_storage: Option<&'a T>,
|
||||
offchain_externalities: Option<&'a mut O>,
|
||||
) -> Self {
|
||||
Ext {
|
||||
overlay,
|
||||
backend,
|
||||
storage_transaction: None,
|
||||
changes_trie_storage,
|
||||
changes_trie_transaction: None,
|
||||
offchain_externalities,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -152,12 +162,13 @@ where
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
impl<'a, H, B, T> Ext<'a, H, B, T>
|
||||
impl<'a, H, B, T, O> Ext<'a, H, B, T, O>
|
||||
where
|
||||
H: Hasher,
|
||||
|
||||
B: 'a + Backend<H>,
|
||||
T: 'a + ChangesTrieStorage<H>,
|
||||
O: 'a + OffchainExt,
|
||||
{
|
||||
pub fn storage_pairs(&self) -> Vec<(Vec<u8>, Vec<u8>)> {
|
||||
use std::collections::HashMap;
|
||||
@@ -173,11 +184,12 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, B: 'a, T: 'a, H> Externalities<H> for Ext<'a, H, B, T>
|
||||
impl<'a, B, T, H, O> Externalities<H> for Ext<'a, H, B, T, O>
|
||||
where
|
||||
H: Hasher,
|
||||
B: 'a + Backend<H>,
|
||||
T: 'a + ChangesTrieStorage<H>,
|
||||
O: 'a + OffchainExt,
|
||||
H::Out: Ord + HeapSizeOf,
|
||||
{
|
||||
fn storage(&self, key: &[u8]) -> Option<Vec<u8>> {
|
||||
@@ -329,6 +341,17 @@ where
|
||||
self.changes_trie_transaction = root_and_tx;
|
||||
root
|
||||
}
|
||||
|
||||
fn submit_extrinsic(&mut self, extrinsic: Vec<u8>) -> Result<(), ()> {
|
||||
let _guard = panic_handler::AbortGuard::new(true);
|
||||
if let Some(ext) = self.offchain_externalities.as_mut() {
|
||||
ext.submit_extrinsic(extrinsic);
|
||||
Ok(())
|
||||
} else {
|
||||
warn!("Call to submit_extrinsic without offchain externalities set.");
|
||||
Err(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
@@ -345,7 +368,7 @@ mod tests {
|
||||
|
||||
type TestBackend = InMemory<Blake2Hasher>;
|
||||
type TestChangesTrieStorage = InMemoryChangesTrieStorage<Blake2Hasher>;
|
||||
type TestExt<'a> = Ext<'a, Blake2Hasher, TestBackend, TestChangesTrieStorage>;
|
||||
type TestExt<'a> = Ext<'a, Blake2Hasher, TestBackend, TestChangesTrieStorage, crate::NeverOffchainExt>;
|
||||
|
||||
fn prepare_overlay_with_changes() -> OverlayedChanges {
|
||||
OverlayedChanges {
|
||||
@@ -371,7 +394,7 @@ mod tests {
|
||||
fn storage_changes_root_is_none_when_storage_is_not_provided() {
|
||||
let mut overlay = prepare_overlay_with_changes();
|
||||
let backend = TestBackend::default();
|
||||
let mut ext = TestExt::new(&mut overlay, &backend, None);
|
||||
let mut ext = TestExt::new(&mut overlay, &backend, None, None);
|
||||
assert_eq!(ext.storage_changes_root(Default::default(), 100), None);
|
||||
}
|
||||
|
||||
@@ -381,7 +404,7 @@ mod tests {
|
||||
overlay.changes_trie_config = None;
|
||||
let storage = TestChangesTrieStorage::new();
|
||||
let backend = TestBackend::default();
|
||||
let mut ext = TestExt::new(&mut overlay, &backend, Some(&storage));
|
||||
let mut ext = TestExt::new(&mut overlay, &backend, Some(&storage), None);
|
||||
assert_eq!(ext.storage_changes_root(Default::default(), 100), None);
|
||||
}
|
||||
|
||||
@@ -390,7 +413,7 @@ mod tests {
|
||||
let mut overlay = prepare_overlay_with_changes();
|
||||
let storage = TestChangesTrieStorage::new();
|
||||
let backend = TestBackend::default();
|
||||
let mut ext = TestExt::new(&mut overlay, &backend, Some(&storage));
|
||||
let mut ext = TestExt::new(&mut overlay, &backend, Some(&storage), None);
|
||||
assert_eq!(ext.storage_changes_root(Default::default(), 99),
|
||||
Some(hex!("5b829920b9c8d554a19ee2a1ba593c4f2ee6fc32822d083e04236d693e8358d5").into()));
|
||||
}
|
||||
@@ -401,7 +424,7 @@ mod tests {
|
||||
overlay.prospective.top.get_mut(&vec![1]).unwrap().value = None;
|
||||
let storage = TestChangesTrieStorage::new();
|
||||
let backend = TestBackend::default();
|
||||
let mut ext = TestExt::new(&mut overlay, &backend, Some(&storage));
|
||||
let mut ext = TestExt::new(&mut overlay, &backend, Some(&storage), None);
|
||||
assert_eq!(ext.storage_changes_root(Default::default(), 99),
|
||||
Some(hex!("bcf494e41e29a15c9ae5caa053fe3cb8b446ee3e02a254efbdec7a19235b76e4").into()));
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@ use log::warn;
|
||||
use hash_db::Hasher;
|
||||
use heapsize::HeapSizeOf;
|
||||
use parity_codec::{Decode, Encode};
|
||||
use primitives::{storage::well_known_keys, NativeOrEncoded, NeverNativeValue};
|
||||
use primitives::{storage::well_known_keys, NativeOrEncoded, NeverNativeValue, OffchainExt};
|
||||
|
||||
pub mod backend;
|
||||
mod changes_trie;
|
||||
@@ -153,6 +153,25 @@ pub trait Externalities<H: Hasher> {
|
||||
|
||||
/// Get the change trie root of the current storage overlay at a block with given parent.
|
||||
fn storage_changes_root(&mut self, parent: H::Out, parent_num: u64) -> Option<H::Out> where H::Out: Ord;
|
||||
|
||||
/// Submit extrinsic.
|
||||
///
|
||||
/// Returns an error in case the API is not available.
|
||||
fn submit_extrinsic(&mut self, extrinsic: Vec<u8>) -> Result<(), ()>;
|
||||
}
|
||||
|
||||
/// An implementation of offchain extensions that should never be triggered.
|
||||
pub enum NeverOffchainExt {}
|
||||
|
||||
impl NeverOffchainExt {
|
||||
/// Create new offchain extensions.
|
||||
pub fn new<'a>() -> Option<&'a mut Self> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
impl OffchainExt for NeverOffchainExt {
|
||||
fn submit_extrinsic(&mut self, _extrinsic: Vec<u8>) { unreachable!() }
|
||||
}
|
||||
|
||||
/// Code execution engine.
|
||||
@@ -252,17 +271,19 @@ pub fn always_wasm<E, R: Decode>() -> ExecutionManager<DefaultHandler<R, E>> {
|
||||
}
|
||||
|
||||
/// Creates new substrate state machine.
|
||||
pub fn new<'a, H, B, T, Exec>(
|
||||
pub fn new<'a, H, B, T, O, Exec>(
|
||||
backend: &'a B,
|
||||
changes_trie_storage: Option<&'a T>,
|
||||
offchain_ext: Option<&'a mut O>,
|
||||
overlay: &'a mut OverlayedChanges,
|
||||
exec: &'a Exec,
|
||||
method: &'a str,
|
||||
call_data: &'a [u8],
|
||||
) -> StateMachine<'a, H, B, T, Exec> {
|
||||
) -> StateMachine<'a, H, B, T, O, Exec> {
|
||||
StateMachine {
|
||||
backend,
|
||||
changes_trie_storage,
|
||||
offchain_ext,
|
||||
overlay,
|
||||
exec,
|
||||
method,
|
||||
@@ -272,9 +293,10 @@ pub fn new<'a, H, B, T, Exec>(
|
||||
}
|
||||
|
||||
/// The substrate state machine.
|
||||
pub struct StateMachine<'a, H, B, T, Exec> {
|
||||
pub struct StateMachine<'a, H, B, T, O, Exec> {
|
||||
backend: &'a B,
|
||||
changes_trie_storage: Option<&'a T>,
|
||||
offchain_ext: Option<&'a mut O>,
|
||||
overlay: &'a mut OverlayedChanges,
|
||||
exec: &'a Exec,
|
||||
method: &'a str,
|
||||
@@ -282,11 +304,12 @@ pub struct StateMachine<'a, H, B, T, Exec> {
|
||||
_hasher: PhantomData<H>,
|
||||
}
|
||||
|
||||
impl<'a, H, B, T, Exec> StateMachine<'a, H, B, T, Exec> where
|
||||
impl<'a, H, B, T, O, Exec> StateMachine<'a, H, B, T, O, Exec> where
|
||||
H: Hasher,
|
||||
Exec: CodeExecutor<H>,
|
||||
B: Backend<H>,
|
||||
T: ChangesTrieStorage<H>,
|
||||
O: OffchainExt,
|
||||
H::Out: Ord + HeapSizeOf,
|
||||
{
|
||||
/// Execute a call using the given state backend, overlayed changes, and call executor.
|
||||
@@ -324,7 +347,13 @@ impl<'a, H, B, T, Exec> StateMachine<'a, H, B, T, Exec> where
|
||||
R: Decode + Encode + PartialEq,
|
||||
NC: FnOnce() -> result::Result<R, &'static str> + UnwindSafe,
|
||||
{
|
||||
let mut externalities = ext::Ext::new(self.overlay, self.backend, self.changes_trie_storage);
|
||||
let offchain = self.offchain_ext.as_mut();
|
||||
let mut externalities = ext::Ext::new(
|
||||
self.overlay,
|
||||
self.backend,
|
||||
self.changes_trie_storage,
|
||||
offchain.map(|x| &mut **x),
|
||||
);
|
||||
let (result, was_native) = self.exec.call(
|
||||
&mut externalities,
|
||||
self.method,
|
||||
@@ -505,6 +534,7 @@ where
|
||||
let mut sm = StateMachine {
|
||||
backend: &proving_backend,
|
||||
changes_trie_storage: None as Option<&changes_trie::InMemoryStorage<H>>,
|
||||
offchain_ext: NeverOffchainExt::new(),
|
||||
overlay,
|
||||
exec,
|
||||
method,
|
||||
@@ -554,6 +584,7 @@ where
|
||||
let mut sm = StateMachine {
|
||||
backend: trie_backend,
|
||||
changes_trie_storage: None as Option<&changes_trie::InMemoryStorage<H>>,
|
||||
offchain_ext: NeverOffchainExt::new(),
|
||||
overlay,
|
||||
exec,
|
||||
method,
|
||||
@@ -730,6 +761,7 @@ mod tests {
|
||||
assert_eq!(new(
|
||||
&trie_backend::tests::test_trie(),
|
||||
Some(&InMemoryChangesTrieStorage::new()),
|
||||
NeverOffchainExt::new(),
|
||||
&mut Default::default(),
|
||||
&DummyCodeExecutor {
|
||||
change_changes_trie_config: false,
|
||||
@@ -750,6 +782,7 @@ mod tests {
|
||||
assert_eq!(new(
|
||||
&trie_backend::tests::test_trie(),
|
||||
Some(&InMemoryChangesTrieStorage::new()),
|
||||
NeverOffchainExt::new(),
|
||||
&mut Default::default(),
|
||||
&DummyCodeExecutor {
|
||||
change_changes_trie_config: false,
|
||||
@@ -770,6 +803,7 @@ mod tests {
|
||||
assert!(new(
|
||||
&trie_backend::tests::test_trie(),
|
||||
Some(&InMemoryChangesTrieStorage::new()),
|
||||
NeverOffchainExt::new(),
|
||||
&mut Default::default(),
|
||||
&DummyCodeExecutor {
|
||||
change_changes_trie_config: false,
|
||||
@@ -838,7 +872,7 @@ mod tests {
|
||||
|
||||
{
|
||||
let changes_trie_storage = InMemoryChangesTrieStorage::new();
|
||||
let mut ext = Ext::new(&mut overlay, &backend, Some(&changes_trie_storage));
|
||||
let mut ext = Ext::new(&mut overlay, &backend, Some(&changes_trie_storage), NeverOffchainExt::new());
|
||||
ext.clear_prefix(b"ab");
|
||||
}
|
||||
overlay.commit_prospective();
|
||||
@@ -862,7 +896,7 @@ mod tests {
|
||||
let backend = InMemory::<Blake2Hasher>::default().try_into_trie_backend().unwrap();
|
||||
let changes_trie_storage = InMemoryChangesTrieStorage::new();
|
||||
let mut overlay = OverlayedChanges::default();
|
||||
let mut ext = Ext::new(&mut overlay, &backend, Some(&changes_trie_storage));
|
||||
let mut ext = Ext::new(&mut overlay, &backend, Some(&changes_trie_storage), NeverOffchainExt::new());
|
||||
|
||||
assert!(ext.set_child_storage(b":child_storage:testchild".to_vec(), b"abc".to_vec(), b"def".to_vec()));
|
||||
assert_eq!(ext.child_storage(b":child_storage:testchild", b"abc"), Some(b"def".to_vec()));
|
||||
@@ -889,6 +923,7 @@ mod tests {
|
||||
assert!(new(
|
||||
&trie_backend::tests::test_trie(),
|
||||
Some(&InMemoryChangesTrieStorage::new()),
|
||||
NeverOffchainExt::new(),
|
||||
&mut Default::default(),
|
||||
&DummyCodeExecutor {
|
||||
change_changes_trie_config: true,
|
||||
@@ -908,6 +943,7 @@ mod tests {
|
||||
assert!(new(
|
||||
&trie_backend::tests::test_trie(),
|
||||
Some(&InMemoryChangesTrieStorage::new()),
|
||||
NeverOffchainExt::new(),
|
||||
&mut Default::default(),
|
||||
&DummyCodeExecutor {
|
||||
change_changes_trie_config: true,
|
||||
|
||||
@@ -375,7 +375,12 @@ mod tests {
|
||||
};
|
||||
|
||||
let changes_trie_storage = InMemoryChangesTrieStorage::new();
|
||||
let mut ext = Ext::new(&mut overlay, &backend, Some(&changes_trie_storage));
|
||||
let mut ext = Ext::new(
|
||||
&mut overlay,
|
||||
&backend,
|
||||
Some(&changes_trie_storage),
|
||||
crate::NeverOffchainExt::new(),
|
||||
);
|
||||
const ROOT: [u8; 32] = hex!("0b41e488cccbd67d1f1089592c2c235f5c5399b053f7fe9152dd4b5f279914cd");
|
||||
assert_eq!(ext.storage_root(), H256::from(ROOT));
|
||||
}
|
||||
|
||||
@@ -168,6 +168,10 @@ impl<H: Hasher> Externalities<H> for TestExternalities<H> where H::Out: Ord + He
|
||||
&AnchorBlockId { hash: parent, number: parent_num },
|
||||
).map(|(root, _)| root.clone())
|
||||
}
|
||||
|
||||
fn submit_extrinsic(&mut self, _extrinsic: Vec<u8>) -> Result<(), ()> {
|
||||
unimplemented!()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
@@ -128,6 +128,7 @@ pub fn new_with_execution_strategy(
|
||||
syncing: execution_strategy,
|
||||
importing: execution_strategy,
|
||||
block_construction: execution_strategy,
|
||||
offchain_worker: execution_strategy,
|
||||
other: execution_strategy,
|
||||
};
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@ runtime_io = { package = "sr-io", path = "../sr-io", default-features = false }
|
||||
runtime_primitives = { package = "sr-primitives", path = "../sr-primitives", default-features = false }
|
||||
runtime_version = { package = "sr-version", path = "../sr-version", default-features = false }
|
||||
runtime_support = { package = "srml-support", path = "../../srml/support", default-features = false }
|
||||
offchain-primitives = { package = "substrate-offchain-primitives", path = "../offchain/primitives", default-features = false}
|
||||
executive = { package = "srml-executive", path = "../../srml/executive", default-features = false }
|
||||
cfg-if = "0.1.6"
|
||||
|
||||
@@ -45,5 +46,6 @@ std = [
|
||||
"runtime_primitives/std",
|
||||
"runtime_version/std",
|
||||
"consensus_aura/std",
|
||||
"offchain-primitives/std",
|
||||
"executive/std",
|
||||
]
|
||||
|
||||
@@ -94,6 +94,7 @@ impl Transfer {
|
||||
pub enum Extrinsic {
|
||||
AuthoritiesChange(Vec<AuthorityId>),
|
||||
Transfer(Transfer, AccountSignature),
|
||||
IncludeData(Vec<u8>),
|
||||
}
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
@@ -117,6 +118,7 @@ impl BlindCheckable for Extrinsic {
|
||||
Err(runtime_primitives::BAD_SIGNATURE)
|
||||
}
|
||||
},
|
||||
Extrinsic::IncludeData(data) => Ok(Extrinsic::IncludeData(data)),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -377,6 +379,13 @@ cfg_if! {
|
||||
impl consensus_aura::AuraApi<Block> for Runtime {
|
||||
fn slot_duration() -> u64 { 1 }
|
||||
}
|
||||
|
||||
impl offchain_primitives::OffchainWorkerApi<Block> for Runtime {
|
||||
fn offchain_worker(block: u64) {
|
||||
let ex = Extrinsic::IncludeData(block.encode());
|
||||
runtime_io::submit_extrinsic(&ex)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
impl_runtime_apis! {
|
||||
@@ -480,6 +489,13 @@ cfg_if! {
|
||||
impl consensus_aura::AuraApi<Block> for Runtime {
|
||||
fn slot_duration() -> u64 { 1 }
|
||||
}
|
||||
|
||||
impl offchain_primitives::OffchainWorkerApi<Block> for Runtime {
|
||||
fn offchain_worker(block: u64) {
|
||||
let ex = Extrinsic::IncludeData(block.encode());
|
||||
runtime_io::submit_extrinsic(&ex)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -242,6 +242,7 @@ fn execute_transaction_backend(utx: &Extrinsic) -> ApplyResult {
|
||||
match utx {
|
||||
Extrinsic::Transfer(ref transfer, _) => execute_transfer_backend(transfer),
|
||||
Extrinsic::AuthoritiesChange(ref new_auth) => execute_new_authorities_backend(new_auth),
|
||||
Extrinsic::IncludeData(_) => Ok(ApplyOutcome::Success),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+9
@@ -2346,6 +2346,14 @@ dependencies = [
|
||||
"substrate-primitives 0.1.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "substrate-offchain-primitives"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"sr-primitives 0.1.0",
|
||||
"substrate-client 0.1.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "substrate-panic-handler"
|
||||
version = "0.1.0"
|
||||
@@ -2446,6 +2454,7 @@ dependencies = [
|
||||
"substrate-consensus-aura-primitives 0.1.0",
|
||||
"substrate-inherents 0.1.0",
|
||||
"substrate-keyring 0.1.0",
|
||||
"substrate-offchain-primitives 0.1.0",
|
||||
"substrate-primitives 0.1.0",
|
||||
]
|
||||
|
||||
|
||||
BIN
Binary file not shown.
@@ -25,6 +25,7 @@ sudo = { package = "srml-sudo", path = "../../srml/sudo", default_features = fal
|
||||
runtime-primitives = { package = "sr-primitives", path = "../../core/sr-primitives", default_features = false }
|
||||
client = { package = "substrate-client", path = "../../core/client", default_features = false }
|
||||
consensus-aura = { package = "substrate-consensus-aura-primitives", path = "../../core/consensus/aura/primitives", default_features = false }
|
||||
offchain-primitives = { package = "substrate-offchain-primitives", path = "../../core/offchain/primitives", default-features = false }
|
||||
|
||||
[features]
|
||||
default = ["std"]
|
||||
@@ -48,4 +49,5 @@ std = [
|
||||
"serde",
|
||||
"safe-mix/std",
|
||||
"consensus-aura/std",
|
||||
"offchain-primitives/std",
|
||||
]
|
||||
|
||||
@@ -14,7 +14,7 @@ use primitives::bytes;
|
||||
use primitives::{ed25519, OpaqueMetadata};
|
||||
use runtime_primitives::{
|
||||
ApplyResult, transaction_validity::TransactionValidity, generic, create_runtime_str,
|
||||
traits::{self, BlakeTwo256, Block as BlockT, StaticLookup, Verify}
|
||||
traits::{self, NumberFor, BlakeTwo256, Block as BlockT, StaticLookup, Verify}
|
||||
};
|
||||
use client::{
|
||||
block_builder::api::{CheckInherentsResult, InherentData, self as block_builder_api},
|
||||
@@ -182,7 +182,7 @@ impl sudo::Trait for Runtime {
|
||||
}
|
||||
|
||||
/// Used for the module template in `./template.rs`
|
||||
impl template::Trait for Runtime {
|
||||
impl template::Trait for Runtime {
|
||||
type Event = Event;
|
||||
}
|
||||
|
||||
@@ -280,4 +280,10 @@ impl_runtime_apis! {
|
||||
Aura::slot_duration()
|
||||
}
|
||||
}
|
||||
|
||||
impl offchain_primitives::OffchainWorkerApi<Block> for Runtime {
|
||||
fn offchain_worker(n: NumberFor<Block>) {
|
||||
Executive::offchain_worker(n)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+9
@@ -1273,6 +1273,7 @@ dependencies = [
|
||||
"srml-timestamp 0.1.0",
|
||||
"substrate-client 0.1.0",
|
||||
"substrate-consensus-aura-primitives 0.1.0",
|
||||
"substrate-offchain-primitives 0.1.0",
|
||||
"substrate-primitives 0.1.0",
|
||||
]
|
||||
|
||||
@@ -2510,6 +2511,14 @@ dependencies = [
|
||||
"substrate-primitives 0.1.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "substrate-offchain-primitives"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"sr-primitives 0.1.0",
|
||||
"substrate-client 0.1.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "substrate-panic-handler"
|
||||
version = "0.1.0"
|
||||
|
||||
@@ -62,6 +62,7 @@ construct_service_factory! {
|
||||
let proposer = Arc::new(ProposerFactory {
|
||||
client: service.client(),
|
||||
transaction_pool: service.transaction_pool(),
|
||||
inherents_pool: service.inherents_pool(),
|
||||
});
|
||||
let client = service.client();
|
||||
executor.spawn(start_aura(
|
||||
|
||||
@@ -85,6 +85,7 @@ construct_service_factory! {
|
||||
let proposer = Arc::new(substrate_basic_authorship::ProposerFactory {
|
||||
client: service.client(),
|
||||
transaction_pool: service.transaction_pool(),
|
||||
inherents_pool: service.inherents_pool(),
|
||||
});
|
||||
|
||||
let client = service.client();
|
||||
|
||||
@@ -12,6 +12,7 @@ substrate-primitives = { path = "../../core/primitives", default-features = fals
|
||||
client = { package = "substrate-client", path = "../../core/client", default-features = false }
|
||||
rstd = { package = "sr-std", path = "../../core/sr-std", default-features = false }
|
||||
runtime_primitives = { package = "sr-primitives", path = "../../core/sr-primitives", default-features = false }
|
||||
offchain-primitives = { package = "substrate-offchain-primitives", path = "../../core/offchain/primitives", default-features = false }
|
||||
version = { package = "sr-version", path = "../../core/sr-version", default-features = false }
|
||||
support = { package = "srml-support", path = "../../srml/support", default-features = false }
|
||||
aura = { package = "srml-aura", path = "../../srml/aura", default-features = false }
|
||||
|
||||
@@ -58,8 +58,8 @@ pub const VERSION: RuntimeVersion = RuntimeVersion {
|
||||
spec_name: create_runtime_str!("node"),
|
||||
impl_name: create_runtime_str!("substrate-node"),
|
||||
authoring_version: 10,
|
||||
spec_version: 42,
|
||||
impl_version: 42,
|
||||
spec_version: 43,
|
||||
impl_version: 43,
|
||||
apis: RUNTIME_API_VERSIONS,
|
||||
};
|
||||
|
||||
@@ -288,6 +288,12 @@ impl_runtime_apis! {
|
||||
}
|
||||
}
|
||||
|
||||
impl offchain_primitives::OffchainWorkerApi<Block> for Runtime {
|
||||
fn offchain_worker(number: NumberFor<Block>) {
|
||||
Executive::offchain_worker(number)
|
||||
}
|
||||
}
|
||||
|
||||
impl fg_primitives::GrandpaApi<Block> for Runtime {
|
||||
fn grandpa_pending_change(digest: &DigestFor<Block>)
|
||||
-> Option<ScheduledChange<NumberFor<Block>>>
|
||||
|
||||
Generated
+9
@@ -1297,6 +1297,7 @@ dependencies = [
|
||||
"substrate-client 0.1.0",
|
||||
"substrate-consensus-aura-primitives 0.1.0",
|
||||
"substrate-keyring 0.1.0",
|
||||
"substrate-offchain-primitives 0.1.0",
|
||||
"substrate-primitives 0.1.0",
|
||||
]
|
||||
|
||||
@@ -2666,6 +2667,14 @@ dependencies = [
|
||||
"substrate-primitives 0.1.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "substrate-offchain-primitives"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"sr-primitives 0.1.0",
|
||||
"substrate-client 0.1.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "substrate-panic-handler"
|
||||
version = "0.1.0"
|
||||
|
||||
BIN
Binary file not shown.
@@ -214,6 +214,15 @@ decl_module! {
|
||||
// We just kill our dummy storage item.
|
||||
<Dummy<T>>::kill();
|
||||
}
|
||||
|
||||
// A runtime code run after every block and have access to extended set of APIs.
|
||||
//
|
||||
// For instance you can generate extrinsics for the upcoming produced block.
|
||||
fn offchain_worker(_n: T::BlockNumber) {
|
||||
// We don't do anything here.
|
||||
// but we could dispatch extrinsic (transaction/inherent) using
|
||||
// runtime_io::submit_extrinsic
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@ use rstd::marker::PhantomData;
|
||||
use rstd::result;
|
||||
use primitives::traits::{
|
||||
self, Header, Zero, One, Checkable, Applyable, CheckEqual, OnFinalise,
|
||||
OnInitialise, Hash, As, Digest, NumberFor, Block as BlockT
|
||||
OnInitialise, Hash, As, Digest, NumberFor, Block as BlockT, OffchainWorker
|
||||
};
|
||||
use srml_support::{Dispatchable, traits::MakePayment};
|
||||
use parity_codec::{Codec, Encode};
|
||||
@@ -65,7 +65,7 @@ impl<
|
||||
Block: traits::Block<Header=System::Header, Hash=System::Hash>,
|
||||
Context: Default,
|
||||
Payment: MakePayment<System::AccountId>,
|
||||
AllModules: OnInitialise<System::BlockNumber> + OnFinalise<System::BlockNumber>,
|
||||
AllModules: OnInitialise<System::BlockNumber> + OnFinalise<System::BlockNumber> + OffchainWorker<System::BlockNumber>,
|
||||
> ExecuteBlock<Block> for Executive<System, Block, Context, Payment, AllModules> where
|
||||
Block::Extrinsic: Checkable<Context> + Codec,
|
||||
<Block::Extrinsic as Checkable<Context>>::Checked: Applyable<Index=System::Index, AccountId=System::AccountId>,
|
||||
@@ -73,11 +73,11 @@ impl<
|
||||
<<<Block::Extrinsic as Checkable<Context>>::Checked as Applyable>::Call as Dispatchable>::Origin: From<Option<System::AccountId>>
|
||||
{
|
||||
fn execute_block(block: Block) {
|
||||
Self::execute_block(block);
|
||||
Executive::<System, Block, Context, Payment, AllModules>::execute_block(block);
|
||||
}
|
||||
|
||||
fn execute_extrinsics_without_checks(block_number: NumberFor<Block>, extrinsics: Vec<Block::Extrinsic>) {
|
||||
Self::execute_extrinsics_without_checks(block_number, extrinsics);
|
||||
Executive::<System, Block, Context, Payment, AllModules>::execute_extrinsics_without_checks(block_number, extrinsics);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -86,7 +86,7 @@ impl<
|
||||
Block: traits::Block<Header=System::Header, Hash=System::Hash>,
|
||||
Context: Default,
|
||||
Payment: MakePayment<System::AccountId>,
|
||||
AllModules: OnInitialise<System::BlockNumber> + OnFinalise<System::BlockNumber>,
|
||||
AllModules: OnInitialise<System::BlockNumber> + OnFinalise<System::BlockNumber> + OffchainWorker<System::BlockNumber>,
|
||||
> Executive<System, Block, Context, Payment, AllModules> where
|
||||
Block::Extrinsic: Checkable<Context> + Codec,
|
||||
<Block::Extrinsic as Checkable<Context>>::Checked: Applyable<Index=System::Index, AccountId=System::AccountId>,
|
||||
@@ -319,6 +319,11 @@ impl<
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Start an offchain worker and generate extrinsics.
|
||||
pub fn offchain_worker(n: System::BlockNumber) {
|
||||
<AllModules as OffchainWorker<System::BlockNumber>>::generate_extrinsics(n)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
@@ -117,6 +117,7 @@ macro_rules! decl_module {
|
||||
{}
|
||||
{}
|
||||
{}
|
||||
{}
|
||||
[]
|
||||
$($t)*
|
||||
);
|
||||
@@ -135,6 +136,7 @@ macro_rules! decl_module {
|
||||
{}
|
||||
{}
|
||||
{}
|
||||
{}
|
||||
[]
|
||||
$($t)*
|
||||
);
|
||||
@@ -147,6 +149,7 @@ macro_rules! decl_module {
|
||||
{}
|
||||
{ $( $on_initialise:tt )* }
|
||||
{ $( $on_finalise:tt )* }
|
||||
{ $( $offchain:tt )* }
|
||||
[ $($t:tt)* ]
|
||||
$(#[doc = $doc_attr:tt])*
|
||||
$vis:vis fn deposit_event $(<$dpeg:ident $(, $dpeg_instance:ident)?>)* () = default;
|
||||
@@ -159,6 +162,7 @@ macro_rules! decl_module {
|
||||
{ $vis fn deposit_event $(<$dpeg $(, $dpeg_instance)?>)* () = default; }
|
||||
{ $( $on_initialise )* }
|
||||
{ $( $on_finalise )* }
|
||||
{ $( $offchain )* }
|
||||
[ $($t)* ]
|
||||
$($rest)*
|
||||
);
|
||||
@@ -170,6 +174,7 @@ macro_rules! decl_module {
|
||||
{}
|
||||
{ $( $on_initialise:tt )* }
|
||||
{ $( $on_finalise:tt )* }
|
||||
{ $( $offchain:tt )* }
|
||||
[ $($t:tt)* ]
|
||||
$(#[doc = $doc_attr:tt])*
|
||||
$vis:vis fn deposit_event $(<$dpeg:ident $(, $dpeg_instance:ident)?>)* (
|
||||
@@ -184,6 +189,7 @@ macro_rules! decl_module {
|
||||
{ $vis fn deposit_event $(<$dpeg $(, $dpeg_instance)?>)* ($( $param_name: $param ),* ) { $( $impl )* } }
|
||||
{ $( $on_initialise )* }
|
||||
{ $( $on_finalise )* }
|
||||
{ $( $offchain )* }
|
||||
[ $($t)* ]
|
||||
$($rest)*
|
||||
);
|
||||
@@ -195,6 +201,7 @@ macro_rules! decl_module {
|
||||
{ $( $deposit_event:tt )* }
|
||||
{ $( $on_initialise:tt )* }
|
||||
{}
|
||||
{ $( $offchain:tt )* }
|
||||
[ $($t:tt)* ]
|
||||
$(#[doc = $doc_attr:tt])*
|
||||
fn on_finalise($($param_name:ident : $param:ty),* ) { $( $impl:tt )* }
|
||||
@@ -207,6 +214,7 @@ macro_rules! decl_module {
|
||||
{ $( $deposit_event )* }
|
||||
{ $( $on_initialise )* }
|
||||
{ fn on_finalise( $( $param_name : $param ),* ) { $( $impl )* } }
|
||||
{ $( $offchain )* }
|
||||
[ $($t)* ]
|
||||
$($rest)*
|
||||
);
|
||||
@@ -218,6 +226,7 @@ macro_rules! decl_module {
|
||||
{ $( $deposit_event:tt )* }
|
||||
{}
|
||||
{ $( $on_finalise:tt )* }
|
||||
{ $( $offchain:tt )* }
|
||||
[ $($t:tt)* ]
|
||||
$(#[doc = $doc_attr:tt])*
|
||||
fn on_initialise($($param_name:ident : $param:ty),* ) { $( $impl:tt )* }
|
||||
@@ -230,6 +239,32 @@ macro_rules! decl_module {
|
||||
{ $( $deposit_event )* }
|
||||
{ fn on_initialise( $( $param_name : $param ),* ) { $( $impl )* } }
|
||||
{ $( $on_finalise )* }
|
||||
{ $( $offchain )* }
|
||||
[ $($t)* ]
|
||||
$($rest)*
|
||||
);
|
||||
};
|
||||
(@normalize
|
||||
$(#[$attr:meta])*
|
||||
pub struct $mod_type:ident<$trait_instance:ident: $trait_name:ident>
|
||||
for enum $call_type:ident where origin: $origin_type:ty, system = $system:ident
|
||||
{ $( $deposit_event:tt )* }
|
||||
{ $( $on_initialise:tt )* }
|
||||
{ $( $on_finalise:tt )* }
|
||||
{ }
|
||||
[ $($t:tt)* ]
|
||||
$(#[doc = $doc_attr:tt])*
|
||||
fn offchain_worker($($param_name:ident : $param:ty),* ) { $( $impl:tt )* }
|
||||
$($rest:tt)*
|
||||
) => {
|
||||
decl_module!(@normalize
|
||||
$(#[$attr])*
|
||||
pub struct $mod_type<$trait_instance: $trait_name>
|
||||
for enum $call_type where origin: $origin_type, system = $system
|
||||
{ $( $deposit_event )* }
|
||||
{ $( $on_initialise )* }
|
||||
{ $( $on_finalise )* }
|
||||
{ fn offchain_worker( $( $param_name : $param ),* ) { $( $impl )* } }
|
||||
[ $($t)* ]
|
||||
$($rest)*
|
||||
);
|
||||
@@ -241,6 +276,7 @@ macro_rules! decl_module {
|
||||
{ $( $deposit_event:tt )* }
|
||||
{ $( $on_initialise:tt )* }
|
||||
{ $( $on_finalise:tt )* }
|
||||
{ $( $offchain:tt )* }
|
||||
[ $($t:tt)* ]
|
||||
$(#[doc = $doc_attr:tt])*
|
||||
$fn_vis:vis fn $fn_name:ident(
|
||||
@@ -255,6 +291,7 @@ macro_rules! decl_module {
|
||||
{ $( $deposit_event )* }
|
||||
{ $( $on_initialise )* }
|
||||
{ $( $on_finalise )* }
|
||||
{ $( $offchain )* }
|
||||
[
|
||||
$($t)*
|
||||
$(#[doc = $doc_attr])*
|
||||
@@ -273,6 +310,7 @@ macro_rules! decl_module {
|
||||
{ $( $deposit_event:tt )* }
|
||||
{ $( $on_initialise:tt )* }
|
||||
{ $( $on_finalise:tt )* }
|
||||
{ $( $offchain:tt )* }
|
||||
[ $($t:tt)* ]
|
||||
$(#[doc = $doc_attr:tt])*
|
||||
$fn_vis:vis fn $fn_name:ident(
|
||||
@@ -293,6 +331,7 @@ macro_rules! decl_module {
|
||||
{ $( $deposit_event:tt )* }
|
||||
{ $( $on_initialise:tt )* }
|
||||
{ $( $on_finalise:tt )* }
|
||||
{ $( $offchain:tt )* }
|
||||
[ $($t:tt)* ]
|
||||
$(#[doc = $doc_attr:tt])*
|
||||
$fn_vis:vis fn $fn_name:ident(
|
||||
@@ -313,6 +352,7 @@ macro_rules! decl_module {
|
||||
{ $( $deposit_event:tt )* }
|
||||
{ $( $on_initialise:tt )* }
|
||||
{ $( $on_finalise:tt )* }
|
||||
{ $( $offchain:tt )* }
|
||||
[ $($t:tt)* ]
|
||||
$(#[doc = $doc_attr:tt])*
|
||||
$fn_vis:vis fn $fn_name:ident(
|
||||
@@ -327,6 +367,7 @@ macro_rules! decl_module {
|
||||
{ $( $deposit_event )* }
|
||||
{ $( $on_initialise )* }
|
||||
{ $( $on_finalise )* }
|
||||
{ $( $offchain )* }
|
||||
[
|
||||
$($t)*
|
||||
$(#[doc = $doc_attr])*
|
||||
@@ -345,6 +386,7 @@ macro_rules! decl_module {
|
||||
{ $( $deposit_event:tt )* }
|
||||
{ $( $on_initialise:tt )* }
|
||||
{ $( $on_finalise:tt )* }
|
||||
{ $( $offchain:tt )* }
|
||||
[ $($t:tt)* ]
|
||||
) => {
|
||||
decl_module!(@imp
|
||||
@@ -356,6 +398,7 @@ macro_rules! decl_module {
|
||||
{ $( $deposit_event )* }
|
||||
{ $( $on_initialise )* }
|
||||
{ $( $on_finalise )* }
|
||||
{ $( $offchain )* }
|
||||
);
|
||||
};
|
||||
|
||||
@@ -477,6 +520,39 @@ macro_rules! decl_module {
|
||||
}
|
||||
};
|
||||
|
||||
(@impl_offchain
|
||||
$module:ident<$trait_instance:ident: $trait_name:ident$(<I>, $instance:ident: $instantiable:path)?>;
|
||||
fn offchain_worker() { $( $impl:tt )* }
|
||||
) => {
|
||||
impl<$trait_instance: $trait_name$(<I>, $instance: $instantiable)?>
|
||||
$crate::runtime_primitives::traits::OffchainWorker<$trait_instance::BlockNumber>
|
||||
for $module<$trait_instance$(, $instance)?>
|
||||
{
|
||||
fn generate_extrinsics(_block_number_not_used: $trait_instance::BlockNumber) { $( $impl )* }
|
||||
}
|
||||
};
|
||||
|
||||
(@impl_offchain
|
||||
$module:ident<$trait_instance:ident: $trait_name:ident$(<I>, $instance:ident: $instantiable:path)?>;
|
||||
fn offchain_worker($param:ident : $param_ty:ty) { $( $impl:tt )* }
|
||||
) => {
|
||||
impl<$trait_instance: $trait_name$(<I>, $instance: $instantiable)?>
|
||||
$crate::runtime_primitives::traits::OffchainWorker<$trait_instance::BlockNumber>
|
||||
for $module<$trait_instance$(, $instance)?>
|
||||
{
|
||||
fn generate_extrinsics($param: $param_ty) { $( $impl )* }
|
||||
}
|
||||
};
|
||||
|
||||
(@impl_offchain
|
||||
$module:ident<$trait_instance:ident: $trait_name:ident$(<I>, $instance:ident: $instantiable:path)?>;
|
||||
) => {
|
||||
impl<$trait_instance: $trait_name$(<I>, $instance: $instantiable)?>
|
||||
$crate::runtime_primitives::traits::OffchainWorker<$trait_instance::BlockNumber>
|
||||
for $module<$trait_instance$(, $instance)?>
|
||||
{}
|
||||
};
|
||||
|
||||
(@impl_function
|
||||
$module:ident<$trait_instance:ident: $trait_name:ident$(<I>, $instance:ident: $instantiable:path)?>;
|
||||
$origin_ty:ty;
|
||||
@@ -556,6 +632,7 @@ macro_rules! decl_module {
|
||||
{ $( $deposit_event:tt )* }
|
||||
{ $( $on_initialise:tt )* }
|
||||
{ $( $on_finalise:tt )* }
|
||||
{ $( $offchain:tt )* }
|
||||
) => {
|
||||
// Workaround for https://github.com/rust-lang/rust/issues/26925 . Remove when sorted.
|
||||
#[derive(Clone, Copy, PartialEq, Eq)]
|
||||
@@ -584,6 +661,12 @@ macro_rules! decl_module {
|
||||
$( $on_finalise )*
|
||||
}
|
||||
|
||||
decl_module! {
|
||||
@impl_offchain
|
||||
$mod_type<$trait_instance: $trait_name $(<I>, $instance: $instantiable)?>;
|
||||
$( $offchain )*
|
||||
}
|
||||
|
||||
decl_module! {
|
||||
@impl_deposit_event
|
||||
$mod_type<$trait_instance: $trait_name $(<I>, $instance: $instantiable)?>;
|
||||
@@ -1086,6 +1169,7 @@ mod tests {
|
||||
|
||||
fn on_initialise(n: T::BlockNumber) { if n.into() == 42 { panic!("on_initialise") } }
|
||||
fn on_finalise(n: T::BlockNumber) { if n.into() == 42 { panic!("on_finalise") } }
|
||||
fn offchain_worker() {}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -124,8 +124,8 @@ pub struct EventRecord<E: Parameter + Member> {
|
||||
pub event: E,
|
||||
}
|
||||
|
||||
/// Event for the system module.
|
||||
decl_event!(
|
||||
/// Event for the system module.
|
||||
pub enum Event {
|
||||
/// An extrinsic completed successfully.
|
||||
ExtrinsicSuccess,
|
||||
|
||||
@@ -21,11 +21,11 @@ substrate-primitives = { path = "../../core/primitives" }
|
||||
[features]
|
||||
default = ["std"]
|
||||
std = [
|
||||
"rstd/std",
|
||||
"srml-support/std",
|
||||
"runtime_primitives/std",
|
||||
"serde",
|
||||
"parity-codec/std",
|
||||
"system/std",
|
||||
"inherents/std",
|
||||
"parity-codec/std",
|
||||
"rstd/std",
|
||||
"runtime_primitives/std",
|
||||
"srml-support/std",
|
||||
"serde",
|
||||
"system/std",
|
||||
]
|
||||
|
||||
Reference in New Issue
Block a user