From 33dd412e67cd60bc6338ea3f90288f18b1e1c14d Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Fri, 12 Apr 2024 00:38:40 -0400 Subject: [PATCH] Add bootnode code prior used in testnet-internal (#554) * Add bootnode code prior used in testnet-internal Also performs the devnet/testnet differentation done since the testnet branch. * Fixes * fmt --- Cargo.lock | 1 + substrate/node/Cargo.toml | 2 + substrate/node/src/chain_spec.rs | 102 ++++++++++++++++++++++++++++--- substrate/node/src/command.rs | 3 +- substrate/node/src/rpc.rs | 12 ++-- substrate/node/src/service.rs | 62 ++++++++++++++++++- 6 files changed, 169 insertions(+), 13 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 6840c5ea..ee2ecdcf 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7613,6 +7613,7 @@ dependencies = [ "futures-util", "hex", "jsonrpsee", + "libp2p", "pallet-transaction-payment-rpc", "rand_core", "sc-authority-discovery", diff --git a/substrate/node/Cargo.toml b/substrate/node/Cargo.toml index 12ba4d17..60f7dc0f 100644 --- a/substrate/node/Cargo.toml +++ b/substrate/node/Cargo.toml @@ -26,6 +26,8 @@ hex = "0.4" rand_core = "0.6" schnorrkel = "0.11" +libp2p = "0.52" + sp-core = { git = "https://github.com/serai-dex/substrate" } sp-keystore = { git = "https://github.com/serai-dex/substrate" } sp-timestamp = { git = "https://github.com/serai-dex/substrate" } diff --git a/substrate/node/src/chain_spec.rs b/substrate/node/src/chain_spec.rs index b630c00b..6fa8d6c3 100644 --- a/substrate/node/src/chain_spec.rs +++ b/substrate/node/src/chain_spec.rs @@ -1,6 +1,7 @@ use core::marker::PhantomData; +use std::collections::HashSet; -use sp_core::Pair as PairTrait; +use sp_core::{Decode, Pair as PairTrait, sr25519::Public}; use sc_service::ChainType; @@ -23,7 +24,7 @@ fn wasm_binary() -> Vec { WASM_BINARY.ok_or("compiled in wasm not available").unwrap().to_vec() } -fn testnet_genesis( +fn devnet_genesis( wasm_binary: &[u8], validators: &[&'static str], endowed_accounts: Vec, @@ -72,6 +73,57 @@ fn testnet_genesis( } } +fn testnet_genesis(wasm_binary: &[u8], validators: Vec<&'static str>) -> RuntimeGenesisConfig { + let validators = validators + .into_iter() + .map(|validator| Public::decode(&mut hex::decode(validator).unwrap().as_slice()).unwrap()) + .collect::>(); + + assert_eq!(validators.iter().collect::>().len(), validators.len()); + + RuntimeGenesisConfig { + system: SystemConfig { code: wasm_binary.to_vec(), _config: PhantomData }, + + transaction_payment: Default::default(), + + coins: CoinsConfig { + accounts: validators + .iter() + .map(|a| (*a, Balance { coin: Coin::Serai, amount: Amount(5_000_000 * 10_u64.pow(8)) })) + .collect(), + _ignore: Default::default(), + }, + + dex: DexConfig { + pools: vec![Coin::Bitcoin, Coin::Ether, Coin::Dai, Coin::Monero], + _ignore: Default::default(), + }, + + validator_sets: ValidatorSetsConfig { + networks: serai_runtime::primitives::NETWORKS + .iter() + .map(|network| match network { + NetworkId::Serai => (NetworkId::Serai, Amount(50_000 * 10_u64.pow(8))), + NetworkId::Bitcoin => (NetworkId::Bitcoin, Amount(1_000_000 * 10_u64.pow(8))), + NetworkId::Ethereum => (NetworkId::Ethereum, Amount(1_000_000 * 10_u64.pow(8))), + NetworkId::Monero => (NetworkId::Monero, Amount(100_000 * 10_u64.pow(8))), + }) + .collect(), + participants: validators.clone(), + }, + signals: SignalsConfig::default(), + babe: BabeConfig { + authorities: validators.iter().map(|validator| ((*validator).into(), 1)).collect(), + epoch_config: Some(BABE_GENESIS_EPOCH_CONFIG), + _config: PhantomData, + }, + grandpa: GrandpaConfig { + authorities: validators.into_iter().map(|validator| (validator.into(), 1)).collect(), + _config: PhantomData, + }, + } +} + pub fn development_config() -> ChainSpec { let wasm_binary = wasm_binary(); @@ -82,7 +134,7 @@ pub fn development_config() -> ChainSpec { "devnet", ChainType::Development, move || { - testnet_genesis( + devnet_genesis( &wasm_binary, &["Alice"], vec![ @@ -100,7 +152,7 @@ pub fn development_config() -> ChainSpec { // Telemetry None, // Protocol ID - Some("serai"), + Some("serai-devnet"), // Fork ID None, // Properties @@ -110,7 +162,7 @@ pub fn development_config() -> ChainSpec { ) } -pub fn testnet_config() -> ChainSpec { +pub fn local_config() -> ChainSpec { let wasm_binary = wasm_binary(); ChainSpec::from_genesis( @@ -120,7 +172,7 @@ pub fn testnet_config() -> ChainSpec { "local", ChainType::Local, move || { - testnet_genesis( + devnet_genesis( &wasm_binary, &["Alice", "Bob", "Charlie", "Dave"], vec![ @@ -138,7 +190,7 @@ pub fn testnet_config() -> ChainSpec { // Telemetry None, // Protocol ID - Some("serai"), + Some("serai-local"), // Fork ID None, // Properties @@ -147,3 +199,39 @@ pub fn testnet_config() -> ChainSpec { None, ) } + +pub fn testnet_config() -> ChainSpec { + let wasm_binary = wasm_binary(); + + ChainSpec::from_genesis( + // Name + "Test Network 2", + // ID + "testnet-2", + 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, + ) +} + +pub fn bootnode_multiaddrs(id: &str) -> Vec { + match id { + "devnet" | "local" => vec![], + "testnet-2" => todo!(), + _ => panic!("unrecognized network ID"), + } +} diff --git a/substrate/node/src/command.rs b/substrate/node/src/command.rs index 2f7ea0f7..71eee047 100644 --- a/substrate/node/src/command.rs +++ b/substrate/node/src/command.rs @@ -40,7 +40,8 @@ impl SubstrateCli for Cli { fn load_spec(&self, id: &str) -> Result, String> { match id { "dev" | "devnet" => Ok(Box::new(chain_spec::development_config())), - "local" => Ok(Box::new(chain_spec::testnet_config())), + "local" => Ok(Box::new(chain_spec::local_config())), + "testnet" => Ok(Box::new(chain_spec::testnet_config())), _ => panic!("Unknown network ID"), } } diff --git a/substrate/node/src/rpc.rs b/substrate/node/src/rpc.rs index d07778cc..b818c798 100644 --- a/substrate/node/src/rpc.rs +++ b/substrate/node/src/rpc.rs @@ -19,6 +19,7 @@ pub use sc_rpc_api::DenyUnsafe; use sc_transaction_pool_api::TransactionPool; pub struct FullDeps { + pub id: String, pub client: Arc, pub pool: Arc

, pub deny_unsafe: DenyUnsafe, @@ -46,18 +47,19 @@ where use pallet_transaction_payment_rpc::{TransactionPayment, TransactionPaymentApiServer}; let mut module = RpcModule::new(()); - let FullDeps { client, pool, deny_unsafe, authority_discovery } = deps; + let FullDeps { id, client, pool, deny_unsafe, authority_discovery } = deps; module.merge(System::new(client.clone(), pool, deny_unsafe).into_rpc())?; module.merge(TransactionPayment::new(client.clone()).into_rpc())?; if let Some(authority_discovery) = authority_discovery { - let mut authority_discovery_module = RpcModule::new((client, RwLock::new(authority_discovery))); + let mut authority_discovery_module = + RpcModule::new((id, client, RwLock::new(authority_discovery))); authority_discovery_module.register_async_method( "p2p_validators", |params, context| async move { let network: NetworkId = params.parse()?; - let (client, authority_discovery) = &*context; + let (id, client, authority_discovery) = &*context; let latest_block = client.info().best_hash; let validators = client.runtime_api().validators(latest_block, network).map_err(|_| { @@ -66,7 +68,9 @@ where "please report this at https://github.com/serai-dex/serai", ))) })?; - let mut all_p2p_addresses = vec![]; + // Always return the protocol's bootnodes + let mut all_p2p_addresses = crate::chain_spec::bootnode_multiaddrs(id); + // Additionally returns validators found over the DHT for validator in validators { let mut returned_addresses = authority_discovery .write() diff --git a/substrate/node/src/service.rs b/substrate/node/src/service.rs index 686e4c39..5f76decf 100644 --- a/substrate/node/src/service.rs +++ b/substrate/node/src/service.rs @@ -161,7 +161,7 @@ pub fn new_partial( )) } -pub fn new_full(config: Configuration) -> Result { +pub fn new_full(mut config: Configuration) -> Result { let ( sc_service::PartialComponents { client, @@ -176,6 +176,11 @@ pub fn new_full(config: Configuration) -> Result { keystore_container, ) = new_partial(&config)?; + config.network.node_name = "serai".to_string(); + config.network.client_version = "0.1.0".to_string(); + 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); let grandpa_protocol_name = grandpa::protocol_standard_name(&client.block_hash(0).unwrap().unwrap(), &config.chain_spec); @@ -203,6 +208,59 @@ pub fn new_full(config: Configuration) -> Result { warp_sync_params: Some(WarpSyncParams::WithProvider(warp_sync)), })?; + task_manager.spawn_handle().spawn("bootnodes", "bootnodes", { + let network = network.clone(); + let id = config.chain_spec.id().to_string(); + + async move { + // Transforms the above Multiaddrs into MultiaddrWithPeerIds + // 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}; + + let bootnode_multiaddrs = crate::chain_spec::bootnode_multiaddrs(&id); + + let mut tasks = vec![]; + for multiaddr in bootnode_multiaddrs { + tasks.push(tokio::time::timeout( + core::time::Duration::from_secs(10), + tokio::task::spawn(async move { + let Ok(noise) = Config::new(&sc_network::Keypair::generate_ed25519()) else { None? }; + let mut transport = Transport::default() + .upgrade(libp2p::core::upgrade::Version::V1) + .authenticate(noise) + .multiplex(libp2p::yamux::Config::default()); + let Ok(transport) = transport.dial(multiaddr.clone()) else { None? }; + let Ok((peer_id, _)) = transport.await else { None? }; + Some(sc_network::config::MultiaddrWithPeerId { multiaddr, peer_id }) + }), + )); + } + + let mut res = vec![]; + for task in tasks { + if let Ok(Ok(Some(bootnode))) = task.await { + res.push(bootnode); + } + } + res + }; + + use sc_network::{NetworkStatusProvider, NetworkPeers}; + loop { + if let Ok(status) = network.status().await { + if status.num_connected_peers < 3 { + for bootnode in bootnodes().await { + let _ = network.add_reserved_peer(bootnode); + } + } + } + tokio::time::sleep(core::time::Duration::from_secs(60)).await; + } + } + }); + if config.offchain_worker.enabled { task_manager.spawn_handle().spawn( "offchain-workers-runner", @@ -258,11 +316,13 @@ pub fn new_full(config: Configuration) -> Result { }; let rpc_builder = { + let id = config.chain_spec.id().to_string(); let client = client.clone(); let pool = transaction_pool.clone(); Box::new(move |deny_unsafe, _| { crate::rpc::create_full(crate::rpc::FullDeps { + id: id.clone(), client: client.clone(), pool: pool.clone(), deny_unsafe,