Skip to content

Commit

Permalink
Merge pull request #3137 from autonomys/gateway-dsn
Browse files Browse the repository at this point in the history
Add DSN client to subspace-gateway binary
  • Loading branch information
teor2345 authored Oct 21, 2024
2 parents 02b054d + 82b6df7 commit 2270c17
Show file tree
Hide file tree
Showing 7 changed files with 202 additions and 4 deletions.
7 changes: 7 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 7 additions & 0 deletions crates/subspace-gateway/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,17 @@ targets = ["x86_64-unknown-linux-gnu"]

[dependencies]
anyhow = "1.0.89"
async-trait = "0.1.83"
clap = { version = "4.5.18", features = ["derive"] }
fdlimit = "0.3.0"
futures = "0.3.31"
hex = "0.4.3"
jsonrpsee = { version = "0.24.5", features = ["server"] }
mimalloc = "0.1.43"
parking_lot = "0.12.2"
subspace-core-primitives = { version = "0.1.0", path = "../subspace-core-primitives" }
subspace-networking = { version = "0.1.0", path = "../subspace-networking" }
subspace-rpc-primitives = { version = "0.1.0", path = "../subspace-rpc-primitives" }
supports-color = "3.0.1"
thiserror = "1.0.64"
tokio = { version = "1.40.0", features = ["rt-multi-thread", "signal", "macros"] }
Expand Down
2 changes: 1 addition & 1 deletion crates/subspace-gateway/README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Subspace Gateway

