diff --git a/Cargo.lock b/Cargo.lock index 10818cb057..5303a9e6b2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -12574,6 +12574,22 @@ dependencies = [ "winapi", ] +[[package]] +name = "subspace-gateway" +version = "0.1.0" +dependencies = [ + "anyhow", + "clap", + "fdlimit", + "futures", + "mimalloc", + "supports-color", + "thiserror", + "tokio", + "tracing", + "tracing-subscriber", +] + [[package]] name = "subspace-kzg" version = "0.1.0" diff --git a/README.md b/README.md index 0a21038b52..fe7fed60c3 100644 --- a/README.md +++ b/README.md @@ -15,11 +15,12 @@ The structure of this repository is the following: - `crates` contains Subspace-specific Rust crates used to build node and farmer, most are following Substrate naming conventions - `subspace-node` is an implementation of the node for Subspace protocol - `subspace-farmer` is a CLI farmer app + - `subspace-gateway` is a Subspace Distributed Storage Network gateway - `domains` contains client and runtime code for decoupled execution and domains - `shared` contains low-level primitives used by the node, farmer, and other applications ## How to run -Please refer to [farming.md](/docs/farming.md) on how to run farmer. +Please refer to [farming.md](/docs/farming.md) for how to run the farmer. If you are looking to farm offline, or build from source for development purposes please refer to [development.md](/docs/development.md). diff --git a/crates/subspace-gateway/Cargo.toml b/crates/subspace-gateway/Cargo.toml new file mode 100644 index 0000000000..739eca1990 --- /dev/null +++ b/crates/subspace-gateway/Cargo.toml @@ -0,0 +1,29 @@ +[package] +name = "subspace-gateway" +version = "0.1.0" +authors = ["Teor "] +description = "A Subspace Network data gateway." +edition = "2021" +license = "MIT OR Apache-2.0" +homepage = "https://subspace.network" +repository = "https://github.com/autonomys/subspace" +include = [ + "/src", + "/Cargo.toml", + "/README.md" +] + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] + +[dependencies] +anyhow = "1.0.89" +clap = { version = "4.5.18", features = ["derive"] } +fdlimit = "0.3.0" +futures = "0.3.30" +mimalloc = "0.1.43" +supports-color = "3.0.1" +thiserror = "1.0.64" +tokio = { version = "1.40.0", features = ["macros"] } +tracing = "0.1.40" +tracing-subscriber = { version = "0.3.18", features = ["env-filter"] } diff --git a/crates/subspace-gateway/README.md b/crates/subspace-gateway/README.md new file mode 100644 index 0000000000..4f6869d7a0 --- /dev/null +++ b/crates/subspace-gateway/README.md @@ -0,0 +1,74 @@ +# Subspace Gateway + +Data Gateway implementation for Subspace Network Blockchain using [Substrate](https://docs.substrate.io/) framework. + +## Getting Started + +Follow these steps to get started with the Subspace Gateway :hammer_and_wrench: + +## Running + +It is recommended to follow general farming instructions that explain how to run both farmer and node together. + +## Build from source + +A Rust toolchain is required to compile this repository, but there are some extra dependencies for the gateway. + +`protoc` is required for `libp2p`. + +### Ubuntu + +LLVM/Clang and `make` are necessary: +```bash +sudo apt-get install llvm clang cmake make protobuf-compiler +``` + +### macOS + +1. Install via Homebrew: + +```bash +brew install llvm cmake make protobuf +``` + +2. Add `llvm` to your `~/.zshrc` or `~/.bashrc`: + +```bash +export PATH="/opt/homebrew/opt/llvm/bin:$PATH" +``` + +3. Activate the changes: + +```bash +source ~/.zshrc +``` + +4. Verify that `llvm` is installed: + +```bash +llvm-config --version +``` + +### Build + +Then build the gateway using Cargo: +``` +cargo build --profile production --bin subspace-gateway +target/production/subspace-gateway --version +``` + +#### Start the gateway + +Start a gateway connected to a single node development chain: +```bash +target/production/subspace-gateway run \ + --dev +``` + +### Embedded Docs + +Once the project has been built, the following command can be used to explore all parameters and subcommands: + +```bash +target/production/subspace-gateway --help +``` diff --git a/crates/subspace-gateway/src/commands.rs b/crates/subspace-gateway/src/commands.rs new file mode 100644 index 0000000000..cbdf1190c6 --- /dev/null +++ b/crates/subspace-gateway/src/commands.rs @@ -0,0 +1,92 @@ +//! Gateway subcommands. + +pub(crate) mod run; + +use crate::commands::run::RunOptions; +use clap::Parser; +use tokio::signal; +use tracing::level_filters::LevelFilter; +use tracing::{debug, warn}; +use tracing_subscriber::layer::SubscriberExt; +use tracing_subscriber::util::SubscriberInitExt; +use tracing_subscriber::{fmt, EnvFilter, Layer}; + +/// Commands for working with a gateway. +#[derive(Debug, Parser)] +#[clap(about, version)] +pub enum Command { + /// Run data gateway + Run(RunOptions), + // TODO: subcommand to run various benchmarks +} + +pub(crate) fn init_logger() { + // TODO: Workaround for https://github.com/tokio-rs/tracing/issues/2214, also on + // Windows terminal doesn't support the same colors as bash does + let enable_color = if cfg!(windows) { + false + } else { + supports_color::on(supports_color::Stream::Stderr).is_some() + }; + tracing_subscriber::registry() + .with( + fmt::layer().with_ansi(enable_color).with_filter( + EnvFilter::builder() + .with_default_directive(LevelFilter::INFO.into()) + .from_env_lossy(), + ), + ) + .init(); +} + +pub(crate) fn raise_fd_limit() { + match fdlimit::raise_fd_limit() { + Ok(fdlimit::Outcome::LimitRaised { from, to }) => { + debug!( + "Increased file descriptor limit from previous (most likely soft) limit {} to \ + new (most likely hard) limit {}", + from, to + ); + } + Ok(fdlimit::Outcome::Unsupported) => { + // Unsupported platform (a platform other than Linux or macOS) + } + Err(error) => { + warn!( + "Failed to increase file descriptor limit for the process due to an error: {}.", + error + ); + } + } +} + +#[cfg(unix)] +pub(crate) async fn shutdown_signal() { + use futures::FutureExt; + use std::pin::pin; + + futures::future::select( + pin!(signal::unix::signal(signal::unix::SignalKind::interrupt()) + .expect("Setting signal handlers must never fail") + .recv() + .map(|_| { + tracing::info!("Received SIGINT, shutting down gateway..."); + }),), + pin!(signal::unix::signal(signal::unix::SignalKind::terminate()) + .expect("Setting signal handlers must never fail") + .recv() + .map(|_| { + tracing::info!("Received SIGTERM, shutting down gateway..."); + }),), + ) + .await; +} + +#[cfg(not(unix))] +pub(crate) async fn shutdown_signal() { + signal::ctrl_c() + .await + .expect("Setting signal handlers must never fail"); + + tracing::info!("Received Ctrl+C, shutting down gateway..."); +} diff --git a/crates/subspace-gateway/src/commands/run.rs b/crates/subspace-gateway/src/commands/run.rs new file mode 100644 index 0000000000..1733699a1e --- /dev/null +++ b/crates/subspace-gateway/src/commands/run.rs @@ -0,0 +1,66 @@ +//! Gateway run command. +//! This is the primary command for the gateway. + +use crate::commands::shutdown_signal; +use clap::Parser; +use futures::{select, FutureExt}; +use std::pin::pin; +use std::{env, future}; +use tracing::info; + +/// Options for running a node +#[derive(Debug, Parser)] +pub struct RunOptions { + #[clap(flatten)] + gateway: GatewayOptions, +} + +/// Options for running a gateway +#[derive(Debug, Parser)] +pub(super) struct GatewayOptions { + /// Enable development mode. + #[arg(long)] + dev: bool, +} + +/// Default run command for gateway +#[expect(clippy::redundant_locals, reason = "code is incomplete")] +pub async fn run(run_options: RunOptions) -> anyhow::Result<()> { + let signal = shutdown_signal(); + + let RunOptions { + gateway: GatewayOptions { dev: _ }, + } = run_options; + + info!("Subspace Gateway"); + info!("✌️ version {}", env!("CARGO_PKG_VERSION")); + info!("❤️ by {}", env!("CARGO_PKG_AUTHORS")); + + let dsn_fut = future::pending::<()>(); + let rpc_fut = future::pending::<()>(); + + // This defines order in which things are dropped + let dsn_fut = dsn_fut; + let rpc_fut = rpc_fut; + + let dsn_fut = pin!(dsn_fut); + let rpc_fut = pin!(rpc_fut); + + select! { + // Signal future + () = signal.fuse() => {}, + + // Networking future + () = dsn_fut.fuse() => { + info!("DSN network runner exited."); + }, + + // RPC service future + () = rpc_fut.fuse() => { + info!("RPC server exited."); + }, + + } + + anyhow::Ok(()) +} diff --git a/crates/subspace-gateway/src/main.rs b/crates/subspace-gateway/src/main.rs new file mode 100644 index 0000000000..0e30450697 --- /dev/null +++ b/crates/subspace-gateway/src/main.rs @@ -0,0 +1,45 @@ +//! Subspace gateway implementation. + +// TODO: Remove +#![allow( + clippy::needless_return, + reason = "https://github.com/rust-lang/rust-clippy/issues/13458" +)] + +mod commands; + +use crate::commands::{init_logger, raise_fd_limit, Command}; +use clap::Parser; + +#[global_allocator] +static GLOBAL: mimalloc::MiMalloc = mimalloc::MiMalloc; + +/// Subspace gateway error. +#[derive(thiserror::Error, Debug)] +pub enum Error { + /// Other kind of error. + #[error("Other: {0}")] + Other(String), +} + +impl From for Error { + #[inline] + fn from(s: String) -> Self { + Self::Other(s) + } +} + +#[tokio::main] +async fn main() -> anyhow::Result<()> { + init_logger(); + raise_fd_limit(); + + let command = Command::parse(); + + match command { + Command::Run(run_options) => { + commands::run::run(run_options).await?; + } + } + Ok(()) +}