diff --git a/core/src/miner/stratum.rs b/core/src/miner/stratum.rs index 11ad73a4c6..3cd18b6a7b 100644 --- a/core/src/miner/stratum.rs +++ b/core/src/miner/stratum.rs @@ -19,6 +19,7 @@ use std::net::{AddrParseError, SocketAddr}; use std::sync::Arc; +use super::super::error::Error as MinerError; use cstratum::{Error as StratumServiceError, JobDispatcher, PushWorkHandler, Stratum as StratumService}; use primitives::{Bytes, H256, U256}; @@ -56,14 +57,14 @@ impl JobDispatcher for StratumJobDispatcher { if !self.miner.can_produce_work_package() { cwarn!(STRATUM, "Cannot get work package - engine seals internally."); - return Err(StratumServiceError::NoWork) + return Err(StratumServiceError::InternalError) } match self.miner.submit_seal(&*self.client, pow_hash, seal) { Ok(_) => Ok(()), Err(e) => { cwarn!(STRATUM, "submit_seal error: {:?}", e); - Err(StratumServiceError::Dispatch(e.to_string())) + Err(StratumServiceError::from(e)) } } } @@ -98,6 +99,16 @@ pub enum Error { Address(AddrParseError), } +impl From for StratumServiceError { + fn from(err: MinerError) -> Self { + match err { + MinerError::PowHashInvalid => StratumServiceError::PowHashInvalid, + MinerError::PowInvalid => StratumServiceError::PowInvalid, + _ => StratumServiceError::InternalError, + } + } +} + impl From for Error { fn from(service_err: StratumServiceError) -> Error { Error::Service(service_err) diff --git a/spec/Stratum.md b/spec/Stratum.md index 146d879f2f..7e84bde05a 100644 --- a/spec/Stratum.md +++ b/spec/Stratum.md @@ -101,7 +101,7 @@ Params: 1. powHash: `string` 2. seal: `string[]` -Return Type: `bool` +Return Type: `null` Request Example ``` @@ -118,27 +118,24 @@ Response Example { "jsonrpc": "2.0", "id": 4, - "result": true, + "result": null, "error": null } ``` -## Exception Handling (DRAFT) +## Exception Handling Stratum defines simple exception handling. Example of a rejected share looks like: ``` { "jsonrpc": "2.0", "id": 5, - "result": null, - "error": (21, "Job not found", null) + "error": {"code":21, "message":"Invalid Pow hash"} } ``` -Where the error field is defined as (error_code, human_readable_message, traceback). Traceback may contain additional information about debugging errors. +Where the error field is defined as (error_code, human_readable_message). Proposed error codes for mining services are: -* 20 - Other/Unknown -* 21 - Job not found (=stale) -* 22 - Duplicate share -* 23 - Low target share -* 24 - Unauthorized worker -* 25 - Not subscribed +* 20 - Internal Error +* 21 - Invalid Pow hash (=stale) +* 22 - Invalid the nonce +* 23 - Unauthorized worker diff --git a/stratum/src/lib.rs b/stratum/src/lib.rs index fb7b4546a5..af1565fea7 100644 --- a/stratum/src/lib.rs +++ b/stratum/src/lib.rs @@ -181,22 +181,24 @@ impl StratumImpl { /// rpc method `mining.submit` fn submit(&self, params: Params, meta: SocketMetadata) -> RpcResult { - params - .parse::<(H256, Vec)>() - .map(|(pow_hash, seal)| { - let seal = seal.iter().cloned().map(Into::into).collect(); - match self.dispatcher.submit((pow_hash, seal)) { - Ok(()) => { - self.update_peers(&meta.tcp_dispatcher.expect("tcp_dispatcher is always initialized")); - to_value(true) - } - Err(submit_err) => { - cwarn!(STRATUM, "Error while submitting share: {:?}", submit_err); - to_value(false) - } + let workers = self.workers.read(); + if workers.contains_key(&meta.addr) == false { + return Err(Error::UnauthorizedWorker.into()) + } + + params.parse::<(H256, Vec)>().and_then(|(pow_hash, seal)| { + let seal = seal.iter().cloned().map(Into::into).collect(); + match self.dispatcher.submit((pow_hash, seal)) { + Ok(()) => { + self.update_peers(&meta.tcp_dispatcher.expect("tcp_dispatcher is always initialized")); + Ok(jsonrpc_core::Value::Null) } - }) - .map(|v| v.expect("Only true/false is returned and it's always serializable")) + Err(submit_err) => { + cwarn!(STRATUM, "Error while submitting share: {:?}", submit_err); + Err(submit_err.into()) + } + } + }) } /// Helper method @@ -524,6 +526,36 @@ mod tests { let response = String::from_utf8(core.run(stream).expect("Core should run with no errors")) .expect("Response should be utf-8"); - assert_eq!("{\"jsonrpc\":\"2.0\",\"result\":true,\"id\":2}\n", response); + assert_eq!("{\"jsonrpc\":\"2.0\",\"result\":null,\"id\":2}\n", response); + } + + #[test] + fn should_return_error_when_unauthorized_worker_submits() { + let addr = SocketAddr::from_str("127.0.0.1:19991").unwrap(); + let _stratum = + Stratum::start(&addr, Arc::new(DummyManager::build().of_initial(r#"["dummy authorize payload"]"#)), None) + .expect("There should be no error starting stratum"); + + let mut submit_request = + r#"{"jsonrpc": "2.0", "method": "mining.submit", "params": ["0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef", ["0x56642f04d519ae3262c7ba6facf1c5b11450ebaeb7955337cfbc45420d573077"]], "id": 2}"#.as_bytes() + .to_vec(); + submit_request.extend(b"\n"); + + let mut core = Core::new().expect("Tokio Core should be created with no errors"); + let mut buffer = vec![0u8; 2048]; + let stream = TcpStream::connect(&addr, &core.handle()) + .and_then(|stream| io::write_all(stream, &submit_request)) + .and_then(|(stream, _)| io::read(stream, &mut buffer)) + .and_then(|(_, read_buf, len)| { + ctrace!(STRATUM, "Received result from server"); + future::ok(read_buf[0..len].to_vec()) + }); + + let response = String::from_utf8(core.run(stream).expect("Core should run with no errors")) + .expect("Response should be utf-8"); + assert_eq!( + "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":23,\"message\":\"Unauthorized worker\"},\"id\":2}\n", + response + ); } } diff --git a/stratum/src/traits.rs b/stratum/src/traits.rs index 9bf40bb867..016db419e4 100644 --- a/stratum/src/traits.rs +++ b/stratum/src/traits.rs @@ -17,16 +17,20 @@ use std; use std::error::Error as StdError; +use jsonrpc_core::{Error as JsonError, ErrorCode as JsonErrorCode}; use jsonrpc_tcp_server::PushMessageError; use primitives::{Bytes, H256}; #[derive(Debug, Clone)] pub enum Error { + InternalError, + PowHashInvalid, + PowInvalid, + UnauthorizedWorker, NoWork, NoWorkers, Io(String), Tcp(String), - Dispatch(String), } impl From for Error { @@ -41,6 +45,23 @@ impl From for Error { } } +impl Into for Error { + fn into(self) -> JsonError { + let (code, message) = match self { + Error::PowHashInvalid => (21, format!("Invalid Pow hash")), + Error::PowInvalid => (22, format!("Invalid the nonce")), + Error::UnauthorizedWorker => (23, format!("Unauthorized worker")), + _ => (20, format!("Internal error")), + }; + + JsonError { + code: JsonErrorCode::ServerError(code), + message, + data: None, + } + } +} + /// Interface that can provide pow/blockchain-specific responses for the clients pub trait JobDispatcher: Send + Sync { // json for initial client handshake