Custom RPC implementation for node. (#3109)

* Allow RPCs to be customized.

* Implement node-rpc extensions.

* Working on a test.

* Add node-testing crate.

* Fix genesis test config

* Fix nonce lookups.

* Clean up.

* Fix expected block type.

* Make the RPC extension function optional.

* Fix service doc test.

* Bump jsonrpc.

* Bump client version.

* Update Cargo.lock

* Update jsonrpc.

* Fix build.

* Remove unused imports.

* Fix signed extra.

* Post merge clean up.

* Fix tests.

* Patch hashmap-core.

* Fix build.

* Fix build.

* Remove hashmap_core patches.
This commit is contained in:
Tomasz Drwięga
2019-08-20 11:06:35 +02:00
committed by Gavin Wood
parent 95abffc8e4
commit 56296386ab
29 changed files with 838 additions and 278 deletions
+4 -4
View File
@@ -5,12 +5,12 @@ authors = ["Parity Technologies <admin@parity.io>"]
edition = "2018"
[dependencies]
pubsub = { package = "jsonrpc-pubsub", version = "12.0.0" }
jsonrpc-core = "13.0.0"
pubsub = { package = "jsonrpc-pubsub", version = "13.0.0" }
log = "0.4"
serde = "1.0"
substrate-rpc = { path = "../rpc" }
sr-primitives = { path = "../sr-primitives" }
[target.'cfg(not(target_os = "unknown"))'.dependencies]
http = { package = "jsonrpc-http-server", version = "12.0.0" }
ws = { package = "jsonrpc-ws-server", version = "12.0.0" }
http = { package = "jsonrpc-http-server", version = "13.0.0" }
ws = { package = "jsonrpc-ws-server", version = "13.0.0" }
+13 -27
View File
@@ -18,11 +18,10 @@
#[warn(missing_docs)]
pub use substrate_rpc as apis;
use std::io;
use jsonrpc_core::IoHandlerExtension;
use log::error;
use sr_primitives::{traits::{Block as BlockT, NumberFor}, generic::SignedBlock};
use pubsub::PubSubMetadata;
/// Maximal payload accepted by RPC servers.
const MAX_PAYLOAD: usize = 15 * 1024 * 1024;
@@ -30,30 +29,17 @@ const MAX_PAYLOAD: usize = 15 * 1024 * 1024;
/// Default maximum number of connections for WS RPC servers.
const WS_MAX_CONNECTIONS: usize = 100;
pub type Metadata = apis::metadata::Metadata;
pub type RpcHandler = pubsub::PubSubHandler<Metadata>;
/// The RPC IoHandler containing all requested APIs.
pub type RpcHandler<T> = pubsub::PubSubHandler<T>;
pub use self::inner::*;
/// Construct rpc `IoHandler`
pub fn rpc_handler<Block: BlockT, ExHash, S, C, A, Y>(
state: S,
chain: C,
author: A,
system: Y,
) -> RpcHandler where
Block: BlockT + 'static,
ExHash: Send + Sync + 'static + sr_primitives::Serialize + sr_primitives::DeserializeOwned,
S: apis::state::StateApi<Block::Hash, Metadata=Metadata>,
C: apis::chain::ChainApi<NumberFor<Block>, Block::Hash, Block::Header, SignedBlock<Block>, Metadata=Metadata>,
A: apis::author::AuthorApi<ExHash, Block::Hash, Metadata=Metadata>,
Y: apis::system::SystemApi<Block::Hash, NumberFor<Block>>,
{
pub fn rpc_handler<M: PubSubMetadata>(
extension: impl IoHandlerExtension<M>
) -> RpcHandler<M> {
let mut io = pubsub::PubSubHandler::default();
io.extend_with(state.to_delegate());
io.extend_with(chain.to_delegate());
io.extend_with(author.to_delegate());
io.extend_with(system.to_delegate());
extension.augment(&mut io);
io
}
@@ -67,10 +53,10 @@ mod inner {
/// Start HTTP server listening on given address.
///
/// **Note**: Only available if `not(target_os = "unknown")`.
pub fn start_http(
pub fn start_http<M: pubsub::PubSubMetadata + Default>(
addr: &std::net::SocketAddr,
cors: Option<&Vec<String>>,
io: RpcHandler,
io: RpcHandler<M>,
) -> io::Result<http::Server> {
http::ServerBuilder::new(io)
.threads(4)
@@ -89,13 +75,13 @@ mod inner {
/// Start WS server listening on given address.
///
/// **Note**: Only available if `not(target_os = "unknown")`.
pub fn start_ws(
pub fn start_ws<M: pubsub::PubSubMetadata + From<jsonrpc_core::futures::sync::mpsc::Sender<String>>> (
addr: &std::net::SocketAddr,
max_connections: Option<usize>,
cors: Option<&Vec<String>>,
io: RpcHandler,
io: RpcHandler<M>,
) -> io::Result<ws::Server> {
ws::ServerBuilder::with_meta_extractor(io, |context: &ws::RequestContext| Metadata::new(context.sender()))
ws::ServerBuilder::with_meta_extractor(io, |context: &ws::RequestContext| context.sender().into())
.max_payload(MAX_PAYLOAD)
.max_connections(max_connections.unwrap_or(WS_MAX_CONNECTIONS))
.allowed_origins(map_cors(cors))
+5 -5
View File
@@ -7,11 +7,11 @@ edition = "2018"
[dependencies]
derive_more = "0.14.0"
futures = "0.1"
futures03 = { package = "futures-preview", version = "=0.3.0-alpha.17", features = ["compat"] }
jsonrpc-core = "12.0.0"
jsonrpc-core-client = "12.0.0"
jsonrpc-pubsub = "12.0.0"
jsonrpc-derive = "12.0.0"
futures03 = { package = "futures-preview", version = "0.3.0-alpha.17", features = ["compat"] }
jsonrpc-core = "13.0.0"
jsonrpc-core-client = "13.0.0"
jsonrpc-pubsub = "13.0.0"
jsonrpc-derive = "13.0.0"
log = "0.4"
parking_lot = "0.9.0"
codec = { package = "parity-scale-codec", version = "1.0.0" }
+7 -3
View File
@@ -15,19 +15,23 @@
// along with Substrate. If not, see <http://www.gnu.org/licenses/>.
//! Substrate RPC interfaces.
//!
//! A collection of RPC methods and subscriptions supported by all substrate clients.
#![warn(missing_docs)]
mod errors;
mod helpers;
mod metadata;
mod subscriptions;
use jsonrpc_core as rpc;
pub use metadata::Metadata;
pub use rpc::IoHandlerExtension as RpcExtension;
pub use subscriptions::Subscriptions;
pub mod author;
pub mod chain;
pub mod metadata;
pub mod state;
pub mod system;
use jsonrpc_core as rpc;
+6
View File
@@ -52,3 +52,9 @@ impl Metadata {
(rx, Self::new(tx))
}
}
impl From<mpsc::Sender<String>> for Metadata {
fn from(sender: mpsc::Sender<String>) -> Self {
Self::new(sender)
}
}
+1 -1
View File
@@ -29,8 +29,8 @@ use network;
use sr_primitives::traits::{self, Header as HeaderT};
use self::error::Result;
pub use self::helpers::{Properties, SystemInfo, Health, PeerInfo};
pub use self::helpers::{Properties, SystemInfo, Health, PeerInfo};
pub use self::gen_client::Client as SystemClient;
/// Substrate system RPC API
+2 -1
View File
@@ -32,7 +32,8 @@ client_db = { package = "substrate-client-db", path = "../../core/client/db", fe
codec = { package = "parity-scale-codec", version = "1.0.0" }
substrate-executor = { path = "../../core/executor" }
transaction_pool = { package = "substrate-transaction-pool", path = "../../core/transaction-pool" }
rpc = { package = "substrate-rpc-servers", path = "../../core/rpc-servers" }
rpc-servers = { package = "substrate-rpc-servers", path = "../../core/rpc-servers" }
rpc = { package = "substrate-rpc", path = "../../core/rpc" }
tel = { package = "substrate-telemetry", path = "../../core/telemetry" }
offchain = { package = "substrate-offchain", path = "../../core/offchain" }
parity-multiaddr = { package = "parity-multiaddr", version = "0.5.0" }
+68 -21
View File
@@ -34,7 +34,7 @@ use sr_primitives::{
};
use crate::config::Configuration;
use primitives::{Blake2Hasher, H256, traits::BareCryptoStorePtr};
use rpc::{self, apis::system::SystemInfo};
use rpc::{self, system::SystemInfo};
use futures::{prelude::*, future::Executor};
use futures03::{FutureExt as _, channel::mpsc, compat::Compat};
@@ -145,6 +145,9 @@ pub type PoolApi<C> = <C as Components>::TransactionPoolApi;
pub trait RuntimeGenesis: Serialize + DeserializeOwned + BuildStorage {}
impl<T: Serialize + DeserializeOwned + BuildStorage> RuntimeGenesis for T {}
/// A transport-agnostic handler of the RPC queries.
pub type RpcHandler = rpc_servers::RpcHandler<rpc::Metadata>;
/// Something that can create and store initial session keys from given seeds.
pub trait InitialSessionKeys<C: Components> {
/// Generate the initial session keys for the given seeds and store them in
@@ -168,46 +171,51 @@ impl<C: Components> InitialSessionKeys<Self> for C where
}
/// Something that can start the RPC service.
pub trait StartRPC<C: Components> {
pub trait StartRpc<C: Components> {
fn start_rpc(
client: Arc<ComponentClient<C>>,
system_send_back: mpsc::UnboundedSender<rpc::apis::system::Request<ComponentBlock<C>>>,
system_send_back: mpsc::UnboundedSender<rpc::system::Request<ComponentBlock<C>>>,
system_info: SystemInfo,
task_executor: TaskExecutor,
transaction_pool: Arc<TransactionPool<C::TransactionPoolApi>>,
rpc_extensions: impl rpc::RpcExtension<rpc::Metadata>,
keystore: KeyStorePtr,
) -> rpc::RpcHandler;
) -> RpcHandler;
}
impl<C: Components> StartRPC<Self> for C where
impl<C: Components> StartRpc<C> for C where
ComponentClient<C>: ProvideRuntimeApi,
<ComponentClient<C> as ProvideRuntimeApi>::Api:
runtime_api::Metadata<ComponentBlock<C>> + session::SessionKeys<ComponentBlock<C>>,
{
fn start_rpc(
client: Arc<ComponentClient<C>>,
system_send_back: mpsc::UnboundedSender<rpc::apis::system::Request<ComponentBlock<C>>>,
system_send_back: mpsc::UnboundedSender<rpc::system::Request<ComponentBlock<C>>>,
rpc_system_info: SystemInfo,
task_executor: TaskExecutor,
transaction_pool: Arc<TransactionPool<C::TransactionPoolApi>>,
rpc_extensions: impl rpc::RpcExtension<rpc::Metadata>,
keystore: KeyStorePtr,
) -> rpc::RpcHandler {
let subscriptions = rpc::apis::Subscriptions::new(task_executor.clone());
let chain = rpc::apis::chain::Chain::new(client.clone(), subscriptions.clone());
let state = rpc::apis::state::State::new(client.clone(), subscriptions.clone());
let author = rpc::apis::author::Author::new(
) -> RpcHandler {
use rpc::{chain, state, author, system};
let subscriptions = rpc::Subscriptions::new(task_executor.clone());
let chain = chain::Chain::new(client.clone(), subscriptions.clone());
let state = state::State::new(client.clone(), subscriptions.clone());
let author = rpc::author::Author::new(
client,
transaction_pool,
subscriptions,
keystore,
);
let system = rpc::apis::system::System::new(rpc_system_info, system_send_back);
rpc::rpc_handler::<ComponentBlock<C>, ComponentExHash<C>, _, _, _, _>(
state,
chain,
author,
system,
)
let system = system::System::new(rpc_system_info, system_send_back);
rpc_servers::rpc_handler((
state::StateApi::to_delegate(state),
chain::ChainApi::to_delegate(chain),
author::AuthorApi::to_delegate(author),
system::SystemApi::to_delegate(system),
rpc_extensions,
))
}
}
@@ -299,7 +307,7 @@ pub trait ServiceTrait<C: Components>:
Deref<Target = Service<C>>
+ Send
+ 'static
+ StartRPC<C>
+ StartRpc<C>
+ MaintainTransactionPool<C>
+ OffchainWorker<C>
+ InitialSessionKeys<C>
@@ -308,7 +316,7 @@ impl<C: Components, T> ServiceTrait<C> for T where
T: Deref<Target = Service<C>>
+ Send
+ 'static
+ StartRPC<C>
+ StartRpc<C>
+ MaintainTransactionPool<C>
+ OffchainWorker<C>
+ InitialSessionKeys<C>
@@ -335,6 +343,8 @@ pub trait ServiceFactory: 'static + Sized {
type Genesis: RuntimeGenesis;
/// Other configuration for service members.
type Configuration: Default;
/// RPC initialisation.
type RpcExtensions: rpc::RpcExtension<rpc::Metadata>;
/// Extended full service type.
type FullService: ServiceTrait<FullComponents<Self>>;
/// Extended light service type.
@@ -407,6 +417,18 @@ pub trait ServiceFactory: 'static + Sized {
Err("Chain Specification doesn't contain any consensus_engine name".into())
}
}
/// Create custom RPC method handlers for full node.
fn build_full_rpc_extensions(
client: Arc<FullClient<Self>>,
transaction_pool: Arc<TransactionPool<Self::FullTransactionPoolApi>>,
) -> Self::RpcExtensions;
/// Create custom RPC method handlers for light node.
fn build_light_rpc_extensions(
client: Arc<LightClient<Self>>,
transaction_pool: Arc<TransactionPool<Self::LightTransactionPoolApi>>,
) -> Self::RpcExtensions;
}
/// A collection of types and function to generalize over full / light client type.
@@ -419,8 +441,10 @@ 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 all runtime-dependent services.
/// The type that can start all runtime-dependent services.
type RuntimeServices: ServiceTrait<Self>;
/// The type that can extend the RPC methods.
type RpcExtensions: rpc::RpcExtension<rpc::Metadata>;
// TODO: Traitify transaction pool and allow people to implement their own. (#1242)
/// Extrinsic pool type.
type TransactionPoolApi: 'static + txpool::ChainApi<
@@ -468,6 +492,12 @@ pub trait Components: Sized + 'static {
config: &mut FactoryFullConfiguration<Self::Factory>,
client: Arc<ComponentClient<Self>>
) -> Result<Option<Self::SelectChain>, error::Error>;
/// Build RPC extensions
fn build_rpc_extensions(
client: Arc<ComponentClient<Self>>,
transaction_pool: Arc<TransactionPool<Self::TransactionPoolApi>>,
) -> Self::RpcExtensions;
}
/// A struct that implement `Components` for the full client.
@@ -529,6 +559,7 @@ impl<Factory: ServiceFactory> Components for FullComponents<Factory> {
type ImportQueue = Factory::FullImportQueue;
type RuntimeApi = Factory::RuntimeApi;
type RuntimeServices = Factory::FullService;
type RpcExtensions = Factory::RpcExtensions;
type SelectChain = Factory::SelectChain;
fn build_client(
@@ -594,6 +625,13 @@ impl<Factory: ServiceFactory> Components for FullComponents<Factory> {
) -> Result<Option<Arc<dyn FinalityProofProvider<<Self::Factory as ServiceFactory>::Block>>>, error::Error> {
Factory::build_finality_proof_provider(client)
}
fn build_rpc_extensions(
client: Arc<ComponentClient<Self>>,
transaction_pool: Arc<TransactionPool<Self::TransactionPoolApi>>,
) -> Self::RpcExtensions {
Factory::build_full_rpc_extensions(client, transaction_pool)
}
}
/// A struct that implement `Components` for the light client.
@@ -655,6 +693,7 @@ impl<Factory: ServiceFactory> Components for LightComponents<Factory> {
type ImportQueue = <Factory as ServiceFactory>::LightImportQueue;
type RuntimeApi = Factory::RuntimeApi;
type RuntimeServices = Factory::LightService;
type RpcExtensions = Factory::RpcExtensions;
type SelectChain = Factory::SelectChain;
fn build_client(
@@ -709,12 +748,20 @@ impl<Factory: ServiceFactory> Components for LightComponents<Factory> {
) -> Result<Option<Arc<dyn FinalityProofProvider<<Self::Factory as ServiceFactory>::Block>>>, error::Error> {
Ok(None)
}
fn build_select_chain(
_config: &mut FactoryFullConfiguration<Self::Factory>,
_client: Arc<ComponentClient<Self>>
) -> Result<Option<Self::SelectChain>, error::Error> {
Ok(None)
}
fn build_rpc_extensions(
client: Arc<ComponentClient<Self>>,
transaction_pool: Arc<TransactionPool<Self::TransactionPoolApi>>,
) -> Self::RpcExtensions {
Factory::build_light_rpc_extensions(client, transaction_pool)
}
}
#[cfg(test)]
+33 -14
View File
@@ -63,7 +63,7 @@ pub use components::{
FactoryFullConfiguration, RuntimeGenesis, FactoryGenesis,
ComponentExHash, ComponentExtrinsic, FactoryExtrinsic, InitialSessionKeys,
};
use components::{StartRPC, MaintainTransactionPool, OffchainWorker};
use components::{StartRpc, MaintainTransactionPool, OffchainWorker};
#[doc(hidden)]
pub use std::{ops::Deref, result::Result, sync::Arc};
#[doc(hidden)]
@@ -101,7 +101,7 @@ pub struct Service<Components: components::Components> {
to_poll: Vec<Box<dyn Future<Item = (), Error = ()> + Send>>,
/// Configuration of this Service
config: FactoryFullConfiguration<Components::Factory>,
rpc_handlers: rpc::RpcHandler,
rpc_handlers: components::RpcHandler,
_rpc: Box<dyn std::any::Any + Send + Sync>,
_telemetry: Option<tel::Telemetry>,
_telemetry_on_connect_sinks: Arc<Mutex<Vec<mpsc::UnboundedSender<()>>>>,
@@ -373,7 +373,7 @@ impl<Components: components::Components> Service<Components> {
// RPC
let (system_rpc_tx, system_rpc_rx) = futures03::channel::mpsc::unbounded();
let gen_handler = || {
let system_info = rpc::apis::system::SystemInfo {
let system_info = rpc::system::SystemInfo {
chain_name: config.chain_spec.name().into(),
impl_name: config.impl_name.into(),
impl_version: config.impl_version.into(),
@@ -385,6 +385,7 @@ impl<Components: components::Components> Service<Components> {
system_info.clone(),
Arc::new(SpawnTaskHandle { sender: to_spawn_tx.clone() }),
transaction_pool.clone(),
Components::build_rpc_extensions(client.clone(), transaction_pool.clone()),
keystore.clone(),
)
};
@@ -630,7 +631,7 @@ fn build_network_future<
mut network: network::NetworkWorker<B, S, H>,
client: Arc<C>,
status_sinks: Arc<Mutex<Vec<mpsc::UnboundedSender<(NetworkStatus<B>, NetworkState)>>>>,
rpc_rx: futures03::channel::mpsc::UnboundedReceiver<rpc::apis::system::Request<B>>,
rpc_rx: futures03::channel::mpsc::UnboundedReceiver<rpc::system::Request<B>>,
should_have_peers: bool,
) -> impl Future<Item = (), Error = ()> {
// Compatibility shim while we're transitionning to stable Futures.
@@ -666,16 +667,16 @@ fn build_network_future<
// Poll the RPC requests and answer them.
while let Ok(Async::Ready(Some(request))) = rpc_rx.poll() {
match request {
rpc::apis::system::Request::Health(sender) => {
let _ = sender.send(rpc::apis::system::Health {
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::apis::system::Request::Peers(sender) => {
rpc::system::Request::Peers(sender) => {
let _ = sender.send(network.peers_debug_info().into_iter().map(|(peer_id, p)|
rpc::apis::system::PeerInfo {
rpc::system::PeerInfo {
peer_id: peer_id.to_base58(),
roles: format!("{:?}", p.roles),
protocol_version: p.protocol_version,
@@ -684,7 +685,7 @@ fn build_network_future<
}
).collect());
}
rpc::apis::system::Request::NetworkState(sender) => {
rpc::system::Request::NetworkState(sender) => {
let _ = sender.send(network.network_state());
}
};
@@ -756,7 +757,7 @@ impl<Components> Drop for Service<Components> where Components: components::Comp
/// 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, H: FnMut() -> rpc::RpcHandler>(
fn start_rpc_servers<C, G, H: FnMut() -> components::RpcHandler>(
config: &Configuration<C, G>,
mut gen_handler: H
) -> Result<Box<dyn std::any::Any + Send + Sync>, error::Error> {
@@ -781,11 +782,11 @@ fn start_rpc_servers<C, G, H: FnMut() -> rpc::RpcHandler>(
Ok(Box::new((
maybe_start_server(
config.rpc_http,
|address| rpc::start_http(address, config.rpc_cors.as_ref(), gen_handler()),
|address| rpc_servers::start_http(address, config.rpc_cors.as_ref(), gen_handler()),
)?,
maybe_start_server(
config.rpc_ws,
|address| rpc::start_ws(
|address| rpc_servers::start_ws(
address,
config.rpc_ws_max_connections,
config.rpc_cors.as_ref(),
@@ -797,7 +798,7 @@ fn start_rpc_servers<C, G, H: FnMut() -> rpc::RpcHandler>(
/// 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, H: FnMut() -> rpc::RpcHandler>(
fn start_rpc_servers<C, G, H: FnMut() -> components::RpcHandler>(
_: &Configuration<C, G>,
_: H
) -> Result<Box<std::any::Any + Send + Sync>, error::Error> {
@@ -819,7 +820,7 @@ impl RpcSession {
/// The `RpcSession` must be kept alive in order to receive messages on the sender.
pub fn new(sender: mpsc::Sender<String>) -> RpcSession {
RpcSession {
metadata: rpc::Metadata::new(sender),
metadata: sender.into(),
}
}
}
@@ -989,6 +990,7 @@ where
/// FinalityProofProvider = { |client: Arc<FullClient<Self>>| {
/// Ok(Some(Arc::new(grandpa::FinalityProofProvider::new(client.clone(), client)) as _))
/// }},
/// RpcExtensions = (),
/// }
/// }
/// ```
@@ -1015,6 +1017,8 @@ macro_rules! construct_service_factory {
SelectChain = $select_chain:ty
{ $( $select_chain_init:tt )* },
FinalityProofProvider = { $( $finality_proof_provider_init:tt )* },
RpcExtensions = $rpc_extensions_ty:ty
$( { $( $rpc_extensions:tt )* } )?,
}
) => {
$( #[$attr] )*
@@ -1035,6 +1039,7 @@ macro_rules! construct_service_factory {
type FullImportQueue = $full_import_queue;
type LightImportQueue = $light_import_queue;
type SelectChain = $select_chain;
type RpcExtensions = $rpc_extensions_ty;
fn build_full_transaction_pool(
config: $crate::TransactionPoolOptions,
@@ -1102,6 +1107,20 @@ macro_rules! construct_service_factory {
($( $authority_setup )*)(service)
})
}
fn build_full_rpc_extensions(
client: Arc<$crate::FullClient<Self>>,
transaction_pool: Arc<$crate::TransactionPool<Self::FullTransactionPoolApi>>,
) -> Self::RpcExtensions {
$( ( $( $rpc_extensions )* ) (client, transaction_pool) )?
}
fn build_light_rpc_extensions(
client: Arc<$crate::LightClient<Self>>,
transaction_pool: Arc<$crate::TransactionPool<Self::LightTransactionPoolApi>>,
) -> Self::RpcExtensions {
$( ( $( $rpc_extensions )* ) (client, transaction_pool) )?
}
}
}
}
+3 -2
View File
@@ -67,7 +67,7 @@ impl GenesisInit for () {
}
/// A builder for creating a test client instance.
pub struct TestClientBuilder<Executor, Backend, G: GenesisInit = ()> {
pub struct TestClientBuilder<Executor, Backend, G: GenesisInit> {
execution_strategies: ExecutionStrategies,
genesis_init: G,
child_storage_extension: HashMap<Vec<u8>, Vec<(Vec<u8>, Vec<u8>)>>,
@@ -76,9 +76,10 @@ pub struct TestClientBuilder<Executor, Backend, G: GenesisInit = ()> {
keystore: Option<BareCryptoStorePtr>,
}
impl<Block, Executor> Default for TestClientBuilder<
impl<Block, Executor, G: GenesisInit> Default for TestClientBuilder<
Executor,
Backend<Block>,
G,
> where
Block: BlockT<Hash=<Blake2Hasher as Hasher>::Out>,
{