Integrate litep2p into Polkadot SDK (#2944)

[litep2p](https://github.com/altonen/litep2p) is a libp2p-compatible P2P
networking library. It supports all of the features of `rust-libp2p`
that are currently being utilized by Polkadot SDK.

Compared to `rust-libp2p`, `litep2p` has a quite different architecture
which is why the new `litep2p` network backend is only able to use a
little of the existing code in `sc-network`. The design has been mainly
influenced by how we'd wish to structure our networking-related code in
Polkadot SDK: independent higher-levels protocols directly communicating
with the network over links that support bidirectional backpressure. A
good example would be `NotificationHandle`/`RequestResponseHandle`
abstractions which allow, e.g., `SyncingEngine` to directly communicate
with peers to announce/request blocks.

I've tried running `polkadot --network-backend litep2p` with a few
different peer configurations and there is a noticeable reduction in
networking CPU usage. For high load (`--out-peers 200`), networking CPU
usage goes down from ~110% to ~30% (80 pp) and for normal load
(`--out-peers 40`), the usage goes down from ~55% to ~18% (37 pp).

These should not be taken as final numbers because:

a) there are still some low-hanging optimization fruits, such as
enabling [receive window
auto-tuning](https://github.com/libp2p/rust-yamux/pull/176), integrating
`Peerset` more closely with `litep2p` or improving memory usage of the
WebSocket transport
b) fixing bugs/instabilities that incorrectly cause `litep2p` to do less
work will increase the networking CPU usage
c) verification in a more diverse set of tests/conditions is needed

Nevertheless, these numbers should give an early estimate for CPU usage
of the new networking backend.

This PR consists of three separate changes:
* introduce a generic `PeerId` (wrapper around `Multihash`) so that we
don't have use `NetworkService::PeerId` in every part of the code that
uses a `PeerId`
* introduce `NetworkBackend` trait, implement it for the libp2p network
stack and make Polkadot SDK generic over `NetworkBackend`
  * implement `NetworkBackend` for litep2p

The new library should be considered experimental which is why
`rust-libp2p` will remain as the default option for the time being. This
PR currently depends on the master branch of `litep2p` but I'll cut a
new release for the library once all review comments have been
addresses.

