2018-04-13 16:42:25 +03:00
|
|
|
// Copyright 2018 The Grin Developers
|
|
|
|
//
|
|
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
// you may not use this file except in compliance with the License.
|
|
|
|
// You may obtain a copy of the License at
|
|
|
|
//
|
|
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
//
|
|
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
// See the License for the specific language governing permissions and
|
|
|
|
// limitations under the License.
|
|
|
|
|
|
|
|
//! Mining Stratum Server
|
2018-05-23 21:32:38 +03:00
|
|
|
use bufstream::BufStream;
|
|
|
|
use serde_json;
|
2018-06-17 22:08:17 +03:00
|
|
|
use serde_json::Value;
|
2018-04-13 16:42:25 +03:00
|
|
|
use std::error::Error;
|
2018-06-14 15:16:14 +03:00
|
|
|
use std::io::{BufRead, ErrorKind, Write};
|
2018-05-23 21:32:38 +03:00
|
|
|
use std::net::{TcpListener, TcpStream};
|
|
|
|
use std::sync::atomic::{AtomicBool, Ordering};
|
2018-04-13 16:42:25 +03:00
|
|
|
use std::sync::{Arc, Mutex, RwLock};
|
2018-06-14 15:16:14 +03:00
|
|
|
use std::time::{Duration, SystemTime};
|
|
|
|
use std::{cmp, thread};
|
2018-05-23 21:32:38 +03:00
|
|
|
use time;
|
2018-04-13 16:42:25 +03:00
|
|
|
|
2018-05-23 21:32:38 +03:00
|
|
|
use chain;
|
2018-04-13 16:42:25 +03:00
|
|
|
use common::adapters::PoolToChainAdapter;
|
2018-05-23 21:32:38 +03:00
|
|
|
use common::stats::{StratumStats, WorkerStats};
|
2018-04-24 11:18:24 +03:00
|
|
|
use common::types::StratumServerConfig;
|
2018-05-23 21:32:38 +03:00
|
|
|
use core::core::{Block, BlockHeader};
|
2018-06-14 15:16:14 +03:00
|
|
|
use core::{consensus, pow};
|
2018-05-23 21:32:38 +03:00
|
|
|
use keychain;
|
2018-04-13 16:42:25 +03:00
|
|
|
use mining::mine_block;
|
|
|
|
use pool;
|
2018-06-04 13:22:42 +03:00
|
|
|
use util::LOGGER;
|
2018-04-13 16:42:25 +03:00
|
|
|
|
|
|
|
// Max number of transactions this miner will assemble in a block
|
|
|
|
const MAX_TX: u32 = 5000;
|
|
|
|
|
|
|
|
// ----------------------------------------
|
|
|
|
// http://www.jsonrpc.org/specification
|
|
|
|
// RPC Methods
|
|
|
|
|
|
|
|
#[derive(Serialize, Deserialize, Debug)]
|
|
|
|
struct RpcRequest {
|
|
|
|
id: String,
|
|
|
|
jsonrpc: String,
|
|
|
|
method: String,
|
2018-06-17 22:08:17 +03:00
|
|
|
params: Option<Value>,
|
2018-04-13 16:42:25 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Serialize, Deserialize, Debug)]
|
|
|
|
struct RpcResponse {
|
|
|
|
id: String,
|
|
|
|
jsonrpc: String,
|
2018-04-23 11:41:35 +03:00
|
|
|
method: String,
|
2018-06-17 22:08:17 +03:00
|
|
|
result: Option<Value>,
|
|
|
|
error: Option<Value>,
|
2018-04-13 16:42:25 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Serialize, Deserialize, Debug)]
|
|
|
|
struct RpcError {
|
|
|
|
code: i32,
|
|
|
|
message: String,
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Serialize, Deserialize, Debug)]
|
|
|
|
struct LoginParams {
|
|
|
|
login: String,
|
|
|
|
pass: String,
|
|
|
|
agent: String,
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Serialize, Deserialize, Debug)]
|
|
|
|
struct SubmitParams {
|
|
|
|
height: u64,
|
|
|
|
nonce: u64,
|
|
|
|
pow: Vec<u32>,
|
|
|
|
}
|
|
|
|
|
2018-04-17 11:16:58 +03:00
|
|
|
#[derive(Serialize, Deserialize, Debug)]
|
|
|
|
pub struct JobTemplate {
|
2018-04-17 18:17:30 +03:00
|
|
|
height: u64,
|
2018-04-17 11:16:58 +03:00
|
|
|
difficulty: u64,
|
|
|
|
pre_pow: String,
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Serialize, Deserialize, Debug)]
|
|
|
|
pub struct WorkerStatus {
|
|
|
|
id: String,
|
|
|
|
height: u64,
|
|
|
|
difficulty: u64,
|
|
|
|
accepted: u64,
|
|
|
|
rejected: u64,
|
|
|
|
stale: u64,
|
|
|
|
}
|
|
|
|
|
2018-04-13 16:42:25 +03:00
|
|
|
// ----------------------------------------
|
|
|
|
// Worker Factory Thread Function
|
|
|
|
|
|
|
|
// Run in a thread. Adds new connections to the workers list
|
2018-04-17 18:17:30 +03:00
|
|
|
fn accept_workers(
|
|
|
|
id: String,
|
|
|
|
address: String,
|
|
|
|
workers: &mut Arc<Mutex<Vec<Worker>>>,
|
|
|
|
stratum_stats: &mut Arc<RwLock<StratumStats>>,
|
|
|
|
) {
|
2018-04-13 16:42:25 +03:00
|
|
|
let listener = TcpListener::bind(address).expect("Failed to bind to listen address");
|
|
|
|
let mut worker_id: u32 = 0;
|
|
|
|
for stream in listener.incoming() {
|
|
|
|
match stream {
|
|
|
|
Ok(stream) => {
|
|
|
|
warn!(
|
|
|
|
LOGGER,
|
|
|
|
"(Server ID: {}) New connection: {}",
|
|
|
|
id,
|
|
|
|
stream.peer_addr().unwrap()
|
|
|
|
);
|
|
|
|
stream
|
|
|
|
.set_nonblocking(true)
|
|
|
|
.expect("set_nonblocking call failed");
|
|
|
|
let mut worker = Worker::new(worker_id.to_string(), BufStream::new(stream));
|
|
|
|
workers.lock().unwrap().push(worker);
|
2018-04-17 18:17:30 +03:00
|
|
|
// stats for this worker (worker stat objects are added and updated but never
|
|
|
|
// removed)
|
2018-04-13 16:42:25 +03:00
|
|
|
let mut worker_stats = WorkerStats::default();
|
|
|
|
worker_stats.is_connected = true;
|
|
|
|
worker_stats.id = worker_id.to_string();
|
2018-04-17 18:17:30 +03:00
|
|
|
worker_stats.pow_difficulty = 1; // XXX TODO
|
2018-04-13 16:42:25 +03:00
|
|
|
let mut stratum_stats = stratum_stats.write().unwrap();
|
|
|
|
stratum_stats.worker_stats.push(worker_stats);
|
|
|
|
worker_id = worker_id + 1;
|
|
|
|
}
|
|
|
|
Err(e) => {
|
|
|
|
warn!(
|
|
|
|
LOGGER,
|
|
|
|
"(Server ID: {}) Error accepting connection: {:?}", id, e
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// close the socket server
|
|
|
|
drop(listener);
|
|
|
|
}
|
|
|
|
|
|
|
|
// ----------------------------------------
|
|
|
|
// Worker Object - a connected stratum client - a miner, pool, proxy, etc...
|
|
|
|
|
|
|
|
pub struct Worker {
|
|
|
|
id: String,
|
2018-06-17 22:08:17 +03:00
|
|
|
agent: String,
|
2018-05-09 12:10:47 +03:00
|
|
|
login: Option<String>,
|
2018-04-13 16:42:25 +03:00
|
|
|
stream: BufStream<TcpStream>,
|
|
|
|
error: bool,
|
|
|
|
authenticated: bool,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Worker {
|
|
|
|
/// Creates a new Stratum Worker.
|
|
|
|
pub fn new(id: String, stream: BufStream<TcpStream>) -> Worker {
|
|
|
|
Worker {
|
|
|
|
id: id,
|
2018-06-17 22:08:17 +03:00
|
|
|
agent: String::from(""),
|
2018-05-09 12:10:47 +03:00
|
|
|
login: None,
|
2018-04-13 16:42:25 +03:00
|
|
|
stream: stream,
|
|
|
|
error: false,
|
|
|
|
authenticated: false,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Get Message from the worker
|
|
|
|
fn read_message(&mut self) -> Option<String> {
|
|
|
|
// Read and return a single message or None
|
|
|
|
let mut line = String::new();
|
|
|
|
match self.stream.read_line(&mut line) {
|
|
|
|
Ok(_) => {
|
|
|
|
return Some(line);
|
|
|
|
}
|
|
|
|
Err(ref e) if e.kind() == ErrorKind::WouldBlock => {
|
|
|
|
// Not an error, just no messages ready
|
|
|
|
return None;
|
|
|
|
}
|
|
|
|
Err(e) => {
|
|
|
|
warn!(
|
|
|
|
LOGGER,
|
|
|
|
"(Server ID: {}) Error in connection with stratum client: {}", self.id, e
|
|
|
|
);
|
|
|
|
self.error = true;
|
|
|
|
return None;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Send Message to the worker
|
2018-04-17 11:16:58 +03:00
|
|
|
fn write_message(&mut self, message_in: String) {
|
2018-04-13 16:42:25 +03:00
|
|
|
// Write and Flush the message
|
|
|
|
let mut message = message_in.clone();
|
|
|
|
if !message.ends_with("\n") {
|
|
|
|
message += "\n";
|
|
|
|
}
|
|
|
|
match self.stream.write(message.as_bytes()) {
|
|
|
|
Ok(_) => match self.stream.flush() {
|
|
|
|
Ok(_) => {}
|
|
|
|
Err(e) => {
|
|
|
|
warn!(
|
|
|
|
LOGGER,
|
|
|
|
"(Server ID: {}) Error in connection with stratum client: {}", self.id, e
|
|
|
|
);
|
|
|
|
self.error = true;
|
|
|
|
}
|
|
|
|
},
|
|
|
|
Err(e) => {
|
|
|
|
warn!(
|
|
|
|
LOGGER,
|
|
|
|
"(Server ID: {}) Error in connection with stratum client: {}", self.id, e
|
|
|
|
);
|
|
|
|
self.error = true;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} // impl Worker
|
|
|
|
|
|
|
|
// ----------------------------------------
|
|
|
|
// Grin Stratum Server
|
|
|
|
|
|
|
|
pub struct StratumServer {
|
|
|
|
id: String,
|
2018-04-24 11:18:24 +03:00
|
|
|
config: StratumServerConfig,
|
2018-04-13 16:42:25 +03:00
|
|
|
chain: Arc<chain::Chain>,
|
|
|
|
tx_pool: Arc<RwLock<pool::TransactionPool<PoolToChainAdapter>>>,
|
|
|
|
current_block: Block,
|
2018-04-17 11:16:58 +03:00
|
|
|
current_difficulty: u64,
|
2018-06-04 13:22:42 +03:00
|
|
|
minimum_share_difficulty: u64,
|
2018-05-23 21:32:38 +03:00
|
|
|
current_key_id: Option<keychain::Identifier>,
|
2018-04-13 16:42:25 +03:00
|
|
|
workers: Arc<Mutex<Vec<Worker>>>,
|
2018-04-24 11:18:24 +03:00
|
|
|
currently_syncing: Arc<AtomicBool>,
|
2018-04-13 16:42:25 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
impl StratumServer {
|
|
|
|
/// Creates a new Stratum Server.
|
|
|
|
pub fn new(
|
2018-04-24 11:18:24 +03:00
|
|
|
config: StratumServerConfig,
|
2018-04-13 16:42:25 +03:00
|
|
|
chain_ref: Arc<chain::Chain>,
|
|
|
|
tx_pool: Arc<RwLock<pool::TransactionPool<PoolToChainAdapter>>>,
|
|
|
|
) -> StratumServer {
|
|
|
|
StratumServer {
|
|
|
|
id: String::from("StratumServer"),
|
2018-06-04 13:22:42 +03:00
|
|
|
minimum_share_difficulty: config.minimum_share_difficulty,
|
2018-04-13 16:42:25 +03:00
|
|
|
config: config,
|
|
|
|
chain: chain_ref,
|
|
|
|
tx_pool: tx_pool,
|
|
|
|
current_block: Block::default(),
|
2018-04-17 11:16:58 +03:00
|
|
|
current_difficulty: <u64>::max_value(),
|
2018-05-23 21:32:38 +03:00
|
|
|
current_key_id: None,
|
2018-04-13 16:42:25 +03:00
|
|
|
workers: Arc::new(Mutex::new(Vec::new())),
|
2018-04-24 11:18:24 +03:00
|
|
|
currently_syncing: Arc::new(AtomicBool::new(false)),
|
2018-04-13 16:42:25 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Build and return a JobTemplate for mining the current block
|
|
|
|
fn build_block_template(&self, bh: BlockHeader) -> JobTemplate {
|
|
|
|
// Serialize the block header into pre and post nonce strings
|
2018-04-17 11:16:58 +03:00
|
|
|
let mut pre_pow_writer = mine_block::HeaderPrePowWriter::default();
|
2018-04-13 16:42:25 +03:00
|
|
|
bh.write_pre_pow(&mut pre_pow_writer).unwrap();
|
|
|
|
let pre = pre_pow_writer.as_hex_string(false);
|
2018-04-17 11:16:58 +03:00
|
|
|
let job_template = JobTemplate {
|
2018-04-17 18:17:30 +03:00
|
|
|
height: bh.height,
|
2018-06-04 13:22:42 +03:00
|
|
|
difficulty: self.minimum_share_difficulty,
|
2018-04-17 18:17:30 +03:00
|
|
|
pre_pow: pre,
|
2018-04-17 11:16:58 +03:00
|
|
|
};
|
2018-04-13 16:42:25 +03:00
|
|
|
return job_template;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Handle an RPC request message from the worker(s)
|
|
|
|
fn handle_rpc_requests(&mut self, stratum_stats: &mut Arc<RwLock<StratumStats>>) {
|
|
|
|
let mut workers_l = self.workers.lock().unwrap();
|
|
|
|
for num in 0..workers_l.len() {
|
|
|
|
match workers_l[num].read_message() {
|
|
|
|
Some(the_message) => {
|
|
|
|
// Decompose the request from the JSONRpc wrapper
|
|
|
|
let request: RpcRequest = match serde_json::from_str(&the_message) {
|
|
|
|
Ok(request) => request,
|
|
|
|
Err(e) => {
|
|
|
|
// not a valid JSON RpcRequest - disconnect the worker
|
|
|
|
warn!(
|
|
|
|
LOGGER,
|
|
|
|
"(Server ID: {}) Failed to parse JSONRpc: {} - {:?}",
|
|
|
|
self.id,
|
|
|
|
e.description(),
|
|
|
|
the_message.as_bytes(),
|
|
|
|
);
|
|
|
|
workers_l[num].error = true;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
let mut stratum_stats = stratum_stats.write().unwrap();
|
2018-04-17 18:17:30 +03:00
|
|
|
let worker_stats_id = stratum_stats
|
|
|
|
.worker_stats
|
|
|
|
.iter()
|
|
|
|
.position(|r| r.id == workers_l[num].id)
|
|
|
|
.unwrap();
|
2018-04-13 16:42:25 +03:00
|
|
|
stratum_stats.worker_stats[worker_stats_id].last_seen = SystemTime::now();
|
|
|
|
|
|
|
|
// Call the handler function for requested method
|
2018-06-20 12:34:39 +03:00
|
|
|
let response = match request.method.as_str() {
|
2018-06-20 22:18:52 +03:00
|
|
|
"login" => self.handle_login(request.params, &mut workers_l[num]),
|
2018-05-23 21:32:38 +03:00
|
|
|
"submit" => {
|
|
|
|
let res = self.handle_submit(
|
|
|
|
request.params,
|
|
|
|
&mut workers_l[num],
|
|
|
|
&mut stratum_stats.worker_stats[worker_stats_id],
|
|
|
|
);
|
|
|
|
// this key_id has been used now, reset
|
|
|
|
self.current_key_id = None;
|
|
|
|
res
|
|
|
|
}
|
2018-04-13 16:42:25 +03:00
|
|
|
"keepalive" => self.handle_keepalive(),
|
|
|
|
"getjobtemplate" => {
|
2018-04-24 11:18:24 +03:00
|
|
|
if self.currently_syncing.load(Ordering::Relaxed) {
|
2018-06-17 22:08:17 +03:00
|
|
|
let e = RpcError {
|
|
|
|
code: -32701,
|
|
|
|
message: "Node is syncing - Please wait".to_string(),
|
|
|
|
};
|
2018-06-20 12:34:39 +03:00
|
|
|
Err(serde_json::to_value(e).unwrap())
|
2018-04-24 11:18:24 +03:00
|
|
|
} else {
|
|
|
|
let b = self.current_block.header.clone();
|
|
|
|
self.handle_getjobtemplate(b)
|
|
|
|
}
|
2018-04-13 16:42:25 +03:00
|
|
|
}
|
2018-04-17 18:17:30 +03:00
|
|
|
"status" => {
|
|
|
|
self.handle_status(&stratum_stats.worker_stats[worker_stats_id])
|
|
|
|
}
|
2018-04-13 16:42:25 +03:00
|
|
|
_ => {
|
|
|
|
// Called undefined method
|
2018-06-17 22:08:17 +03:00
|
|
|
let e = RpcError {
|
|
|
|
code: -32601,
|
|
|
|
message: "Method not found".to_string(),
|
|
|
|
};
|
2018-06-20 12:34:39 +03:00
|
|
|
Err(serde_json::to_value(e).unwrap())
|
2018-04-13 16:42:25 +03:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
// Package the reply as RpcResponse json
|
|
|
|
let rpc_response: String;
|
2018-06-20 12:34:39 +03:00
|
|
|
match response {
|
|
|
|
Err(response) => {
|
|
|
|
let resp = RpcResponse {
|
|
|
|
id: request.id,
|
|
|
|
jsonrpc: String::from("2.0"),
|
|
|
|
method: request.method,
|
|
|
|
result: None,
|
|
|
|
error: Some(response),
|
|
|
|
};
|
|
|
|
rpc_response = serde_json::to_string(&resp).unwrap();
|
|
|
|
}
|
|
|
|
Ok(response) => {
|
|
|
|
let resp = RpcResponse {
|
|
|
|
id: request.id,
|
|
|
|
jsonrpc: String::from("2.0"),
|
|
|
|
method: request.method,
|
|
|
|
result: Some(response),
|
|
|
|
error: None,
|
|
|
|
};
|
|
|
|
rpc_response = serde_json::to_string(&resp).unwrap();
|
|
|
|
}
|
2018-04-13 16:42:25 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
// Send the reply
|
2018-04-17 11:16:58 +03:00
|
|
|
workers_l[num].write_message(rpc_response);
|
2018-04-13 16:42:25 +03:00
|
|
|
}
|
|
|
|
None => {} // No message for us from this worker
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Handle STATUS message
|
2018-06-20 12:34:39 +03:00
|
|
|
fn handle_status(&self, worker_stats: &WorkerStats) -> Result<Value, Value> {
|
2018-04-17 11:16:58 +03:00
|
|
|
// 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,
|
2018-04-17 18:17:30 +03:00
|
|
|
difficulty: worker_stats.pow_difficulty,
|
2018-04-17 11:16:58 +03:00
|
|
|
accepted: worker_stats.num_accepted,
|
|
|
|
rejected: worker_stats.num_rejected,
|
|
|
|
stale: worker_stats.num_stale,
|
|
|
|
};
|
2018-06-17 22:08:17 +03:00
|
|
|
let response = serde_json::to_value(&status).unwrap();
|
2018-06-20 12:34:39 +03:00
|
|
|
return Ok(response);
|
2018-04-13 16:42:25 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
// Handle GETJOBTEMPLATE message
|
2018-06-20 12:34:39 +03:00
|
|
|
fn handle_getjobtemplate(&self, bh: BlockHeader) -> Result<Value, Value> {
|
2018-04-13 16:42:25 +03:00
|
|
|
// Build a JobTemplate from a BlockHeader and return JSON
|
|
|
|
let job_template = self.build_block_template(bh);
|
2018-06-17 22:08:17 +03:00
|
|
|
let response = serde_json::to_value(&job_template).unwrap();
|
2018-06-20 12:34:39 +03:00
|
|
|
return Ok(response);
|
2018-04-13 16:42:25 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
// Handle KEEPALIVE message
|
2018-06-20 12:34:39 +03:00
|
|
|
fn handle_keepalive(&self) -> Result<Value, Value> {
|
|
|
|
return Ok(serde_json::to_value("ok".to_string()).unwrap());
|
2018-04-13 16:42:25 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
// Handle LOGIN message
|
2018-06-20 12:34:39 +03:00
|
|
|
fn handle_login(&self, params: Option<Value>, worker: &mut Worker) -> Result<Value, Value> {
|
2018-06-17 22:08:17 +03:00
|
|
|
let params: LoginParams = match params {
|
|
|
|
Some(val) => serde_json::from_value(val).unwrap(),
|
|
|
|
None => {
|
|
|
|
let e = RpcError {
|
|
|
|
code: -32600,
|
|
|
|
message: "Invalid Request".to_string(),
|
|
|
|
};
|
2018-06-20 12:34:39 +03:00
|
|
|
return Err(serde_json::to_value(e).unwrap());
|
2018-04-13 16:42:25 +03:00
|
|
|
}
|
|
|
|
};
|
2018-06-17 22:08:17 +03:00
|
|
|
worker.login = Some(params.login);
|
|
|
|
// XXX TODO Future - Validate password?
|
|
|
|
worker.agent = params.agent;
|
2018-05-09 12:10:47 +03:00
|
|
|
worker.authenticated = true;
|
2018-06-20 12:34:39 +03:00
|
|
|
return Ok(serde_json::to_value("ok".to_string()).unwrap());
|
2018-04-13 16:42:25 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
// Handle SUBMIT message
|
2018-06-17 22:08:17 +03:00
|
|
|
// params contains a solved block header
|
2018-06-04 13:22:42 +03:00
|
|
|
// We accept and log valid shares of all difficulty above configured minimum
|
|
|
|
// Accepted shares that are full solutions will also be submitted to the
|
|
|
|
// network
|
2018-04-17 18:17:30 +03:00
|
|
|
fn handle_submit(
|
|
|
|
&self,
|
2018-06-17 22:08:17 +03:00
|
|
|
params: Option<Value>,
|
2018-05-09 12:10:47 +03:00
|
|
|
worker: &mut Worker,
|
2018-04-17 18:17:30 +03:00
|
|
|
worker_stats: &mut WorkerStats,
|
2018-06-20 12:34:39 +03:00
|
|
|
) -> Result<Value, Value> {
|
2018-06-17 22:08:17 +03:00
|
|
|
// Validate parameters
|
|
|
|
let params: SubmitParams = match params {
|
|
|
|
Some(val) => serde_json::from_value(val).unwrap(),
|
|
|
|
None => {
|
|
|
|
let e = RpcError {
|
|
|
|
code: -32600,
|
|
|
|
message: "Invalid Request".to_string(),
|
|
|
|
};
|
2018-06-20 12:34:39 +03:00
|
|
|
return Err(serde_json::to_value(e).unwrap());
|
2018-04-13 16:42:25 +03:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
let mut b: Block;
|
2018-06-04 13:22:42 +03:00
|
|
|
let share_difficulty: u64;
|
2018-06-17 22:08:17 +03:00
|
|
|
if params.height == self.current_block.header.height {
|
2018-04-13 16:42:25 +03:00
|
|
|
// Reconstruct the block header with this nonce and pow added
|
|
|
|
b = self.current_block.clone();
|
2018-06-17 22:08:17 +03:00
|
|
|
b.header.nonce = params.nonce;
|
|
|
|
b.header.pow.nonces = params.pow;
|
2018-06-04 13:22:42 +03:00
|
|
|
// 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
|
2018-04-13 16:42:25 +03:00
|
|
|
error!(
|
|
|
|
LOGGER,
|
2018-06-04 13:22:42 +03:00
|
|
|
"(Server ID: {}) Share rejected due to low difficulty: {}/{}",
|
|
|
|
self.id,
|
|
|
|
share_difficulty,
|
|
|
|
self.minimum_share_difficulty,
|
2018-04-13 16:42:25 +03:00
|
|
|
);
|
|
|
|
worker_stats.num_rejected += 1;
|
2018-06-17 22:08:17 +03:00
|
|
|
let e = RpcError {
|
|
|
|
code: -32501,
|
|
|
|
message: "Share rejected due to low difficulty".to_string(),
|
|
|
|
};
|
2018-06-20 12:34:39 +03:00
|
|
|
return Err(serde_json::to_value(e).unwrap());
|
2018-04-13 16:42:25 +03:00
|
|
|
}
|
2018-06-04 13:22:42 +03:00
|
|
|
// 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,
|
2018-06-17 22:08:17 +03:00
|
|
|
params.height,
|
2018-06-04 13:22:42 +03:00
|
|
|
e
|
|
|
|
);
|
|
|
|
worker_stats.num_rejected += 1;
|
2018-06-17 22:08:17 +03:00
|
|
|
let e = RpcError {
|
|
|
|
code: -32502,
|
|
|
|
message: "Failed to validate solution".to_string(),
|
|
|
|
};
|
2018-06-20 12:34:39 +03:00
|
|
|
return Err(serde_json::to_value(e).unwrap());
|
2018-06-04 13:22:42 +03:00
|
|
|
}
|
|
|
|
// Success case falls through to be logged
|
|
|
|
} else {
|
2018-06-11 18:39:04 +03:00
|
|
|
// This is a low-difficulty share, not a full solution
|
2018-06-04 13:22:42 +03:00
|
|
|
// Do some validation but dont submit
|
2018-06-11 18:39:04 +03:00
|
|
|
if !pow::verify_size(&b.header, consensus::DEFAULT_SIZESHIFT) {
|
2018-06-04 13:22:42 +03:00
|
|
|
// Return error status
|
|
|
|
error!(
|
|
|
|
LOGGER,
|
|
|
|
"(Server ID: {}) Failed to validate share at height {} with nonce {}",
|
|
|
|
self.id,
|
2018-06-17 22:08:17 +03:00
|
|
|
params.height,
|
2018-06-04 13:22:42 +03:00
|
|
|
b.header.nonce
|
|
|
|
);
|
|
|
|
worker_stats.num_rejected += 1;
|
2018-06-17 22:08:17 +03:00
|
|
|
let e = RpcError {
|
|
|
|
code: -32502,
|
|
|
|
message: "Failed to validate solution".to_string(),
|
|
|
|
};
|
2018-06-20 12:34:39 +03:00
|
|
|
return Err(serde_json::to_value(e).unwrap());
|
2018-06-04 13:22:42 +03:00
|
|
|
}
|
|
|
|
}
|
2018-04-13 16:42:25 +03:00
|
|
|
} else {
|
2018-06-04 13:22:42 +03:00
|
|
|
// Return error status
|
|
|
|
error!(
|
2018-04-13 16:42:25 +03:00
|
|
|
LOGGER,
|
2018-06-17 22:08:17 +03:00
|
|
|
"(Server ID: {}) Share at height {} submitted too late", self.id, params.height,
|
2018-04-13 16:42:25 +03:00
|
|
|
);
|
|
|
|
worker_stats.num_stale += 1;
|
2018-06-17 22:08:17 +03:00
|
|
|
let e = RpcError {
|
|
|
|
code: -32503,
|
|
|
|
message: "Solution submitted too late".to_string(),
|
|
|
|
};
|
2018-06-20 12:34:39 +03:00
|
|
|
return Err(serde_json::to_value(e).unwrap());
|
2018-04-13 16:42:25 +03:00
|
|
|
}
|
2018-06-04 13:22:42 +03:00
|
|
|
// Log this as a valid share
|
2018-05-09 12:10:47 +03:00
|
|
|
let submitted_by = match worker.login.clone() {
|
|
|
|
None => worker.id.to_string(),
|
2018-05-23 21:32:38 +03:00
|
|
|
Some(login) => login.clone(),
|
2018-05-09 12:10:47 +03:00
|
|
|
};
|
|
|
|
info!(
|
|
|
|
LOGGER,
|
2018-06-04 13:22:42 +03:00
|
|
|
"(Server ID: {}) Got share for block: hash {}, height {}, nonce {}, difficulty {}/{}, submitted by {}",
|
2018-05-09 12:10:47 +03:00
|
|
|
self.id,
|
|
|
|
b.hash(),
|
|
|
|
b.header.height,
|
|
|
|
b.header.nonce,
|
2018-06-04 13:22:42 +03:00
|
|
|
share_difficulty,
|
|
|
|
self.current_difficulty,
|
2018-05-09 12:10:47 +03:00
|
|
|
submitted_by,
|
|
|
|
);
|
2018-04-13 16:42:25 +03:00
|
|
|
worker_stats.num_accepted += 1;
|
2018-06-20 12:34:39 +03:00
|
|
|
return Ok(serde_json::to_value("ok".to_string()).unwrap());
|
2018-04-13 16:42:25 +03:00
|
|
|
} // handle submit a solution
|
|
|
|
|
|
|
|
// Purge dead/sick workers - remove all workers marked in error state
|
2018-05-01 11:29:39 +03:00
|
|
|
fn clean_workers(&mut self, stratum_stats: &mut Arc<RwLock<StratumStats>>) -> usize {
|
2018-04-13 16:42:25 +03:00
|
|
|
let mut start = 0;
|
|
|
|
let mut workers_l = self.workers.lock().unwrap();
|
|
|
|
loop {
|
|
|
|
for num in start..workers_l.len() {
|
|
|
|
if workers_l[num].error == true {
|
|
|
|
warn!(
|
|
|
|
LOGGER,
|
|
|
|
"(Server ID: {}) Dropping worker: {}",
|
|
|
|
self.id,
|
|
|
|
workers_l[num].id;
|
|
|
|
);
|
|
|
|
// Update worker stats
|
|
|
|
let mut stratum_stats = stratum_stats.write().unwrap();
|
2018-04-17 18:17:30 +03:00
|
|
|
let worker_stats_id = stratum_stats
|
|
|
|
.worker_stats
|
|
|
|
.iter()
|
|
|
|
.position(|r| r.id == workers_l[num].id)
|
|
|
|
.unwrap();
|
2018-04-13 16:42:25 +03:00
|
|
|
stratum_stats.worker_stats[worker_stats_id].is_connected = false;
|
|
|
|
// Remove the dead worker
|
|
|
|
workers_l.remove(num);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
start = num + 1;
|
|
|
|
}
|
|
|
|
if start >= workers_l.len() {
|
|
|
|
let mut stratum_stats = stratum_stats.write().unwrap();
|
|
|
|
stratum_stats.num_workers = workers_l.len();
|
2018-05-01 11:29:39 +03:00
|
|
|
return stratum_stats.num_workers;
|
2018-04-13 16:42:25 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-04-17 18:17:30 +03:00
|
|
|
// Broadcast a jobtemplate RpcRequest to all connected workers - no response
|
|
|
|
// expected
|
2018-04-13 16:42:25 +03:00
|
|
|
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_json = serde_json::to_string(&job_template).unwrap();
|
2018-06-17 22:08:17 +03:00
|
|
|
// 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();
|
2018-04-13 16:42:25 +03:00
|
|
|
let job_request = RpcRequest {
|
|
|
|
id: String::from("Stratum"),
|
|
|
|
jsonrpc: String::from("2.0"),
|
|
|
|
method: String::from("job"),
|
2018-06-17 22:08:17 +03:00
|
|
|
params: Some(job_template_value),
|
2018-04-13 16:42:25 +03:00
|
|
|
};
|
|
|
|
let job_request_json = serde_json::to_string(&job_request).unwrap();
|
|
|
|
|
|
|
|
// Push the new block to all connected clients
|
2018-06-13 19:03:34 +03:00
|
|
|
// NOTE: We do not give a unique nonce (should we?) so miners need
|
2018-06-04 13:22:42 +03:00
|
|
|
// to choose one for themselves
|
2018-04-13 16:42:25 +03:00
|
|
|
let mut workers_l = self.workers.lock().unwrap();
|
|
|
|
for num in 0..workers_l.len() {
|
2018-04-17 11:16:58 +03:00
|
|
|
workers_l[num].write_message(job_request_json.clone());
|
2018-04-13 16:42:25 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-05-23 21:32:38 +03:00
|
|
|
/// "main()" - Starts the stratum-server. Creates a thread to Listens for
|
|
|
|
/// a connection, then enters a loop, building a new block on top of the
|
|
|
|
/// existing chain anytime required and sending that to the connected
|
|
|
|
/// stratum miner, proxy, or pool, and accepts full solutions to
|
2018-04-17 18:17:30 +03:00
|
|
|
/// be submitted.
|
|
|
|
pub fn run_loop(
|
|
|
|
&mut self,
|
|
|
|
stratum_stats: Arc<RwLock<StratumStats>>,
|
|
|
|
cuckoo_size: u32,
|
|
|
|
proof_size: usize,
|
2018-04-24 11:18:24 +03:00
|
|
|
currently_syncing: Arc<AtomicBool>,
|
2018-04-17 18:17:30 +03:00
|
|
|
) {
|
2018-04-13 16:42:25 +03:00
|
|
|
info!(
|
|
|
|
LOGGER,
|
|
|
|
"(Server ID: {}) Starting stratum server with cuckoo_size = {}, proof_size = {}",
|
|
|
|
self.id,
|
|
|
|
cuckoo_size,
|
|
|
|
proof_size
|
|
|
|
);
|
|
|
|
|
2018-04-24 11:18:24 +03:00
|
|
|
self.currently_syncing = currently_syncing;
|
|
|
|
|
2018-04-13 16:42:25 +03:00
|
|
|
// "globals" for this function
|
2018-06-04 13:22:42 +03:00
|
|
|
let attempt_time_per_block = self.config.attempt_time_per_block;
|
2018-04-13 16:42:25 +03:00
|
|
|
let mut deadline: i64 = 0;
|
|
|
|
// to prevent the wallet from generating a new HD key derivation for each
|
|
|
|
// iteration, we keep the returned derivation to provide it back when
|
|
|
|
// nothing has changed. We only want to create a key_id for each new block,
|
|
|
|
// and reuse it when we rebuild the current block to add new tx.
|
2018-05-01 11:29:39 +03:00
|
|
|
let mut num_workers: usize;
|
2018-04-13 16:42:25 +03:00
|
|
|
let mut head = self.chain.head().unwrap();
|
|
|
|
let mut current_hash = head.prev_block_h;
|
|
|
|
let mut latest_hash;
|
2018-06-04 13:22:42 +03:00
|
|
|
let listen_addr = self.config.stratum_server_addr.clone().unwrap();
|
2018-04-13 16:42:25 +03:00
|
|
|
|
|
|
|
// Start a thread to accept new worker connections
|
|
|
|
let mut workers_th = self.workers.clone();
|
|
|
|
let id_th = self.id.clone();
|
|
|
|
let mut stats_th = stratum_stats.clone();
|
|
|
|
let _listener_th = thread::spawn(move || {
|
|
|
|
accept_workers(id_th, listen_addr, &mut workers_th, &mut stats_th);
|
|
|
|
});
|
|
|
|
|
|
|
|
// We have started
|
2018-04-17 18:17:30 +03:00
|
|
|
{
|
|
|
|
let mut stratum_stats = stratum_stats.write().unwrap();
|
|
|
|
stratum_stats.is_running = true;
|
|
|
|
stratum_stats.cuckoo_size = cuckoo_size as u16;
|
|
|
|
}
|
2018-04-13 16:42:25 +03:00
|
|
|
|
|
|
|
warn!(
|
|
|
|
LOGGER,
|
|
|
|
"Stratum server started on {}",
|
2018-06-04 13:22:42 +03:00
|
|
|
self.config.stratum_server_addr.clone().unwrap()
|
2018-04-13 16:42:25 +03:00
|
|
|
);
|
|
|
|
|
|
|
|
// Main Loop
|
|
|
|
loop {
|
2018-04-24 11:18:24 +03:00
|
|
|
// If we're fallen into sync mode, (or are just starting up,
|
|
|
|
// tell connected clients to stop what they're doing
|
|
|
|
let mining_stopped = self.currently_syncing.load(Ordering::Relaxed);
|
|
|
|
|
2018-04-13 16:42:25 +03:00
|
|
|
// Remove workers with failed connections
|
2018-05-01 11:29:39 +03:00
|
|
|
num_workers = self.clean_workers(&mut stratum_stats.clone());
|
2018-04-13 16:42:25 +03:00
|
|
|
|
|
|
|
// get the latest chain state
|
|
|
|
head = self.chain.head().unwrap();
|
|
|
|
latest_hash = head.last_block_h;
|
|
|
|
|
|
|
|
// Build a new block if:
|
|
|
|
// There is a new block on the chain
|
|
|
|
// or We are rebuilding the current one to include new transactions
|
2018-04-24 11:18:24 +03:00
|
|
|
// and we're not synching
|
2018-05-01 11:29:39 +03:00
|
|
|
// and there is at least one worker connected
|
|
|
|
if (current_hash != latest_hash || time::get_time().sec >= deadline) && !mining_stopped
|
|
|
|
&& num_workers > 0
|
|
|
|
{
|
2018-04-13 16:42:25 +03:00
|
|
|
let mut wallet_listener_url: Option<String> = None;
|
|
|
|
if !self.config.burn_reward {
|
|
|
|
wallet_listener_url = Some(self.config.wallet_listener_url.clone());
|
|
|
|
}
|
|
|
|
|
|
|
|
let (new_block, block_fees) = mine_block::get_block(
|
|
|
|
&self.chain,
|
|
|
|
&self.tx_pool,
|
2018-05-23 21:32:38 +03:00
|
|
|
self.current_key_id.clone(),
|
2018-04-13 16:42:25 +03:00
|
|
|
MAX_TX.clone(),
|
|
|
|
wallet_listener_url,
|
|
|
|
);
|
|
|
|
self.current_block = new_block;
|
2018-04-17 18:17:30 +03:00
|
|
|
self.current_difficulty = (self.current_block.header.total_difficulty.clone()
|
|
|
|
- head.total_difficulty.clone())
|
2018-06-01 22:41:26 +03:00
|
|
|
.to_num();
|
2018-05-23 21:32:38 +03:00
|
|
|
self.current_key_id = block_fees.key_id();
|
2018-04-13 16:42:25 +03:00
|
|
|
current_hash = latest_hash;
|
2018-06-04 13:22:42 +03:00
|
|
|
// set the minimum acceptable share difficulty for this block
|
|
|
|
self.minimum_share_difficulty = cmp::min(
|
|
|
|
self.config.minimum_share_difficulty,
|
|
|
|
self.current_difficulty,
|
|
|
|
);
|
2018-04-13 16:42:25 +03:00
|
|
|
// set a new deadline for rebuilding with fresh transactions
|
|
|
|
deadline = time::get_time().sec + attempt_time_per_block as i64;
|
|
|
|
|
|
|
|
{
|
|
|
|
let mut stratum_stats = stratum_stats.write().unwrap();
|
|
|
|
stratum_stats.block_height = self.current_block.header.height;
|
2018-04-17 11:16:58 +03:00
|
|
|
stratum_stats.network_difficulty = self.current_difficulty;
|
2018-04-13 16:42:25 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
// Send this job to all connected workers
|
|
|
|
self.broadcast_job();
|
|
|
|
}
|
|
|
|
|
|
|
|
// Handle any messages from the workers
|
|
|
|
self.handle_rpc_requests(&mut stratum_stats.clone());
|
|
|
|
|
|
|
|
// sleep before restarting loop
|
2018-05-23 21:32:38 +03:00
|
|
|
thread::sleep(Duration::from_millis(50));
|
2018-04-13 16:42:25 +03:00
|
|
|
} // Main Loop
|
|
|
|
} // fn run_loop()
|
|
|
|
} // StratumServer
|