mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-04-30 17:47:56 +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
@@ -0,0 +1,143 @@
|
||||
use primitives::{Pair, Public, sr25519};
|
||||
use runtime::{
|
||||
AccountId, AuraConfig, BalancesConfig, GenesisConfig, GrandpaConfig,
|
||||
SudoConfig, IndicesConfig, SystemConfig, WASM_BINARY, Signature
|
||||
};
|
||||
use aura_primitives::sr25519::{AuthorityId as AuraId};
|
||||
use grandpa_primitives::{AuthorityId as GrandpaId};
|
||||
use substrate_service;
|
||||
use sr_primitives::traits::{Verify, IdentifyAccount};
|
||||
|
||||
// Note this is the URL for the telemetry server
|
||||
//const STAGING_TELEMETRY_URL: &str = "wss://telemetry.polkadot.io/submit/";
|
||||
|
||||
/// Specialized `ChainSpec`. This is a specialization of the general Substrate ChainSpec type.
|
||||
pub type ChainSpec = substrate_service::ChainSpec<GenesisConfig>;
|
||||
|
||||
/// The chain specification option. This is expected to come in from the CLI and
|
||||
/// is little more than one of a number of alternatives which can easily be converted
|
||||
/// from a string (`--chain=...`) into a `ChainSpec`.
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum Alternative {
|
||||
/// Whatever the current runtime is, with just Alice as an auth.
|
||||
Development,
|
||||
/// Whatever the current runtime is, with simple Alice/Bob auths.
|
||||
LocalTestnet,
|
||||
}
|
||||
|
||||
/// Helper function to generate a crypto pair from seed
|
||||
pub fn get_from_seed<TPublic: Public>(seed: &str) -> <TPublic::Pair as Pair>::Public {
|
||||
TPublic::Pair::from_string(&format!("//{}", seed), None)
|
||||
.expect("static values are valid; qed")
|
||||
.public()
|
||||
}
|
||||
|
||||
type AccountPublic = <Signature as Verify>::Signer;
|
||||
|
||||
/// Helper function to generate an account ID from seed
|
||||
pub fn get_account_id_from_seed<TPublic: Public>(seed: &str) -> AccountId where
|
||||
AccountPublic: From<<TPublic::Pair as Pair>::Public>
|
||||
{
|
||||
AccountPublic::from(get_from_seed::<TPublic>(seed)).into_account()
|
||||
}
|
||||
|
||||
/// Helper function to generate an authority key for Aura
|
||||
pub fn get_authority_keys_from_seed(s: &str) -> (AuraId, GrandpaId) {
|
||||
(
|
||||
get_from_seed::<AuraId>(s),
|
||||
get_from_seed::<GrandpaId>(s),
|
||||
)
|
||||
}
|
||||
|
||||
impl Alternative {
|
||||
/// Get an actual chain config from one of the alternatives.
|
||||
pub(crate) fn load(self) -> Result<ChainSpec, String> {
|
||||
Ok(match self {
|
||||
Alternative::Development => ChainSpec::from_genesis(
|
||||
"Development",
|
||||
"dev",
|
||||
|| testnet_genesis(vec![
|
||||
get_authority_keys_from_seed("Alice"),
|
||||
],
|
||||
get_account_id_from_seed::<sr25519::Public>("Alice"),
|
||||
vec![
|
||||
get_account_id_from_seed::<sr25519::Public>("Alice"),
|
||||
get_account_id_from_seed::<sr25519::Public>("Bob"),
|
||||
get_account_id_from_seed::<sr25519::Public>("Alice//stash"),
|
||||
get_account_id_from_seed::<sr25519::Public>("Bob//stash"),
|
||||
],
|
||||
true),
|
||||
vec![],
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None
|
||||
),
|
||||
Alternative::LocalTestnet => ChainSpec::from_genesis(
|
||||
"Local Testnet",
|
||||
"local_testnet",
|
||||
|| testnet_genesis(vec![
|
||||
get_authority_keys_from_seed("Alice"),
|
||||
get_authority_keys_from_seed("Bob"),
|
||||
],
|
||||
get_account_id_from_seed::<sr25519::Public>("Alice"),
|
||||
vec![
|
||||
get_account_id_from_seed::<sr25519::Public>("Alice"),
|
||||
get_account_id_from_seed::<sr25519::Public>("Bob"),
|
||||
get_account_id_from_seed::<sr25519::Public>("Charlie"),
|
||||
get_account_id_from_seed::<sr25519::Public>("Dave"),
|
||||
get_account_id_from_seed::<sr25519::Public>("Eve"),
|
||||
get_account_id_from_seed::<sr25519::Public>("Ferdie"),
|
||||
get_account_id_from_seed::<sr25519::Public>("Alice//stash"),
|
||||
get_account_id_from_seed::<sr25519::Public>("Bob//stash"),
|
||||
get_account_id_from_seed::<sr25519::Public>("Charlie//stash"),
|
||||
get_account_id_from_seed::<sr25519::Public>("Dave//stash"),
|
||||
get_account_id_from_seed::<sr25519::Public>("Eve//stash"),
|
||||
get_account_id_from_seed::<sr25519::Public>("Ferdie//stash"),
|
||||
],
|
||||
true),
|
||||
vec![],
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None
|
||||
),
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn from(s: &str) -> Option<Self> {
|
||||
match s {
|
||||
"dev" => Some(Alternative::Development),
|
||||
"" | "local" => Some(Alternative::LocalTestnet),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn testnet_genesis(initial_authorities: Vec<(AuraId, GrandpaId)>,
|
||||
root_key: AccountId,
|
||||
endowed_accounts: Vec<AccountId>,
|
||||
_enable_println: bool) -> GenesisConfig {
|
||||
GenesisConfig {
|
||||
system: Some(SystemConfig {
|
||||
code: WASM_BINARY.to_vec(),
|
||||
changes_trie_config: Default::default(),
|
||||
}),
|
||||
indices: Some(IndicesConfig {
|
||||
ids: endowed_accounts.clone(),
|
||||
}),
|
||||
balances: Some(BalancesConfig {
|
||||
balances: endowed_accounts.iter().cloned().map(|k|(k, 1 << 60)).collect(),
|
||||
vesting: vec![],
|
||||
}),
|
||||
sudo: Some(SudoConfig {
|
||||
key: root_key,
|
||||
}),
|
||||
aura: Some(AuraConfig {
|
||||
authorities: initial_authorities.iter().map(|x| (x.0.clone())).collect(),
|
||||
}),
|
||||
grandpa: Some(GrandpaConfig {
|
||||
authorities: initial_authorities.iter().map(|x| (x.1.clone(), 1)).collect(),
|
||||
}),
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
use crate::service;
|
||||
use futures::{future, Future, sync::oneshot};
|
||||
use std::cell::RefCell;
|
||||
use tokio::runtime::Runtime;
|
||||
pub use substrate_cli::{VersionInfo, IntoExit, error};
|
||||
use substrate_cli::{display_role, informant, parse_and_prepare, ParseAndPrepare, NoCustom};
|
||||
use substrate_service::{AbstractService, Roles as ServiceRoles, Configuration};
|
||||
use aura_primitives::sr25519::{AuthorityPair as AuraPair};
|
||||
use crate::chain_spec;
|
||||
use log::info;
|
||||
|
||||
/// Parse command line arguments into service configuration.
|
||||
pub fn run<I, T, E>(args: I, exit: E, version: VersionInfo) -> error::Result<()> where
|
||||
I: IntoIterator<Item = T>,
|
||||
T: Into<std::ffi::OsString> + Clone,
|
||||
E: IntoExit,
|
||||
{
|
||||
type Config<T> = Configuration<(), T>;
|
||||
match parse_and_prepare::<NoCustom, NoCustom, _>(&version, "substrate-node", args) {
|
||||
ParseAndPrepare::Run(cmd) => cmd.run(load_spec, exit,
|
||||
|exit, _cli_args, _custom_args, config: Config<_>| {
|
||||
info!("{}", version.name);
|
||||
info!(" version {}", config.full_version());
|
||||
info!(" by {}, 2017, 2018", version.author);
|
||||
info!("Chain specification: {}", config.chain_spec.name());
|
||||
info!("Node name: {}", config.name);
|
||||
info!("Roles: {}", display_role(&config));
|
||||
let runtime = Runtime::new().map_err(|e| format!("{:?}", e))?;
|
||||
match config.roles {
|
||||
ServiceRoles::LIGHT => run_until_exit(
|
||||
runtime,
|
||||
service::new_light(config)?,
|
||||
exit
|
||||
),
|
||||
_ => run_until_exit(
|
||||
runtime,
|
||||
service::new_full(config)?,
|
||||
exit
|
||||
),
|
||||
}
|
||||
}),
|
||||
ParseAndPrepare::BuildSpec(cmd) => cmd.run::<NoCustom, _, _, _>(load_spec),
|
||||
ParseAndPrepare::ExportBlocks(cmd) => cmd.run_with_builder(|config: Config<_>|
|
||||
Ok(new_full_start!(config).0), load_spec, exit),
|
||||
ParseAndPrepare::ImportBlocks(cmd) => cmd.run_with_builder(|config: Config<_>|
|
||||
Ok(new_full_start!(config).0), load_spec, exit),
|
||||
ParseAndPrepare::PurgeChain(cmd) => cmd.run(load_spec),
|
||||
ParseAndPrepare::RevertChain(cmd) => cmd.run_with_builder(|config: Config<_>|
|
||||
Ok(new_full_start!(config).0), load_spec),
|
||||
ParseAndPrepare::CustomCommand(_) => Ok(())
|
||||
}?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn load_spec(id: &str) -> Result<Option<chain_spec::ChainSpec>, String> {
|
||||
Ok(match chain_spec::Alternative::from(id) {
|
||||
Some(spec) => Some(spec.load()?),
|
||||
None => None,
|
||||
})
|
||||
}
|
||||
|
||||
fn run_until_exit<T, E>(
|
||||
mut runtime: Runtime,
|
||||
service: T,
|
||||
e: E,
|
||||
) -> error::Result<()>
|
||||
where
|
||||
T: AbstractService,
|
||||
E: IntoExit,
|
||||
{
|
||||
let (exit_send, exit) = exit_future::signal();
|
||||
|
||||
let informant = informant::build(&service);
|
||||
runtime.executor().spawn(exit.until(informant).map(|_| ()));
|
||||
|
||||
// we eagerly drop the service so that the internal exit future is fired,
|
||||
// but we need to keep holding a reference to the global telemetry guard
|
||||
let _telemetry = service.telemetry();
|
||||
|
||||
let service_res = {
|
||||
let exit = e.into_exit().map_err(|_| error::Error::Other("Exit future failed.".into()));
|
||||
let service = service.map_err(|err| error::Error::Service(err));
|
||||
let select = service.select(exit).map(|_| ()).map_err(|(err, _)| err);
|
||||
runtime.block_on(select)
|
||||
};
|
||||
|
||||
exit_send.fire();
|
||||
|
||||
// TODO [andre]: timeout this future #1318
|
||||
let _ = runtime.shutdown_on_idle().wait();
|
||||
|
||||
service_res
|
||||
}
|
||||
|
||||
// handles ctrl-c
|
||||
pub struct Exit;
|
||||
impl IntoExit for Exit {
|
||||
type Exit = future::MapErr<oneshot::Receiver<()>, fn(oneshot::Canceled) -> ()>;
|
||||
fn into_exit(self) -> Self::Exit {
|
||||
// can't use signal directly here because CtrlC takes only `Fn`.
|
||||
let (exit_send, exit) = oneshot::channel();
|
||||
|
||||
let exit_send_cell = RefCell::new(Some(exit_send));
|
||||
ctrlc::set_handler(move || {
|
||||
let exit_send = exit_send_cell.try_borrow_mut().expect("signal handler not reentrant; qed").take();
|
||||
if let Some(exit_send) = exit_send {
|
||||
exit_send.send(()).expect("Error sending exit notification");
|
||||
}
|
||||
}).expect("Error setting Ctrl-C handler");
|
||||
|
||||
exit.map_err(drop)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
//! Substrate Node Template CLI library.
|
||||
|
||||
#![warn(missing_docs)]
|
||||
#![warn(unused_extern_crates)]
|
||||
|
||||
mod chain_spec;
|
||||
#[macro_use]
|
||||
mod service;
|
||||
mod cli;
|
||||
|
||||
pub use substrate_cli::{VersionInfo, IntoExit, error};
|
||||
|
||||
fn main() -> Result<(), cli::error::Error> {
|
||||
let version = VersionInfo {
|
||||
name: "Substrate Node",
|
||||
commit: env!("VERGEN_SHA_SHORT"),
|
||||
version: env!("CARGO_PKG_VERSION"),
|
||||
executable_name: "node-template",
|
||||
author: "Anonymous",
|
||||
description: "Template Node",
|
||||
support_url: "support.anonymous.an",
|
||||
};
|
||||
|
||||
cli::run(std::env::args(), cli::Exit, version)
|
||||
}
|
||||
@@ -0,0 +1,225 @@
|
||||
//! Service and ServiceFactory implementation. Specialized wrapper over substrate service.
|
||||
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
use substrate_client::LongestChain;
|
||||
use runtime::{self, GenesisConfig, opaque::Block, RuntimeApi};
|
||||
use substrate_service::{error::{Error as ServiceError}, AbstractService, Configuration, ServiceBuilder};
|
||||
use transaction_pool::{self, txpool::{Pool as TransactionPool}};
|
||||
use inherents::InherentDataProviders;
|
||||
use network::{construct_simple_protocol};
|
||||
use substrate_executor::native_executor_instance;
|
||||
pub use substrate_executor::NativeExecutor;
|
||||
use aura_primitives::sr25519::{AuthorityPair as AuraPair};
|
||||
use grandpa::{self, FinalityProofProvider as GrandpaFinalityProofProvider};
|
||||
use basic_authorship;
|
||||
|
||||
// Our native executor instance.
|
||||
native_executor_instance!(
|
||||
pub Executor,
|
||||
runtime::api::dispatch,
|
||||
runtime::native_version,
|
||||
);
|
||||
|
||||
construct_simple_protocol! {
|
||||
/// Demo protocol attachment for substrate.
|
||||
pub struct NodeProtocol where Block = Block { }
|
||||
}
|
||||
|
||||
/// Starts a `ServiceBuilder` for a full service.
|
||||
///
|
||||
/// Use this macro if you don't actually need the full service, but just the builder in order to
|
||||
/// be able to perform chain operations.
|
||||
macro_rules! new_full_start {
|
||||
($config:expr) => {{
|
||||
let mut import_setup = None;
|
||||
let inherent_data_providers = inherents::InherentDataProviders::new();
|
||||
|
||||
let builder = substrate_service::ServiceBuilder::new_full::<
|
||||
runtime::opaque::Block, runtime::RuntimeApi, crate::service::Executor
|
||||
>($config)?
|
||||
.with_select_chain(|_config, backend| {
|
||||
Ok(substrate_client::LongestChain::new(backend.clone()))
|
||||
})?
|
||||
.with_transaction_pool(|config, client|
|
||||
Ok(transaction_pool::txpool::Pool::new(config, transaction_pool::FullChainApi::new(client)))
|
||||
)?
|
||||
.with_import_queue(|_config, client, mut select_chain, transaction_pool| {
|
||||
let select_chain = select_chain.take()
|
||||
.ok_or_else(|| substrate_service::Error::SelectChainRequired)?;
|
||||
|
||||
let (grandpa_block_import, grandpa_link) =
|
||||
grandpa::block_import::<_, _, _, runtime::RuntimeApi, _>(
|
||||
client.clone(), &*client, select_chain
|
||||
)?;
|
||||
|
||||
let import_queue = aura::import_queue::<_, _, AuraPair, _>(
|
||||
aura::SlotDuration::get_or_compute(&*client)?,
|
||||
Box::new(grandpa_block_import.clone()),
|
||||
Some(Box::new(grandpa_block_import.clone())),
|
||||
None,
|
||||
client,
|
||||
inherent_data_providers.clone(),
|
||||
Some(transaction_pool),
|
||||
)?;
|
||||
|
||||
import_setup = Some((grandpa_block_import, grandpa_link));
|
||||
|
||||
Ok(import_queue)
|
||||
})?;
|
||||
|
||||
(builder, import_setup, inherent_data_providers)
|
||||
}}
|
||||
}
|
||||
|
||||
/// Builds a new service for a full client.
|
||||
pub fn new_full<C: Send + Default + 'static>(config: Configuration<C, GenesisConfig>)
|
||||
-> Result<impl AbstractService, ServiceError>
|
||||
{
|
||||
let is_authority = config.roles.is_authority();
|
||||
let force_authoring = config.force_authoring;
|
||||
let name = config.name.clone();
|
||||
let disable_grandpa = config.disable_grandpa;
|
||||
|
||||
// sentry nodes announce themselves as authorities to the network
|
||||
// and should run the same protocols authorities do, but it should
|
||||
// never actively participate in any consensus process.
|
||||
let participates_in_consensus = is_authority && !config.sentry_mode;
|
||||
|
||||
let (builder, mut import_setup, inherent_data_providers) = new_full_start!(config);
|
||||
|
||||
let (block_import, grandpa_link) =
|
||||
import_setup.take()
|
||||
.expect("Link Half and Block Import are present for Full Services or setup failed before. qed");
|
||||
|
||||
let service = builder.with_network_protocol(|_| Ok(NodeProtocol::new()))?
|
||||
.with_finality_proof_provider(|client, backend|
|
||||
Ok(Arc::new(GrandpaFinalityProofProvider::new(backend, client)) as _)
|
||||
)?
|
||||
.build()?;
|
||||
|
||||
if participates_in_consensus {
|
||||
let proposer = basic_authorship::ProposerFactory {
|
||||
client: service.client(),
|
||||
transaction_pool: service.transaction_pool(),
|
||||
};
|
||||
|
||||
let client = service.client();
|
||||
let select_chain = service.select_chain()
|
||||
.ok_or(ServiceError::SelectChainRequired)?;
|
||||
|
||||
let aura = aura::start_aura::<_, _, _, _, _, AuraPair, _, _, _>(
|
||||
aura::SlotDuration::get_or_compute(&*client)?,
|
||||
client,
|
||||
select_chain,
|
||||
block_import,
|
||||
proposer,
|
||||
service.network(),
|
||||
inherent_data_providers.clone(),
|
||||
force_authoring,
|
||||
service.keystore(),
|
||||
)?;
|
||||
|
||||
// the AURA authoring task is considered essential, i.e. if it
|
||||
// fails we take down the service with it.
|
||||
service.spawn_essential_task(aura);
|
||||
}
|
||||
|
||||
// if the node isn't actively participating in consensus then it doesn't
|
||||
// need a keystore, regardless of which protocol we use below.
|
||||
let keystore = if participates_in_consensus {
|
||||
Some(service.keystore())
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let grandpa_config = grandpa::Config {
|
||||
// FIXME #1578 make this available through chainspec
|
||||
gossip_duration: Duration::from_millis(333),
|
||||
justification_period: 512,
|
||||
name: Some(name),
|
||||
observer_enabled: true,
|
||||
keystore,
|
||||
is_authority,
|
||||
};
|
||||
|
||||
match (is_authority, disable_grandpa) {
|
||||
(false, false) => {
|
||||
// start the lightweight GRANDPA observer
|
||||
service.spawn_task(grandpa::run_grandpa_observer(
|
||||
grandpa_config,
|
||||
grandpa_link,
|
||||
service.network(),
|
||||
service.on_exit(),
|
||||
)?);
|
||||
},
|
||||
(true, false) => {
|
||||
// start the full GRANDPA voter
|
||||
let voter_config = grandpa::GrandpaParams {
|
||||
config: grandpa_config,
|
||||
link: grandpa_link,
|
||||
network: service.network(),
|
||||
inherent_data_providers: inherent_data_providers.clone(),
|
||||
on_exit: service.on_exit(),
|
||||
telemetry_on_connect: Some(service.telemetry_on_connect_stream()),
|
||||
voting_rule: grandpa::VotingRulesBuilder::default().build(),
|
||||
};
|
||||
|
||||
// the GRANDPA voter task is considered infallible, i.e.
|
||||
// if it fails we take down the service with it.
|
||||
service.spawn_essential_task(grandpa::run_grandpa_voter(voter_config)?);
|
||||
},
|
||||
(_, true) => {
|
||||
grandpa::setup_disabled_grandpa(
|
||||
service.client(),
|
||||
&inherent_data_providers,
|
||||
service.network(),
|
||||
)?;
|
||||
},
|
||||
}
|
||||
|
||||
Ok(service)
|
||||
}
|
||||
|
||||
/// Builds a new service for a light client.
|
||||
pub fn new_light<C: Send + Default + 'static>(config: Configuration<C, GenesisConfig>)
|
||||
-> Result<impl AbstractService, ServiceError>
|
||||
{
|
||||
let inherent_data_providers = InherentDataProviders::new();
|
||||
|
||||
ServiceBuilder::new_light::<Block, RuntimeApi, Executor>(config)?
|
||||
.with_select_chain(|_config, backend| {
|
||||
Ok(LongestChain::new(backend.clone()))
|
||||
})?
|
||||
.with_transaction_pool(|config, client|
|
||||
Ok(TransactionPool::new(config, transaction_pool::FullChainApi::new(client)))
|
||||
)?
|
||||
.with_import_queue_and_fprb(|_config, client, backend, fetcher, _select_chain, _tx_pool| {
|
||||
let fetch_checker = fetcher
|
||||
.map(|fetcher| fetcher.checker().clone())
|
||||
.ok_or_else(|| "Trying to start light import queue without active fetch checker")?;
|
||||
let grandpa_block_import = grandpa::light_block_import::<_, _, _, RuntimeApi>(
|
||||
client.clone(), backend, &*client.clone(), Arc::new(fetch_checker),
|
||||
)?;
|
||||
let finality_proof_import = grandpa_block_import.clone();
|
||||
let finality_proof_request_builder =
|
||||
finality_proof_import.create_finality_proof_request_builder();
|
||||
|
||||
let import_queue = aura::import_queue::<_, _, AuraPair, ()>(
|
||||
aura::SlotDuration::get_or_compute(&*client)?,
|
||||
Box::new(grandpa_block_import),
|
||||
None,
|
||||
Some(Box::new(finality_proof_import)),
|
||||
client,
|
||||
inherent_data_providers.clone(),
|
||||
None,
|
||||
)?;
|
||||
|
||||
Ok((import_queue, finality_proof_request_builder))
|
||||
})?
|
||||
.with_network_protocol(|_| Ok(NodeProtocol::new()))?
|
||||
.with_finality_proof_provider(|client, backend|
|
||||
Ok(Arc::new(GrandpaFinalityProofProvider::new(backend, client)) as _)
|
||||
)?
|
||||
.build()
|
||||
}
|
||||
Reference in New Issue
Block a user