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