/* 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::{HashMap, HashSet},
sync::Arc,
};
use darkfi::{
async_daemonize, cli_desc,
rpc::{
jsonrpc::{ErrorCode, JsonError, JsonRequest, JsonResult},
server::{listen_and_serve, RequestHandler},
},
system::{StoppableTask, StoppableTaskPtr},
Error, Result,
};
use darkfi_serial::async_trait;
use log::{error, info};
use serde::Deserialize;
use smol::{
lock::{Mutex, MutexGuard, RwLock},
net::TcpStream,
stream::StreamExt,
Executor,
};
use structopt::StructOpt;
use structopt_toml::StructOptToml;
use url::Url;
use uuid::Uuid;
mod error;
mod stratum;
const CONFIG_FILE: &str = "darkfi_mmproxy.toml";
const CONFIG_FILE_CONTENTS: &str = include_str!("../darkfi_mmproxy.toml");
#[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, default_value = "tcp://127.0.0.1:3333")]
/// mmproxy JSON-RPC server listen URL
rpc_listen: Url,
#[structopt(long)]
/// List of worker logins
workers: Vec,
#[structopt(long)]
/// Set log file output
log: Option,
#[structopt(flatten)]
monerod: MonerodArgs,
}
#[derive(Clone, Debug, Deserialize, StructOpt, StructOptToml)]
#[structopt()]
struct MonerodArgs {
#[structopt(long, default_value = "mainnet")]
/// Mining reward wallet address
network: String,
#[structopt(long, default_value = "http://127.0.0.1:28081/json_rpc")]
/// monerod JSON-RPC server listen URL
rpc: Url,
}
struct MiningProxy {
/// monerod network type
monerod_network: monero::Network,
/// monerod RPC address
monerod_rpc: Url,
/// Workers UUIDs
workers: Arc>>,
/// JSON-RPC connection tracker
rpc_connections: Mutex>,
/// Main async executor reference
executor: Arc>,
}
impl MiningProxy {
async fn new(monerod: MonerodArgs, executor: Arc>) -> Result {
let monerod_network = match monerod.network.as_str() {
"mainnet" => monero::Network::Mainnet,
"testnet" => monero::Network::Testnet,
_ => {
error!("Invalid Monero network \"{}\"", monerod.network);
return Err(Error::Custom("Invalid Monero network".to_string()))
}
};
// Test that monerod RPC is reachable
if let Err(e) = TcpStream::connect(monerod.rpc.socket_addrs(|| None)?[0]).await {
error!("Failed connecting to monerod RPC: {}", e);
return Err(e.into())
}
let workers = Arc::new(RwLock::new(HashMap::new()));
let rpc_connections = Mutex::new(HashSet::new());
Ok(Self { monerod_network, monerod_rpc: monerod.rpc, workers, rpc_connections, executor })
}
}
#[async_trait]
#[rustfmt::skip]
impl RequestHandler for MiningProxy {
async fn handle_request(&self, req: JsonRequest) -> JsonResult {
match req.method.as_str() {
"ping" => self.pong(req.id, req.params).await,
// Stratum methods
"login" => self.stratum_login(req.id, req.params).await,
"submit" => self.stratum_submit(req.id, req.params).await,
"keepalived" => self.stratum_keepalived(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
}
}
async_daemonize!(realmain);
async fn realmain(args: Args, ex: Arc>) -> Result<()> {
info!("Starting DarkFi x Monero merge mining proxy...");
let mmproxy = Arc::new(MiningProxy::new(args.monerod, ex.clone()).await?);
info!("Starting JSON-RPC server");
let rpc_task = StoppableTask::new();
rpc_task.clone().start(
listen_and_serve(args.rpc_listen, mmproxy.clone(), None, ex.clone()),
|res| async move {
match res {
Ok(()) | Err(Error::RpcServerStopped) => mmproxy.stop_connections().await,
Err(e) => error!("Failed stopping JSON-RPC server: {}", e),
}
},
Error::RpcServerStopped,
ex.clone(),
);
info!("Merge mining proxy ready, waiting for connections...");
// 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...");
Ok(())
}