diff --git a/Cargo.lock b/Cargo.lock index a8a71ca2d61e..bff84f766287 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1612,6 +1612,7 @@ dependencies = [ "structopt", "structopt-toml", "url", + "uuid", ] [[package]] @@ -6653,6 +6654,9 @@ name = "uuid" version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "79daa5ed5740825c40b389c5e50312b9c86df53fccd33f281df655642b43869d" +dependencies = [ + "getrandom 0.2.10", +] [[package]] name = "valuable" diff --git a/bin/darkfi-mmproxy/Cargo.toml b/bin/darkfi-mmproxy/Cargo.toml index 3fb024e40924..97e40febb645 100644 --- a/bin/darkfi-mmproxy/Cargo.toml +++ b/bin/darkfi-mmproxy/Cargo.toml @@ -17,6 +17,7 @@ log = "0.4.20" # Encoding url = "2.4.1" +uuid = {version = "1.4.1", features = ["v4"]} # Daemon easy-parallel = "3.3.0" diff --git a/bin/darkfi-mmproxy/darkfi_mmproxy.toml b/bin/darkfi-mmproxy/darkfi_mmproxy.toml index e69de29bb2d1..db1554ede8aa 100644 --- a/bin/darkfi-mmproxy/darkfi_mmproxy.toml +++ b/bin/darkfi-mmproxy/darkfi_mmproxy.toml @@ -0,0 +1,2 @@ +# List of worker logins, comment out to allow anything. +workers = ["x:x"] \ No newline at end of file diff --git a/bin/darkfi-mmproxy/src/main.rs b/bin/darkfi-mmproxy/src/main.rs index ad168bf14503..157c75e2e658 100644 --- a/bin/darkfi-mmproxy/src/main.rs +++ b/bin/darkfi-mmproxy/src/main.rs @@ -16,7 +16,10 @@ * along with this program. If not, see . */ -use std::{collections::HashSet, sync::Arc}; +use std::{ + collections::{HashMap, HashSet}, + sync::Arc, +}; use darkfi::{ async_daemonize, cli_desc, @@ -61,19 +64,27 @@ struct Args { /// JSON-RPC server listen URL rpc_listen: Url, + #[structopt(long)] + /// List of worker logins + workers: Vec, + #[structopt(long)] /// Set log file output log: Option, } struct MiningProxy { + /// Worker logins + logins: HashMap, /// JSON-RPC connection tracker rpc_connections: Mutex>, + /// Main async executor reference + executor: Arc>, } impl MiningProxy { - fn new() -> Self { - Self { rpc_connections: Mutex::new(HashSet::new()) } + fn new(logins: HashMap, executor: Arc>) -> Self { + Self { logins, rpc_connections: Mutex::new(HashSet::new()), executor } } } @@ -136,9 +147,20 @@ impl RequestHandler for MiningProxy { async_daemonize!(realmain); async fn realmain(args: Args, ex: Arc>) -> Result<()> { - info!("Starting JSON-RPC server"); - let mmproxy = Arc::new(MiningProxy::new()); + // Parse worker logins + let mut logins = HashMap::new(); + for worker in args.workers { + let mut split = worker.split(':'); + let user = split.next().unwrap().to_string(); + let pass = split.next().unwrap().to_string(); + info!("Whitelisting worker \"{}:{}\"", user, pass); + logins.insert(user, pass); + } + + let mmproxy = Arc::new(MiningProxy::new(logins, ex.clone())); let mmproxy_ = Arc::clone(&mmproxy); + + 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()), diff --git a/bin/darkfi-mmproxy/src/stratum.rs b/bin/darkfi-mmproxy/src/stratum.rs index c2137882e8cf..0d63abc339bf 100644 --- a/bin/darkfi-mmproxy/src/stratum.rs +++ b/bin/darkfi-mmproxy/src/stratum.rs @@ -16,20 +16,84 @@ * along with this program. If not, see . */ +use std::collections::HashMap; + use darkfi::rpc::{ jsonrpc::{ErrorCode, JsonError, JsonResult}, util::JsonValue, }; +use uuid::Uuid; use super::MiningProxy; +/// Algo string representing Monero's RandomX +pub const RANDOMX_ALGO: &str = "rx/0"; + impl MiningProxy { + /// Stratum login method. `darkfi-mmproxy` will check that it is a valid worker + /// login, and will also search for `RANDOMX_ALGO`. + /// TODO: More proper error codes pub async fn stratum_login(&self, id: u16, params: JsonValue) -> JsonResult { let params = params.get::>().unwrap(); if params.len() != 1 || !params[0].is_object() { return JsonError::new(ErrorCode::InvalidParams, None, id).into() } + let params = params[0].get::>().unwrap(); + + if !params.contains_key("login") || + !params.contains_key("pass") || + !params.contains_key("agent") || + !params.contains_key("algo") + { + return JsonError::new(ErrorCode::InvalidParams, None, id).into() + } + + let Some(login) = params["login"].get::() else { + return JsonError::new(ErrorCode::InvalidParams, None, id).into() + }; + + let Some(pass) = params["pass"].get::() else { + return JsonError::new(ErrorCode::InvalidParams, None, id).into() + }; + + let Some(agent) = params["agent"].get::() else { + return JsonError::new(ErrorCode::InvalidParams, None, id).into() + }; + + let Some(algos) = params["algo"].get::>() else { + return JsonError::new(ErrorCode::InvalidParams, None, id).into() + }; + + // We'll only support rx/0 algo. + let mut found_xmr_algo = false; + for algo in algos { + if !algo.is_string() { + return JsonError::new(ErrorCode::InvalidParams, None, id).into() + } + + if algo.get::().unwrap() == RANDOMX_ALGO { + found_xmr_algo = true; + break + } + } + + if !found_xmr_algo { + return JsonError::new(ErrorCode::InvalidParams, None, id).into() + } + + // Check valid login + let Some(known_pass) = self.logins.get(login) else { + return JsonError::new(ErrorCode::InvalidParams, None, id).into() + }; + + if known_pass != pass { + return JsonError::new(ErrorCode::InvalidParams, None, id).into() + } + + // Login success, generate UUID + let uuid = Uuid::new_v4(); + todo!() }