Merge branch 'develop' into next

This commit is contained in:
Luke Parker
2025-10-05 18:43:53 -04:00
102 changed files with 3016 additions and 4149 deletions

View File

@@ -1,3 +1,5 @@
#![expect(unused_imports, dead_code, redundant_closure_call)]
use core::marker::PhantomData;
use sp_core::Pair as PairTrait;
@@ -14,7 +16,7 @@ use serai_runtime::{
SignalsConfig, BabeConfig, GrandpaConfig, EmissionsConfig,
};
pub type ChainSpec = sc_service::GenericChainSpec<RuntimeGenesisConfig>;
pub type ChainSpec = sc_service::GenericChainSpec;
fn account_from_name(name: &'static str) -> PublicKey {
insecure_pair_from_name(name).public()
@@ -36,8 +38,8 @@ fn wasm_binary() -> Vec<u8> {
WASM_BINARY.ok_or("compiled in wasm not available").unwrap().to_vec()
}
/*
fn devnet_genesis(
wasm_binary: &[u8],
validators: &[&'static str],
endowed_accounts: Vec<PublicKey>,
) -> RuntimeGenesisConfig {
@@ -72,7 +74,7 @@ fn devnet_genesis(
.collect::<Vec<_>>();
RuntimeGenesisConfig {
system: SystemConfig { code: wasm_binary.to_vec(), _config: PhantomData },
system: SystemConfig { _config: PhantomData },
transaction_payment: Default::default(),
@@ -94,8 +96,8 @@ fn devnet_genesis(
},
signals: SignalsConfig::default(),
babe: BabeConfig {
authorities: validators.iter().map(|validator| (validator.0.into(), 1)).collect(),
epoch_config: Some(BABE_GENESIS_EPOCH_CONFIG),
authorities: validators.iter().map(|validator| ((*validator).into(), 1)).collect(),
epoch_config: BABE_GENESIS_EPOCH_CONFIG,
_config: PhantomData,
},
grandpa: GrandpaConfig {
@@ -105,8 +107,7 @@ fn devnet_genesis(
}
}
/*
fn testnet_genesis(wasm_binary: &[u8], validators: Vec<&'static str>) -> RuntimeGenesisConfig {
fn testnet_genesis(validators: Vec<&'static str>) -> RuntimeGenesisConfig {
let validators = validators
.into_iter()
.map(|validator| Public::decode(&mut hex::decode(validator).unwrap().as_slice()).unwrap())
@@ -130,7 +131,7 @@ fn testnet_genesis(wasm_binary: &[u8], validators: Vec<&'static str>) -> Runtime
assert_eq!(validators.iter().collect::<HashSet<_>>().len(), validators.len());
RuntimeGenesisConfig {
system: SystemConfig { code: wasm_binary.to_vec(), _config: PhantomData },
system: SystemConfig { _config: PhantomData },
transaction_payment: Default::default(),
@@ -150,7 +151,7 @@ fn testnet_genesis(wasm_binary: &[u8], validators: Vec<&'static str>) -> Runtime
signals: SignalsConfig::default(),
babe: BabeConfig {
authorities: validators.iter().map(|validator| ((*validator).into(), 1)).collect(),
epoch_config: Some(BABE_GENESIS_EPOCH_CONFIG),
epoch_config: BABE_GENESIS_EPOCH_CONFIG,
_config: PhantomData,
},
grandpa: GrandpaConfig {
@@ -161,18 +162,59 @@ fn testnet_genesis(wasm_binary: &[u8], validators: Vec<&'static str>) -> Runtime
}
*/
pub fn development_config() -> ChainSpec {
let wasm_binary = wasm_binary();
fn genesis(
name: &'static str,
id: &'static str,
chain_type: ChainType,
protocol_id: &'static str,
config: &RuntimeGenesisConfig,
) -> ChainSpec {
use sp_core::{
Encode,
traits::{RuntimeCode, WrappedRuntimeCode, CodeExecutor},
};
use sc_service::ChainSpec as _;
ChainSpec::from_genesis(
// Name
let bin = wasm_binary();
let hash = sp_core::blake2_256(&bin).to_vec();
let mut chain_spec = sc_chain_spec::ChainSpecBuilder::new(&bin, None)
.with_name(name)
.with_id(id)
.with_chain_type(chain_type)
.with_protocol_id(protocol_id)
.build();
let mut ext = sp_state_machine::BasicExternalities::new_empty();
let code_fetcher = WrappedRuntimeCode(bin.clone().into());
sc_executor::WasmExecutor::<sp_io::SubstrateHostFunctions>::builder()
.with_allow_missing_host_functions(true)
.build()
.call(
&mut ext,
&RuntimeCode { heap_pages: None, code_fetcher: &code_fetcher, hash },
"GenesisApi_build",
&config.encode(),
sp_core::traits::CallContext::Onchain,
)
.0
.unwrap();
let mut storage = ext.into_storages();
storage.top.insert(sp_core::storage::well_known_keys::CODE.to_vec(), bin);
chain_spec.set_storage(storage);
chain_spec
}
pub fn development_config() -> ChainSpec {
genesis(
"Development Network",
// ID
"devnet",
ChainType::Development,
move || {
devnet_genesis(
&wasm_binary,
"serai-devnet",
&(move || {
/*
let _ = devnet_genesis(
&["Alice"],
vec![
account_from_name("Alice"),
@@ -182,35 +224,22 @@ pub fn development_config() -> ChainSpec {
account_from_name("Eve"),
account_from_name("Ferdie"),
],
)
},
// Bootnodes
vec![],
// Telemetry
None,
// Protocol ID
Some("serai-devnet"),
// Fork ID
None,
// Properties
None,
// Extensions
None,
);
*/
todo!("TODO")
})(),
)
}
pub fn local_config() -> ChainSpec {
let wasm_binary = wasm_binary();
ChainSpec::from_genesis(
// Name
genesis(
"Local Test Network",
// ID
"local",
ChainType::Local,
move || {
devnet_genesis(
&wasm_binary,
"serai-local",
&(move || {
/*
let _ = devnet_genesis(
&["Alice", "Bob", "Charlie", "Dave"],
vec![
account_from_name("Alice"),
@@ -220,55 +249,31 @@ pub fn local_config() -> ChainSpec {
account_from_name("Eve"),
account_from_name("Ferdie"),
],
)
},
// Bootnodes
vec![],
// Telemetry
None,
// Protocol ID
Some("serai-local"),
// Fork ID
None,
// Properties
None,
// Extensions
None,
);
*/
todo!("TODO")
})(),
)
}
#[allow(clippy::redundant_closure_call)]
pub fn testnet_config() -> ChainSpec {
// let wasm_binary = wasm_binary();
ChainSpec::from_genesis(
// Name
"Test Network 2",
// ID
"testnet-2",
genesis(
"Test Network 0",
"testnet-0",
ChainType::Live,
move || {
// let _ = testnet_genesis(&wasm_binary, vec![])
todo!()
},
// Bootnodes
vec![],
// Telemetry
None,
// Protocol ID
Some("serai-testnet-2"),
// Fork ID
None,
// Properties
None,
// Extensions
None,
"serai-testnet-0",
&(move || {
// let _ = testnet_genesis(vec![]);
todo!("TODO")
})(),
)
}
pub fn bootnode_multiaddrs(id: &str) -> Vec<libp2p::Multiaddr> {
match id {
"devnet" | "local" => vec![],
"testnet-2" => todo!(),
"testnet-0" => todo!("TODO"),
_ => panic!("unrecognized network ID"),
}
}

View File

@@ -48,7 +48,7 @@ impl SubstrateCli for Cli {
}
pub fn run() -> sc_cli::Result<()> {
let cli = Cli::from_args();
let mut cli = Cli::from_args();
match &cli.subcommand {
Some(Subcommand::Key(cmd)) => cmd.run(&cli),
@@ -98,11 +98,20 @@ pub fn run() -> sc_cli::Result<()> {
cli.create_runner(cmd)?.sync_run(|config| cmd.run::<Block>(&config))
}
None => cli.create_runner(&cli.run)?.run_node_until_exit(|mut config| async {
if config.role.is_authority() {
config.state_pruning = Some(PruningMode::ArchiveAll);
}
service::new_full(config).map_err(sc_cli::Error::Service)
}),
None => {
cli.run.network_params.node_key_params = sc_cli::NodeKeyParams {
node_key: None,
node_key_file: None,
node_key_type: sc_cli::arg_enums::NodeKeyType::Ed25519,
unsafe_force_node_key_generation: true,
};
cli.create_runner(&cli.run)?.run_node_until_exit(|mut config| async {
if config.role.is_authority() {
config.state_pruning = Some(PruningMode::ArchiveAll);
}
service::new_full(config).map_err(sc_cli::Error::Service)
})
}
}
}

View File

@@ -1,6 +1,6 @@
use zeroize::Zeroize;
use sp_core::{crypto::*, ed25519, sr25519};
use sp_core::{crypto::*, sr25519};
use sp_keystore::*;
pub struct Keystore(sr25519::Pair);
@@ -58,36 +58,19 @@ impl sp_keystore::Keystore for Keystore {
}
}
fn sr25519_vrf_output(
fn sr25519_vrf_pre_output(
&self,
_: KeyTypeId,
public: &sr25519::Public,
input: &sr25519::vrf::VrfInput,
) -> Result<Option<sr25519::vrf::VrfOutput>, Error> {
) -> Result<Option<sr25519::vrf::VrfPreOutput>, Error> {
if public == &self.0.public() {
Ok(Some(self.0.vrf_output(input)))
Ok(Some(self.0.vrf_pre_output(input)))
} else {
Ok(None)
}
}
fn ed25519_public_keys(&self, _: KeyTypeId) -> Vec<ed25519::Public> {
panic!("asked for ed25519 keys");
}
fn ed25519_generate_new(&self, _: KeyTypeId, _: Option<&str>) -> Result<ed25519::Public, Error> {
panic!("asked to generate an ed25519 key");
}
fn ed25519_sign(
&self,
_: KeyTypeId,
_: &ed25519::Public,
_: &[u8],
) -> Result<Option<ed25519::Signature>, Error> {
panic!("asked to produce an ed25519 signature");
}
fn insert(&self, _: KeyTypeId, _: &str, _: &[u8]) -> Result<(), ()> {
panic!("asked to insert a key");
}

View File

@@ -1,7 +1,10 @@
#![expect(unused_imports)]
use std::{sync::Arc, ops::Deref, collections::HashSet};
use rand_core::{RngCore, OsRng};
use sp_core::Encode;
use sp_blockchain::{Error as BlockchainError, HeaderBackend, HeaderMetadata};
use sp_block_builder::BlockBuilder;
use sp_api::ProvideRuntimeApi;
@@ -9,24 +12,25 @@ use sp_api::ProvideRuntimeApi;
use serai_runtime::{
in_instructions::primitives::Shorthand,
primitives::{ExternalNetworkId, NetworkId, PublicKey, SubstrateAmount, QuotePriceParams},
validator_sets::ValidatorSetsApi,
// validator_sets::ValidatorSetsApi,
dex::DexApi,
Block, Nonce,
Block,
Nonce,
SeraiRuntimeApi,
};
use tokio::sync::RwLock;
use jsonrpsee::{RpcModule, core::Error};
use scale::Encode;
use jsonrpsee::RpcModule;
// use scale::Encode;
pub use sc_rpc_api::DenyUnsafe;
use sc_client_api::BlockBackend;
use sc_transaction_pool_api::TransactionPool;
pub struct FullDeps<C, P> {
pub id: String,
pub client: Arc<C>,
pub pool: Arc<P>,
pub deny_unsafe: DenyUnsafe,
pub authority_discovery: Option<sc_authority_discovery::Service>,
}
@@ -34,6 +38,7 @@ pub fn create_full<
C: ProvideRuntimeApi<Block>
+ HeaderBackend<Block>
+ HeaderMetadata<Block, Error = BlockchainError>
+ BlockBackend<Block>
+ Send
+ Sync
+ 'static,
@@ -42,9 +47,9 @@ pub fn create_full<
deps: FullDeps<C, P>,
) -> Result<RpcModule<()>, Box<dyn std::error::Error + Send + Sync>>
where
C::Api: substrate_frame_rpc_system::AccountNonceApi<Block, PublicKey, Nonce>
C::Api: frame_system_rpc_runtime_api::AccountNonceApi<Block, PublicKey, Nonce>
+ pallet_transaction_payment_rpc::TransactionPaymentRuntimeApi<Block, SubstrateAmount>
+ ValidatorSetsApi<Block>
+ SeraiRuntimeApi<Block>
+ DexApi<Block>
+ BlockBuilder<Block>,
{
@@ -56,9 +61,9 @@ where
use bitcoin_serai::bitcoin;
let mut module = RpcModule::new(());
let FullDeps { id, client, pool, deny_unsafe, authority_discovery } = deps;
let FullDeps { id, client, pool, authority_discovery } = deps;
module.merge(System::new(client.clone(), pool, deny_unsafe).into_rpc())?;
module.merge(System::new(client.clone(), pool).into_rpc())?;
module.merge(TransactionPayment::new(client.clone()).into_rpc())?;
if let Some(authority_discovery) = authority_discovery {
@@ -66,17 +71,25 @@ where
RpcModule::new((id, client.clone(), RwLock::new(authority_discovery)));
authority_discovery_module.register_async_method(
"p2p_validators",
|params, context| async move {
let network: NetworkId = params.parse()?;
|params, context, _ext| async move {
let [network]: [NetworkId; 1] = params.parse()?;
let (id, client, authority_discovery) = &*context;
let latest_block = client.info().best_hash;
let validators = client.runtime_api().validators(latest_block, network).map_err(|_| {
Error::to_call_error(std::io::Error::other(format!(
"couldn't get validators from the latest block, which is likely a fatal bug. {}",
"please report this at https://github.com/serai-dex/serai/issues",
)))
})?;
jsonrpsee::types::error::ErrorObjectOwned::owned(
-1,
format!(
"couldn't get validators from the latest block, which is likely a fatal bug. {}",
"please report this at https://github.com/serai-dex/serai",
),
Option::<()>::None,
)
});
let validators = match validators {
Ok(validators) => validators,
Err(e) => return Err(e),
};
// Always return the protocol's bootnodes
let mut all_p2p_addresses = crate::chain_spec::bootnode_multiaddrs(id);
// Additionally returns validators found over the DHT
@@ -96,9 +109,9 @@ where
// It isn't beneficial to use multiple addresses for a single peer here
if !returned_addresses.is_empty() {
all_p2p_addresses.push(
returned_addresses.remove(
usize::try_from(OsRng.next_u64() >> 32).unwrap() % returned_addresses.len(),
),
returned_addresses
.remove(usize::try_from(OsRng.next_u64() >> 32).unwrap() % returned_addresses.len())
.into(),
);
}
}
@@ -108,6 +121,7 @@ where
module.merge(authority_discovery_module)?;
}
/* TODO
let mut serai_json_module = RpcModule::new(client);
// add network address rpc
@@ -199,5 +213,35 @@ where
})?;
module.merge(serai_json_module)?;
*/
let mut block_bin_module = RpcModule::new(client);
block_bin_module.register_async_method(
"chain_getBlockBin",
|params, client, _ext| async move {
let [block_hash]: [String; 1] = params.parse()?;
let Some(block_hash) = hex::decode(&block_hash).ok().and_then(|bytes| {
<[u8; 32]>::try_from(bytes.as_slice())
.map(<Block as sp_runtime::traits::Block>::Hash::from)
.ok()
}) else {
return Err(jsonrpsee::types::error::ErrorObjectOwned::owned(
-1,
"requested block hash wasn't a valid hash",
Option::<()>::None,
));
};
let Some(block) = client.block(block_hash).ok().flatten() else {
return Err(jsonrpsee::types::error::ErrorObjectOwned::owned(
-1,
"couldn't find requested block",
Option::<()>::None,
));
};
Ok(hex::encode(block.block.encode()))
},
)?;
module.merge(block_bin_module)?;
Ok(module)
}

View File

@@ -8,12 +8,11 @@ use sp_consensus_babe::{SlotDuration, inherents::InherentDataProvider as BabeInh
use sp_io::SubstrateHostFunctions;
use sc_executor::{sp_wasm_interface::ExtendedHostFunctions, WasmExecutor};
use sc_network_common::sync::warp::WarpSyncParams;
use sc_network::{Event, NetworkEventStream};
use sc_network::{Event, NetworkEventStream, NetworkBackend};
use sc_service::{error::Error as ServiceError, Configuration, TaskManager, TFullClient};
use sc_transaction_pool_api::OffchainTransactionPoolFactory;
use sc_client_api::{BlockBackend, Backend};
use sc_client_api::BlockBackend;
use sc_telemetry::{Telemetry, TelemetryWorker};
@@ -40,8 +39,8 @@ type PartialComponents = sc_service::PartialComponents<
FullClient,
FullBackend,
SelectChain,
sc_consensus::DefaultImportQueue<Block, FullClient>,
sc_transaction_pool::FullPool<Block, FullClient>,
sc_consensus::DefaultImportQueue<Block>,
sc_transaction_pool::TransactionPoolWrapper<Block, FullClient>,
(
BabeBlockImport,
sc_consensus_babe::BabeLink<Block>,
@@ -74,11 +73,11 @@ pub fn new_partial(
#[allow(deprecated)]
let executor = Executor::new(
config.wasm_method,
config.default_heap_pages,
config.max_runtime_instances,
config.executor.wasm_method,
config.executor.default_heap_pages,
config.executor.max_runtime_instances,
None,
config.runtime_cache_size,
config.executor.runtime_cache_size,
);
let (client, backend, keystore_container, task_manager) =
@@ -103,16 +102,19 @@ pub fn new_partial(
let select_chain = sc_consensus::LongestChain::new(backend.clone());
let transaction_pool = sc_transaction_pool::BasicPool::new_full(
config.transaction_pool.clone(),
config.role.is_authority().into(),
config.prometheus_registry(),
let transaction_pool = sc_transaction_pool::Builder::new(
task_manager.spawn_essential_handle(),
client.clone(),
);
config.role.is_authority().into(),
)
.with_options(config.transaction_pool.clone())
.with_prometheus(config.prometheus_registry())
.build();
let transaction_pool = Arc::new(transaction_pool);
let (grandpa_block_import, grandpa_link) = grandpa::block_import(
client.clone(),
u32::MAX,
&client,
select_chain.clone(),
telemetry.as_ref().map(Telemetry::handle),
@@ -181,22 +183,26 @@ pub fn new_full(mut config: Configuration) -> Result<TaskManager, ServiceError>
config.network.listen_addresses =
vec!["/ip4/0.0.0.0/tcp/30333".parse().unwrap(), "/ip6/::/tcp/30333".parse().unwrap()];
let mut net_config = sc_network::config::FullNetworkConfiguration::new(&config.network);
type N = sc_network::service::NetworkWorker<Block, <Block as sp_runtime::traits::Block>::Hash>;
let mut net_config = sc_network::config::FullNetworkConfiguration::<_, _, N>::new(
&config.network,
config.prometheus_registry().cloned(),
);
let metrics = N::register_notification_metrics(config.prometheus_registry());
let grandpa_protocol_name =
grandpa::protocol_standard_name(&client.block_hash(0).unwrap().unwrap(), &config.chain_spec);
net_config.add_notification_protocol(sc_consensus_grandpa::grandpa_peers_set_config(
grandpa_protocol_name.clone(),
));
let (grandpa_protocol_config, grandpa_notification_service) =
sc_consensus_grandpa::grandpa_peers_set_config::<Block, N>(
grandpa_protocol_name.clone(),
metrics.clone(),
net_config.peer_store_handle(),
);
net_config.add_notification_protocol(grandpa_protocol_config);
let publish_non_global_ips = config.network.allow_non_globals_in_dht;
let warp_sync = Arc::new(grandpa::warp_proof::NetworkProvider::new(
backend.clone(),
grandpa_link.shared_authority_set().clone(),
vec![],
));
let (network, system_rpc_tx, tx_handler_controller, network_starter, sync_service) =
let (network, system_rpc_tx, tx_handler_controller, sync_service) =
sc_service::build_network(sc_service::BuildNetworkParams {
config: &config,
net_config,
@@ -205,7 +211,9 @@ pub fn new_full(mut config: Configuration) -> Result<TaskManager, ServiceError>
spawn_handle: task_manager.spawn_handle(),
import_queue,
block_announce_validator_builder: None,
warp_sync_params: Some(WarpSyncParams::WithProvider(warp_sync)),
metrics,
block_relay: None,
warp_sync_config: None,
})?;
task_manager.spawn_handle().spawn("bootnodes", "bootnodes", {
@@ -217,7 +225,15 @@ pub fn new_full(mut config: Configuration) -> Result<TaskManager, ServiceError>
// While the PeerIds *should* be known in advance and hardcoded, that data wasn't collected in
// time and this fine for a testnet
let bootnodes = || async {
use libp2p::{Transport as TransportTrait, tcp::tokio::Transport, noise::Config};
use libp2p::{
core::{
Endpoint,
transport::{PortUse, DialOpts},
},
Transport as TransportTrait,
tcp::tokio::Transport,
noise::Config,
};
let bootnode_multiaddrs = crate::chain_spec::bootnode_multiaddrs(&id);
@@ -231,9 +247,17 @@ pub fn new_full(mut config: Configuration) -> Result<TaskManager, ServiceError>
.upgrade(libp2p::core::upgrade::Version::V1)
.authenticate(noise)
.multiplex(libp2p::yamux::Config::default());
let Ok(transport) = transport.dial(multiaddr.clone()) else { None? };
let Ok(transport) = transport.dial(
multiaddr.clone(),
DialOpts { role: Endpoint::Dialer, port_use: PortUse::Reuse },
) else {
None?
};
let Ok((peer_id, _)) = transport.await else { None? };
Some(sc_network::config::MultiaddrWithPeerId { multiaddr, peer_id })
Some(sc_network::config::MultiaddrWithPeerId {
multiaddr: multiaddr.into(),
peer_id: peer_id.into(),
})
}),
));
}
@@ -261,26 +285,12 @@ pub fn new_full(mut config: Configuration) -> Result<TaskManager, ServiceError>
}
});
if config.offchain_worker.enabled {
task_manager.spawn_handle().spawn(
"offchain-workers-runner",
"offchain-worker",
sc_offchain::OffchainWorkers::new(sc_offchain::OffchainWorkerOptions {
runtime_api_provider: client.clone(),
is_validator: config.role.is_authority(),
keystore: Some(keystore_container.clone()),
offchain_db: backend.offchain_storage(),
transaction_pool: Some(OffchainTransactionPoolFactory::new(transaction_pool.clone())),
network_provider: network.clone(),
enable_http_requests: true,
custom_extensions: |_| vec![],
})
.run(client.clone(), task_manager.spawn_handle()),
);
}
let role = config.role.clone();
let role = config.role;
let keystore = keystore_container;
if let Some(seed) = config.dev_key_seed.as_ref() {
let _ =
keystore.sr25519_generate_new(sp_core::crypto::key_types::AUTHORITY_DISCOVERY, Some(seed));
}
let prometheus_registry = config.prometheus_registry().cloned();
// TODO: Ensure we're considered as an authority is a validator of an external network
@@ -294,7 +304,7 @@ pub fn new_full(mut config: Configuration) -> Result<TaskManager, ServiceError>
worker
},
client.clone(),
network.clone(),
Arc::new(network.clone()),
Box::pin(network.event_stream("authority-discovery").filter_map(|e| async move {
match e {
Event::Dht(e) => Some(e),
@@ -303,6 +313,7 @@ pub fn new_full(mut config: Configuration) -> Result<TaskManager, ServiceError>
})),
sc_authority_discovery::Role::PublishAndDiscover(keystore.clone()),
prometheus_registry.clone(),
task_manager.spawn_handle(),
);
task_manager.spawn_handle().spawn(
"authority-discovery-worker",
@@ -320,12 +331,11 @@ pub fn new_full(mut config: Configuration) -> Result<TaskManager, ServiceError>
let client = client.clone();
let pool = transaction_pool.clone();
Box::new(move |deny_unsafe, _| {
Box::new(move |_| {
crate::rpc::create_full(crate::rpc::FullDeps {
id: id.clone(),
client: client.clone(),
pool: pool.clone(),
deny_unsafe,
authority_discovery: authority_discovery.clone(),
})
.map_err(Into::into)
@@ -392,7 +402,7 @@ pub fn new_full(mut config: Configuration) -> Result<TaskManager, ServiceError>
grandpa::run_grandpa_voter(grandpa::GrandpaParams {
config: grandpa::Config {
gossip_duration: std::time::Duration::from_millis(333),
justification_period: 512,
justification_generation_period: 512,
name: Some(name),
observer_enabled: false,
keystore: if role.is_authority() { Some(keystore) } else { None },
@@ -408,10 +418,10 @@ pub fn new_full(mut config: Configuration) -> Result<TaskManager, ServiceError>
prometheus_registry,
shared_voter_state,
offchain_tx_pool_factory: OffchainTransactionPoolFactory::new(transaction_pool),
notification_service: grandpa_notification_service,
})?,
);
}
network_starter.start_network();
Ok(task_manager)
}