---------

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>
Co-authored-by: Dmitry Markin <dmitry@markin.tech>
Co-authored-by: Alexandru Vasile <60601340+lexnv@users.noreply.github.com>
Co-authored-by: Alexandru Vasile <alexandru.vasile@parity.io>
This commit is contained in:
Aaro Altonen
2024-04-08 19:44:13 +03:00
committed by GitHub
parent 9543d31474
commit 80616f6d03
181 changed files with 11055 additions and 1862 deletions
+68 -61
View File
@@ -43,10 +43,12 @@ use sc_executor::{
use sc_keystore::LocalKeystore;
use sc_network::{
config::{FullNetworkConfiguration, SyncMode},
peer_store::PeerStore,
NetworkService, NetworkStateInfo, NetworkStatusProvider,
service::{
traits::{PeerStore, RequestResponseConfig},
NotificationMetrics,
},
NetworkBackend, NetworkStateInfo,
};
use sc_network_bitswap::BitswapRequestHandler;
use sc_network_common::role::Roles;
use sc_network_light::light_client_requests::handler::LightClientRequestHandler;
use sc_network_sync::{
@@ -342,19 +344,6 @@ where
)
}
/// Shared network instance implementing a set of mandatory traits.
pub trait SpawnTaskNetwork<Block: BlockT>:
NetworkStateInfo + NetworkStatusProvider + Send + Sync + 'static
{
}
impl<T, Block> SpawnTaskNetwork<Block> for T
where
Block: BlockT,
T: NetworkStateInfo + NetworkStatusProvider + Send + Sync + 'static,
{
}
/// Parameters to pass into `build`.
pub struct SpawnTasksParams<'a, TBl: BlockT, TCl, TExPool, TRpc, Backend> {
/// The service configuration.
@@ -373,7 +362,7 @@ pub struct SpawnTasksParams<'a, TBl: BlockT, TCl, TExPool, TRpc, Backend> {
pub rpc_builder:
Box<dyn Fn(DenyUnsafe, SubscriptionTaskExecutor) -> Result<RpcModule<TRpc>, Error>>,
/// A shared network instance.
pub network: Arc<dyn SpawnTaskNetwork<TBl>>,
pub network: Arc<dyn sc_network::service::traits::NetworkService>,
/// A Sender for RPC requests.
pub system_rpc_tx: TracingUnboundedSender<sc_rpc::system::Request<TBl>>,
/// Controller for transactions handlers
@@ -736,11 +725,18 @@ where
}
/// Parameters to pass into `build_network`.
pub struct BuildNetworkParams<'a, TBl: BlockT, TExPool, TImpQu, TCl> {
pub struct BuildNetworkParams<
'a,
TBl: BlockT,
TNet: NetworkBackend<TBl, <TBl as BlockT>::Hash>,
TExPool,
TImpQu,
TCl,
> {
/// The service configuration.
pub config: &'a Configuration,
/// Full network configuration.
pub net_config: FullNetworkConfiguration,
pub net_config: FullNetworkConfiguration<TBl, <TBl as BlockT>::Hash, TNet>,
/// A shared client returned by `new_full_parts`.
pub client: Arc<TCl>,
/// A shared transaction pool.
@@ -756,15 +752,17 @@ pub struct BuildNetworkParams<'a, TBl: BlockT, TExPool, TImpQu, TCl> {
pub warp_sync_params: Option<WarpSyncParams<TBl>>,
/// User specified block relay params. If not specified, the default
/// block request handler will be used.
pub block_relay: Option<BlockRelayParams<TBl>>,
pub block_relay: Option<BlockRelayParams<TBl, TNet>>,
/// Metrics.
pub metrics: NotificationMetrics,
}
/// Build the network service, the network status sinks and an RPC sender.
pub fn build_network<TBl, TExPool, TImpQu, TCl>(
params: BuildNetworkParams<TBl, TExPool, TImpQu, TCl>,
pub fn build_network<TBl, TNet, TExPool, TImpQu, TCl>(
params: BuildNetworkParams<TBl, TNet, TExPool, TImpQu, TCl>,
) -> Result<
(
Arc<NetworkService<TBl, <TBl as BlockT>::Hash>>,
Arc<dyn sc_network::service::traits::NetworkService>,
TracingUnboundedSender<sc_rpc::system::Request<TBl>>,
sc_network_transactions::TransactionsHandlerController<<TBl as BlockT>::Hash>,
NetworkStarter,
@@ -785,6 +783,7 @@ where
+ 'static,
TExPool: TransactionPool<Block = TBl, Hash = <TBl as BlockT>::Hash> + 'static,
TImpQu: ImportQueue<TBl> + 'static,
TNet: NetworkBackend<TBl, <TBl as BlockT>::Hash>,
{
let BuildNetworkParams {
config,
@@ -796,6 +795,7 @@ where
block_announce_validator_builder,
warp_sync_params,
block_relay,
metrics,
} = params;
if warp_sync_params.is_none() && config.network.sync_mode.is_warp() {
@@ -830,7 +830,7 @@ where
None => {
// Custom protocol was not specified, use the default block handler.
// Allow both outgoing and incoming requests.
let params = BlockRequestHandler::new(
let params = BlockRequestHandler::new::<TNet>(
chain_sync_network_handle.clone(),
&protocol_id,
config.chain_spec.fork_id(),
@@ -849,13 +849,13 @@ where
let num_peer_hint = net_config.network_config.default_peers_set_num_full as usize +
net_config.network_config.default_peers_set.reserved_nodes.len();
// Allow both outgoing and incoming requests.
let (handler, protocol_config) = StateRequestHandler::new(
let (handler, protocol_config) = StateRequestHandler::new::<TNet>(
&protocol_id,
config.chain_spec.fork_id(),
client.clone(),
num_peer_hint,
);
let config_name = protocol_config.name.clone();
let config_name = protocol_config.protocol_name().clone();
spawn_handle.spawn("state-request-handler", Some("networking"), handler.run());
(protocol_config, config_name)
@@ -864,13 +864,13 @@ where
let (warp_sync_protocol_config, warp_request_protocol_name) = match warp_sync_params.as_ref() {
Some(WarpSyncParams::WithProvider(warp_with_provider)) => {
// Allow both outgoing and incoming requests.
let (handler, protocol_config) = WarpSyncRequestHandler::new(
let (handler, protocol_config) = WarpSyncRequestHandler::new::<_, TNet>(
protocol_id.clone(),
genesis_hash,
config.chain_spec.fork_id(),
warp_with_provider.clone(),
);
let config_name = protocol_config.name.clone();
let config_name = protocol_config.protocol_name().clone();
spawn_handle.spawn("warp-sync-request-handler", Some("networking"), handler.run());
(Some(protocol_config), Some(config_name))
@@ -880,7 +880,7 @@ where
let light_client_request_protocol_config = {
// Allow both outgoing and incoming requests.
let (handler, protocol_config) = LightClientRequestHandler::new(
let (handler, protocol_config) = LightClientRequestHandler::new::<TNet>(
&protocol_id,
config.chain_spec.fork_id(),
client.clone(),
@@ -898,30 +898,27 @@ where
net_config.add_request_response_protocol(config);
}
if config.network.ipfs_server {
let (handler, protocol_config) = BitswapRequestHandler::new(client.clone());
spawn_handle.spawn("bitswap-request-handler", Some("networking"), handler.run());
net_config.add_request_response_protocol(protocol_config);
}
let bitswap_config = config.network.ipfs_server.then(|| {
let (handler, config) = TNet::bitswap_server(client.clone());
spawn_handle.spawn("bitswap-request-handler", Some("networking"), handler);
config
});
// create transactions protocol and add it to the list of supported protocols of
let peer_store_handle = net_config.peer_store_handle();
let (transactions_handler_proto, transactions_config) =
sc_network_transactions::TransactionsHandlerPrototype::new(
sc_network_transactions::TransactionsHandlerPrototype::new::<_, TBl, TNet>(
protocol_id.clone(),
genesis_hash,
config.chain_spec.fork_id(),
metrics.clone(),
Arc::clone(&peer_store_handle),
);
net_config.add_notification_protocol(transactions_config);
// Create `PeerStore` and initialize it with bootnode peer ids.
let peer_store = PeerStore::new(
net_config
.network_config
.boot_nodes
.iter()
.map(|bootnode| bootnode.peer_id)
.collect(),
);
// Start task for `PeerStore`
let peer_store = net_config.take_peer_store();
let peer_store_handle = peer_store.handle();
spawn_handle.spawn("peer-store", Some("networking"), peer_store.run());
@@ -929,6 +926,7 @@ where
Roles::from(&config.role),
client.clone(),
config.prometheus_config.as_ref().map(|config| config.registry.clone()).as_ref(),
metrics.clone(),
&net_config,
protocol_id.clone(),
&config.chain_spec.fork_id().map(ToOwned::to_owned),
@@ -939,13 +937,13 @@ where
block_downloader,
state_request_protocol_name,
warp_request_protocol_name,
peer_store_handle.clone(),
Arc::clone(&peer_store_handle),
)?;
let sync_service_import_queue = sync_service.clone();
let sync_service = Arc::new(sync_service);
let genesis_hash = client.hash(Zero::zero()).ok().flatten().expect("Genesis block exists; qed");
let network_params = sc_network::config::Params::<TBl> {
let network_params = sc_network::config::Params::<TBl, <TBl as BlockT>::Hash, TNet> {
role: config.role.clone(),
executor: {
let spawn_handle = Clone::clone(&spawn_handle);
@@ -954,17 +952,18 @@ where
})
},
network_config: net_config,
peer_store: peer_store_handle,
genesis_hash,
protocol_id: protocol_id.clone(),
fork_id: config.chain_spec.fork_id().map(ToOwned::to_owned),
metrics_registry: config.prometheus_config.as_ref().map(|config| config.registry.clone()),
block_announce_config,
bitswap_config,
notification_metrics: metrics,
};
let has_bootnodes = !network_params.network_config.network_config.boot_nodes.is_empty();
let network_mut = sc_network::NetworkWorker::new(network_params)?;
let network = network_mut.service().clone();
let network_mut = TNet::new(network_params)?;
let network = network_mut.network_service().clone();
let (tx_handler, tx_handler_controller) = transactions_handler_proto.build(
network.clone(),
@@ -972,12 +971,16 @@ where
Arc::new(TransactionPoolAdapter { pool: transaction_pool, client: client.clone() }),
config.prometheus_config.as_ref().map(|config| &config.registry),
)?;
spawn_handle.spawn("network-transactions-handler", Some("networking"), tx_handler.run());
spawn_handle.spawn_blocking(
"network-transactions-handler",
Some("networking"),
tx_handler.run(),
);
spawn_handle.spawn_blocking(
"chain-sync-network-service-provider",
Some("networking"),
chain_sync_network_provider.run(network.clone()),
chain_sync_network_provider.run(Arc::new(network.clone())),
);
spawn_handle.spawn("import-queue", None, import_queue.run(Box::new(sync_service_import_queue)));
spawn_handle.spawn_blocking("syncing", None, engine.run());
@@ -986,9 +989,9 @@ where
spawn_handle.spawn(
"system-rpc-handler",
Some("networking"),
build_system_rpc_future(
build_system_rpc_future::<_, _, <TBl as BlockT>::Hash>(
config.role.clone(),
network_mut.service().clone(),
network_mut.network_service(),
sync_service.clone(),
client.clone(),
system_rpc_rx,
@@ -996,18 +999,22 @@ where
),
);
let future =
build_network_future(network_mut, client, sync_service.clone(), config.announce_block);
let future = build_network_future::<_, _, <TBl as BlockT>::Hash, _>(
network_mut,
client,
sync_service.clone(),
config.announce_block,
);
// TODO: Normally, one is supposed to pass a list of notifications protocols supported by the
// node through the `NetworkConfiguration` struct. But because this function doesn't know in
// advance which components, such as GrandPa or Polkadot, will be plugged on top of the
// service, it is unfortunately not possible to do so without some deep refactoring. To bypass
// this problem, the `NetworkService` provides a `register_notifications_protocol` method that
// can be called even after the network has been initialized. However, we want to avoid the
// situation where `register_notifications_protocol` is called *after* the network actually
// connects to other peers. For this reason, we delay the process of the network future until
// the user calls `NetworkStarter::start_network`.
// service, it is unfortunately not possible to do so without some deep refactoring. To
// bypass this problem, the `NetworkService` provides a `register_notifications_protocol`
// method that can be called even after the network has been initialized. However, we want to
// avoid the situation where `register_notifications_protocol` is called *after* the network
// actually connects to other peers. For this reason, we delay the process of the network
// future until the user calls `NetworkStarter::start_network`.
//
// This entire hack should eventually be removed in favour of passing the list of protocols
// through the configuration.
+12 -11
View File
@@ -42,9 +42,11 @@ use jsonrpsee::RpcModule;
use log::{debug, error, warn};
use sc_client_api::{blockchain::HeaderBackend, BlockBackend, BlockchainEvents, ProofProvider};
use sc_network::{
config::MultiaddrWithPeerId, NetworkBlock, NetworkPeers, NetworkStateInfo, PeerId,
config::MultiaddrWithPeerId, service::traits::NetworkService, NetworkBackend, NetworkBlock,
NetworkPeers, NetworkStateInfo,
};
use sc_network_sync::SyncingService;
use sc_network_types::PeerId;
use sc_utils::mpsc::TracingUnboundedReceiver;
use sp_blockchain::HeaderMetadata;
use sp_consensus::SyncOracle;
@@ -157,8 +159,9 @@ async fn build_network_future<
+ Sync
+ 'static,
H: sc_network_common::ExHashT,
N: NetworkBackend<B, <B as BlockT>::Hash>,
>(
network: sc_network::NetworkWorker<B, H>,
network: N,
client: Arc<C>,
sync_service: Arc<SyncingService<B>>,
announce_imported_blocks: bool,
@@ -225,7 +228,7 @@ pub async fn build_system_rpc_future<
H: sc_network_common::ExHashT,
>(
role: Role,
network_service: Arc<sc_network::NetworkService<B, H>>,
network_service: Arc<dyn NetworkService>,
sync_service: Arc<SyncingService<B>>,
client: Arc<C>,
mut rpc_rx: TracingUnboundedReceiver<sc_rpc::system::Request<B>>,
@@ -310,14 +313,12 @@ pub async fn build_system_rpc_future<
};
},
sc_rpc::system::Request::NetworkReservedPeers(sender) => {
let reserved_peers = network_service.reserved_peers().await;
if let Ok(reserved_peers) = reserved_peers {
let reserved_peers =
reserved_peers.iter().map(|peer_id| peer_id.to_base58()).collect();
let _ = sender.send(reserved_peers);
} else {
break
}
let Ok(reserved_peers) = network_service.reserved_peers().await else {
break;
};
let _ =
sender.send(reserved_peers.iter().map(|peer_id| peer_id.to_base58()).collect());
},
sc_rpc::system::Request::NodeRoles(sender) => {
use sc_rpc::system::NodeRole;