diff --git a/Cargo.lock b/Cargo.lock index fcf4cdeb1..28f56d1b3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8959,6 +8959,7 @@ dependencies = [ "bls12_381", "bs58", "cid 0.11.1", + "ed25519-dalek", "futures", "hex", "libp2p 0.54.1", @@ -13798,12 +13799,14 @@ dependencies = [ "frame-benchmarking-cli", "futures", "jsonrpsee 0.24.7", + "libp2p 0.54.1", "log", "pallet-transaction-payment-rpc", "parity-scale-codec", "polka-storage-runtime", "polkadot-cli", "polkadot-primitives 7.0.0", + "primitives", "sc-basic-authorship", "sc-chain-spec", "sc-cli", @@ -13835,6 +13838,7 @@ dependencies = [ "substrate-build-script-utils", "substrate-frame-rpc-system", "substrate-prometheus-endpoint", + "thiserror 2.0.8", ] [[package]] @@ -13928,9 +13932,7 @@ dependencies = [ "ciborium", "cid 0.11.1", "clap", - "ed25519-dalek", "futures", - "hex", "hyper 1.5.2", "integer-encoding", "jsonrpsee 0.24.7", @@ -16058,7 +16060,10 @@ version = "0.1.0" dependencies = [ "cid 0.11.1", "clap", + "ed25519-dalek", "filecoin-proofs", + "hex", + "libp2p 0.54.1", "parity-scale-codec", "rand", "scale-decode 0.14.0", diff --git a/Justfile b/Justfile index ea7fb4da3..0a50abda9 100644 --- a/Justfile +++ b/Justfile @@ -24,8 +24,12 @@ release: lint release-testnet: cargo build --release --features polka-storage-runtime/testnet --bin polka-storage-node -# Run the testnet without building +# Generate a private key for the P2P network and +# run the testnet without building run-testnet: + mkdir -p /tmp/zombienet + openssl genpkey -algorithm ED25519 -out /tmp/zombienet/private.pem + openssl pkey -in /tmp/zombienet/private.pem -pubout -out /tmp/zombienet/public.pem # Generate public key so script can get the Peer ID zombienet -p native spawn zombienet/local-testnet.toml # Run the testing building it before @@ -152,6 +156,9 @@ load-to-minikube: minikube image load ghcr.io/polka-storage-node:"$(cargo metadata --format-version=1 --no-deps | jq -r '.packages[] | select(.name == "polka-storage-node") | .version')" kube-testnet: + mkdir -p /tmp/zombienet + openssl genpkey -algorithm ED25519 -out /tmp/zombienet/private.pem + openssl pkey -in /tmp/zombienet/private.pem -pubout -out /tmp/zombienet/public.pem # Generate public key so script can get the Peer ID zombienet -p kubernetes spawn zombienet/local-kube-testnet.toml # The tarpaulin calls for test coverage have the following options: diff --git a/docs/src/storage-provider-cli/server.md b/docs/src/storage-provider-cli/server.md index a7ffaa46a..38dc00126 100644 --- a/docs/src/storage-provider-cli/server.md +++ b/docs/src/storage-provider-cli/server.md @@ -5,6 +5,8 @@ This chapter covers the Polka Storage Provider server. ## P2P Key generation The Polka Storage Provider server runs a p2p node that is used to map Peer ID's to Multi-addresses. +The generated Peer ID is used for on-chain registration and inside the P2P network. +Both these Peer ID's must be the same. The server can run either a bootstrap node, used to aid in discovery, or a registration node, which registers to a bootstrap node with their Peer ID to Multi-address mapping. For both of these node types, the server needs an ed25519 private key. diff --git a/examples/rpc_publish.sh b/examples/rpc_publish.sh index e308473fb..8b08d8ee2 100755 --- a/examples/rpc_publish.sh +++ b/examples/rpc_publish.sh @@ -26,17 +26,20 @@ INPUT_TMP_FILE="/tmp/$INPUT_FILE_NAME.car" # Config file location CONFIG="/tmp/config.toml" # P2P Node variables -P2P_PUBLIC_KEY="/tmp/public.pem" -P2P_PRIVATE_KEY="/tmp/private.pem" +P2P_PUBLIC_KEY="/tmp/polka-storage/public.pem" +P2P_PRIVATE_KEY="/tmp/polka-storage/private.pem" P2P_ADDRESS="/ip4/127.0.0.1/tcp/62649" -# Generate ED25519 private key to be replaced with a polka-storage-provider-client command # Generate ED25519 private key +mkdir -p /tmp/storage-provider openssl genpkey -algorithm ED25519 -out "$P2P_PRIVATE_KEY" # -outpubkey is only available in OpenSSL 3.4.0 onwards # https://github.com/openssl/openssl/commit/6c03fa21ed4bbc9fd6d3013fdf9f4646d231f831 openssl pkey -in "$P2P_PRIVATE_KEY" -pubout -out "$P2P_PUBLIC_KEY" +# Generate Peer ID +P2P_BOOTSTRAP_PEER_ID="$(target/release/polka-storage-provider-client generate-peer-id --pubkey "$P2P_PUBLIC_KEY")" + # Convert file to CARv2 format target/release/mater-cli convert -q --overwrite "$INPUT_FILE" "$INPUT_TMP_FILE" && @@ -54,7 +57,8 @@ post_proof = '2KiB' porep_parameters = '2KiB.porep.params' post_parameters = '2KiB.post.params' rendezvous_point_address = '$P2P_ADDRESS' -p2p_key = '@$P2P_PRIVATE_KEY'" > "$CONFIG" +p2p_key = '@$P2P_PRIVATE_KEY' +rendezvous_point = '$P2P_BOOTSTRAP_PEER_ID'" > "$CONFIG" # Setup balances diff --git a/examples/start_sp.sh b/examples/start_sp.sh index eca6231d1..b1122b0e7 100755 --- a/examples/start_sp.sh +++ b/examples/start_sp.sh @@ -8,18 +8,29 @@ export DISABLE_XT_WAIT_WARNING=1 CLIENT="//Alice" PROVIDER="//Charlie" -P2P_PUBLIC_KEY="/tmp/public.pem" -P2P_PRIVATE_KEY="/tmp/private.pem" P2P_ADDRESS="/ip4/127.0.0.1/tcp/62649" +P2P_PUBLIC_KEY="/tmp/polka-storage/public.pem" +P2P_PRIVATE_KEY="/tmp/polka-storage/private.pem" +P2P_BOOTSTRAP_PUBLIC_KEY="/tmp/zombienet/public.pem" +# Config file location +CONFIG="/tmp/storage-provider/config.toml" # Generate ED25519 private key +mkdir -p /tmp/storage-provider openssl genpkey -algorithm ED25519 -out "$P2P_PRIVATE_KEY" # -outpubkey is only available in OpenSSL 3.4.0 onwards # https://github.com/openssl/openssl/commit/6c03fa21ed4bbc9fd6d3013fdf9f4646d231f831 openssl pkey -in "$P2P_PRIVATE_KEY" -pubout -out "$P2P_PUBLIC_KEY" # Generate Peer ID -PEER_ID="$(target/release/polka-storage-provider-client generate-peer-id --pubkey "$P2P_PUBLIC_KEY")" +P2P_SP_PEER_ID="$(target/release/polka-storage-provider-client generate-peer-id --pubkey "$P2P_PUBLIC_KEY")" + +echo "Generated new peer ID for $PROVIDER: $P2P_SP_PEER_ID" + +# Get bootstrap P2P Peer ID. This works after running zombienet locally or in kubernetes +P2P_BOOTSTRAP_PEER_ID="$(target/release/polka-storage-provider-client generate-peer-id --pubkey "$P2P_BOOTSTRAP_PUBLIC_KEY")" + +echo "Peer ID for bootstrap node: $P2P_BOOTSTRAP_PEER_ID" # Setup balances RUST_LOG=debug target/release/storagext-cli --sr25519-key "$CLIENT" market add-balance 250000000000 & @@ -31,22 +42,21 @@ wait # It's a test setup based on the local verifying keys, everyone can run those extrinsics currently. # Each of the keys is different, because the processes are running in parallel. # If they were running in parallel on the same account, they'd conflict with each other on the transaction nonce. -RUST_LOG=debug target/release/storagext-cli --sr25519-key "//Charlie" storage-provider register "$PEER_ID" & +RUST_LOG=debug target/release/storagext-cli --sr25519-key "//Charlie" storage-provider register "$P2P_SP_PEER_ID" & RUST_LOG=debug target/release/storagext-cli --sr25519-key "//Alice" proofs set-porep-verifying-key @2KiB.porep.vk.scale & RUST_LOG=debug target/release/storagext-cli --sr25519-key "//Bob" proofs set-post-verifying-key @2KiB.post.vk.scale & wait -echo '{ - "seal_proof": "2KiB", - "post_proof": "2KiB", - "porep_parameters": "2KiB.porep.params", - "post_parameters": "2KiB.post.params", - "p2p_key": "@/tmp/private.pem", - "rendezvous_point_address": "/ip4/127.0.0.1/tcp/62649", - "sealing_configuration": { - "fill_percentage": 75 - } -}' > /tmp/storage_provider.config.json +echo "seal_proof = '2KiB' +post_proof = '2KiB' +porep_parameters = '2KiB.porep.params' +post_parameters = '2KiB.post.params' +rendezvous_point_address = '$P2P_ADDRESS' +p2p_key = '@$P2P_PRIVATE_KEY' +rendezvous_point = '$P2P_BOOTSTRAP_PEER_ID' +[sealing_configuration] +fill_percentage = 75" > "$CONFIG" + RUST_LOG=debug target/release/polka-storage-provider-server \ --sr25519-key "$PROVIDER" \ - --config /tmp/storage_provider.config.json + --config "$CONFIG" diff --git a/maat/Cargo.toml b/maat/Cargo.toml index e609c6f8f..b08a255ef 100644 --- a/maat/Cargo.toml +++ b/maat/Cargo.toml @@ -23,6 +23,7 @@ bls12_381 = { workspace = true } bs58.workspace = true cid.workspace = true codec = { workspace = true } +ed25519-dalek = { workspace = true, features = ["pem"] } futures.workspace = true hex = { workspace = true } libp2p = { workspace = true, features = ["ecdsa", "identify"] } @@ -33,6 +34,7 @@ serde = { workspace = true, features = ["derive"] } serde_json = { workspace = true, features = ["std"] } storagext = { workspace = true } subxt = { workspace = true, features = ["substrate-compat"] } +tempfile = { workspace = true } thiserror.workspace = true tokio = { workspace = true, features = ["full"] } tokio-util = { workspace = true, features = ["rt"] } @@ -43,8 +45,5 @@ zombienet-configuration.workspace = true zombienet-sdk.workspace = true zombienet-support.workspace = true -[dev-dependencies] -tempfile = { workspace = true } - [lints] workspace = true diff --git a/maat/src/lib.rs b/maat/src/lib.rs index 7e5825f7f..121ffd89f 100644 --- a/maat/src/lib.rs +++ b/maat/src/lib.rs @@ -1,5 +1,13 @@ -use std::path::PathBuf; +use std::{ + fs::File, + io::Write, + path::{Path, PathBuf}, +}; +use ed25519_dalek::{ + pkcs8::{spki::der::pem::LineEnding, EncodePrivateKey}, + SigningKey, +}; use storagext::PolkaStorageConfig; use subxt::{ ext::{ @@ -8,6 +16,7 @@ use subxt::{ }, tx::PairSigner, }; +use tempfile::tempdir; use tracing::level_filters::LevelFilter; use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt, EnvFilter, Layer}; use zombienet_configuration::shared::node::{Buildable, Initial, NodeConfigBuilder}; @@ -92,6 +101,9 @@ pub fn local_testnet_config() -> NetworkConfig { .display() .to_string(); let polka_storage_node_binary_path = binding.as_str(); + let temp_dir = tempdir().unwrap(); + let file_path = temp_dir.path().join("private_key.pem"); + generate_pem_file(&file_path); NetworkConfigBuilder::new() .with_relaychain(|relaychain| { @@ -111,6 +123,8 @@ pub fn local_testnet_config() -> NetworkConfig { .with_args(vec![ ("--pool-type", "fork-aware").into(), ("-lruntime=trace,parachain=debug").into(), + ("--p2p-listen-address=/ip4/127.0.0.1/tcp/62649").into(), + (format!("--p2p-key={}", file_path.display()).as_str()).into(), ]) }) }) @@ -138,3 +152,10 @@ where let keypair = Pair::from_string(s, None).unwrap(); PairSigner::::new(keypair) } + +fn generate_pem_file>(path: P) { + let signing_key = SigningKey::from([0; 32]); + let pem = signing_key.to_pkcs8_pem(LineEnding::default()).unwrap(); + let mut file = File::create(path).unwrap(); + write!(file, "{}", *pem).unwrap(); +} diff --git a/node/Cargo.toml b/node/Cargo.toml index 59f24419b..b06999723 100644 --- a/node/Cargo.toml +++ b/node/Cargo.toml @@ -15,6 +15,7 @@ name = "polka-storage-node" [dependencies] polka-storage-runtime.workspace = true +primitives = { workspace = true, features = ["std"] } clap = { features = ["derive"], workspace = true } codec = { workspace = true, default-features = true } @@ -33,6 +34,7 @@ frame-benchmarking = { workspace = true, default-features = true } frame-benchmarking-cli = { workspace = true, default-features = true } futures = { workspace = true } jsonrpsee = { features = ["server"], workspace = true } +libp2p = { workspace = true, features = ["identify", "macros", "noise", "rendezvous", "tcp", "tokio", "yamux"] } log = { workspace = true, default-features = true } pallet-transaction-payment-rpc = { workspace = true, default-features = true } polkadot-cli = { features = ["rococo-native"], workspace = true, default-features = true } @@ -66,6 +68,7 @@ sp-keystore = { workspace = true, default-features = true } sp-runtime = { workspace = true, default-features = true } sp-timestamp = { workspace = true, default-features = true } substrate-frame-rpc-system = { workspace = true, default-features = true } +thiserror = { workspace = true } xcm.workspace = true [build-dependencies] diff --git a/node/src/cli.rs b/node/src/cli.rs index f836aee96..7ca019150 100644 --- a/node/src/cli.rs +++ b/node/src/cli.rs @@ -1,5 +1,8 @@ use std::path::PathBuf; +use libp2p::{identity::Keypair, Multiaddr}; +use primitives::p2p::keypair_value_parser; + /// Sub-commands supported by the collator. #[derive(Debug, clap::Subcommand)] #[allow(clippy::large_enum_variant)] @@ -64,7 +67,7 @@ pub struct Cli { pub subcommand: Option, #[command(flatten)] - pub run: cumulus_client_cli::RunCmd, + pub run: RunCmd, /// Disable automatic hardware benchmarks. /// @@ -109,3 +112,21 @@ impl RelayChainCli { } } } + +#[derive(Debug, clap::Parser)] +#[group(skip)] +pub struct RunCmd { + #[clap(flatten)] + pub base: cumulus_client_cli::RunCmd, + + /// Key used in the P2P network of Storage Providers and Collators. + /// This key generates the Peer ID that storage providers use to register. + /// It must be an ED25519 private key, either in PEM format or passed in directly. + #[arg(long, value_parser = keypair_value_parser, required = false)] + pub p2p_key: Option, + + /// Listen address in the P2P network of Storage Providers and Collators + /// that the bootstrap node binds to. + #[arg(long, required = false)] + pub p2p_listen_address: Option, +} diff --git a/node/src/command.rs b/node/src/command.rs index e6694815c..9dd0ee670 100644 --- a/node/src/command.rs +++ b/node/src/command.rs @@ -12,7 +12,7 @@ use sc_service::config::{BasePath, PrometheusConfig}; use crate::{ chain_spec, cli::{Cli, RelayChainCli, Subcommand}, - service::new_partial, + service::{new_partial, p2p::BootstrapConfig}, }; fn load_spec(id: &str) -> std::result::Result, String> { @@ -221,10 +221,28 @@ pub fn run() -> Result<()> { } } None => { - let runner = cli.create_runner(&cli.run.normalize())?; - let collator_options = cli.run.collator_options(); + let runner = cli.create_runner(&cli.run.base.normalize())?; + let collator_options = cli.run.base.collator_options(); runner.run_node_until_exit(|config| async move { + let bootstrap_config = if config.role.is_authority() { + let p2p_key = cli + .run + .p2p_key + .ok_or( + "This node is configured as authority, so it will be used as Bootstrap node for Storage Provider & Collator network, but the key is missing. Set the --p2p-key argument of the node." + )?; + let p2p_listen_address = cli + .run + .p2p_listen_address + .ok_or( + "This node is configured as authority, so it will be used as Bootstrap node for Storage Provider & Collator network, but the listen address is missing. Set the --p2p-listen-address argument of the node." + )?; + Some(BootstrapConfig::new(p2p_key, p2p_listen_address)) + } else { + None + }; + let hwbench = (!cli.no_hardware_benchmarks) .then_some(config.database.path().map(|database_path| { let _ = std::fs::create_dir_all(database_path); @@ -268,6 +286,7 @@ pub fn run() -> Result<()> { collator_options, id, hwbench, + bootstrap_config, ) .await .map(|r| r.0) diff --git a/node/src/service.rs b/node/src/service/mod.rs similarity index 98% rename from node/src/service.rs rename to node/src/service/mod.rs index c1bb46f52..7862ba5b4 100644 --- a/node/src/service.rs +++ b/node/src/service/mod.rs @@ -38,6 +38,10 @@ use sc_telemetry::{Telemetry, TelemetryHandle, TelemetryWorker, TelemetryWorkerH use sc_transaction_pool_api::OffchainTransactionPoolFactory; use sp_keystore::KeystorePtr; +pub mod p2p; + +use crate::service::p2p::{run_bootstrap_node, BootstrapConfig}; + #[docify::export(wasm_executor)] type ParachainExecutor = WasmExecutor; @@ -245,6 +249,7 @@ pub async fn start_parachain_node( collator_options: CollatorOptions, para_id: ParaId, hwbench: Option, + bootstrap_config: Option, ) -> sc_service::error::Result<(TaskManager, Arc)> { let parachain_config = prepare_node_config(parachain_config); @@ -332,6 +337,12 @@ pub async fn start_parachain_node( }) }; + if let Some(config) = bootstrap_config { + task_manager + .spawn_handle() + .spawn("p2p", None, run_bootstrap_node(config)); + } + sc_service::spawn_tasks(sc_service::SpawnTasksParams { rpc_builder, client: client.clone(), diff --git a/storage-provider/server/src/p2p/bootstrap.rs b/node/src/service/p2p/bootstrap.rs similarity index 85% rename from storage-provider/server/src/p2p/bootstrap.rs rename to node/src/service/p2p/bootstrap.rs index 7f6ca1b24..11f339889 100644 --- a/storage-provider/server/src/p2p/bootstrap.rs +++ b/node/src/service/p2p/bootstrap.rs @@ -8,9 +8,9 @@ use libp2p::{ swarm::{NetworkBehaviour, SwarmEvent}, tcp, yamux, Multiaddr, Swarm, SwarmBuilder, }; +use log::{debug, info}; -use super::P2PError; -use crate::config::DEFAULT_REGISTRATION_TTL; +use crate::service::p2p::{P2PError, DEFAULT_REGISTRATION_TTL}; #[derive(NetworkBehaviour)] pub struct BootstrapBehaviour { @@ -62,21 +62,19 @@ pub(crate) async fn bootstrap( mut swarm: Swarm, addr: Multiaddr, ) -> Result<(), P2PError> { - tracing::info!("Starting P2P bootstrap node at {addr}"); + info!("Starting P2P bootstrap node at {addr}"); swarm.listen_on(addr)?; - while let Some(event) = swarm.next().await { - match event { + loop { + match swarm.select_next_some().await { SwarmEvent::NewListenAddr { address, .. } => { - tracing::info!("Listening on {}", address); + info!("Listening on {}", address); } SwarmEvent::Behaviour(BootstrapBehaviourEvent::Rendezvous( rendezvous::server::Event::PeerRegistered { peer, registration }, )) => { - tracing::info!( + info!( "Peer {} registered for namespace '{}' for {} seconds", - peer, - registration.namespace, - registration.ttl + peer, registration.namespace, registration.ttl ); } SwarmEvent::Behaviour(BootstrapBehaviourEvent::Rendezvous( @@ -86,7 +84,7 @@ pub(crate) async fn bootstrap( }, )) => { if !registrations.is_empty() { - tracing::info!( + info!( "Served peer {} with {} new registrations", enquirer, registrations.len() @@ -96,14 +94,13 @@ pub(crate) async fn bootstrap( SwarmEvent::Behaviour(BootstrapBehaviourEvent::Rendezvous( rendezvous::server::Event::RegistrationExpired(registration), )) => { - tracing::info!( + info!( "Registration for peer {} expired in namespace {}", registration.record.peer_id(), registration.namespace ); } - other => tracing::debug!("Encountered event: {other:?}"), + other => debug!("Encountered event: {other:?}"), } } - Ok(()) } diff --git a/node/src/service/p2p/mod.rs b/node/src/service/p2p/mod.rs new file mode 100644 index 000000000..ea2e5f5ea --- /dev/null +++ b/node/src/service/p2p/mod.rs @@ -0,0 +1,33 @@ +use bootstrap::bootstrap; +use log::info; + +mod bootstrap; + +pub(crate) use bootstrap::BootstrapConfig; + +const DEFAULT_REGISTRATION_TTL: u64 = 86400; + +#[derive(Debug, thiserror::Error)] +pub enum P2PError { + #[error(transparent)] + Io(#[from] std::io::Error), + #[error(transparent)] + Dial(#[from] libp2p::swarm::DialError), + #[error("Invalid TCP config for swarm")] + InvalidTcpConfig, + #[error("Invalid behaviour config for swarm")] + InvalidBehaviourConfig, + #[error(transparent)] + P2PTransport(#[from] libp2p::TransportError), +} + +/// Runs a bootstrap node from the given config. +/// The `CancellationToken` is used for a graceful shutdown if the user presses ctrl+c +pub async fn run_bootstrap_node(config: BootstrapConfig) { + info!("Starting P2P bootstrap node"); + let (swarm, addr) = config.create_swarm().expect("Could not create swarm"); + + bootstrap(swarm, addr) + .await + .expect("Could not run bootstrap node"); +} diff --git a/primitives/Cargo.toml b/primitives/Cargo.toml index bfb842d0f..cf60784d9 100644 --- a/primitives/Cargo.toml +++ b/primitives/Cargo.toml @@ -11,7 +11,10 @@ version = "0.1.0" cid = { workspace = true, default-features = false, features = ["alloc"] } clap = { workspace = true, features = ["derive"], optional = true } codec = { workspace = true, features = ["derive"] } +ed25519-dalek = { workspace = true, optional = true, features = ["pem"] } filecoin-proofs = { workspace = true, optional = true } +hex = { workspace = true, optional = true } +libp2p = { workspace = true, optional = true } scale-decode = { workspace = true, features = ["derive"] } scale-encode = { workspace = true, features = ["derive"] } scale-info = { workspace = true, features = ["derive"] } @@ -40,7 +43,10 @@ std = [ "cid/scale-codec", "cid/std", "codec/std", + "dep:ed25519-dalek", "dep:filecoin-proofs", + "dep:hex", + "dep:libp2p", "scale-info/std", "serde/std", "sha2/std", diff --git a/primitives/src/lib.rs b/primitives/src/lib.rs index 7a29b5c47..4f3d5e55f 100644 --- a/primitives/src/lib.rs +++ b/primitives/src/lib.rs @@ -6,6 +6,8 @@ pub mod proofs; pub mod randomness; pub mod sector; +#[cfg(feature = "std")] +pub mod p2p; #[cfg(feature = "testing")] pub mod testing { // NOTE(@jmg-duarte,22/01/2025): Since there's only one thing, star import for now. diff --git a/primitives/src/p2p.rs b/primitives/src/p2p.rs new file mode 100644 index 000000000..3132a4784 --- /dev/null +++ b/primitives/src/p2p.rs @@ -0,0 +1,21 @@ +use std::{path::PathBuf, str::FromStr}; + +use ed25519_dalek::{pkcs8::DecodePrivateKey, SigningKey}; +use libp2p::identity::Keypair; + +/// Parses a ED25519 private key into a Keypair. +/// Takes in a private key or the path to a PEM file, depending on the @ prefix. +#[cfg(feature = "std")] +pub fn keypair_value_parser(src: &str) -> Result { + let key = if let Some(stripped) = src.strip_prefix('@') { + let path = PathBuf::from_str(stripped) + .map_err(|e| e.to_string())? + .canonicalize() + .map_err(|e| e.to_string())?; + SigningKey::read_pkcs8_pem_file(path).map_err(|e| e.to_string())? + } else { + let hex_key = hex::decode(src).map_err(|e| e.to_string())?; + SigningKey::try_from(hex_key.as_slice()).map_err(|e| e.to_string())? + }; + Keypair::ed25519_from_bytes(key.to_bytes()).map_err(|e| e.to_string()) +} diff --git a/storage-provider/server/Cargo.toml b/storage-provider/server/Cargo.toml index 597cdc4f3..f871435e4 100644 --- a/storage-provider/server/Cargo.toml +++ b/storage-provider/server/Cargo.toml @@ -26,9 +26,7 @@ chrono = { workspace = true, features = ["serde"] } ciborium = { workspace = true } cid = { workspace = true, features = ["serde", "std"] } clap = { workspace = true, features = ["derive"] } -ed25519-dalek = { workspace = true, features = ["pem", "std"] } futures = { workspace = true } -hex = { workspace = true, features = ["std"] } hyper = { workspace = true } integer-encoding = { workspace = true } jsonrpsee = { workspace = true, features = ["http-client", "macros", "server", "ws-client"] } diff --git a/storage-provider/server/src/config.rs b/storage-provider/server/src/config.rs index ca458b019..66e0c2603 100644 --- a/storage-provider/server/src/config.rs +++ b/storage-provider/server/src/config.rs @@ -7,12 +7,15 @@ use std::{ use clap::Args; use libp2p::{identity::Keypair, Multiaddr, PeerId}; use polka_storage_provider_common::config::sealing::SealingConfiguration; -use primitives::proofs::{RegisteredPoStProof, RegisteredSealProof}; +use primitives::{ + p2p::keypair_value_parser, + proofs::{RegisteredPoStProof, RegisteredSealProof}, +}; use serde::Deserialize; use url::Url; use crate::{ - p2p::{deser_keypair, keypair_value_parser, string_to_peer_id_option, NodeType}, + p2p::{deser_keypair, deserialize_string_to_peer_id}, DEFAULT_NODE_ADDRESS, }; @@ -111,11 +114,6 @@ pub struct ConfigurationArgs { #[arg(long, required = false)] pub(crate) post_parameters: PathBuf, - /// P2P Node type, can be either a bootstrap node or a registration node. - #[serde(default = "NodeType::default")] - #[arg(long, default_value_t = NodeType::Bootstrap, required = false)] - pub(crate) node_type: NodeType, - /// P2P ED25519 private key #[serde(deserialize_with = "deser_keypair")] #[arg(long, value_parser = keypair_value_parser, required = false)] @@ -127,10 +125,9 @@ pub struct ConfigurationArgs { pub(crate) rendezvous_point_address: Multiaddr, /// PeerID of the bootstrap node used by the registration node. - /// Optional because it is not used by the bootstrap node. - #[serde(default, deserialize_with = "string_to_peer_id_option")] + #[serde(deserialize_with = "deserialize_string_to_peer_id")] #[arg(long, required = false)] - pub(crate) rendezvous_point: Option, + pub(crate) rendezvous_point: PeerId, /// TTL of the p2p registration in seconds #[serde(default = "default_registration_ttl")] diff --git a/storage-provider/server/src/main.rs b/storage-provider/server/src/main.rs index fb5b0fac2..15e779ae0 100644 --- a/storage-provider/server/src/main.rs +++ b/storage-provider/server/src/main.rs @@ -14,10 +14,7 @@ use std::{env::temp_dir, net::SocketAddr, path::PathBuf, sync::Arc, time::Durati use clap::Parser; use libp2p::{identity::Keypair, Multiaddr, PeerId}; -use p2p::{ - run_bootstrap_node, run_register_node, BootstrapConfig, NodeType, P2PError, P2PState, - RegisterConfig, -}; +use p2p::{run_register_node, P2PError, P2PState, RegisterConfig}; use pipeline::types::PipelineMessage; use polka_storage_proofs::{ porep::{self, PoRepParameters}, @@ -260,9 +257,6 @@ pub struct Server { /// The number of prove commits to be run in parallel. parallel_prove_commits: usize, - /// P2P Network node type, can either be a bootstrap or registration node - node_type: NodeType, - /// P2P ED25519 private key p2p_key: Keypair, @@ -272,7 +266,7 @@ pub struct Server { /// PeerID of the bootstrap node used by the registration node. /// Optional because it is not used by the bootstrap node. - rendezvous_point: Option, + rendezvous_point: PeerId, /// TTL of the p2p registration in seconds registration_ttl: u64, @@ -356,7 +350,6 @@ impl TryFrom for Server { porep_parameters, post_parameters, parallel_prove_commits: args.parallel_prove_commits.get(), - node_type: args.node_type, p2p_key: args.p2p_key, rendezvous_point_address: args.rendezvous_point_address, rendezvous_point: args.rendezvous_point, @@ -488,7 +481,6 @@ impl Server { }; let p2p_state = P2PState { - node_type: self.node_type, p2p_key: self.p2p_key, rendezvous_point_address: self.rendezvous_point_address, rendezvous_point: self.rendezvous_point, @@ -566,23 +558,11 @@ fn spawn_p2p_task( p2p_state: P2PState, cancellation_token: CancellationToken, ) -> Result>, ServerError> { - match p2p_state.node_type { - NodeType::Bootstrap => { - let config = - BootstrapConfig::new(p2p_state.p2p_key, p2p_state.rendezvous_point_address); - Ok(tokio::spawn(run_bootstrap_node(config, cancellation_token))) - } - NodeType::Register => { - let Some(rendezvous_point) = p2p_state.rendezvous_point else { - return Err(ServerError::P2P(P2PError::InvalidBehaviourConfig)); - }; - let config = RegisterConfig::new( - p2p_state.p2p_key, - p2p_state.rendezvous_point_address, - rendezvous_point, - p2p_state.registration_ttl, - ); - Ok(tokio::spawn(run_register_node(config, cancellation_token))) - } - } + let config = RegisterConfig::new( + p2p_state.p2p_key, + p2p_state.rendezvous_point_address, + p2p_state.rendezvous_point, + p2p_state.registration_ttl, + ); + Ok(tokio::spawn(run_register_node(config, cancellation_token))) } diff --git a/storage-provider/server/src/p2p/mod.rs b/storage-provider/server/src/p2p/mod.rs index 83814adac..7200aba60 100644 --- a/storage-provider/server/src/p2p/mod.rs +++ b/storage-provider/server/src/p2p/mod.rs @@ -1,38 +1,17 @@ -use std::{fmt::Display, path::PathBuf, str::FromStr}; +use std::str::FromStr; -use bootstrap::bootstrap; -use clap::ValueEnum; -use ed25519_dalek::{pkcs8::DecodePrivateKey, SigningKey}; use libp2p::{identity::Keypair, rendezvous::Namespace, Multiaddr, PeerId}; +use primitives::p2p::keypair_value_parser; use register::register; -use serde::{de, Deserialize}; +use serde::de; use tokio_util::sync::CancellationToken; -mod bootstrap; mod register; -pub(crate) use bootstrap::BootstrapConfig; pub(crate) use register::RegisterConfig; const P2P_NAMESPACE: &str = "polka-storage"; -#[derive(Default, Debug, Clone, Copy, ValueEnum, Deserialize)] -#[serde(rename_all = "lowercase")] -pub enum NodeType { - #[default] - Bootstrap, - Register, -} - -impl Display for NodeType { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - NodeType::Bootstrap => write!(f, "bootstrap"), - NodeType::Register => write!(f, "register"), - } - } -} - #[derive(Debug, thiserror::Error)] pub enum P2PError { #[error(transparent)] @@ -55,9 +34,6 @@ pub enum P2PError { /// Holds all the information needed for spawning a node. /// Node can be either a bootstrap or a registration node. pub(crate) struct P2PState { - /// P2P Node type, bootstrap or registration - pub(crate) node_type: NodeType, - /// P2P ED25519 private key pub(crate) p2p_key: Keypair, @@ -67,7 +43,7 @@ pub(crate) struct P2PState { /// PeerID of the bootstrap node used by the registration node. /// Optional because it is not used by the bootstrap node. - pub(crate) rendezvous_point: Option, + pub(crate) rendezvous_point: PeerId, /// TTL of the p2p registration in seconds pub(crate) registration_ttl: u64, @@ -81,56 +57,13 @@ pub(crate) fn deser_keypair<'de, D: de::Deserializer<'de>>(d: D) -> Result Result { - let key = if let Some(stripped) = src.strip_prefix('@') { - let path = PathBuf::from_str(stripped) - .map_err(|e| e.to_string())? - .canonicalize() - .map_err(|e| e.to_string())?; - SigningKey::read_pkcs8_pem_file(path).map_err(|e| e.to_string())? - } else { - let hex_key = hex::decode(src).map_err(|e| e.to_string())?; - SigningKey::try_from(hex_key.as_slice()).map_err(|e| e.to_string())? - }; - Keypair::ed25519_from_bytes(key.to_bytes()).map_err(|e| e.to_string()) -} - /// Parses a string to an optional Peer ID. /// Used in the [`ConfigurationArgs`] rendezvous_point field. -pub(crate) fn string_to_peer_id_option<'de, D: de::Deserializer<'de>>( +pub(crate) fn deserialize_string_to_peer_id<'de, D: de::Deserializer<'de>>( d: D, -) -> Result, D::Error> { - let s: Option = de::Deserialize::deserialize(d)?; - match s { - Some(s) => Ok(Some(PeerId::from_str(&s).map_err(de::Error::custom)?)), - None => Ok(None), - } -} - -/// Runs a bootstrap node from the given config. -/// The `CancellationToken` is used for a graceful shutdown if the user presses ctrl+c -pub async fn run_bootstrap_node( - config: BootstrapConfig, - token: CancellationToken, -) -> Result<(), P2PError> { - tracing::info!("Starting P2P bootstrap node"); - let (swarm, addr) = config.create_swarm()?; - - tokio::select! { - res = bootstrap(swarm, addr) => { - if let Err(e) = res { - tracing::error!("Failed to start P2P node. Reason: {e}"); - return Err(e); - } - }, - _ = token.cancelled() => { - tracing::info!("P2P node has been stopped by the cancellation token..."); - }, - } - - Ok(()) +) -> Result { + let s: String = de::Deserialize::deserialize(d)?; + PeerId::from_str(&s).map_err(de::Error::custom) } /// Runs a registration node from the given config. diff --git a/zombienet/local-kube-testnet.toml b/zombienet/local-kube-testnet.toml index d0b8d377d..92c89e562 100644 --- a/zombienet/local-kube-testnet.toml +++ b/zombienet/local-kube-testnet.toml @@ -25,7 +25,12 @@ id = 1000 # run charlie as parachain collator [[parachains.collators]] -args = ["--detailed-log-output", "-lparachain=debug,xcm=trace,runtime=trace"] +args = [ + "--detailed-log-output", + "--p2p-key=@/tmp/zombienet/private.pem", + "--p2p-listen-address=/ip4/127.0.0.1/tcp/62649", + "-lparachain=debug,xcm=trace,runtime=trace", +] command = "polka-storage-node" image = "polkadotstorage.azurecr.io/parachain-node:0.1.0" name = "charlie" diff --git a/zombienet/local-testnet.toml b/zombienet/local-testnet.toml index 0dce2b278..60ea29229 100644 --- a/zombienet/local-testnet.toml +++ b/zombienet/local-testnet.toml @@ -27,7 +27,13 @@ id = 1000 # run charlie as parachain collator [[parachains.collators]] -args = ["--detailed-log-output", "--pool-type=fork-aware", "-lparachain=debug,xcm=trace,runtime=trace,txpool=debug,basic-authorship=debug"] +args = [ + "--detailed-log-output", + "--p2p-key=@/tmp/zombienet/private.pem", + "--p2p-listen-address=/ip4/127.0.0.1/tcp/62649", + "--pool-type=fork-aware", + "-lparachain=debug,xcm=trace,runtime=trace,txpool=debug,basic-authorship=debug", +] command = "target/release/polka-storage-node" name = "charlie" validator = true