diff --git a/.gitignore b/.gitignore index 48eca7c9fe47..241250e003d9 100644 --- a/.gitignore +++ b/.gitignore @@ -28,6 +28,9 @@ /bin/lilith/lilith /lilith +/bin/swapd/swapd +/swapd + /bin/tau/taud/taud /taud diff --git a/Cargo.lock b/Cargo.lock index 799c1eb6d9dc..f4124adeff0d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6335,6 +6335,25 @@ dependencies = [ "sval_fmt", ] +[[package]] +name = "swapd" +version = "0.4.1" +dependencies = [ + "darkfi", + "darkfi-serial", + "easy-parallel", + "log", + "serde", + "signal-hook", + "signal-hook-async-std", + "simplelog", + "sled", + "smol", + "structopt", + "structopt-toml", + "url", +] + [[package]] name = "syn" version = "1.0.109" diff --git a/Cargo.toml b/Cargo.toml index 733eeec14bb5..4bda18cfe196 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -30,6 +30,7 @@ members = [ "bin/genev/genevd", "bin/genev/genev-cli", "bin/darkirc", + "bin/swapd", "bin/tau/taud", #"bin/tau/tau-cli", "bin/vanityaddr", diff --git a/Makefile b/Makefile index 7c2fefc5bd30..a1b037bfce88 100644 --- a/Makefile +++ b/Makefile @@ -24,6 +24,7 @@ BINS = \ genev \ genevd \ lilith \ + swapd \ taud \ vanityaddr @@ -87,6 +88,13 @@ lilith: RUST_TARGET="$(RUST_TARGET)" \ RUSTFLAGS="$(RUSTFLAGS)" +swapd: + $(MAKE) -C bin/$@ \ + PREFIX="$(PREFIX)" \ + CARGO="$(CARGO)" \ + RUST_TARGET="$(RUST_TARGET)" \ + RUSTFLAGS="$(RUSTFLAGS)" + taud: $(MAKE) -C bin/tau/$@ \ PREFIX="$(PREFIX)" \ @@ -142,6 +150,7 @@ clean: $(MAKE) -C bin/genev/genev-cli clean $(MAKE) -C bin/genev/genevd clean $(MAKE) -C bin/lilith clean + $(MAKE) -C bin/swapd clean $(MAKE) -C bin/tau/taud clean $(MAKE) -C bin/vanityaddr clean RUSTFLAGS="$(RUSTFLAGS)" $(CARGO) clean --target=$(RUST_TARGET) --release diff --git a/bin/swapd/Cargo.toml b/bin/swapd/Cargo.toml new file mode 100644 index 000000000000..955d8a46a547 --- /dev/null +++ b/bin/swapd/Cargo.toml @@ -0,0 +1,34 @@ +[package] +name = "swapd" +version = "0.4.1" +homepage = "https://dark.fi" +description = "Atomic Swap Daemon" +authors = ["Dyne.org foundation "] +repository = "https://github.com/darkrenaissance/darkfi" +license = "AGPL-3.0-only" +edition = "2021" + +[dependencies] +darkfi = {path = "../../", features = ["async-daemonize", "async-serial", "system", "util", "net", "rpc", "sled"]} +darkfi-serial = {path = "../../src/serial", features = ["async"]} + +# Misc +log = "0.4.20" + +# Encoding +url = "2.5.0" + +# Database +sled = "0.34.7" + +# Daemon +easy-parallel = "3.3.1" +signal-hook-async-std = "0.2.2" +signal-hook = "0.3.17" +simplelog = "0.12.1" +smol = "1.3.0" + +# Argument parsing +serde = {version = "1.0.193", features = ["derive"]} +structopt = "0.3.26" +structopt-toml = "0.5.1" diff --git a/bin/swapd/Makefile b/bin/swapd/Makefile new file mode 100644 index 000000000000..dbdadc8fd259 --- /dev/null +++ b/bin/swapd/Makefile @@ -0,0 +1,41 @@ +.POSIX: + +# Install prefix +PREFIX = $(HOME)/.cargo + +# Cargo binary +CARGO = cargo +nightly + +# Compile target +RUST_TARGET = $(shell rustc -Vv | grep '^host: ' | cut -d' ' -f2) +# Uncomment when doing musl static builds +#RUSTFLAGS = -C target-feature=+crt-static -C link-self-contained=yes + +SRC = \ + Cargo.toml \ + ../../Cargo.toml \ + $(shell find src -type f -name '*.rs') \ + $(shell find ../../src -type f -name '*.rs') \ + +BIN = $(shell grep '^name = ' Cargo.toml | cut -d' ' -f3 | tr -d '"') + +all: $(BIN) + +$(BIN): $(SRC) + RUSTFLAGS="$(RUSTFLAGS)" $(CARGO) build --target=$(RUST_TARGET) --release --package $@ + cp -f ../../target/$(RUST_TARGET)/release/$@ $@ + cp -f ../../target/$(RUST_TARGET)/release/$@ ../../$@ + +clean: + RUSTFLAGS="$(RUSTFLAGS)" $(CARGO) clean --target=$(RUST_TARGET) --release --package $(BIN) + rm -f $(BIN) ../../$(BIN) + +install: all + mkdir -p $(DESTDIR)$(PREFIX)/bin + cp -f $(BIN) $(DESTDIR)$(PREFIX)/bin + chmod 755 $(DESTDIR)$(PREFIX)/bin/$(BIN) + +uninstall: + rm -f $(DESTDIR)$(PREFIX)/bin/$(BIN) + +.PHONY: all clean install uninstall diff --git a/bin/swapd/darkfi-swapd b/bin/swapd/darkfi-swapd new file mode 100755 index 000000000000..3d87095e9ebc Binary files /dev/null and b/bin/swapd/darkfi-swapd differ diff --git a/bin/swapd/src/main.rs b/bin/swapd/src/main.rs new file mode 100644 index 000000000000..23e87d3e0430 --- /dev/null +++ b/bin/swapd/src/main.rs @@ -0,0 +1,127 @@ +/* This file is part of DarkFi (https://dark.fi) + * + * Copyright (C) 2020-2023 Dyne.org foundation + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +use std::{collections::HashSet, sync::Arc}; + +use darkfi::{ + async_daemonize, cli_desc, + rpc::server::{listen_and_serve, RequestHandler}, + system::{StoppableTask, StoppableTaskPtr}, + util::path::expand_path, + Error, Result, +}; +use log::{error, info}; +use serde::Deserialize; +use smol::{fs, lock::Mutex, stream::StreamExt, Executor}; +use structopt::StructOpt; +use structopt_toml::StructOptToml; +use url::Url; + +const CONFIG_FILE: &str = "swapd.toml"; +const CONFIG_FILE_CONTENTS: &str = include_str!("../swapd.toml"); + +/// JSON-RPC server methods +mod rpc; + +#[derive(Clone, Debug, Deserialize, StructOpt, StructOptToml)] +#[serde(default)] +#[structopt(name = "darkfi-mmproxy", about = cli_desc!())] +struct Args { + #[structopt(short, parse(from_occurrences))] + /// Increase verbosity (-vvv supported) + verbose: u8, + + #[structopt(short, long)] + /// Configuration file to use + config: Option, + + #[structopt(long)] + /// Set log file output + log: Option, + + #[structopt(flatten)] + swapd: SwapdArgs, +} + +#[derive(Clone, Debug, Deserialize, StructOpt, StructOptToml)] +#[structopt()] +struct SwapdArgs { + #[structopt(long, default_value = "tcp://127.0.0.1:52821")] + /// darkfi-swapd JSON-RPC listen URL + swapd_rpc: Url, + + #[structopt(long, default_value = "~/.local/darkfi/swapd")] + /// Path to swapd's filesystem database + swapd_db: String, +} + +/// Swapd daemon state +struct Swapd { + /// Main reference to the swapd filesystem databaase + _sled_db: sled::Db, + /// JSON-RPC connection tracker + rpc_connections: Mutex>, +} + +impl Swapd { + /// Instantiate `Swapd` state + async fn new(_swapd_args: &SwapdArgs, sled_db: sled::Db) -> Result { + Ok(Self { _sled_db: sled_db, rpc_connections: Mutex::new(HashSet::new()) }) + } +} + +async_daemonize!(realmain); +async fn realmain(args: Args, ex: Arc>) -> Result<()> { + info!("Starting DarkFi Atomic Swap Daemon..."); + + // Create datastore path if not there already. + let datastore = expand_path(&args.swapd.swapd_db)?; + fs::create_dir_all(&datastore).await?; + let sled_db = sled::open(datastore)?; + + info!("Initializing daemon state"); + let swapd = Arc::new(Swapd::new(&args.swapd, sled_db.clone()).await?); + + info!("Starting JSON-RPC server on {}", args.swapd.swapd_rpc); + let swapd_ = Arc::clone(&swapd); + let rpc_task = StoppableTask::new(); + rpc_task.clone().start( + listen_and_serve(args.swapd.swapd_rpc, swapd.clone(), None, ex.clone()), + |res| async move { + match res { + Ok(()) | Err(Error::RpcServerStopped) => swapd_.stop_connections().await, + Err(e) => error!("Failed stopping JSON-RPC server: {}", e), + } + }, + Error::RpcServerStopped, + ex.clone(), + ); + + info!("Ready to operate"); + + // Signal handling for graceful termination. + let (signals_handler, signals_task) = SignalHandler::new(ex)?; + signals_handler.wait_termination(signals_task).await?; + info!("Caught termination signal, cleaning up and exiting"); + + info!("Flushing sled database"); + sled_db.flush_async().await?; + + info!("Shut down successfully"); + Ok(()) +} diff --git a/bin/swapd/src/rpc.rs b/bin/swapd/src/rpc.rs new file mode 100644 index 000000000000..07fe0b6dbaeb --- /dev/null +++ b/bin/swapd/src/rpc.rs @@ -0,0 +1,67 @@ +/* This file is part of DarkFi (https://dark.fi) + * + * Copyright (C) 2020-2023 Dyne.org foundation + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +use std::collections::HashSet; + +use darkfi::{ + rpc::{ + jsonrpc::{ErrorCode, JsonError, JsonRequest, JsonResponse, JsonResult}, + server::RequestHandler, + util::JsonValue, + }, + system::StoppableTaskPtr, +}; +use darkfi_serial::async_trait; +use smol::lock::MutexGuard; + +use super::Swapd; + +#[async_trait] +impl RequestHandler for Swapd { + async fn handle_request(&self, req: JsonRequest) -> JsonResult { + match req.method.as_str() { + "ping" => self.pong(req.id, req.params).await, + "hello" => self.hello(req.id, req.params).await, + _ => JsonError::new(ErrorCode::MethodNotFound, None, req.id).into(), + } + } + + async fn connections_mut(&self) -> MutexGuard<'_, HashSet> { + self.rpc_connections.lock().await + } +} + +impl Swapd { + // RPCAPI: + // Use this kind of comment in order to have the RPC spec automatically + // generated in the mdbook. You should be able to write any kind of + // markdown in here. + // + // At the bottom, you should have the reqrep in JSON: + // + // --> {"jsonrpc": "2.0", "method": "hello", "params": ["hello"], "id": 42} + // --> {"jsonrpc": "2.0", "result": "hello", "id": 42} + async fn hello(&self, id: u16, params: JsonValue) -> JsonResult { + let params = params.get::>().unwrap(); + if params.len() != 1 || !params[0].is_string() { + return JsonError::new(ErrorCode::InvalidParams, None, id).into() + } + + return JsonResponse::new(params[0].clone(), id).into() + } +} diff --git a/bin/swapd/swapd.toml b/bin/swapd/swapd.toml new file mode 100644 index 000000000000..91112fff20a2 --- /dev/null +++ b/bin/swapd/swapd.toml @@ -0,0 +1,14 @@ +## darkfi-swapd configuration file +## +## Please make sure you go through all the settings so you can configure +## your daemon properly. +## +## The default values are left commented. They can be overridden either by +## uncommenting, or by using the command-line. + +#[swapd] +# darkfi-swapd JSON-RPC listen URL. +#swapd_rpc = "tcp://127.0.0.1:52821" + +# Path to swapd's filesystem database +#swapd_db = "~/.local/darkfi/swapd"