From c7d78446f36d917ba56114f2dd36c3e3bebb9ed9 Mon Sep 17 00:00:00 2001 From: Blade Doyle Date: Tue, 10 Jul 2018 01:18:09 -0700 Subject: [PATCH] add job_id to stratum job and solution rpc messages (#1240) * add job_id to stratum job and solution rpc messages * remove unnecessary clone --- servers/src/mining/stratumserver.rs | 217 ++++++++++++++++------------ 1 file changed, 125 insertions(+), 92 deletions(-) diff --git a/servers/src/mining/stratumserver.rs b/servers/src/mining/stratumserver.rs index 2af4ca419..41253c16b 100644 --- a/servers/src/mining/stratumserver.rs +++ b/servers/src/mining/stratumserver.rs @@ -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, } @@ -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, tx_pool: Arc>>, - current_block: Block, + current_block_versions: Vec, current_difficulty: u64, minimum_share_difficulty: u64, current_key_id: Option, @@ -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: ::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 { + fn handle_getjobtemplate(&self) -> Result { // 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(); }