Skip to content

Commit

Permalink
client: add WebRTC transport
Browse files Browse the repository at this point in the history
Refs paritytech/smoldot#1712
Impl libp2p/rust-libp2p#2622

- WebRTC transport is enabled for non-validators and developers by default.
- The transport will generate and store the certificate, which is required for WebRTC identity, in
base dir. In the future, when a new version of `ring` library is released, the certificate will be
deterministically derived from the node's peer ID.
  • Loading branch information
melekes committed Nov 28, 2022
1 parent dbb7bec commit f793dad
Show file tree
Hide file tree
Showing 15 changed files with 2,227 additions and 961 deletions.
2,825 changes: 1,902 additions & 923 deletions Cargo.lock

Large diffs are not rendered by default.

3 changes: 2 additions & 1 deletion bin/node/cli/benches/block_production.rs
Expand Up @@ -29,7 +29,7 @@ use sc_consensus::{
use sc_service::{
config::{
BlocksPruning, DatabaseSource, KeystoreConfig, NetworkConfiguration, OffchainWorkerConfig,
PruningMode, WasmExecutionMethod, WasmtimeInstantiationStrategy,
PruningMode, WasmExecutionMethod, WasmtimeInstantiationStrategy, WebRTCConfig,
},
BasePath, Configuration, Role,
};
Expand All @@ -52,6 +52,7 @@ fn new_node(tokio_handle: Handle) -> node_cli::service::NewFullBase {
Sr25519Keyring::Alice.to_seed(),
"network/test/0.1",
Default::default(),
WebRTCConfig::Ephemeral,
None,
);

Expand Down
3 changes: 2 additions & 1 deletion bin/node/cli/benches/transaction_pool.rs
Expand Up @@ -27,7 +27,7 @@ use sc_client_api::execution_extensions::ExecutionStrategies;
use sc_service::{
config::{
BlocksPruning, DatabaseSource, KeystoreConfig, NetworkConfiguration, OffchainWorkerConfig,
PruningMode, TransactionPoolOptions, WasmExecutionMethod,
PruningMode, TransactionPoolOptions, WasmExecutionMethod, WebRTCConfig,
},
BasePath, Configuration, Role,
};
Expand All @@ -46,6 +46,7 @@ fn new_node(tokio_handle: Handle) -> node_cli::service::NewFullBase {
Sr25519Keyring::Alice.to_seed(),
"network/test/0.1",
Default::default(),
WebRTCConfig::Ephemeral,
None,
);

Expand Down
24 changes: 22 additions & 2 deletions client/cli/src/config.rs
Expand Up @@ -21,6 +21,7 @@
use crate::{
arg_enums::Database, error::Result, DatabaseParams, ImportParams, KeystoreParams,
NetworkParams, NodeKeyParams, OffchainWorkerParams, PruningParams, SharedParams, SubstrateCli,
WebRTCCertificateParams,
};
use log::warn;
use names::{Generator, Name};
Expand All @@ -29,7 +30,7 @@ use sc_service::{
config::{
BasePath, Configuration, DatabaseSource, KeystoreConfig, NetworkConfiguration,
NodeKeyConfig, OffchainWorkerConfig, PrometheusConfig, PruningMode, Role, RpcMethods,
TelemetryEndpoints, TransactionPoolOptions, WasmExecutionMethod,
TelemetryEndpoints, TransactionPoolOptions, WasmExecutionMethod, WebRTCConfig,
},
BlocksPruning, ChainSpec, TracingReceiver,
};
Expand Down Expand Up @@ -116,6 +117,11 @@ pub trait CliConfiguration<DCV: DefaultConfigurationValues = ()>: Sized {
self.network_params().map(|x| &x.node_key_params)
}

/// Get the [`WebRTCCertificateParams`] for this object.
fn webrtc_certificate_params(&self) -> Option<&WebRTCCertificateParams> {
self.network_params().map(|x| &x.webrtc_certificate_params)
}

/// Get the DatabaseParams for this object
fn database_params(&self) -> Option<&DatabaseParams> {
self.import_params().map(|x| &x.database_params)
Expand Down Expand Up @@ -163,6 +169,7 @@ pub trait CliConfiguration<DCV: DefaultConfigurationValues = ()>: Sized {
client_id: &str,
node_name: &str,
node_key: NodeKeyConfig,
webrtc: WebRTCConfig,
default_listen_port: u16,
) -> Result<NetworkConfiguration> {
Ok(if let Some(network_params) = self.network_params() {
Expand All @@ -174,10 +181,11 @@ pub trait CliConfiguration<DCV: DefaultConfigurationValues = ()>: Sized {
client_id,
node_name,
node_key,
webrtc,
default_listen_port,
)
} else {
NetworkConfiguration::new(node_name, client_id, node_key, Some(net_config_dir))
NetworkConfiguration::new(node_name, client_id, node_key, webrtc, Some(net_config_dir))
})
}

Expand Down Expand Up @@ -454,6 +462,16 @@ pub trait CliConfiguration<DCV: DefaultConfigurationValues = ()>: Sized {
.unwrap_or_else(|| Ok(Default::default()))
}

/// Get the WebRTC config from the current object.
///
/// By default this is retrieved from [`WebRTCCertificateParams`] if it is available. Otherwise
/// its [`WebRTCConfig::Ephemeral`].
fn webrtc(&self, net_config_dir: &PathBuf) -> Result<WebRTCConfig> {
self.webrtc_certificate_params()
.map(|x| x.webrtc_certificate(net_config_dir))
.unwrap_or_else(|| Ok(WebRTCConfig::Ephemeral))
}

/// Get maximum runtime instances
///
/// By default this is `None`.
Expand Down Expand Up @@ -502,6 +520,7 @@ pub trait CliConfiguration<DCV: DefaultConfigurationValues = ()>: Sized {
},
);
let node_key = self.node_key(&net_config_dir)?;
let webrtc = self.webrtc(&net_config_dir)?;
let role = self.role(is_dev)?;
let max_runtime_instances = self.max_runtime_instances()?.unwrap_or(8);
let is_validator = role.is_authority();
Expand All @@ -522,6 +541,7 @@ pub trait CliConfiguration<DCV: DefaultConfigurationValues = ()>: Sized {
client_id.as_str(),
self.node_name()?.as_str(),
node_key,
webrtc,
DCV::p2p_listen_port(),
)?,
keystore_remote,
Expand Down
3 changes: 2 additions & 1 deletion client/cli/src/params/mod.rs
Expand Up @@ -24,6 +24,7 @@ mod offchain_worker_params;
mod pruning_params;
mod shared_params;
mod transaction_pool_params;
mod webrtc_certificate_params;

use crate::arg_enums::{CryptoScheme, OutputType};
use clap::Args;
Expand All @@ -37,7 +38,7 @@ use std::{fmt::Debug, str::FromStr};
pub use crate::params::{
database_params::*, import_params::*, keystore_params::*, network_params::*,
node_key_params::*, offchain_worker_params::*, pruning_params::*, shared_params::*,
transaction_pool_params::*,
transaction_pool_params::*, webrtc_certificate_params::*,
};

/// Parse Ss58AddressFormat
Expand Down
33 changes: 30 additions & 3 deletions client/cli/src/params/network_params.rs
Expand Up @@ -16,10 +16,13 @@
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.

use crate::{arg_enums::SyncMode, params::node_key_params::NodeKeyParams};
use crate::{
arg_enums::SyncMode,
params::{node_key_params::NodeKeyParams, webrtc_certificate_params::WebRTCCertificateParams},
};
use clap::Args;
use sc_network::{
config::{NetworkConfiguration, NodeKeyConfig},
config::{NetworkConfiguration, NodeKeyConfig, WebRTCConfig},
multiaddr::Protocol,
};
use sc_network_common::config::{NonReservedPeerMode, SetConfig, TransportConfig};
Expand Down Expand Up @@ -111,6 +114,10 @@ pub struct NetworkParams {
#[clap(flatten)]
pub node_key_params: NodeKeyParams,

#[allow(missing_docs)]
#[clap(flatten)]
pub webrtc_certificate_params: WebRTCCertificateParams,

/// Enable peer discovery on local networks.
///
/// By default this option is `true` for `--dev` or when the chain type is
Expand Down Expand Up @@ -158,12 +165,13 @@ impl NetworkParams {
client_id: &str,
node_name: &str,
node_key: NodeKeyConfig,
webrtc: WebRTCConfig,
default_listen_port: u16,
) -> NetworkConfiguration {
let port = self.port.unwrap_or(default_listen_port);

let listen_addresses = if self.listen_addr.is_empty() {
if is_validator || is_dev {
let mut addrs = if is_validator || is_dev {
vec![
Multiaddr::empty()
.with(Protocol::Ip6([0, 0, 0, 0, 0, 0, 0, 0].into()))
Expand All @@ -183,7 +191,25 @@ impl NetworkParams {
.with(Protocol::Tcp(port))
.with(Protocol::Ws(Cow::Borrowed("/"))),
]
};

// Enable WebRTC for non-validators and developers by default.
if !is_validator || is_dev {
addrs.push(
Multiaddr::empty()
.with(Protocol::Ip6([0, 0, 0, 0, 0, 0, 0, 0].into()))
.with(Protocol::Udp(port))
.with(Protocol::WebRTC),
);
addrs.push(
Multiaddr::empty()
.with(Protocol::Ip4([0, 0, 0, 0].into()))
.with(Protocol::Tcp(port))
.with(Protocol::WebRTC),
);
}

addrs
} else {
self.listen_addr.clone()
};
Expand Down Expand Up @@ -227,6 +253,7 @@ impl NetworkParams {
extra_sets: Vec::new(),
request_response_protocols: Vec::new(),
node_key,
webrtc,
node_name: node_name.to_string(),
client_version: client_id.to_string(),
transport: TransportConfig::Normal {
Expand Down
118 changes: 118 additions & 0 deletions client/cli/src/params/webrtc_certificate_params.rs
@@ -0,0 +1,118 @@
// This file is part of Substrate.

// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0

// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.

// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.

// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.

use clap::Args;
use sc_network::config::WebRTCConfig;
use std::path::PathBuf;

use crate::error;

/// The file name of the WebRTC's certificate inside the chain-specific
/// network config directory.
const WEBRTC_CERTIFICATE_FILENAME: &str = "webrtc_certificate";

/// Parameters used to create the `WebRTCConfig`, which determines the certificate used
/// for libp2p WebRTC transport.
#[derive(Debug, Clone, Args)]
pub struct WebRTCCertificateParams {
/// The file from which to read the node's WebRTC certificate to use for libp2p networking.
///
/// The contents of the file are parsed as follows:
///
/// The file must contain an ASCII PEM encoded
/// [`webrtc::peer_connection::certificate::RTCCertificate`].
///
/// If the file does not exist, it is created with a newly generated certificate.
#[clap(long, value_name = "FILE")]
pub webrtc_certificate_file: Option<PathBuf>,

/// When set to `true`, a new WebRTC certificate will be created each time you start a node.
///
/// The certificate won't be stored on disk. Use this option only if you DON'T want to preserve
/// node's WebRTC identity between (re)starts.
///
/// This option takes precedence over `--webrtc-certificate-file` option.
#[arg(long, value_name = "EPHEMERAL")]
pub webrtc_certificate_ephemeral: Option<bool>,
}

impl WebRTCCertificateParams {
/// Create a `WebRTCConfig` from the given `WebRTCCertificateParams` in the context
/// of an optional network config storage directory.
pub fn webrtc_certificate(&self, net_config_dir: &PathBuf) -> error::Result<WebRTCConfig> {
if let Some(true) = self.webrtc_certificate_ephemeral {
return Ok(WebRTCConfig::Ephemeral)
}

let filename = self
.webrtc_certificate_file
.clone()
.unwrap_or_else(|| net_config_dir.join(WEBRTC_CERTIFICATE_FILENAME));

Ok(WebRTCConfig::File(filename))
}
}

#[cfg(test)]
mod tests {
use super::*;
use libp2p::webrtc::tokio::Certificate as WebRTCCertificate;
use rand::thread_rng;
use std::fs;

#[test]
fn test_webrtc_certificate_file() {
fn load_cert_and_assert_eq(file: PathBuf, cert: &WebRTCCertificate) {
let params = WebRTCCertificateParams { webrtc_certificate_file: Some(file) };

let loaded_cert = params
.webrtc_certificate(&PathBuf::from("not-used"))
.expect("Creates certificate config")
.into_certificate()
.expect("Creates certificate");

assert_eq!(cert, loaded_cert, "expected the same certificate");
}

let tmp = tempfile::Builder::new().prefix("alice").tempdir().expect("Creates tempfile");
let file = tmp.path().join("mycertificate").to_path_buf();

let cert = WebRTCCertificate::generate(&mut thread_rng()).expect("Generates certificate");

fs::write(&file, cert.serialize_pem().as_bytes()).expect("Writes certificate");
load_cert_and_assert_eq(file.clone(), &cert);
}

#[test]
fn test_webrtc_certificate_ephemeral() {
let filepath = PathBuf::from("not-used");
let params = WebRTCCertificateParams {
webrtc_certificate_ephemeral: Some(true),
webrtc_certificate_file: Some(&filepath),
};

let _loaded_cert = params
.webrtc_certificate(&filepath)
.expect("Creates certificate config")
.into_certificate()
.expect("Creates certificate");

assert!(!filepath.exists(), "Does not create a file");
assert!(!filepath.join(WEBRTC_CERTIFICATE_FILENAME).exists(), "Does not create a file");
}
}
25 changes: 12 additions & 13 deletions client/network/Cargo.toml
Expand Up @@ -16,49 +16,48 @@ targets = ["x86_64-unknown-linux-gnu"]
[dependencies]
array-bytes = "4.1"
async-trait = "0.1"
asynchronous-codec = "0.6"
asynchronous-codec = "0.6.1"
bitflags = "1.3.2"
bytes = "1"
cid = "0.8.4"
codec = { package = "parity-scale-codec", version = "3.0.0", features = ["derive"] }
either = "1.5.3"
fnv = "1.0.6"
fork-tree = { version = "3.0.0", path = "../../utils/fork-tree" }
futures = "0.3.21"
futures-timer = "3.0.2"
ip_network = "0.4.1"
libp2p = { version = "0.50.0", features = ["async-std", "dns", "identify", "kad", "macros", "mdns", "mplex", "noise", "ping", "tcp", "yamux", "websocket"] }
linked_hash_set = "0.1.3"
libp2p = { version = "0.50.0", features = ["async-std", "dns", "identify", "kad", "macros", "mdns", "mplex", "noise", "ping", "tcp", "tokio", "yamux", "webrtc", "websocket"] }
linked-hash-map = "0.5.4"
linked_hash_set = "0.1.3"
log = "0.4.17"
lru = "0.8.1"
parking_lot = "0.12.1"
pin-project = "1.0.12"
prost = "0.11"
rand = "0.7.2"
serde = { version = "1.0.136", features = ["derive"] }
serde_json = "1.0.85"
smallvec = "1.8.0"
thiserror = "1.0"
unsigned-varint = { version = "0.7.1", features = ["futures", "asynchronous_codec"] }
zeroize = "1.4.3"
fork-tree = { version = "3.0.0", path = "../../utils/fork-tree" }
prometheus-endpoint = { package = "substrate-prometheus-endpoint", version = "0.10.0-dev", path = "../../utils/prometheus" }
prost = "0.11"
rand = "0.8"
sc-block-builder = { version = "0.10.0-dev", path = "../block-builder" }
sc-client-api = { version = "4.0.0-dev", path = "../api" }
sc-consensus = { version = "0.10.0-dev", path = "../consensus/common" }
sc-network-common = { version = "0.10.0-dev", path = "./common" }
sc-peerset = { version = "4.0.0-dev", path = "../peerset" }
sc-utils = { version = "4.0.0-dev", path = "../utils" }
serde = { version = "1.0.136", features = ["derive"] }
serde_json = "1.0.85"
smallvec = "1.8.0"
sp-arithmetic = { version = "6.0.0", path = "../../primitives/arithmetic" }
sp-blockchain = { version = "4.0.0-dev", path = "../../primitives/blockchain" }
sp-consensus = { version = "0.10.0-dev", path = "../../primitives/consensus/common" }
sp-core = { version = "7.0.0", path = "../../primitives/core" }
sp-runtime = { version = "7.0.0", path = "../../primitives/runtime" }
thiserror = "1.0"
unsigned-varint = { version = "0.7.1", features = ["futures", "asynchronous_codec"] }
zeroize = "1.4.3"

[dev-dependencies]
assert_matches = "1.3"
async-std = { version = "1.11.0", features = ["attributes"] }
rand = "0.7.2"
tempfile = "3.1.0"
sc-network-light = { version = "0.10.0-dev", path = "./light" }
sc-network-sync = { version = "0.10.0-dev", path = "./sync" }
Expand Down

0 comments on commit f793dad

Please sign in to comment.