StratumServer RPC extra-quotes fix (#1164)

* StratumServer RPC extra-quotes fix
* better - skip intermediate string
* Fix invalid json _ok_ response
* Undo protocol-breaking change to ok response
* use RpcError structure rather than string to avoid extra quoting
This commit is contained in:
Blade Doyle 2018-06-17 12:08:17 -07:00 committed by Ignotus Peverell
parent 9b69b5376d
commit 59472e9570

View file

@ -15,6 +15,7 @@
//! Mining Stratum Server //! Mining Stratum Server
use bufstream::BufStream; use bufstream::BufStream;
use serde_json; use serde_json;
use serde_json::Value;
use std::error::Error; use std::error::Error;
use std::io::{BufRead, ErrorKind, Write}; use std::io::{BufRead, ErrorKind, Write};
use std::net::{TcpListener, TcpStream}; use std::net::{TcpListener, TcpStream};
@ -47,7 +48,7 @@ struct RpcRequest {
id: String, id: String,
jsonrpc: String, jsonrpc: String,
method: String, method: String,
params: Option<String>, params: Option<Value>,
} }
#[derive(Serialize, Deserialize, Debug)] #[derive(Serialize, Deserialize, Debug)]
@ -55,8 +56,8 @@ struct RpcResponse {
id: String, id: String,
jsonrpc: String, jsonrpc: String,
method: String, method: String,
result: Option<String>, result: Option<Value>,
error: Option<RpcError>, error: Option<Value>,
} }
#[derive(Serialize, Deserialize, Debug)] #[derive(Serialize, Deserialize, Debug)]
@ -149,6 +150,7 @@ fn accept_workers(
pub struct Worker { pub struct Worker {
id: String, id: String,
agent: String,
login: Option<String>, login: Option<String>,
stream: BufStream<TcpStream>, stream: BufStream<TcpStream>,
error: bool, error: bool,
@ -160,6 +162,7 @@ impl Worker {
pub fn new(id: String, stream: BufStream<TcpStream>) -> Worker { pub fn new(id: String, stream: BufStream<TcpStream>) -> Worker {
Worker { Worker {
id: id, id: id,
agent: String::from(""),
login: None, login: None,
stream: stream, stream: stream,
error: false, error: false,
@ -322,9 +325,11 @@ impl StratumServer {
"keepalive" => self.handle_keepalive(), "keepalive" => self.handle_keepalive(),
"getjobtemplate" => { "getjobtemplate" => {
if self.currently_syncing.load(Ordering::Relaxed) { if self.currently_syncing.load(Ordering::Relaxed) {
let e = r#"{"code": -32701, "message": "Node is syncing - Please wait"}"#; let e = RpcError {
let err = e.to_string(); code: -32701,
(err, true) message: "Node is syncing - Please wait".to_string(),
};
(serde_json::to_value(e).unwrap(), true)
} else { } else {
let b = self.current_block.header.clone(); let b = self.current_block.header.clone();
self.handle_getjobtemplate(b) self.handle_getjobtemplate(b)
@ -335,22 +340,23 @@ impl StratumServer {
} }
_ => { _ => {
// Called undefined method // Called undefined method
let e = r#"{"code": -32601, "message": "Method not found"}"#; let e = RpcError {
let err = e.to_string(); code: -32601,
(err, true) message: "Method not found".to_string(),
};
(serde_json::to_value(e).unwrap(), true)
} }
}; };
// Package the reply as RpcResponse json // Package the reply as RpcResponse json
let rpc_response: String; let rpc_response: String;
if err == true { if err == true {
let rpc_err: RpcError = serde_json::from_str(&response).unwrap();
let resp = RpcResponse { let resp = RpcResponse {
id: workers_l[num].id.clone(), id: workers_l[num].id.clone(),
jsonrpc: String::from("2.0"), jsonrpc: String::from("2.0"),
method: request.method, method: request.method,
result: None, result: None,
error: Some(rpc_err), error: Some(response),
}; };
rpc_response = serde_json::to_string(&resp).unwrap(); rpc_response = serde_json::to_string(&resp).unwrap();
} else { } else {
@ -373,7 +379,7 @@ impl StratumServer {
} }
// Handle STATUS message // Handle STATUS message
fn handle_status(&self, worker_stats: &WorkerStats) -> (String, bool) { fn handle_status(&self, worker_stats: &WorkerStats) -> (Value, bool) {
// Return worker status in json for use by a dashboard or healthcheck. // Return worker status in json for use by a dashboard or healthcheck.
let status = WorkerStatus { let status = WorkerStatus {
id: worker_stats.id.clone(), id: worker_stats.id.clone(),
@ -383,41 +389,40 @@ impl StratumServer {
rejected: worker_stats.num_rejected, rejected: worker_stats.num_rejected,
stale: worker_stats.num_stale, stale: worker_stats.num_stale,
}; };
let response = serde_json::to_string(&status).unwrap(); let response = serde_json::to_value(&status).unwrap();
return (response, false); return (response, false);
} }
// Handle GETJOBTEMPLATE message // Handle GETJOBTEMPLATE message
fn handle_getjobtemplate(&self, bh: BlockHeader) -> (String, bool) { fn handle_getjobtemplate(&self, bh: BlockHeader) -> (Value, bool) {
// Build a JobTemplate from a BlockHeader and return JSON // Build a JobTemplate from a BlockHeader and return JSON
let job_template = self.build_block_template(bh); let job_template = self.build_block_template(bh);
let job_template_json = serde_json::to_string(&job_template).unwrap(); let response = serde_json::to_value(&job_template).unwrap();
return (job_template_json, false); return (response, false);
} }
// Handle KEEPALIVE message // Handle KEEPALIVE message
fn handle_keepalive(&self) -> (String, bool) { fn handle_keepalive(&self) -> (Value, bool) {
return (String::from("ok"), false); return (serde_json::to_value("ok".to_string()).unwrap(), false);
} }
// Handle LOGIN message // Handle LOGIN message
fn handle_login(&self, params: Option<String>, worker: &mut Worker) -> (String, bool) { fn handle_login(&self, params: Option<Value>, worker: &mut Worker) -> (Value, bool) {
// Extract the params string into a LoginParams struct let params: LoginParams = match params {
let params_str = match params { Some(val) => serde_json::from_value(val).unwrap(),
Some(val) => val, None => {
None => String::from("{}"), let e = RpcError {
code: -32600,
message: "Invalid Request".to_string(),
}; };
let login_params: LoginParams = match serde_json::from_str(&params_str) { return (serde_json::to_value(e).unwrap(), true);
Ok(val) => val,
Err(_e) => {
let r = r#"{"code": -32600, "message": "Invalid Request"}"#;
return (String::from(r), true);
} }
}; };
worker.login = Some(login_params.login); worker.login = Some(params.login);
// XXX TODO Future? - Validate login and password // XXX TODO Future - Validate password?
worker.agent = params.agent;
worker.authenticated = true; worker.authenticated = true;
return (String::from("ok"), false); return (serde_json::to_value("ok".to_string()).unwrap(), false);
} }
// Handle SUBMIT message // Handle SUBMIT message
@ -427,30 +432,29 @@ impl StratumServer {
// network // network
fn handle_submit( fn handle_submit(
&self, &self,
params: Option<String>, params: Option<Value>,
worker: &mut Worker, worker: &mut Worker,
worker_stats: &mut WorkerStats, worker_stats: &mut WorkerStats,
) -> (String, bool) { ) -> (Value, bool) {
// Extract the params string into a SubmitParams struct // Validate parameters
let params_str = match params { let params: SubmitParams = match params {
Some(val) => val, Some(val) => serde_json::from_value(val).unwrap(),
None => String::from("{}"), None => {
let e = RpcError {
code: -32600,
message: "Invalid Request".to_string(),
}; };
let submit_params: SubmitParams = match serde_json::from_str(&params_str) { return (serde_json::to_value(e).unwrap(), true);
Ok(val) => val,
Err(_e) => {
let r = r#"{"code": -32600, "message": "Invalid Request"}"#;
return (String::from(r), true);
} }
}; };
let mut b: Block; let mut b: Block;
let share_difficulty: u64; let share_difficulty: u64;
if submit_params.height == self.current_block.header.height { if params.height == self.current_block.header.height {
// Reconstruct the block header with this nonce and pow added // Reconstruct the block header with this nonce and pow added
b = self.current_block.clone(); b = self.current_block.clone();
b.header.nonce = submit_params.nonce; b.header.nonce = params.nonce;
b.header.pow.nonces = submit_params.pow; b.header.pow.nonces = params.pow;
// Get share difficulty // Get share difficulty
share_difficulty = b.header.pow.to_difficulty().to_num(); share_difficulty = b.header.pow.to_difficulty().to_num();
// If the difficulty is too low its an error // If the difficulty is too low its an error
@ -464,9 +468,11 @@ impl StratumServer {
self.minimum_share_difficulty, self.minimum_share_difficulty,
); );
worker_stats.num_rejected += 1; worker_stats.num_rejected += 1;
let e = r#"{"code": -32501, "message": "Share rejected due to low difficulty"}"#; let e = RpcError {
let err = e.to_string(); code: -32501,
return (err, true); message: "Share rejected due to low difficulty".to_string(),
};
return (serde_json::to_value(e).unwrap(), true);
} }
// If the difficulty is high enough, submit it (which also validates it) // If the difficulty is high enough, submit it (which also validates it)
if share_difficulty >= self.current_difficulty { if share_difficulty >= self.current_difficulty {
@ -477,13 +483,15 @@ impl StratumServer {
LOGGER, LOGGER,
"(Server ID: {}) Failed to validate solution at height {}: {:?}", "(Server ID: {}) Failed to validate solution at height {}: {:?}",
self.id, self.id,
submit_params.height, params.height,
e e
); );
worker_stats.num_rejected += 1; worker_stats.num_rejected += 1;
let e = r#"{"code": -32502, "message": "Failed to validate solution"}"#; let e = RpcError {
let err = e.to_string(); code: -32502,
return (err, true); message: "Failed to validate solution".to_string(),
};
return (serde_json::to_value(e).unwrap(), true);
} }
// Success case falls through to be logged // Success case falls through to be logged
} else { } else {
@ -495,27 +503,29 @@ impl StratumServer {
LOGGER, LOGGER,
"(Server ID: {}) Failed to validate share at height {} with nonce {}", "(Server ID: {}) Failed to validate share at height {} with nonce {}",
self.id, self.id,
submit_params.height, params.height,
b.header.nonce b.header.nonce
); );
worker_stats.num_rejected += 1; worker_stats.num_rejected += 1;
let e = r#"{"code": -32502, "message": "Failed to validate share"}"#; let e = RpcError {
let err = e.to_string(); code: -32502,
return (err, true); message: "Failed to validate solution".to_string(),
};
return (serde_json::to_value(e).unwrap(), true);
} }
} }
} else { } else {
// Return error status // Return error status
error!( error!(
LOGGER, LOGGER,
"(Server ID: {}) Share at height {} submitted too late", "(Server ID: {}) Share at height {} submitted too late", self.id, params.height,
self.id,
submit_params.height
); );
worker_stats.num_stale += 1; worker_stats.num_stale += 1;
let e = r#"{"code": -32503, "message": "Solution submitted too late"}"#; let e = RpcError {
let err = e.to_string(); code: -32503,
return (err, true); message: "Solution submitted too late".to_string(),
};
return (serde_json::to_value(e).unwrap(), true);
} }
// Log this as a valid share // Log this as a valid share
let submitted_by = match worker.login.clone() { let submitted_by = match worker.login.clone() {
@ -534,7 +544,7 @@ impl StratumServer {
submitted_by, submitted_by,
); );
worker_stats.num_accepted += 1; worker_stats.num_accepted += 1;
return (String::from("ok"), false); return (serde_json::to_value("ok".to_string()).unwrap(), false);
} // handle submit a solution } // handle submit a solution
// Purge dead/sick workers - remove all workers marked in error state // Purge dead/sick workers - remove all workers marked in error state
@ -585,11 +595,13 @@ impl StratumServer {
// Package new block into RpcRequest // Package new block into RpcRequest
let job_template = self.build_block_template(self.current_block.header.clone()); let job_template = self.build_block_template(self.current_block.header.clone());
let job_template_json = serde_json::to_string(&job_template).unwrap(); let job_template_json = serde_json::to_string(&job_template).unwrap();
// Issue #1159 - use a serde_json Value type to avoid extra quoting
let job_template_value: Value = serde_json::from_str(&job_template_json).unwrap();
let job_request = RpcRequest { let job_request = RpcRequest {
id: String::from("Stratum"), id: String::from("Stratum"),
jsonrpc: String::from("2.0"), jsonrpc: String::from("2.0"),
method: String::from("job"), method: String::from("job"),
params: Some(job_template_json), params: Some(job_template_value),
}; };
let job_request_json = serde_json::to_string(&job_request).unwrap(); let job_request_json = serde_json::to_string(&job_request).unwrap();