Stratum up (#4233)
* flush work * flush work * flush work * flush work * generalized notifiers * general setup with modules * general setup with modules * all binded * catch up with master * all dependencies injected * stratum another up * tcp update * submitwork routine * finalize & fix warnings * merge bugs, review fixes * merge bugs, review fixes * new cli mess cleanup * usage.txt swap * flush work * cli adopt * compilation with new cli sorted * subid space in json * serialization issues * grumbles addressed * more grumbles * remove last_work note for now * fix compilation * fix tests * merge bugs * no obliged ipc * moving notifiers * no optional feature now * refactored again * working on tests * refactor to new tcp/ip * stratum lib ok * ethcore crate ok * wip on tests * final test working * fix warnings, \n-terminated response * new compatibility * re-pushing work once anybody submitted * various review and general fixes * reviewe fixes * remove redundant notifier * one symbol -> huge bug * ensure write lock isn't held when calling handlers * extern declarations moved * options to stratum mod, SocketAddr strongly-typed instantiation * Minor style fix. * Whitespace and phrasing * Whitespace
This commit is contained in:
@@ -39,6 +39,7 @@ ethstore = { path = "../ethstore" }
|
||||
ethkey = { path = "../ethkey" }
|
||||
ethcore-ipc-nano = { path = "../ipc/nano" }
|
||||
rlp = { path = "../util/rlp" }
|
||||
ethcore-stratum = { path = "../stratum" }
|
||||
lru-cache = "0.1.0"
|
||||
ethcore-bloom-journal = { path = "../util/bloom" }
|
||||
ethabi = "0.2.2"
|
||||
|
||||
@@ -102,6 +102,9 @@ extern crate ethcore_bloom_journal as bloom_journal;
|
||||
extern crate byteorder;
|
||||
extern crate transient_hashmap;
|
||||
extern crate linked_hash_map;
|
||||
extern crate lru_cache;
|
||||
extern crate ethcore_stratum;
|
||||
extern crate ethabi;
|
||||
|
||||
#[macro_use]
|
||||
extern crate log;
|
||||
@@ -113,11 +116,9 @@ extern crate lazy_static;
|
||||
extern crate heapsize;
|
||||
#[macro_use]
|
||||
extern crate ethcore_ipc as ipc;
|
||||
extern crate lru_cache;
|
||||
|
||||
#[cfg(feature = "jit" )]
|
||||
extern crate evmjit;
|
||||
extern crate ethabi;
|
||||
|
||||
pub extern crate ethstore;
|
||||
|
||||
|
||||
@@ -32,7 +32,7 @@ use engines::{Engine, Seal};
|
||||
use miner::{MinerService, MinerStatus, TransactionQueue, TransactionQueueDetailsProvider, PrioritizationStrategy,
|
||||
AccountDetails, TransactionOrigin};
|
||||
use miner::banning_queue::{BanningTransactionQueue, Threshold};
|
||||
use miner::work_notify::WorkPoster;
|
||||
use miner::work_notify::{WorkPoster, NotifyWork};
|
||||
use miner::price_info::PriceInfo;
|
||||
use miner::local_transactions::{Status as LocalTransactionStatus};
|
||||
use miner::service_transaction_checker::ServiceTransactionChecker;
|
||||
@@ -224,18 +224,24 @@ pub struct Miner {
|
||||
engine: Arc<Engine>,
|
||||
|
||||
accounts: Option<Arc<AccountProvider>>,
|
||||
work_poster: Option<WorkPoster>,
|
||||
notifiers: RwLock<Vec<Box<NotifyWork>>>,
|
||||
gas_pricer: Mutex<GasPricer>,
|
||||
service_transaction_action: ServiceTransactionAction,
|
||||
}
|
||||
|
||||
impl Miner {
|
||||
/// Push notifier that will handle new jobs
|
||||
pub fn push_notifier(&self, notifier: Box<NotifyWork>) {
|
||||
self.notifiers.write().push(notifier)
|
||||
}
|
||||
|
||||
/// Creates new instance of miner Arc.
|
||||
pub fn new(options: MinerOptions, gas_pricer: GasPricer, spec: &Spec, accounts: Option<Arc<AccountProvider>>) -> Arc<Miner> {
|
||||
Arc::new(Miner::new_raw(options, gas_pricer, spec, accounts))
|
||||
}
|
||||
|
||||
/// Creates new instance of miner.
|
||||
fn new_raw(options: MinerOptions, gas_pricer: GasPricer, spec: &Spec, accounts: Option<Arc<AccountProvider>>) -> Miner {
|
||||
let work_poster = match options.new_work_notify.is_empty() {
|
||||
true => None,
|
||||
false => Some(WorkPoster::new(&options.new_work_notify))
|
||||
};
|
||||
let gas_limit = match options.tx_queue_gas_limit {
|
||||
GasLimit::Fixed(ref limit) => *limit,
|
||||
_ => !U256::zero(),
|
||||
@@ -250,10 +256,17 @@ impl Miner {
|
||||
ban_duration,
|
||||
),
|
||||
};
|
||||
|
||||
let notifiers: Vec<Box<NotifyWork>> = match options.new_work_notify.is_empty() {
|
||||
true => Vec::new(),
|
||||
false => vec![Box::new(WorkPoster::new(&options.new_work_notify))],
|
||||
};
|
||||
|
||||
let service_transaction_action = match options.refuse_service_transactions {
|
||||
true => ServiceTransactionAction::Refuse,
|
||||
false => ServiceTransactionAction::Check(ServiceTransactionChecker::default()),
|
||||
};
|
||||
|
||||
Miner {
|
||||
transaction_queue: Arc::new(Mutex::new(txq)),
|
||||
next_allowed_reseal: Mutex::new(Instant::now()),
|
||||
@@ -271,7 +284,7 @@ impl Miner {
|
||||
options: options,
|
||||
accounts: accounts,
|
||||
engine: spec.engine.clone(),
|
||||
work_poster: work_poster,
|
||||
notifiers: RwLock::new(notifiers),
|
||||
gas_pricer: Mutex::new(gas_pricer),
|
||||
service_transaction_action: service_transaction_action,
|
||||
}
|
||||
@@ -287,11 +300,6 @@ impl Miner {
|
||||
Miner::new_raw(Default::default(), GasPricer::new_fixed(20_000_000_000u64.into()), spec, None)
|
||||
}
|
||||
|
||||
/// Creates new instance of a miner Arc.
|
||||
pub fn new(options: MinerOptions, gas_pricer: GasPricer, spec: &Spec, accounts: Option<Arc<AccountProvider>>) -> Arc<Miner> {
|
||||
Arc::new(Miner::new_raw(options, gas_pricer, spec, accounts))
|
||||
}
|
||||
|
||||
fn forced_sealing(&self) -> bool {
|
||||
self.options.force_sealing || !self.options.new_work_notify.is_empty()
|
||||
}
|
||||
@@ -522,7 +530,7 @@ impl Miner {
|
||||
let is_new = original_work_hash.map_or(true, |h| block.block().fields().header.hash() != h);
|
||||
sealing_work.queue.push(block);
|
||||
// If push notifications are enabled we assume all work items are used.
|
||||
if self.work_poster.is_some() && is_new {
|
||||
if !self.notifiers.read().is_empty() && is_new {
|
||||
sealing_work.queue.use_last_ref();
|
||||
}
|
||||
(Some((pow_hash, difficulty, number)), is_new)
|
||||
@@ -533,7 +541,11 @@ impl Miner {
|
||||
(work, is_new)
|
||||
};
|
||||
if is_new {
|
||||
work.map(|(pow_hash, difficulty, number)| self.work_poster.as_ref().map(|p| p.notify(pow_hash, difficulty, number)));
|
||||
work.map(|(pow_hash, difficulty, number)| {
|
||||
for notifier in self.notifiers.read().iter() {
|
||||
notifier.notify(pow_hash, difficulty, number)
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -49,13 +49,17 @@ mod price_info;
|
||||
mod service_transaction_checker;
|
||||
mod transaction_queue;
|
||||
mod work_notify;
|
||||
mod stratum;
|
||||
|
||||
pub use self::external::{ExternalMiner, ExternalMinerService};
|
||||
|
||||
pub use self::miner::{Miner, MinerOptions, Banning, PendingSet, GasPricer, GasPriceCalibratorOptions, GasLimit};
|
||||
pub use self::transaction_queue::{TransactionQueue, TransactionDetailsProvider as TransactionQueueDetailsProvider,
|
||||
PrioritizationStrategy, AccountDetails, TransactionOrigin};
|
||||
pub use self::local_transactions::{Status as LocalTransactionStatus};
|
||||
pub use client::TransactionImportResult;
|
||||
pub use self::work_notify::NotifyWork;
|
||||
pub use self::stratum::{Stratum, Error as StratumError, Options as StratumOptions};
|
||||
|
||||
use std::collections::BTreeMap;
|
||||
use util::{H256, U256, Address, Bytes};
|
||||
|
||||
248
ethcore/src/miner/stratum.rs
Normal file
248
ethcore/src/miner/stratum.rs
Normal file
@@ -0,0 +1,248 @@
|
||||
// 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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! 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<H256>,
|
||||
}
|
||||
|
||||
struct SubmitPayload {
|
||||
nonce: H64,
|
||||
pow_hash: H256,
|
||||
mix_hash: H256,
|
||||
}
|
||||
|
||||
impl SubmitPayload {
|
||||
fn from_args(payload: Vec<String>) -> Result<Self, PayloadError> {
|
||||
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<SeedHashCompute>,
|
||||
client: Weak<Client>,
|
||||
miner: Weak<Miner>,
|
||||
}
|
||||
|
||||
|
||||
impl JobDispatcher for StratumJobDispatcher {
|
||||
fn initial(&self) -> Option<String> {
|
||||
// initial payload may contain additional data, not in this case
|
||||
self.job()
|
||||
}
|
||||
|
||||
fn job(&self) -> Option<String> {
|
||||
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<String>) -> 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<Miner>, client: Weak<Client>) -> 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<F, R>(&self, f: F) -> Option<R> where F: Fn(Arc<Client>, Arc<Miner>) -> Option<R> {
|
||||
self.client.upgrade().and_then(|client| self.miner.upgrade().and_then(|miner| (f)(client, miner)))
|
||||
}
|
||||
|
||||
fn with_core_void<F>(&self, f: F) where F: Fn(Arc<Client>, Arc<Miner>) {
|
||||
self.client.upgrade().map(|client| self.miner.upgrade().map(|miner| (f)(client, miner)));
|
||||
}
|
||||
}
|
||||
|
||||
/// Wrapper for dedicated stratum service
|
||||
pub struct Stratum {
|
||||
dispatcher: Arc<StratumJobDispatcher>,
|
||||
service: Arc<StratumService>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
/// Stratum error
|
||||
pub enum Error {
|
||||
/// IPC sockets error
|
||||
Service(StratumServiceError),
|
||||
/// Invalid network address
|
||||
Address(AddrParseError),
|
||||
}
|
||||
|
||||
impl From<StratumServiceError> for Error {
|
||||
fn from(service_err: StratumServiceError) -> Error { Error::Service(service_err) }
|
||||
}
|
||||
|
||||
impl From<AddrParseError> 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<Miner>, client: Weak<Client>) -> Result<Stratum, Error> {
|
||||
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<Miner>, client: Weak<Client>) -> Result<(), Error> {
|
||||
let stratum = miner::Stratum::start(cfg, Arc::downgrade(&miner.clone()), client)?;
|
||||
miner.push_notifier(Box::new(stratum) as Box<miner::NotifyWork>);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -26,6 +26,12 @@ use hyper::Url;
|
||||
use util::*;
|
||||
use ethereum::ethash::Ethash;
|
||||
|
||||
/// Trait for notifying about new mining work
|
||||
pub trait NotifyWork : Send + Sync {
|
||||
/// Fired when new mining job available
|
||||
fn notify(&self, pow_hash: H256, difficulty: U256, number: u64);
|
||||
}
|
||||
|
||||
pub struct WorkPoster {
|
||||
urls: Vec<Url>,
|
||||
client: Mutex<Client<PostHandler>>,
|
||||
@@ -57,8 +63,10 @@ impl WorkPoster {
|
||||
.build()
|
||||
.expect("Error creating HTTP client")
|
||||
}
|
||||
}
|
||||
|
||||
pub fn notify(&self, pow_hash: H256, difficulty: U256, number: u64) {
|
||||
impl NotifyWork for WorkPoster {
|
||||
fn notify(&self, pow_hash: H256, difficulty: U256, number: u64) {
|
||||
// TODO: move this to engine
|
||||
let target = Ethash::difficulty_to_boundary(&difficulty);
|
||||
let seed_hash = &self.seed_compute.lock().get_seedhash(number);
|
||||
|
||||
@@ -22,6 +22,7 @@ use spec::Spec;
|
||||
use error::*;
|
||||
use client::{Client, ClientConfig, ChainNotify};
|
||||
use miner::Miner;
|
||||
|
||||
use snapshot::ManifestData;
|
||||
use snapshot::service::{Service as SnapshotService, ServiceParams as SnapServiceParams};
|
||||
use std::sync::atomic::AtomicBool;
|
||||
|
||||
Reference in New Issue
Block a user