mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-06-14 13:21:10 +00:00
Reorganising the repository - external renames and moves (#4074)
* Adding first rough ouline of the repository structure * Remove old CI stuff * add title * formatting fixes * move node-exits job's script to scripts dir * Move docs into subdir * move to bin * move maintainence scripts, configs and helpers into its own dir * add .local to ignore * move core->client * start up 'test' area * move test client * move test runtime * make test move compile * Add dependencies rule enforcement. * Fix indexing. * Update docs to reflect latest changes * Moving /srml->/paint * update docs * move client/sr-* -> primitives/ * clean old readme * remove old broken code in rhd * update lock * Step 1. * starting to untangle client * Fix after merge. * start splitting out client interfaces * move children and blockchain interfaces * Move trie and state-machine to primitives. * Fix WASM builds. * fixing broken imports * more interface moves * move backend and light to interfaces * move CallExecutor * move cli off client * moving around more interfaces * re-add consensus crates into the mix * fix subkey path * relieve client from executor * starting to pull out client from grandpa * move is_decendent_of out of client * grandpa still depends on client directly * lemme tests pass * rename srml->paint * Make it compile. * rename interfaces->client-api * Move keyring to primitives. * fixup libp2p dep * fix broken use * allow dependency enforcement to fail * move fork-tree * Moving wasm-builder * make env * move build-script-utils * fixup broken crate depdencies and names * fix imports for authority discovery * fix typo * update cargo.lock * fixing imports * Fix paths and add missing crates * re-add missing crates
This commit is contained in:
committed by
Bastian Köcher
parent
becc3b0a4f
commit
60e5011c72
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,232 @@
|
||||
// Copyright 2017-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/>.
|
||||
|
||||
//! Chain utilities.
|
||||
|
||||
use crate::error;
|
||||
use chain_spec::{ChainSpec, RuntimeGenesis, Extension};
|
||||
|
||||
/// Defines the logic for an operation exporting blocks within a range.
|
||||
#[macro_export]
|
||||
/// Export blocks
|
||||
macro_rules! export_blocks {
|
||||
($client:ident, $exit:ident, $output:ident, $from:ident, $to:ident, $json:ident) => {{
|
||||
let mut block = $from;
|
||||
|
||||
let last = match $to {
|
||||
Some(v) if v.is_zero() => One::one(),
|
||||
Some(v) => v,
|
||||
None => $client.info().chain.best_number,
|
||||
};
|
||||
|
||||
if last < block {
|
||||
return Err("Invalid block range specified".into());
|
||||
}
|
||||
|
||||
let (exit_send, exit_recv) = std::sync::mpsc::channel();
|
||||
std::thread::spawn(move || {
|
||||
let _ = $exit.wait();
|
||||
let _ = exit_send.send(());
|
||||
});
|
||||
info!("Exporting blocks from #{} to #{}", block, last);
|
||||
if !$json {
|
||||
let last_: u64 = last.saturated_into::<u64>();
|
||||
let block_: u64 = block.saturated_into::<u64>();
|
||||
let len: u64 = last_ - block_ + 1;
|
||||
$output.write_all(&len.encode())?;
|
||||
}
|
||||
|
||||
loop {
|
||||
if exit_recv.try_recv().is_ok() {
|
||||
break;
|
||||
}
|
||||
match $client.block(&BlockId::number(block))? {
|
||||
Some(block) => {
|
||||
if $json {
|
||||
serde_json::to_writer(&mut $output, &block)
|
||||
.map_err(|e| format!("Error writing JSON: {}", e))?;
|
||||
} else {
|
||||
$output.write_all(&block.encode())?;
|
||||
}
|
||||
},
|
||||
None => break,
|
||||
}
|
||||
if (block % 10000.into()).is_zero() {
|
||||
info!("#{}", block);
|
||||
}
|
||||
if block == last {
|
||||
break;
|
||||
}
|
||||
block += One::one();
|
||||
}
|
||||
Ok(())
|
||||
}}
|
||||
}
|
||||
|
||||
/// Defines the logic for an operation importing blocks from some known import.
|
||||
#[macro_export]
|
||||
/// Import blocks
|
||||
macro_rules! import_blocks {
|
||||
($block:ty, $client:ident, $queue:ident, $exit:ident, $input:ident) => {{
|
||||
use consensus_common::import_queue::{IncomingBlock, Link, BlockImportError, BlockImportResult};
|
||||
use consensus_common::BlockOrigin;
|
||||
use network::message;
|
||||
use sr_primitives::generic::SignedBlock;
|
||||
use sr_primitives::traits::Block;
|
||||
use futures03::TryFutureExt as _;
|
||||
|
||||
struct WaitLink {
|
||||
imported_blocks: u64,
|
||||
has_error: bool,
|
||||
}
|
||||
|
||||
impl WaitLink {
|
||||
fn new() -> WaitLink {
|
||||
WaitLink {
|
||||
imported_blocks: 0,
|
||||
has_error: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<B: Block> Link<B> for WaitLink {
|
||||
fn blocks_processed(
|
||||
&mut self,
|
||||
imported: usize,
|
||||
_count: usize,
|
||||
results: Vec<(Result<BlockImportResult<NumberFor<B>>, BlockImportError>, B::Hash)>
|
||||
) {
|
||||
self.imported_blocks += imported as u64;
|
||||
|
||||
for result in results {
|
||||
if let (Err(err), hash) = result {
|
||||
warn!("There was an error importing block with hash {:?}: {:?}", hash, err);
|
||||
self.has_error = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let (exit_send, exit_recv) = std::sync::mpsc::channel();
|
||||
std::thread::spawn(move || {
|
||||
let _ = $exit.wait();
|
||||
let _ = exit_send.send(());
|
||||
});
|
||||
|
||||
let mut io_reader_input = IoReader($input);
|
||||
let count: u64 = Decode::decode(&mut io_reader_input)
|
||||
.map_err(|e| format!("Error reading file: {}", e))?;
|
||||
info!("Importing {} blocks", count);
|
||||
let mut block_count = 0;
|
||||
for b in 0 .. count {
|
||||
if exit_recv.try_recv().is_ok() {
|
||||
break;
|
||||
}
|
||||
match SignedBlock::<$block>::decode(&mut io_reader_input) {
|
||||
Ok(signed) => {
|
||||
let (header, extrinsics) = signed.block.deconstruct();
|
||||
let hash = header.hash();
|
||||
let block = message::BlockData::<$block> {
|
||||
hash,
|
||||
justification: signed.justification,
|
||||
header: Some(header),
|
||||
body: Some(extrinsics),
|
||||
receipt: None,
|
||||
message_queue: None
|
||||
};
|
||||
// import queue handles verification and importing it into the client
|
||||
$queue.import_blocks(BlockOrigin::File, vec![
|
||||
IncomingBlock::<$block> {
|
||||
hash: block.hash,
|
||||
header: block.header,
|
||||
body: block.body,
|
||||
justification: block.justification,
|
||||
origin: None,
|
||||
allow_missing_state: false,
|
||||
}
|
||||
]);
|
||||
}
|
||||
Err(e) => {
|
||||
warn!("Error reading block data at {}: {}", b, e);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
block_count = b;
|
||||
if b % 1000 == 0 && b != 0 {
|
||||
info!("#{} blocks were added to the queue", b);
|
||||
}
|
||||
}
|
||||
|
||||
let mut link = WaitLink::new();
|
||||
Ok(futures::future::poll_fn(move || {
|
||||
if exit_recv.try_recv().is_ok() {
|
||||
return Ok(Async::Ready(()));
|
||||
}
|
||||
|
||||
let blocks_before = link.imported_blocks;
|
||||
let _ = futures03::future::poll_fn(|cx| {
|
||||
$queue.poll_actions(cx, &mut link);
|
||||
std::task::Poll::Pending::<Result<(), ()>>
|
||||
}).compat().poll();
|
||||
if link.has_error {
|
||||
info!(
|
||||
"Stopping after #{} blocks because of an error",
|
||||
link.imported_blocks,
|
||||
);
|
||||
return Ok(Async::Ready(()));
|
||||
}
|
||||
if link.imported_blocks / 1000 != blocks_before / 1000 {
|
||||
info!(
|
||||
"#{} blocks were imported (#{} left)",
|
||||
link.imported_blocks,
|
||||
count - link.imported_blocks
|
||||
);
|
||||
}
|
||||
if link.imported_blocks >= count {
|
||||
info!("Imported {} blocks. Best: #{}", block_count, $client.info().chain.best_number);
|
||||
Ok(Async::Ready(()))
|
||||
} else {
|
||||
Ok(Async::NotReady)
|
||||
}
|
||||
}))
|
||||
}}
|
||||
}
|
||||
|
||||
/// Revert the chain some number of blocks.
|
||||
#[macro_export]
|
||||
macro_rules! revert_chain {
|
||||
($client:ident, $blocks:ident) => {{
|
||||
let reverted = $client.revert($blocks)?;
|
||||
let info = $client.info().chain;
|
||||
|
||||
if reverted.is_zero() {
|
||||
info!("There aren't any non-finalized blocks to revert.");
|
||||
} else {
|
||||
info!("Reverted {} blocks. Best: #{} ({})", reverted, info.best_number, info.best_hash);
|
||||
}
|
||||
Ok(())
|
||||
}}
|
||||
}
|
||||
|
||||
/// Build a chain spec json
|
||||
pub fn build_spec<G, E>(spec: ChainSpec<G, E>, raw: bool) -> error::Result<String> where
|
||||
G: RuntimeGenesis,
|
||||
E: Extension,
|
||||
{
|
||||
Ok(spec.to_json(raw)?)
|
||||
}
|
||||
@@ -0,0 +1,203 @@
|
||||
// Copyright 2017-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/>.
|
||||
|
||||
//! Service configuration.
|
||||
|
||||
pub use client::ExecutionStrategies;
|
||||
pub use client_db::{kvdb::KeyValueDB, PruningMode};
|
||||
pub use network::config::{ExtTransport, NetworkConfiguration, Roles};
|
||||
pub use substrate_executor::WasmExecutionMethod;
|
||||
|
||||
use std::{path::PathBuf, net::SocketAddr, sync::Arc};
|
||||
use transaction_pool;
|
||||
use chain_spec::{ChainSpec, RuntimeGenesis, Extension, NoExtension};
|
||||
use primitives::crypto::Protected;
|
||||
use target_info::Target;
|
||||
use tel::TelemetryEndpoints;
|
||||
|
||||
/// Service configuration.
|
||||
#[derive(Clone)]
|
||||
pub struct Configuration<C, G, E = NoExtension> {
|
||||
/// Implementation name
|
||||
pub impl_name: &'static str,
|
||||
/// Implementation version
|
||||
pub impl_version: &'static str,
|
||||
/// Git commit if any.
|
||||
pub impl_commit: &'static str,
|
||||
/// Node roles.
|
||||
pub roles: Roles,
|
||||
/// Extrinsic pool configuration.
|
||||
pub transaction_pool: transaction_pool::txpool::Options,
|
||||
/// Network configuration.
|
||||
pub network: NetworkConfiguration,
|
||||
/// Path to the base configuration directory.
|
||||
pub config_dir: Option<PathBuf>,
|
||||
/// Path to key files.
|
||||
pub keystore_path: Option<PathBuf>,
|
||||
/// Configuration for the database.
|
||||
pub database: DatabaseConfig,
|
||||
/// Size of internal state cache in Bytes
|
||||
pub state_cache_size: usize,
|
||||
/// Size in percent of cache size dedicated to child tries
|
||||
pub state_cache_child_ratio: Option<usize>,
|
||||
/// Pruning settings.
|
||||
pub pruning: PruningMode,
|
||||
/// Chain configuration.
|
||||
pub chain_spec: ChainSpec<G, E>,
|
||||
/// Custom configuration.
|
||||
pub custom: C,
|
||||
/// Node name.
|
||||
pub name: String,
|
||||
/// Wasm execution method.
|
||||
pub wasm_method: WasmExecutionMethod,
|
||||
/// Execution strategies.
|
||||
pub execution_strategies: ExecutionStrategies,
|
||||
/// RPC over HTTP binding address. `None` if disabled.
|
||||
pub rpc_http: Option<SocketAddr>,
|
||||
/// RPC over Websockets binding address. `None` if disabled.
|
||||
pub rpc_ws: Option<SocketAddr>,
|
||||
/// Maximum number of connections for WebSockets RPC server. `None` if default.
|
||||
pub rpc_ws_max_connections: Option<usize>,
|
||||
/// CORS settings for HTTP & WS servers. `None` if all origins are allowed.
|
||||
pub rpc_cors: Option<Vec<String>>,
|
||||
/// Telemetry service URL. `None` if disabled.
|
||||
pub telemetry_endpoints: Option<TelemetryEndpoints>,
|
||||
/// External WASM transport for the telemetry. If `Some`, when connection to a telemetry
|
||||
/// endpoint, this transport will be tried in priority before all others.
|
||||
pub telemetry_external_transport: Option<ExtTransport>,
|
||||
/// 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,
|
||||
/// Sentry mode is enabled, the node's role is AUTHORITY but it should not
|
||||
/// actively participate in consensus (i.e. no keystores should be passed to
|
||||
/// consensus modules).
|
||||
pub sentry_mode: bool,
|
||||
/// Enable authoring even when offline.
|
||||
pub force_authoring: bool,
|
||||
/// Disable GRANDPA when running in validator mode
|
||||
pub disable_grandpa: bool,
|
||||
/// Node keystore's password
|
||||
pub keystore_password: Option<Protected<String>>,
|
||||
/// Development key seed.
|
||||
///
|
||||
/// When running in development mode, the seed will be used to generate authority keys by the keystore.
|
||||
///
|
||||
/// Should only be set when `node` is running development mode.
|
||||
pub dev_key_seed: Option<String>,
|
||||
}
|
||||
|
||||
/// Configuration of the database of the client.
|
||||
#[derive(Clone)]
|
||||
pub enum DatabaseConfig {
|
||||
/// Database file at a specific path. Recommended for most uses.
|
||||
Path {
|
||||
/// Path to the database.
|
||||
path: PathBuf,
|
||||
/// Cache Size for internal database in MiB
|
||||
cache_size: Option<u32>,
|
||||
},
|
||||
|
||||
/// A custom implementation of an already-open database.
|
||||
Custom(Arc<dyn KeyValueDB>),
|
||||
}
|
||||
|
||||
impl<C, G, E> Configuration<C, G, E> where
|
||||
C: Default,
|
||||
G: RuntimeGenesis,
|
||||
E: Extension,
|
||||
{
|
||||
/// Create a default config for given chain spec and path to configuration dir
|
||||
pub fn default_with_spec_and_base_path(chain_spec: ChainSpec<G, E>, config_dir: Option<PathBuf>) -> Self {
|
||||
let mut configuration = Configuration {
|
||||
impl_name: "parity-substrate",
|
||||
impl_version: "0.0.0",
|
||||
impl_commit: "",
|
||||
chain_spec,
|
||||
config_dir: config_dir.clone(),
|
||||
name: Default::default(),
|
||||
roles: Roles::FULL,
|
||||
transaction_pool: Default::default(),
|
||||
network: Default::default(),
|
||||
keystore_path: config_dir.map(|c| c.join("keystore")),
|
||||
database: DatabaseConfig::Path {
|
||||
path: Default::default(),
|
||||
cache_size: Default::default(),
|
||||
},
|
||||
state_cache_size: Default::default(),
|
||||
state_cache_child_ratio: Default::default(),
|
||||
custom: Default::default(),
|
||||
pruning: PruningMode::default(),
|
||||
wasm_method: WasmExecutionMethod::Interpreted,
|
||||
execution_strategies: Default::default(),
|
||||
rpc_http: None,
|
||||
rpc_ws: None,
|
||||
rpc_ws_max_connections: None,
|
||||
rpc_cors: Some(vec![]),
|
||||
telemetry_endpoints: None,
|
||||
telemetry_external_transport: None,
|
||||
default_heap_pages: None,
|
||||
offchain_worker: Default::default(),
|
||||
sentry_mode: false,
|
||||
force_authoring: false,
|
||||
disable_grandpa: false,
|
||||
keystore_password: None,
|
||||
dev_key_seed: None,
|
||||
};
|
||||
configuration.network.boot_nodes = configuration.chain_spec.boot_nodes().to_vec();
|
||||
|
||||
configuration.telemetry_endpoints = configuration.chain_spec.telemetry_endpoints().clone();
|
||||
|
||||
configuration
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
impl<C, G, E> Configuration<C, G, E> {
|
||||
/// Returns full version string of this configuration.
|
||||
pub fn full_version(&self) -> String {
|
||||
full_version_from_strs(self.impl_version, self.impl_commit)
|
||||
}
|
||||
|
||||
/// Implementation id and version.
|
||||
pub fn client_id(&self) -> String {
|
||||
format!("{}/v{}", self.impl_name, self.full_version())
|
||||
}
|
||||
|
||||
/// Generate a PathBuf to sub in the chain configuration directory
|
||||
/// if given
|
||||
pub fn in_chain_config_dir(&self, sub: &str) -> Option<PathBuf> {
|
||||
self.config_dir.clone().map(|mut path| {
|
||||
path.push("chains");
|
||||
path.push(self.chain_spec.id());
|
||||
path.push(sub);
|
||||
path
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns platform info
|
||||
pub fn platform() -> String {
|
||||
let env = Target::env();
|
||||
let env_dash = if env.is_empty() { "" } else { "-" };
|
||||
format!("{}-{}{}{}", Target::arch(), Target::os(), env_dash, env)
|
||||
}
|
||||
|
||||
/// Returns full version string, using supplied version and commit.
|
||||
pub fn full_version_from_strs(impl_version: &str, impl_commit: &str) -> String {
|
||||
let commit_dash = if impl_commit.is_empty() { "" } else { "-" };
|
||||
format!("{}{}{}-{}", impl_version, commit_dash, impl_commit, platform())
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
// Copyright 2017-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/>.
|
||||
|
||||
//! Errors that can occur during the service operation.
|
||||
|
||||
use client;
|
||||
use network;
|
||||
use keystore;
|
||||
use consensus_common;
|
||||
|
||||
/// Service Result typedef.
|
||||
pub type Result<T> = std::result::Result<T, Error>;
|
||||
|
||||
/// Service errors.
|
||||
#[derive(Debug, derive_more::Display, derive_more::From)]
|
||||
pub enum Error {
|
||||
/// Client error.
|
||||
Client(client::error::Error),
|
||||
/// IO error.
|
||||
Io(std::io::Error),
|
||||
/// Consensus error.
|
||||
Consensus(consensus_common::Error),
|
||||
/// Network error.
|
||||
Network(network::error::Error),
|
||||
/// Keystore error.
|
||||
Keystore(keystore::Error),
|
||||
/// Best chain selection strategy is missing.
|
||||
#[display(fmt="Best chain selection strategy (SelectChain) is not provided.")]
|
||||
SelectChainRequired,
|
||||
/// Other error.
|
||||
Other(String),
|
||||
}
|
||||
|
||||
impl<'a> From<&'a str> for Error {
|
||||
fn from(s: &'a str) -> Self {
|
||||
Error::Other(s.into())
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for Error {
|
||||
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
|
||||
match self {
|
||||
Error::Client(ref err) => Some(err),
|
||||
Error::Io(ref err) => Some(err),
|
||||
Error::Consensus(ref err) => Some(err),
|
||||
Error::Network(ref err) => Some(err),
|
||||
Error::Keystore(ref err) => Some(err),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,703 @@
|
||||
// Copyright 2017-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 service. Starts a thread that spins up the network, client, and extrinsic pool.
|
||||
//! Manages communication between them.
|
||||
|
||||
#![warn(missing_docs)]
|
||||
|
||||
pub mod config;
|
||||
#[macro_use]
|
||||
pub mod chain_ops;
|
||||
pub mod error;
|
||||
|
||||
mod builder;
|
||||
mod status_sinks;
|
||||
|
||||
use std::io;
|
||||
use std::marker::PhantomData;
|
||||
use std::net::SocketAddr;
|
||||
use std::collections::HashMap;
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use std::time::{Duration, Instant};
|
||||
use futures::sync::mpsc;
|
||||
use parking_lot::Mutex;
|
||||
|
||||
use client::Client;
|
||||
use exit_future::Signal;
|
||||
use futures::prelude::*;
|
||||
use futures03::{
|
||||
future::{ready, FutureExt as _, TryFutureExt as _},
|
||||
stream::{StreamExt as _, TryStreamExt as _},
|
||||
};
|
||||
use network::{
|
||||
NetworkService, NetworkState, specialization::NetworkSpecialization,
|
||||
Event, DhtEvent, PeerId, ReportHandle,
|
||||
};
|
||||
use log::{log, warn, debug, error, Level};
|
||||
use codec::{Encode, Decode};
|
||||
use primitives::{Blake2Hasher, H256};
|
||||
use sr_primitives::generic::BlockId;
|
||||
use sr_primitives::traits::{NumberFor, Block as BlockT};
|
||||
|
||||
pub use self::error::Error;
|
||||
pub use self::builder::{ServiceBuilder, ServiceBuilderExport, ServiceBuilderImport, ServiceBuilderRevert};
|
||||
pub use config::{Configuration, Roles, PruningMode};
|
||||
pub use chain_spec::{ChainSpec, Properties, RuntimeGenesis, Extension as ChainSpecExtension};
|
||||
pub use transaction_pool::txpool::{
|
||||
self, Pool as TransactionPool, Options as TransactionPoolOptions, ChainApi, IntoPoolError
|
||||
};
|
||||
pub use client::FinalityNotifications;
|
||||
pub use rpc::Metadata as RpcMetadata;
|
||||
#[doc(hidden)]
|
||||
pub use std::{ops::Deref, result::Result, sync::Arc};
|
||||
#[doc(hidden)]
|
||||
pub use network::{FinalityProofProvider, OnDemand, config::BoxFinalityProofRequestBuilder};
|
||||
#[doc(hidden)]
|
||||
pub use futures::future::Executor;
|
||||
|
||||
const DEFAULT_PROTOCOL_ID: &str = "sup";
|
||||
|
||||
/// Substrate service.
|
||||
pub struct Service<TBl, TCl, TSc, TNetStatus, TNet, TTxPool, TOc> {
|
||||
client: Arc<TCl>,
|
||||
select_chain: Option<TSc>,
|
||||
network: Arc<TNet>,
|
||||
/// Sinks to propagate network status updates.
|
||||
/// For each element, every time the `Interval` fires we push an element on the sender.
|
||||
network_status_sinks: Arc<Mutex<status_sinks::StatusSinks<(TNetStatus, NetworkState)>>>,
|
||||
transaction_pool: Arc<TTxPool>,
|
||||
/// A future that resolves when the service has exited, this is useful to
|
||||
/// make sure any internally spawned futures stop when the service does.
|
||||
exit: exit_future::Exit,
|
||||
/// A signal that makes the exit future above resolve, fired on service drop.
|
||||
signal: Option<Signal>,
|
||||
/// Set to `true` when a spawned essential task has failed. The next time
|
||||
/// the service future is polled it should complete with an error.
|
||||
essential_failed: Arc<AtomicBool>,
|
||||
/// Sender for futures that must be spawned as background tasks.
|
||||
to_spawn_tx: mpsc::UnboundedSender<Box<dyn Future<Item = (), Error = ()> + Send>>,
|
||||
/// Receiver for futures that must be spawned as background tasks.
|
||||
to_spawn_rx: mpsc::UnboundedReceiver<Box<dyn Future<Item = (), Error = ()> + Send>>,
|
||||
/// List of futures to poll from `poll`.
|
||||
/// If spawning a background task is not possible, we instead push the task into this `Vec`.
|
||||
/// The elements must then be polled manually.
|
||||
to_poll: Vec<Box<dyn Future<Item = (), Error = ()> + Send>>,
|
||||
rpc_handlers: rpc_servers::RpcHandler<rpc::Metadata>,
|
||||
_rpc: Box<dyn std::any::Any + Send + Sync>,
|
||||
_telemetry: Option<tel::Telemetry>,
|
||||
_telemetry_on_connect_sinks: Arc<Mutex<Vec<mpsc::UnboundedSender<()>>>>,
|
||||
_offchain_workers: Option<Arc<TOc>>,
|
||||
keystore: keystore::KeyStorePtr,
|
||||
marker: PhantomData<TBl>,
|
||||
}
|
||||
|
||||
/// Alias for a an implementation of `futures::future::Executor`.
|
||||
pub type TaskExecutor = Arc<dyn Executor<Box<dyn Future<Item = (), Error = ()> + Send>> + Send + Sync>;
|
||||
|
||||
/// An handle for spawning tasks in the service.
|
||||
#[derive(Clone)]
|
||||
pub struct SpawnTaskHandle {
|
||||
sender: mpsc::UnboundedSender<Box<dyn Future<Item = (), Error = ()> + Send>>,
|
||||
on_exit: exit_future::Exit,
|
||||
}
|
||||
|
||||
impl Executor<Box<dyn Future<Item = (), Error = ()> + Send>> for SpawnTaskHandle {
|
||||
fn execute(
|
||||
&self,
|
||||
future: Box<dyn Future<Item = (), Error = ()> + Send>,
|
||||
) -> Result<(), futures::future::ExecuteError<Box<dyn Future<Item = (), Error = ()> + Send>>> {
|
||||
let future = Box::new(future.select(self.on_exit.clone()).then(|_| Ok(())));
|
||||
if let Err(err) = self.sender.unbounded_send(future) {
|
||||
let kind = futures::future::ExecuteErrorKind::Shutdown;
|
||||
Err(futures::future::ExecuteError::new(kind, err.into_inner()))
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Abstraction over a Substrate service.
|
||||
pub trait AbstractService: 'static + Future<Item = (), Error = Error> +
|
||||
Executor<Box<dyn Future<Item = (), Error = ()> + Send>> + Send {
|
||||
/// Type of block of this chain.
|
||||
type Block: BlockT<Hash = H256>;
|
||||
/// Backend storage for the client.
|
||||
type Backend: 'static + client_api::backend::Backend<Self::Block, Blake2Hasher>;
|
||||
/// How to execute calls towards the runtime.
|
||||
type CallExecutor: 'static + client::CallExecutor<Self::Block, Blake2Hasher> + Send + Sync + Clone;
|
||||
/// API that the runtime provides.
|
||||
type RuntimeApi: Send + Sync;
|
||||
/// Chain selection algorithm.
|
||||
type SelectChain: consensus_common::SelectChain<Self::Block>;
|
||||
/// API of the transaction pool.
|
||||
type TransactionPoolApi: ChainApi<Block = Self::Block>;
|
||||
/// Network specialization.
|
||||
type NetworkSpecialization: NetworkSpecialization<Self::Block>;
|
||||
|
||||
/// Get event stream for telemetry connection established events.
|
||||
fn telemetry_on_connect_stream(&self) -> mpsc::UnboundedReceiver<()>;
|
||||
|
||||
/// return a shared instance of Telemetry (if enabled)
|
||||
fn telemetry(&self) -> Option<tel::Telemetry>;
|
||||
|
||||
/// Spawns a task in the background that runs the future passed as parameter.
|
||||
fn spawn_task(&self, task: impl Future<Item = (), Error = ()> + Send + 'static);
|
||||
|
||||
/// Spawns a task in the background that runs the future passed as
|
||||
/// parameter. The given task is considered essential, i.e. if it errors we
|
||||
/// trigger a service exit.
|
||||
fn spawn_essential_task(&self, task: impl Future<Item = (), Error = ()> + Send + 'static);
|
||||
|
||||
/// Returns a handle for spawning tasks.
|
||||
fn spawn_task_handle(&self) -> SpawnTaskHandle;
|
||||
|
||||
/// Returns the keystore that stores keys.
|
||||
fn keystore(&self) -> keystore::KeyStorePtr;
|
||||
|
||||
/// Starts an RPC query.
|
||||
///
|
||||
/// The query is passed as a string and must be a JSON text similar to what an HTTP client
|
||||
/// would for example send.
|
||||
///
|
||||
/// Returns a `Future` that contains the optional response.
|
||||
///
|
||||
/// If the request subscribes you to events, the `Sender` in the `RpcSession` object is used to
|
||||
/// send back spontaneous events.
|
||||
fn rpc_query(&self, mem: &RpcSession, request: &str) -> Box<dyn Future<Item = Option<String>, Error = ()> + Send>;
|
||||
|
||||
/// Get shared client instance.
|
||||
fn client(&self) -> Arc<client::Client<Self::Backend, Self::CallExecutor, Self::Block, Self::RuntimeApi>>;
|
||||
|
||||
/// Get clone of select chain.
|
||||
fn select_chain(&self) -> Option<Self::SelectChain>;
|
||||
|
||||
/// Get shared network instance.
|
||||
fn network(&self) -> Arc<NetworkService<Self::Block, Self::NetworkSpecialization, H256>>;
|
||||
|
||||
/// Returns a receiver that periodically receives a status of the network.
|
||||
fn network_status(&self, interval: Duration) -> mpsc::UnboundedReceiver<(NetworkStatus<Self::Block>, NetworkState)>;
|
||||
|
||||
/// Get shared transaction pool instance.
|
||||
fn transaction_pool(&self) -> Arc<TransactionPool<Self::TransactionPoolApi>>;
|
||||
|
||||
/// Get a handle to a future that will resolve on exit.
|
||||
fn on_exit(&self) -> ::exit_future::Exit;
|
||||
}
|
||||
|
||||
impl<TBl, TBackend, TExec, TRtApi, TSc, TNetSpec, TExPoolApi, TOc> AbstractService for
|
||||
Service<TBl, Client<TBackend, TExec, TBl, TRtApi>, TSc, NetworkStatus<TBl>,
|
||||
NetworkService<TBl, TNetSpec, H256>, TransactionPool<TExPoolApi>, TOc>
|
||||
where
|
||||
TBl: BlockT<Hash = H256>,
|
||||
TBackend: 'static + client_api::backend::Backend<TBl, Blake2Hasher>,
|
||||
TExec: 'static + client::CallExecutor<TBl, Blake2Hasher> + Send + Sync + Clone,
|
||||
TRtApi: 'static + Send + Sync,
|
||||
TSc: consensus_common::SelectChain<TBl> + 'static + Clone + Send,
|
||||
TExPoolApi: 'static + ChainApi<Block = TBl>,
|
||||
TOc: 'static + Send + Sync,
|
||||
TNetSpec: NetworkSpecialization<TBl>,
|
||||
{
|
||||
type Block = TBl;
|
||||
type Backend = TBackend;
|
||||
type CallExecutor = TExec;
|
||||
type RuntimeApi = TRtApi;
|
||||
type SelectChain = TSc;
|
||||
type TransactionPoolApi = TExPoolApi;
|
||||
type NetworkSpecialization = TNetSpec;
|
||||
|
||||
fn telemetry_on_connect_stream(&self) -> mpsc::UnboundedReceiver<()> {
|
||||
let (sink, stream) = mpsc::unbounded();
|
||||
self._telemetry_on_connect_sinks.lock().push(sink);
|
||||
stream
|
||||
}
|
||||
|
||||
fn telemetry(&self) -> Option<tel::Telemetry> {
|
||||
self._telemetry.as_ref().map(|t| t.clone())
|
||||
}
|
||||
|
||||
fn keystore(&self) -> keystore::KeyStorePtr {
|
||||
self.keystore.clone()
|
||||
}
|
||||
|
||||
fn spawn_task(&self, task: impl Future<Item = (), Error = ()> + Send + 'static) {
|
||||
let task = task.select(self.on_exit()).then(|_| Ok(()));
|
||||
let _ = self.to_spawn_tx.unbounded_send(Box::new(task));
|
||||
}
|
||||
|
||||
fn spawn_essential_task(&self, task: impl Future<Item = (), Error = ()> + Send + 'static) {
|
||||
let essential_failed = self.essential_failed.clone();
|
||||
let essential_task = task.map_err(move |_| {
|
||||
error!("Essential task failed. Shutting down service.");
|
||||
essential_failed.store(true, Ordering::Relaxed);
|
||||
});
|
||||
let task = essential_task.select(self.on_exit()).then(|_| Ok(()));
|
||||
|
||||
let _ = self.to_spawn_tx.unbounded_send(Box::new(task));
|
||||
}
|
||||
|
||||
fn spawn_task_handle(&self) -> SpawnTaskHandle {
|
||||
SpawnTaskHandle {
|
||||
sender: self.to_spawn_tx.clone(),
|
||||
on_exit: self.on_exit(),
|
||||
}
|
||||
}
|
||||
|
||||
fn rpc_query(&self, mem: &RpcSession, request: &str) -> Box<dyn Future<Item = Option<String>, Error = ()> + Send> {
|
||||
Box::new(self.rpc_handlers.handle_request(request, mem.metadata.clone()))
|
||||
}
|
||||
|
||||
fn client(&self) -> Arc<client::Client<Self::Backend, Self::CallExecutor, Self::Block, Self::RuntimeApi>> {
|
||||
self.client.clone()
|
||||
}
|
||||
|
||||
fn select_chain(&self) -> Option<Self::SelectChain> {
|
||||
self.select_chain.clone()
|
||||
}
|
||||
|
||||
fn network(&self) -> Arc<NetworkService<Self::Block, Self::NetworkSpecialization, H256>> {
|
||||
self.network.clone()
|
||||
}
|
||||
|
||||
fn network_status(&self, interval: Duration) -> mpsc::UnboundedReceiver<(NetworkStatus<Self::Block>, NetworkState)> {
|
||||
let (sink, stream) = mpsc::unbounded();
|
||||
self.network_status_sinks.lock().push(interval, sink);
|
||||
stream
|
||||
}
|
||||
|
||||
fn transaction_pool(&self) -> Arc<TransactionPool<Self::TransactionPoolApi>> {
|
||||
self.transaction_pool.clone()
|
||||
}
|
||||
|
||||
fn on_exit(&self) -> exit_future::Exit {
|
||||
self.exit.clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl<TBl, TCl, TSc, TNetStatus, TNet, TTxPool, TOc> Future for
|
||||
Service<TBl, TCl, TSc, TNetStatus, TNet, TTxPool, TOc>
|
||||
{
|
||||
type Item = ();
|
||||
type Error = Error;
|
||||
|
||||
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
|
||||
if self.essential_failed.load(Ordering::Relaxed) {
|
||||
return Err(Error::Other("Essential task failed.".into()));
|
||||
}
|
||||
|
||||
while let Ok(Async::Ready(Some(task_to_spawn))) = self.to_spawn_rx.poll() {
|
||||
let executor = tokio_executor::DefaultExecutor::current();
|
||||
if let Err(err) = executor.execute(task_to_spawn) {
|
||||
debug!(
|
||||
target: "service",
|
||||
"Failed to spawn background task: {:?}; falling back to manual polling",
|
||||
err
|
||||
);
|
||||
self.to_poll.push(err.into_future());
|
||||
}
|
||||
}
|
||||
|
||||
// Polling all the `to_poll` futures.
|
||||
while let Some(pos) = self.to_poll.iter_mut().position(|t| t.poll().map(|t| t.is_ready()).unwrap_or(true)) {
|
||||
let _ = self.to_poll.remove(pos);
|
||||
}
|
||||
|
||||
// The service future never ends.
|
||||
Ok(Async::NotReady)
|
||||
}
|
||||
}
|
||||
|
||||
impl<TBl, TCl, TSc, TNetStatus, TNet, TTxPool, TOc> Executor<Box<dyn Future<Item = (), Error = ()> + Send>> for
|
||||
Service<TBl, TCl, TSc, TNetStatus, TNet, TTxPool, TOc>
|
||||
{
|
||||
fn execute(
|
||||
&self,
|
||||
future: Box<dyn Future<Item = (), Error = ()> + Send>
|
||||
) -> Result<(), futures::future::ExecuteError<Box<dyn Future<Item = (), Error = ()> + Send>>> {
|
||||
if let Err(err) = self.to_spawn_tx.unbounded_send(future) {
|
||||
let kind = futures::future::ExecuteErrorKind::Shutdown;
|
||||
Err(futures::future::ExecuteError::new(kind, err.into_inner()))
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Builds a never-ending future that continuously polls the network.
|
||||
///
|
||||
/// The `status_sink` contain a list of senders to send a periodic network status to.
|
||||
fn build_network_future<
|
||||
B: BlockT,
|
||||
C: client::BlockchainEvents<B>,
|
||||
S: network::specialization::NetworkSpecialization<B>,
|
||||
H: network::ExHashT
|
||||
> (
|
||||
roles: Roles,
|
||||
mut network: network::NetworkWorker<B, S, H>,
|
||||
client: Arc<C>,
|
||||
status_sinks: Arc<Mutex<status_sinks::StatusSinks<(NetworkStatus<B>, NetworkState)>>>,
|
||||
rpc_rx: futures03::channel::mpsc::UnboundedReceiver<rpc::system::Request<B>>,
|
||||
should_have_peers: bool,
|
||||
dht_event_tx: Option<mpsc::Sender<DhtEvent>>,
|
||||
) -> impl Future<Item = (), Error = ()> {
|
||||
// Compatibility shim while we're transitioning to stable Futures.
|
||||
// See https://github.com/paritytech/substrate/issues/3099
|
||||
let mut rpc_rx = futures03::compat::Compat::new(rpc_rx.map(|v| Ok::<_, ()>(v)));
|
||||
|
||||
let mut imported_blocks_stream = client.import_notification_stream().fuse()
|
||||
.map(|v| Ok::<_, ()>(v)).compat();
|
||||
let mut finality_notification_stream = client.finality_notification_stream().fuse()
|
||||
.map(|v| Ok::<_, ()>(v)).compat();
|
||||
|
||||
futures::future::poll_fn(move || {
|
||||
let before_polling = Instant::now();
|
||||
|
||||
// We poll `imported_blocks_stream`.
|
||||
while let Ok(Async::Ready(Some(notification))) = imported_blocks_stream.poll() {
|
||||
network.on_block_imported(notification.hash, notification.header, Vec::new(), notification.is_new_best);
|
||||
}
|
||||
|
||||
// We poll `finality_notification_stream`, but we only take the last event.
|
||||
let mut last = None;
|
||||
while let Ok(Async::Ready(Some(item))) = finality_notification_stream.poll() {
|
||||
last = Some(item);
|
||||
}
|
||||
if let Some(notification) = last {
|
||||
network.on_block_finalized(notification.hash, notification.header);
|
||||
}
|
||||
|
||||
// Poll the RPC requests and answer them.
|
||||
while let Ok(Async::Ready(Some(request))) = rpc_rx.poll() {
|
||||
match request {
|
||||
rpc::system::Request::Health(sender) => {
|
||||
let _ = sender.send(rpc::system::Health {
|
||||
peers: network.peers_debug_info().len(),
|
||||
is_syncing: network.service().is_major_syncing(),
|
||||
should_have_peers,
|
||||
});
|
||||
},
|
||||
rpc::system::Request::Peers(sender) => {
|
||||
let _ = sender.send(network.peers_debug_info().into_iter().map(|(peer_id, p)|
|
||||
rpc::system::PeerInfo {
|
||||
peer_id: peer_id.to_base58(),
|
||||
roles: format!("{:?}", p.roles),
|
||||
protocol_version: p.protocol_version,
|
||||
best_hash: p.best_hash,
|
||||
best_number: p.best_number,
|
||||
}
|
||||
).collect());
|
||||
}
|
||||
rpc::system::Request::NetworkState(sender) => {
|
||||
if let Some(network_state) = serde_json::to_value(&network.network_state()).ok() {
|
||||
let _ = sender.send(network_state);
|
||||
}
|
||||
}
|
||||
rpc::system::Request::NodeRoles(sender) => {
|
||||
use rpc::system::NodeRole;
|
||||
|
||||
let node_roles = (0 .. 8)
|
||||
.filter(|&bit_number| (roles.bits() >> bit_number) & 1 == 1)
|
||||
.map(|bit_number| match Roles::from_bits(1 << bit_number) {
|
||||
Some(Roles::AUTHORITY) => NodeRole::Authority,
|
||||
Some(Roles::LIGHT) => NodeRole::LightClient,
|
||||
Some(Roles::FULL) => NodeRole::Full,
|
||||
_ => NodeRole::UnknownRole(bit_number),
|
||||
})
|
||||
.collect();
|
||||
|
||||
let _ = sender.send(node_roles);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// Interval report for the external API.
|
||||
status_sinks.lock().poll(|| {
|
||||
let status = NetworkStatus {
|
||||
sync_state: network.sync_state(),
|
||||
best_seen_block: network.best_seen_block(),
|
||||
num_sync_peers: network.num_sync_peers(),
|
||||
num_connected_peers: network.num_connected_peers(),
|
||||
num_active_peers: network.num_active_peers(),
|
||||
average_download_per_sec: network.average_download_per_sec(),
|
||||
average_upload_per_sec: network.average_upload_per_sec(),
|
||||
};
|
||||
let state = network.network_state();
|
||||
(status, state)
|
||||
});
|
||||
|
||||
// Main network polling.
|
||||
while let Ok(Async::Ready(Some(Event::Dht(event)))) = network.poll().map_err(|err| {
|
||||
warn!(target: "service", "Error in network: {:?}", err);
|
||||
}) {
|
||||
// Given that client/authority-discovery is the only upper stack consumer of Dht events at the moment, all Dht
|
||||
// events are being passed on to the authority-discovery module. In the future there might be multiple
|
||||
// consumers of these events. In that case this would need to be refactored to properly dispatch the events,
|
||||
// e.g. via a subscriber model.
|
||||
if let Some(Err(e)) = dht_event_tx.as_ref().map(|c| c.clone().try_send(event)) {
|
||||
if e.is_full() {
|
||||
warn!(target: "service", "Dht event channel to authority discovery is full, dropping event.");
|
||||
} else if e.is_disconnected() {
|
||||
warn!(target: "service", "Dht event channel to authority discovery is disconnected, dropping event.");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Now some diagnostic for performances.
|
||||
let polling_dur = before_polling.elapsed();
|
||||
log!(
|
||||
target: "service",
|
||||
if polling_dur >= Duration::from_secs(1) { Level::Warn } else { Level::Trace },
|
||||
"Polling the network future took {:?}",
|
||||
polling_dur
|
||||
);
|
||||
|
||||
Ok(Async::NotReady)
|
||||
})
|
||||
}
|
||||
|
||||
/// Overview status of the network.
|
||||
#[derive(Clone)]
|
||||
pub struct NetworkStatus<B: BlockT> {
|
||||
/// Current global sync state.
|
||||
pub sync_state: network::SyncState,
|
||||
/// Target sync block number.
|
||||
pub best_seen_block: Option<NumberFor<B>>,
|
||||
/// Number of peers participating in syncing.
|
||||
pub num_sync_peers: u32,
|
||||
/// Total number of connected peers
|
||||
pub num_connected_peers: usize,
|
||||
/// Total number of active peers.
|
||||
pub num_active_peers: usize,
|
||||
/// Downloaded bytes per second averaged over the past few seconds.
|
||||
pub average_download_per_sec: u64,
|
||||
/// Uploaded bytes per second averaged over the past few seconds.
|
||||
pub average_upload_per_sec: u64,
|
||||
}
|
||||
|
||||
impl<TBl, TCl, TSc, TNetStatus, TNet, TTxPool, TOc> Drop for
|
||||
Service<TBl, TCl, TSc, TNetStatus, TNet, TTxPool, TOc>
|
||||
{
|
||||
fn drop(&mut self) {
|
||||
debug!(target: "service", "Substrate service shutdown");
|
||||
if let Some(signal) = self.signal.take() {
|
||||
signal.fire();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Starts RPC servers that run in their own thread, and returns an opaque object that keeps them alive.
|
||||
#[cfg(not(target_os = "unknown"))]
|
||||
fn start_rpc_servers<C, G, E, H: FnMut() -> rpc_servers::RpcHandler<rpc::Metadata>>(
|
||||
config: &Configuration<C, G, E>,
|
||||
mut gen_handler: H
|
||||
) -> Result<Box<dyn std::any::Any + Send + Sync>, error::Error> {
|
||||
fn maybe_start_server<T, F>(address: Option<SocketAddr>, mut start: F) -> Result<Option<T>, io::Error>
|
||||
where F: FnMut(&SocketAddr) -> Result<T, io::Error>,
|
||||
{
|
||||
Ok(match address {
|
||||
Some(mut address) => Some(start(&address)
|
||||
.or_else(|e| match e.kind() {
|
||||
io::ErrorKind::AddrInUse |
|
||||
io::ErrorKind::PermissionDenied => {
|
||||
warn!("Unable to bind server to {}. Trying random port.", address);
|
||||
address.set_port(0);
|
||||
start(&address)
|
||||
},
|
||||
_ => Err(e),
|
||||
})?),
|
||||
None => None,
|
||||
})
|
||||
}
|
||||
|
||||
Ok(Box::new((
|
||||
maybe_start_server(
|
||||
config.rpc_http,
|
||||
|address| rpc_servers::start_http(address, config.rpc_cors.as_ref(), gen_handler()),
|
||||
)?,
|
||||
maybe_start_server(
|
||||
config.rpc_ws,
|
||||
|address| rpc_servers::start_ws(
|
||||
address,
|
||||
config.rpc_ws_max_connections,
|
||||
config.rpc_cors.as_ref(),
|
||||
gen_handler(),
|
||||
),
|
||||
)?.map(Mutex::new),
|
||||
)))
|
||||
}
|
||||
|
||||
/// Starts RPC servers that run in their own thread, and returns an opaque object that keeps them alive.
|
||||
#[cfg(target_os = "unknown")]
|
||||
fn start_rpc_servers<C, G, E, H: FnMut() -> rpc_servers::RpcHandler<rpc::Metadata>>(
|
||||
_: &Configuration<C, G, E>,
|
||||
_: H
|
||||
) -> Result<Box<dyn std::any::Any + Send + Sync>, error::Error> {
|
||||
Ok(Box::new(()))
|
||||
}
|
||||
|
||||
/// An RPC session. Used to perform in-memory RPC queries (ie. RPC queries that don't go through
|
||||
/// the HTTP or WebSockets server).
|
||||
#[derive(Clone)]
|
||||
pub struct RpcSession {
|
||||
metadata: rpc::Metadata,
|
||||
}
|
||||
|
||||
impl RpcSession {
|
||||
/// Creates an RPC session.
|
||||
///
|
||||
/// The `sender` is stored inside the `RpcSession` and is used to communicate spontaneous JSON
|
||||
/// messages.
|
||||
///
|
||||
/// The `RpcSession` must be kept alive in order to receive messages on the sender.
|
||||
pub fn new(sender: mpsc::Sender<String>) -> RpcSession {
|
||||
RpcSession {
|
||||
metadata: sender.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Transaction pool adapter.
|
||||
pub struct TransactionPoolAdapter<C, P> {
|
||||
imports_external_transactions: bool,
|
||||
pool: Arc<P>,
|
||||
client: Arc<C>,
|
||||
executor: TaskExecutor,
|
||||
}
|
||||
|
||||
/// Get transactions for propagation.
|
||||
///
|
||||
/// Function extracted to simplify the test and prevent creating `ServiceFactory`.
|
||||
fn transactions_to_propagate<PoolApi, B, H, E>(pool: &TransactionPool<PoolApi>)
|
||||
-> Vec<(H, B::Extrinsic)>
|
||||
where
|
||||
PoolApi: ChainApi<Block=B, Hash=H, Error=E>,
|
||||
B: BlockT,
|
||||
H: std::hash::Hash + Eq + sr_primitives::traits::Member + sr_primitives::traits::MaybeSerialize,
|
||||
E: txpool::error::IntoPoolError + From<txpool::error::Error>,
|
||||
{
|
||||
pool.ready()
|
||||
.filter(|t| t.is_propagateable())
|
||||
.map(|t| {
|
||||
let hash = t.hash.clone();
|
||||
let ex: B::Extrinsic = t.data.clone();
|
||||
(hash, ex)
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
impl<B, H, C, PoolApi, E> network::TransactionPool<H, B> for
|
||||
TransactionPoolAdapter<C, TransactionPool<PoolApi>>
|
||||
where
|
||||
C: network::ClientHandle<B> + Send + Sync,
|
||||
PoolApi: 'static + ChainApi<Block=B, Hash=H, Error=E>,
|
||||
B: BlockT,
|
||||
H: std::hash::Hash + Eq + sr_primitives::traits::Member + sr_primitives::traits::MaybeSerialize,
|
||||
E: txpool::error::IntoPoolError + From<txpool::error::Error>,
|
||||
{
|
||||
fn transactions(&self) -> Vec<(H, <B as BlockT>::Extrinsic)> {
|
||||
transactions_to_propagate(&self.pool)
|
||||
}
|
||||
|
||||
fn hash_of(&self, transaction: &B::Extrinsic) -> H {
|
||||
self.pool.hash_of(transaction)
|
||||
}
|
||||
|
||||
fn import(
|
||||
&self,
|
||||
report_handle: ReportHandle,
|
||||
who: PeerId,
|
||||
reputation_change_good: i32,
|
||||
reputation_change_bad: i32,
|
||||
transaction: B::Extrinsic
|
||||
) {
|
||||
if !self.imports_external_transactions {
|
||||
debug!("Transaction rejected");
|
||||
return;
|
||||
}
|
||||
|
||||
let encoded = transaction.encode();
|
||||
match Decode::decode(&mut &encoded[..]) {
|
||||
Ok(uxt) => {
|
||||
let best_block_id = BlockId::hash(self.client.info().chain.best_hash);
|
||||
let import_future = self.pool.submit_one(&best_block_id, uxt);
|
||||
let import_future = import_future
|
||||
.then(move |import_result| {
|
||||
match import_result {
|
||||
Ok(_) => report_handle.report_peer(who, reputation_change_good),
|
||||
Err(e) => match e.into_pool_error() {
|
||||
Ok(txpool::error::Error::AlreadyImported(_)) => (),
|
||||
Ok(e) => {
|
||||
report_handle.report_peer(who, reputation_change_bad);
|
||||
debug!("Error adding transaction to the pool: {:?}", e)
|
||||
}
|
||||
Err(e) => debug!("Error converting pool error: {:?}", e),
|
||||
}
|
||||
}
|
||||
ready(Ok(()))
|
||||
})
|
||||
.compat();
|
||||
|
||||
if let Err(e) = self.executor.execute(Box::new(import_future)) {
|
||||
warn!("Error scheduling extrinsic import: {:?}", e);
|
||||
}
|
||||
}
|
||||
Err(e) => debug!("Error decoding transaction {}", e),
|
||||
}
|
||||
}
|
||||
|
||||
fn on_broadcasted(&self, propagations: HashMap<H, Vec<String>>) {
|
||||
self.pool.on_broadcasted(propagations)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use futures03::executor::block_on;
|
||||
use consensus_common::SelectChain;
|
||||
use sr_primitives::traits::BlindCheckable;
|
||||
use substrate_test_runtime_client::{prelude::*, runtime::{Extrinsic, Transfer}};
|
||||
|
||||
#[test]
|
||||
fn should_not_propagate_transactions_that_are_marked_as_such() {
|
||||
// given
|
||||
let (client, longest_chain) = TestClientBuilder::new().build_with_longest_chain();
|
||||
let client = Arc::new(client);
|
||||
let pool = Arc::new(TransactionPool::new(
|
||||
Default::default(),
|
||||
transaction_pool::FullChainApi::new(client.clone())
|
||||
));
|
||||
let best = longest_chain.best_chain().unwrap();
|
||||
let transaction = Transfer {
|
||||
amount: 5,
|
||||
nonce: 0,
|
||||
from: AccountKeyring::Alice.into(),
|
||||
to: Default::default(),
|
||||
}.into_signed_tx();
|
||||
block_on(pool.submit_one(&BlockId::hash(best.hash()), transaction.clone())).unwrap();
|
||||
block_on(pool.submit_one(&BlockId::hash(best.hash()), Extrinsic::IncludeData(vec![1]))).unwrap();
|
||||
assert_eq!(pool.status().ready, 2);
|
||||
|
||||
// when
|
||||
let transactions = transactions_to_propagate(&pool);
|
||||
|
||||
// then
|
||||
assert_eq!(transactions.len(), 1);
|
||||
assert!(transactions[0].1.clone().check().is_ok());
|
||||
// this should not panic
|
||||
let _ = transactions[0].1.transfer();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,149 @@
|
||||
// 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 futures::prelude::*;
|
||||
use futures::sync::mpsc;
|
||||
use futures::stream::futures_unordered::FuturesUnordered;
|
||||
use std::time::{Duration, Instant};
|
||||
use tokio_timer::Delay;
|
||||
|
||||
/// Holds a list of `UnboundedSender`s, each associated with a certain time period. Every time the
|
||||
/// period elapses, we push an element on the sender.
|
||||
///
|
||||
/// Senders are removed only when they are closed.
|
||||
pub struct StatusSinks<T> {
|
||||
entries: FuturesUnordered<YieldAfter<T>>,
|
||||
}
|
||||
|
||||
struct YieldAfter<T> {
|
||||
delay: tokio_timer::Delay,
|
||||
interval: Duration,
|
||||
sender: Option<mpsc::UnboundedSender<T>>,
|
||||
}
|
||||
|
||||
impl<T> StatusSinks<T> {
|
||||
/// Builds a new empty collection.
|
||||
pub fn new() -> StatusSinks<T> {
|
||||
StatusSinks {
|
||||
entries: FuturesUnordered::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Adds a sender to the collection.
|
||||
///
|
||||
/// The `interval` is the time period between two pushes on the sender.
|
||||
pub fn push(&mut self, interval: Duration, sender: mpsc::UnboundedSender<T>) {
|
||||
self.entries.push(YieldAfter {
|
||||
delay: Delay::new(Instant::now() + interval),
|
||||
interval,
|
||||
sender: Some(sender),
|
||||
})
|
||||
}
|
||||
|
||||
/// Processes all the senders. If any sender is ready, calls the `status_grab` function and
|
||||
/// pushes what it returns to the sender.
|
||||
///
|
||||
/// This function doesn't return anything, but it should be treated as if it implicitly
|
||||
/// returns `Ok(Async::NotReady)`. In particular, it should be called again when the task
|
||||
/// is waken up.
|
||||
///
|
||||
/// # Panic
|
||||
///
|
||||
/// Panics if not called within the context of a task.
|
||||
pub fn poll(&mut self, mut status_grab: impl FnMut() -> T) {
|
||||
loop {
|
||||
match self.entries.poll() {
|
||||
Ok(Async::Ready(Some((sender, interval)))) => {
|
||||
let status = status_grab();
|
||||
if sender.unbounded_send(status).is_ok() {
|
||||
self.entries.push(YieldAfter {
|
||||
// Note that since there's a small delay between the moment a task is
|
||||
// waken up and the moment it is polled, the period is actually not
|
||||
// `interval` but `interval + <delay>`. We ignore this problem in
|
||||
// practice.
|
||||
delay: Delay::new(Instant::now() + interval),
|
||||
interval,
|
||||
sender: Some(sender),
|
||||
});
|
||||
}
|
||||
}
|
||||
Err(()) |
|
||||
Ok(Async::Ready(None)) |
|
||||
Ok(Async::NotReady) => break,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Future for YieldAfter<T> {
|
||||
type Item = (mpsc::UnboundedSender<T>, Duration);
|
||||
type Error = ();
|
||||
|
||||
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
|
||||
match self.delay.poll() {
|
||||
Ok(Async::NotReady) => Ok(Async::NotReady),
|
||||
Ok(Async::Ready(())) => {
|
||||
let sender = self.sender.take()
|
||||
.expect("sender is always Some unless the future is finished; qed");
|
||||
Ok(Async::Ready((sender, self.interval)))
|
||||
},
|
||||
Err(_) => Err(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::StatusSinks;
|
||||
use futures::prelude::*;
|
||||
use futures::sync::mpsc;
|
||||
use std::time::Duration;
|
||||
|
||||
#[test]
|
||||
fn basic_usage() {
|
||||
let mut status_sinks = StatusSinks::new();
|
||||
|
||||
let (tx1, rx1) = mpsc::unbounded();
|
||||
status_sinks.push(Duration::from_millis(200), tx1);
|
||||
|
||||
let (tx2, rx2) = mpsc::unbounded();
|
||||
status_sinks.push(Duration::from_millis(500), tx2);
|
||||
|
||||
let mut runtime = tokio::runtime::Runtime::new().unwrap();
|
||||
|
||||
let mut val_order = 5;
|
||||
runtime.spawn(futures::future::poll_fn(move || {
|
||||
status_sinks.poll(|| { val_order += 1; val_order });
|
||||
Ok(Async::NotReady)
|
||||
}));
|
||||
|
||||
let done = rx1
|
||||
.into_future()
|
||||
.and_then(|(item, rest)| {
|
||||
assert_eq!(item, Some(6));
|
||||
rest.into_future()
|
||||
})
|
||||
.and_then(|(item, _)| {
|
||||
assert_eq!(item, Some(7));
|
||||
rx2.into_future()
|
||||
})
|
||||
.map(|(item, _)| {
|
||||
assert_eq!(item, Some(8));
|
||||
});
|
||||
|
||||
runtime.block_on(done).unwrap();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user