// Copyright 2015-2017 Parity Technologies (UK) Ltd. // This file is part of Parity. // Parity is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // Parity is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // You should have received a copy of the GNU General Public License // along with Parity. If not, see . //! Client-side stratum job dispatcher and mining notifier handler use ethcore_stratum::{ JobDispatcher, PushWorkHandler, Stratum as StratumService, Error as StratumServiceError, }; use std::sync::{Arc, Weak}; use std::net::{SocketAddr, AddrParseError}; use std::fmt; use util::{H256, U256, H64, clean_0x}; use ethereum::ethash::Ethash; use ethash::SeedHashCompute; use util::Mutex; use miner::{self, Miner, MinerService}; use client::Client; use block::IsBlock; use std::str::FromStr; use rlp::encode; /// Configures stratum server options. #[derive(Debug, PartialEq, Clone)] pub struct Options { /// Working directory pub io_path: String, /// Network address pub listen_addr: String, /// Port pub port: u16, /// Secret for peers pub secret: Option, } struct SubmitPayload { nonce: H64, pow_hash: H256, mix_hash: H256, } impl SubmitPayload { fn from_args(payload: Vec) -> Result { if payload.len() != 3 { return Err(PayloadError::ArgumentsAmountUnexpected(payload.len())); } let nonce = match H64::from_str(clean_0x(&payload[0])) { Ok(nonce) => nonce, Err(e) => { warn!(target: "stratum", "submit_work ({}): invalid nonce ({:?})", &payload[0], e); return Err(PayloadError::InvalidNonce(payload[0].clone())) } }; let pow_hash = match H256::from_str(clean_0x(&payload[1])) { Ok(pow_hash) => pow_hash, Err(e) => { warn!(target: "stratum", "submit_work ({}): invalid hash ({:?})", &payload[1], e); return Err(PayloadError::InvalidPowHash(payload[1].clone())); } }; let mix_hash = match H256::from_str(clean_0x(&payload[2])) { Ok(mix_hash) => mix_hash, Err(e) => { warn!(target: "stratum", "submit_work ({}): invalid mix-hash ({:?})", &payload[2], e); return Err(PayloadError::InvalidMixHash(payload[2].clone())); } }; Ok(SubmitPayload { nonce: nonce, pow_hash: pow_hash, mix_hash: mix_hash, }) } } #[derive(Debug)] enum PayloadError { ArgumentsAmountUnexpected(usize), InvalidNonce(String), InvalidPowHash(String), InvalidMixHash(String), } impl fmt::Display for PayloadError { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fmt::Debug::fmt(&self, f) } } /// Job dispatcher for stratum service pub struct StratumJobDispatcher { seed_compute: Mutex, client: Weak, miner: Weak, } impl JobDispatcher for StratumJobDispatcher { fn initial(&self) -> Option { // initial payload may contain additional data, not in this case self.job() } fn job(&self) -> Option { self.with_core(|client, miner| miner.map_sealing_work(&*client, |b| { let pow_hash = b.hash(); let number = b.block().header().number(); let difficulty = b.block().header().difficulty(); self.payload(pow_hash, *difficulty, number) }) ) } fn submit(&self, payload: Vec) -> Result<(), StratumServiceError> { let payload = SubmitPayload::from_args(payload).map_err(|e| StratumServiceError::Dispatch(format!("{}", e)) )?; trace!( target: "stratum", "submit_work: Decoded: nonce={}, pow_hash={}, mix_hash={}", payload.nonce, payload.pow_hash, payload.mix_hash, ); self.with_core_void(|client, miner| { let seal = vec![encode(&payload.mix_hash).into_vec(), encode(&payload.nonce).into_vec()]; if let Err(e) = miner.submit_seal(&*client, payload.pow_hash, seal) { warn!(target: "stratum", "submit_seal error: {:?}", e); }; }); Ok(()) } } impl StratumJobDispatcher { /// New stratum job dispatcher given the miner and client fn new(miner: Weak, client: Weak) -> StratumJobDispatcher { StratumJobDispatcher { seed_compute: Mutex::new(SeedHashCompute::new()), client: client, miner: miner, } } /// Serializes payload for stratum service fn payload(&self, pow_hash: H256, difficulty: U256, number: u64) -> String { // TODO: move this to engine let target = Ethash::difficulty_to_boundary(&difficulty); let seed_hash = &self.seed_compute.lock().get_seedhash(number); let seed_hash = H256::from_slice(&seed_hash[..]); format!( r#"["0x", "0x{}","0x{}","0x{}","0x{:x}"]"#, pow_hash.hex(), seed_hash.hex(), target.hex(), number ) } fn with_core(&self, f: F) -> Option where F: Fn(Arc, Arc) -> Option { self.client.upgrade().and_then(|client| self.miner.upgrade().and_then(|miner| (f)(client, miner))) } fn with_core_void(&self, f: F) where F: Fn(Arc, Arc) { self.client.upgrade().map(|client| self.miner.upgrade().map(|miner| (f)(client, miner))); } } /// Wrapper for dedicated stratum service pub struct Stratum { dispatcher: Arc, service: Arc, } #[derive(Debug)] /// Stratum error pub enum Error { /// IPC sockets error Service(StratumServiceError), /// Invalid network address Address(AddrParseError), } impl From for Error { fn from(service_err: StratumServiceError) -> Error { Error::Service(service_err) } } impl From for Error { fn from(err: AddrParseError) -> Error { Error::Address(err) } } impl super::work_notify::NotifyWork for Stratum { fn notify(&self, pow_hash: H256, difficulty: U256, number: u64) { trace!(target: "stratum", "Notify work"); self.service.push_work_all( self.dispatcher.payload(pow_hash, difficulty, number) ).unwrap_or_else( |e| warn!(target: "stratum", "Error while pushing work: {:?}", e) ); } } impl Stratum { /// New stratum job dispatcher, given the miner, client and dedicated stratum service pub fn start(options: &Options, miner: Weak, client: Weak) -> Result { use std::net::IpAddr; let dispatcher = Arc::new(StratumJobDispatcher::new(miner, client)); let stratum_svc = StratumService::start( &SocketAddr::new(IpAddr::from_str(&options.listen_addr)?, options.port), dispatcher.clone(), options.secret.clone(), )?; Ok(Stratum { dispatcher: dispatcher, service: stratum_svc, }) } /// Start STRATUM job dispatcher and register it in the miner pub fn register(cfg: &Options, miner: Arc, client: Weak) -> Result<(), Error> { let stratum = miner::Stratum::start(cfg, Arc::downgrade(&miner.clone()), client)?; miner.push_notifier(Box::new(stratum) as Box); Ok(()) } }