// Copyright 2015, 2016 Ethcore (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, FixedHash, 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).to_vec(), encode(&payload.nonce).to_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) {
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(())
}
}