add job_id to stratum job and solution rpc messages (#1240)

* add job_id to stratum job and solution rpc messages

* remove unnecessary clone
This commit is contained in:
Blade Doyle 2018-07-10 01:18:09 -07:00 committed by Yeastplume
parent 64db4d92f0
commit c7d78446f3

View file

@ -28,7 +28,7 @@ use chain;
use common::adapters::PoolToChainAdapter;
use common::stats::{StratumStats, WorkerStats};
use common::types::{StratumServerConfig, SyncState};
use core::core::{Block, BlockHeader};
use core::core::Block;
use core::{pow, global};
use keychain;
use mining::mine_block;
@ -75,6 +75,7 @@ struct LoginParams {
#[derive(Serialize, Deserialize, Debug)]
struct SubmitParams {
height: u64,
job_id: u64,
nonce: u64,
pow: Vec<u64>,
}
@ -82,6 +83,7 @@ struct SubmitParams {
#[derive(Serialize, Deserialize, Debug)]
pub struct JobTemplate {
height: u64,
job_id: u64,
difficulty: u64,
pre_pow: String,
}
@ -230,7 +232,7 @@ pub struct StratumServer {
config: StratumServerConfig,
chain: Arc<chain::Chain>,
tx_pool: Arc<RwLock<pool::TransactionPool<PoolToChainAdapter>>>,
current_block: Block,
current_block_versions: Vec<Block>,
current_difficulty: u64,
minimum_share_difficulty: u64,
current_key_id: Option<keychain::Identifier>,
@ -251,7 +253,7 @@ impl StratumServer {
config: config,
chain: chain_ref,
tx_pool: tx_pool,
current_block: Block::default(),
current_block_versions: Vec::new(),
current_difficulty: <u64>::max_value(),
current_key_id: None,
workers: Arc::new(Mutex::new(Vec::new())),
@ -260,13 +262,15 @@ impl StratumServer {
}
// Build and return a JobTemplate for mining the current block
fn build_block_template(&self, bh: BlockHeader) -> JobTemplate {
fn build_block_template(&self) -> JobTemplate {
let bh = self.current_block_versions.last().unwrap().header.clone();
// Serialize the block header into pre and post nonce strings
let mut pre_pow_writer = mine_block::HeaderPrePowWriter::default();
bh.write_pre_pow(&mut pre_pow_writer).unwrap();
let pre = pre_pow_writer.as_hex_string(false);
let job_template = JobTemplate {
height: bh.height,
job_id: (self.current_block_versions.len() - 1) as u64,
difficulty: self.minimum_share_difficulty,
pre_pow: pre,
};
@ -328,8 +332,7 @@ impl StratumServer {
};
Err(serde_json::to_value(e).unwrap())
} else {
let b = self.current_block.header.clone();
self.handle_getjobtemplate(b)
self.handle_getjobtemplate()
}
}
"status" => {
@ -383,7 +386,7 @@ impl StratumServer {
// Return worker status in json for use by a dashboard or healthcheck.
let status = WorkerStatus {
id: worker_stats.id.clone(),
height: self.current_block.header.height,
height: self.current_block_versions.last().unwrap().header.height,
difficulty: worker_stats.pow_difficulty,
accepted: worker_stats.num_accepted,
rejected: worker_stats.num_rejected,
@ -394,10 +397,17 @@ impl StratumServer {
}
// Handle GETJOBTEMPLATE message
fn handle_getjobtemplate(&self, bh: BlockHeader) -> Result<Value, Value> {
fn handle_getjobtemplate(&self) -> Result<Value, Value> {
// Build a JobTemplate from a BlockHeader and return JSON
let job_template = self.build_block_template(bh);
let job_template = self.build_block_template();
let response = serde_json::to_value(&job_template).unwrap();
debug!(
LOGGER,
"(Server ID: {}) sending block {} with id {} to single worker",
self.id,
job_template.height,
job_template.job_id,
);
return Ok(response);
}
@ -447,77 +457,9 @@ impl StratumServer {
return Err(serde_json::to_value(e).unwrap());
}
};
let mut b: Block;
let share_difficulty: u64;
let mut share_is_block = false;
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 = 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
if share_difficulty < self.minimum_share_difficulty {
// Return error status
error!(
LOGGER,
"(Server ID: {}) Share rejected due to low difficulty: {}/{}",
self.id,
share_difficulty,
self.minimum_share_difficulty,
);
worker_stats.num_rejected += 1;
let e = RpcError {
code: -32501,
message: "Share rejected due to low difficulty".to_string(),
};
return Err(serde_json::to_value(e).unwrap());
}
// If the difficulty is high enough, submit it (which also validates it)
if share_difficulty >= self.current_difficulty {
let res = self.chain.process_block(b.clone(), chain::Options::MINE);
if let Err(e) = res {
// Return error status
error!(
LOGGER,
"(Server ID: {}) Failed to validate solution at height {}: {:?}",
self.id,
params.height,
e
);
worker_stats.num_rejected += 1;
let e = RpcError {
code: -32502,
message: "Failed to validate solution".to_string(),
};
return Err(serde_json::to_value(e).unwrap());
} else {
share_is_block = true;
}
// Success case falls through to be logged
} else {
// This is a low-difficulty share, not a full solution
// Do some validation but dont submit
if !pow::verify_size(&b.header, global::min_sizeshift()) {
// Return error status
error!(
LOGGER,
"(Server ID: {}) Failed to validate share at height {} with nonce {}",
self.id,
params.height,
b.header.nonce
);
worker_stats.num_rejected += 1;
let e = RpcError {
code: -32502,
message: "Failed to validate solution".to_string(),
};
return Err(serde_json::to_value(e).unwrap());
}
}
} else {
if params.height != self.current_block_versions.last().unwrap().header.height {
// Return error status
error!(
LOGGER,
@ -530,6 +472,93 @@ impl StratumServer {
};
return Err(serde_json::to_value(e).unwrap());
}
// Find the correct version of the block to match this header
let b: Option<&Block> = self.current_block_versions.get(params.job_id as usize);
if b.is_none() {
// Return error status
error!(
LOGGER,
"(Server ID: {}) Failed to validate solution at height {}: invalid job_id {}",
self.id,
params.height,
params.job_id,
);
worker_stats.num_rejected += 1;
let e = RpcError {
code: -32502,
message: "Failed to validate solution".to_string(),
};
return Err(serde_json::to_value(e).unwrap());
}
let mut b: Block = b.unwrap().clone();
// Reconstruct the block header with this nonce and pow added
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
if share_difficulty < self.minimum_share_difficulty {
// Return error status
error!(
LOGGER,
"(Server ID: {}) Share rejected due to low difficulty: {}/{}",
self.id,
share_difficulty,
self.minimum_share_difficulty,
);
worker_stats.num_rejected += 1;
let e = RpcError {
code: -32501,
message: "Share rejected due to low difficulty".to_string(),
};
return Err(serde_json::to_value(e).unwrap());
}
// If the difficulty is high enough, submit it (which also validates it)
if share_difficulty >= self.current_difficulty {
// This is a full solution, submit it to the network
let res = self.chain.process_block(b.clone(), chain::Options::MINE);
if let Err(e) = res {
// Return error status
error!(
LOGGER,
"(Server ID: {}) Failed to validate solution at height {}: {:?}",
self.id,
params.height,
e
);
worker_stats.num_rejected += 1;
let e = RpcError {
code: -32502,
message: "Failed to validate solution".to_string(),
};
return Err(serde_json::to_value(e).unwrap());
}
share_is_block = true;
// Log message to make it obvious we found a block
warn!(
LOGGER,
"(Server ID: {}) Solution Found for block {} - Yay!!!", self.id, params.height
);
} else {
// Do some validation but dont submit
if !pow::verify_size(&b.header, global::min_sizeshift()) {
// Return error status
error!(
LOGGER,
"(Server ID: {}) Failed to validate share at height {} with nonce {} using job_id {}",
self.id,
params.height,
b.header.nonce,
params.job_id,
);
worker_stats.num_rejected += 1;
let e = RpcError {
code: -32502,
message: "Failed to validate solution".to_string(),
};
return Err(serde_json::to_value(e).unwrap());
}
}
// Log this as a valid share
let submitted_by = match worker.login.clone() {
None => worker.id.to_string(),
@ -588,15 +617,8 @@ impl StratumServer {
// Broadcast a jobtemplate RpcRequest to all connected workers - no response
// expected
fn broadcast_job(&mut self) {
debug!(
LOGGER,
"(Server ID: {}) sending block {} to stratum clients",
self.id,
self.current_block.header.height
);
// Package new block into RpcRequest
let job_template = self.build_block_template(self.current_block.header.clone());
let job_template = self.build_block_template();
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();
@ -607,7 +629,13 @@ impl StratumServer {
params: Some(job_template_value),
};
let job_request_json = serde_json::to_string(&job_request).unwrap();
debug!(
LOGGER,
"(Server ID: {}) sending block {} with id {} to stratum clients",
self.id,
job_template.height,
job_template.job_id,
);
// Push the new block to all connected clients
// NOTE: We do not give a unique nonce (should we?) so miners need
// to choose one for themselves
@ -651,6 +679,7 @@ impl StratumServer {
let mut current_hash = head.prev_block_h;
let mut latest_hash;
let listen_addr = self.config.stratum_server_addr.clone().unwrap();
self.current_block_versions.push(Block::default());
// Start a thread to accept new worker connections
let mut workers_th = self.workers.clone();
@ -698,7 +727,11 @@ impl StratumServer {
if !self.config.burn_reward {
wallet_listener_url = Some(self.config.wallet_listener_url.clone());
}
// If this is a new block, clear the current_block version history
if current_hash != latest_hash {
self.current_block_versions.clear();
}
// Build the new block (version)
let (new_block, block_fees) = mine_block::get_block(
&self.chain,
&self.tx_pool,
@ -706,8 +739,7 @@ impl StratumServer {
MAX_TX.clone(),
wallet_listener_url,
);
self.current_block = new_block;
self.current_difficulty = (self.current_block.header.total_difficulty.clone()
self.current_difficulty = (new_block.header.total_difficulty.clone()
- head.total_difficulty.clone())
.to_num();
self.current_key_id = block_fees.key_id();
@ -722,10 +754,11 @@ impl StratumServer {
{
let mut stratum_stats = stratum_stats.write().unwrap();
stratum_stats.block_height = self.current_block.header.height;
stratum_stats.block_height = new_block.header.height;
stratum_stats.network_difficulty = self.current_difficulty;
}
// Add this new block version to our current block map
self.current_block_versions.push(new_block);
// Send this job to all connected workers
self.broadcast_job();
}