Data Gateway implementation for Subspace Network Blockchain using [Substrate](https://docs.substrate.io/) framework.
Data Gateway implementation for the Subspace Network Blockchain.

## Getting Started

Expand Down
29 changes: 26 additions & 3 deletions crates/subspace-gateway/src/commands/run.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
//! Gateway run command.
//! This is the primary command for the gateway.

mod dsn;

use crate::commands::run::dsn::NetworkArgs;
use crate::commands::shutdown_signal;
use clap::Parser;
use futures::{select, FutureExt};
Expand All @@ -19,8 +22,14 @@ pub struct RunOptions {
#[derive(Debug, Parser)]
pub(super) struct GatewayOptions {
/// Enable development mode.
#[arg(long)]
///
/// Implies following flags (unless customized):
/// * `--allow-private-ips`
#[arg(long, verbatim_doc_comment)]
dev: bool,

#[clap(flatten)]
dsn_options: NetworkArgs,
}

/// Default run command for gateway
Expand All @@ -29,14 +38,28 @@ pub async fn run(run_options: RunOptions) -> anyhow::Result<()> {
let signal = shutdown_signal();

let RunOptions {
gateway: GatewayOptions { dev: _ },
gateway: GatewayOptions {
dev,
mut dsn_options,
},
} = run_options;

// Development mode handling is limited to this section
{
if dev {
dsn_options.allow_private_ips = true;
}
}

info!("Subspace Gateway");
info!("✌️ version {}", env!("CARGO_PKG_VERSION"));
info!("❤️ by {}", env!("CARGO_PKG_AUTHORS"));

let dsn_fut = future::pending::<()>();
// TODO: move this service code into its own function, in a new library part of this crate
#[expect(unused_variables, reason = "implementation is incomplete")]
let (dsn_node, mut dsn_node_runner, node_client) = dsn::configure_network(dsn_options).await?;
let dsn_fut = dsn_node_runner.run();

let rpc_fut = future::pending::<()>();

// This defines order in which things are dropped
Expand Down
99 changes: 99 additions & 0 deletions crates/subspace-gateway/src/commands/run/dsn.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
//! DSN config and implementation for the gateway.

use crate::node_client::{NodeClient, RpcNodeClient};
use anyhow::anyhow;
use clap::{Parser, ValueHint};
use subspace_networking::libp2p::kad::Mode;
use subspace_networking::libp2p::Multiaddr;
use subspace_networking::{construct, Config, KademliaMode, Node, NodeRunner};
use tracing::{debug, info};

/// Configuration for network stack
#[derive(Debug, Parser)]
pub(crate) struct NetworkArgs {
/// WebSocket RPC URL of the Subspace node to connect to.
///
/// This node provides the DSN protocol version, default bootstrap nodes, and piece validation
/// metadata.
#[arg(long, value_hint = ValueHint::Url, default_value = "ws://127.0.0.1:9944")]
node_rpc_url: String,

/// Multiaddrs of DSN bootstrap nodes to connect to on startup, multiple are supported.
///
/// The default bootstrap nodes are fetched from the node RPC connection.
#[arg(long)]
pub(crate) dsn_bootstrap_nodes: Vec<Multiaddr>,

/// Multiaddrs of DSN reserved nodes to maintain a connection to, multiple are supported.
#[arg(long)]
dsn_reserved_peers: Vec<Multiaddr>,

/// Enable non-global (private, shared, loopback..) addresses in the Kademlia DHT.
/// By default these addresses are excluded from the DHT.
#[arg(long, default_value_t = false)]
pub(crate) allow_private_ips: bool,

/// Maximum established outgoing swarm connection limit.
#[arg(long, default_value_t = 100)]
pub(crate) out_connections: u32,

/// Maximum pending outgoing swarm connection limit.
#[arg(long, default_value_t = 100)]
pub(crate) pending_out_connections: u32,
}

/// Create a DSN network client with the supplied configuration.
// TODO:
// - move this DSN code into a new library part of this crate
// - change NetworkArgs to a struct that's independent of clap
pub async fn configure_network(
NetworkArgs {
node_rpc_url,
mut dsn_bootstrap_nodes,
dsn_reserved_peers,
allow_private_ips,
out_connections,
pending_out_connections,
}: NetworkArgs,
) -> anyhow::Result<(Node, NodeRunner<()>, RpcNodeClient)> {
// TODO:
// - store keypair on disk and allow CLI override
// - cache known peers on disk
// - prometheus telemetry
let default_config = Config::<()>::default();

info!(url = %node_rpc_url, "Connecting to node RPC");
let node_client = RpcNodeClient::new(&node_rpc_url)
.await
.map_err(|error| anyhow!("Failed to connect to node RPC: {error}"))?;

// The gateway only needs the first part of the farmer info.
let farmer_app_info = node_client
.farmer_app_info()
.await
.map_err(|error| anyhow!("Failed to get farmer app info: {error}"))?;

// Fall back to the node's bootstrap nodes.
if dsn_bootstrap_nodes.is_empty() {
debug!(dsn_bootstrap_nodes = ?farmer_app_info.dsn_bootstrap_nodes, "Setting DSN bootstrap nodes...");
dsn_bootstrap_nodes.clone_from(&farmer_app_info.dsn_bootstrap_nodes);
}

let dsn_protocol_version = hex::encode(farmer_app_info.genesis_hash);
debug!(?dsn_protocol_version, "Setting DSN protocol version...");

let config = Config {
protocol_version: dsn_protocol_version,
bootstrap_addresses: dsn_bootstrap_nodes,
reserved_peers: dsn_reserved_peers,
allow_non_global_addresses_in_dht: allow_private_ips,
max_established_outgoing_connections: out_connections,
max_pending_outgoing_connections: pending_out_connections,
kademlia_mode: KademliaMode::Static(Mode::Client),
..default_config
};

let (node, node_runner) = construct(config)?;

Ok((node, node_runner, node_client))
}
1 change: 1 addition & 0 deletions crates/subspace-gateway/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
)]

mod commands;
mod node_client;

use crate::commands::{init_logger, raise_fd_limit, Command};
use clap::Parser;
Expand Down
61 changes: 61 additions & 0 deletions crates/subspace-gateway/src/node_client.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
//! Node client implementation that connects to node via RPC (WebSockets)

use async_trait::async_trait;
use jsonrpsee::core::client::{ClientT, Error as JsonError};
use jsonrpsee::rpc_params;
use jsonrpsee::ws_client::{WsClient, WsClientBuilder};
use std::fmt;
use std::sync::Arc;
use subspace_core_primitives::segments::{SegmentHeader, SegmentIndex};
use subspace_rpc_primitives::FarmerAppInfo;

/// Node client implementation that connects to node via RPC (WebSockets).
///
/// This implementation is supposed to be used on local network and not via public Internet due to
/// sensitive contents.
#[derive(Debug, Clone)]
pub struct RpcNodeClient {
client: Arc<WsClient>,
}

impl RpcNodeClient {
/// Create a new instance of [`NodeClient`].
pub async fn new(url: &str) -> Result<Self, JsonError> {
let client = Arc::new(WsClientBuilder::default().build(url).await?);
Ok(Self { client })
}
}

/// Abstraction of the Node Client
#[async_trait]
pub trait NodeClient: fmt::Debug + Send + Sync + 'static {
/// Get farmer app info
async fn farmer_app_info(&self) -> anyhow::Result<FarmerAppInfo>;

/// Get segment headers for the segments
#[expect(dead_code, reason = "implementation is incomplete")]
async fn segment_headers(
&self,
segment_indices: Vec<SegmentIndex>,
) -> anyhow::Result<Vec<Option<SegmentHeader>>>;
}

#[async_trait]
impl NodeClient for RpcNodeClient {
async fn farmer_app_info(&self) -> anyhow::Result<FarmerAppInfo> {
Ok(self
.client
.request("subspace_getFarmerAppInfo", rpc_params![])
.await?)
}

async fn segment_headers(
&self,
segment_indices: Vec<SegmentIndex>,
) -> anyhow::Result<Vec<Option<SegmentHeader>>> {
Ok(self
.client
.request("subspace_segmentHeaders", rpc_params![&segment_indices])
.await?)
}
}

0 comments on commit 2270c17

Please sign in to comment.