mirror of
https://github.com/darkrenaissance/darkfi.git
synced 2026-01-08 22:28:12 -05:00
script/research/minerd: miner daemon skeleton
This commit is contained in:
3
script/research/minerd/.gitignore
vendored
Normal file
3
script/research/minerd/.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
/target
|
||||
Cargo.lock
|
||||
rustfmt.toml
|
||||
41
script/research/minerd/Cargo.toml
Normal file
41
script/research/minerd/Cargo.toml
Normal file
@@ -0,0 +1,41 @@
|
||||
[package]
|
||||
name = "minerd"
|
||||
version = "0.4.1"
|
||||
homepage = "https://dark.fi"
|
||||
description = "Darkfi mining daemon"
|
||||
authors = ["Dyne.org foundation <foundation@dyne.org>"]
|
||||
repository = "https://github.com/darkrenaissance/darkfi"
|
||||
license = "AGPL-3.0-only"
|
||||
edition = "2021"
|
||||
|
||||
[workspace]
|
||||
|
||||
[dependencies]
|
||||
# Darkfi
|
||||
darkfi = {path = "../../../", features = ["async-daemonize", "validator"]}
|
||||
darkfi-sdk = {path = "../../../src/sdk"}
|
||||
darkfi-serial = {path = "../../../src/serial", features = ["async"]}
|
||||
|
||||
# Misc
|
||||
log = "0.4.20"
|
||||
num-bigint = "0.4.4"
|
||||
|
||||
# JSON-RPC
|
||||
tinyjson = "2.5.1"
|
||||
url = "2.5.0"
|
||||
|
||||
# Daemon
|
||||
easy-parallel = "3.3.1"
|
||||
signal-hook-async-std = "0.2.2"
|
||||
signal-hook = "0.3.17"
|
||||
simplelog = "0.12.1"
|
||||
smol = "1.3.0"
|
||||
|
||||
# Argument parsing
|
||||
serde = {version = "1.0.195", features = ["derive"]}
|
||||
structopt = "0.3.26"
|
||||
structopt-toml = "0.5.1"
|
||||
|
||||
[patch.crates-io]
|
||||
halo2_proofs = {git="https://github.com/parazyd/halo2", branch="v4"}
|
||||
halo2_gadgets = {git="https://github.com/parazyd/halo2", branch="v4"}
|
||||
10
script/research/minerd/minerd.toml
Normal file
10
script/research/minerd/minerd.toml
Normal file
@@ -0,0 +1,10 @@
|
||||
## minerd configuration file
|
||||
##
|
||||
## Please make sure you go through all the settings so you can configure
|
||||
## your daemon properly.
|
||||
##
|
||||
## The default values are left commented. They can be overridden either by
|
||||
## uncommenting, or by using the command-line.
|
||||
|
||||
# JSON-RPC listen URL
|
||||
#rpc_listen = "tcp://127.0.0.1:28467"
|
||||
54
script/research/minerd/src/error.rs
Normal file
54
script/research/minerd/src/error.rs
Normal file
@@ -0,0 +1,54 @@
|
||||
/* This file is part of DarkFi (https://dark.fi)
|
||||
*
|
||||
* Copyright (C) 2020-2024 Dyne.org foundation
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program 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 Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
use darkfi::rpc::jsonrpc::{ErrorCode::ServerError, JsonError, JsonResult};
|
||||
|
||||
/// Custom RPC errors available for minerd.
|
||||
/// Please sort them sensefully.
|
||||
pub enum RpcError {
|
||||
// Parsing errors
|
||||
TargetParseError = -32101,
|
||||
BlockParseError = -32102,
|
||||
|
||||
// Miner errors
|
||||
MiningFailed = -32201,
|
||||
HashingFailed = -32202,
|
||||
}
|
||||
|
||||
fn to_tuple(e: RpcError) -> (i32, String) {
|
||||
let msg = match e {
|
||||
// Parsing errors
|
||||
RpcError::TargetParseError => "Target parse error",
|
||||
RpcError::BlockParseError => "Block parse error",
|
||||
// Miner errors
|
||||
RpcError::MiningFailed => "Mining block failed",
|
||||
RpcError::HashingFailed => "Hashing block failed",
|
||||
};
|
||||
|
||||
(e as i32, msg.to_string())
|
||||
}
|
||||
|
||||
pub fn server_error(e: RpcError, id: u16, msg: Option<&str>) -> JsonResult {
|
||||
let (code, default_msg) = to_tuple(e);
|
||||
|
||||
if let Some(message) = msg {
|
||||
return JsonError::new(ServerError(code), Some(message.to_string()), id).into()
|
||||
}
|
||||
|
||||
JsonError::new(ServerError(code), Some(default_msg), id).into()
|
||||
}
|
||||
117
script/research/minerd/src/main.rs
Normal file
117
script/research/minerd/src/main.rs
Normal file
@@ -0,0 +1,117 @@
|
||||
/* This file is part of DarkFi (https://dark.fi)
|
||||
*
|
||||
* Copyright (C) 2020-2024 Dyne.org foundation
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program 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 Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
use std::{collections::HashSet, sync::Arc};
|
||||
|
||||
use log::{error, info};
|
||||
use smol::{channel::Receiver, lock::Mutex, stream::StreamExt, Executor};
|
||||
use structopt_toml::{serde::Deserialize, structopt::StructOpt, StructOptToml};
|
||||
use url::Url;
|
||||
|
||||
use darkfi::{
|
||||
async_daemonize, cli_desc,
|
||||
rpc::server::{listen_and_serve, RequestHandler},
|
||||
system::{StoppableTask, StoppableTaskPtr},
|
||||
Error, Result,
|
||||
};
|
||||
|
||||
const CONFIG_FILE: &str = "minerd.toml";
|
||||
const CONFIG_FILE_CONTENTS: &str = include_str!("../minerd.toml");
|
||||
|
||||
/// Daemon error codes
|
||||
mod error;
|
||||
|
||||
/// JSON-RPC server methods
|
||||
mod rpc;
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, StructOpt, StructOptToml)]
|
||||
#[serde(default)]
|
||||
#[structopt(name = "minerd", about = cli_desc!())]
|
||||
struct Args {
|
||||
#[structopt(short, long)]
|
||||
/// Configuration file to use
|
||||
config: Option<String>,
|
||||
|
||||
#[structopt(short, long, default_value = "tcp://127.0.0.1:28467")]
|
||||
/// JSON-RPC listen URL
|
||||
rpc_listen: Url,
|
||||
|
||||
#[structopt(short, long, default_value = "4")]
|
||||
/// PoW miner number of threads to use
|
||||
threads: usize,
|
||||
|
||||
#[structopt(short, long)]
|
||||
/// Set log file to ouput into
|
||||
log: Option<String>,
|
||||
|
||||
#[structopt(short, parse(from_occurrences))]
|
||||
/// Increase verbosity (-vvv supported)
|
||||
verbose: u8,
|
||||
}
|
||||
|
||||
/// Daemon structure
|
||||
pub struct Minerd {
|
||||
/// PoW miner number of threads to use
|
||||
threads: usize,
|
||||
// Receiver to stop miner threads
|
||||
stop_signal: Receiver<()>,
|
||||
/// JSON-RPC connection tracker
|
||||
rpc_connections: Mutex<HashSet<StoppableTaskPtr>>,
|
||||
}
|
||||
|
||||
impl Minerd {
|
||||
pub fn new(threads: usize, stop_signal: Receiver<()>) -> Self {
|
||||
Self { threads, stop_signal, rpc_connections: Mutex::new(HashSet::new()) }
|
||||
}
|
||||
}
|
||||
|
||||
async_daemonize!(realmain);
|
||||
async fn realmain(args: Args, ex: Arc<Executor<'static>>) -> Result<()> {
|
||||
info!(target: "minerd", "Starting DarkFi Mining Daemon...");
|
||||
let (sender, recvr) = smol::channel::bounded(1);
|
||||
let minerd = Arc::new(Minerd::new(args.threads, recvr));
|
||||
|
||||
info!(target: "minerd", "Starting JSON-RPC server on {}", args.rpc_listen);
|
||||
let minerd_ = Arc::clone(&minerd);
|
||||
let rpc_task = StoppableTask::new();
|
||||
rpc_task.clone().start(
|
||||
listen_and_serve(args.rpc_listen, minerd.clone(), None, ex.clone()),
|
||||
|res| async move {
|
||||
match res {
|
||||
Ok(()) | Err(Error::RpcServerStopped) => minerd_.stop_connections().await,
|
||||
Err(e) => error!(target: "minerd", "Failed stopping JSON-RPC server: {}", e),
|
||||
}
|
||||
},
|
||||
Error::RpcServerStopped,
|
||||
ex.clone(),
|
||||
);
|
||||
|
||||
// Signal handling for graceful termination.
|
||||
let (signals_handler, signals_task) = SignalHandler::new(ex)?;
|
||||
signals_handler.wait_termination(signals_task).await?;
|
||||
info!(target: "minerd", "Caught termination signal, cleaning up and exiting");
|
||||
|
||||
info!(target: "minerd", "Stopping miner threads...");
|
||||
sender.send(()).await?;
|
||||
|
||||
info!(target: "minerd", "Stopping JSON-RPC server...");
|
||||
rpc_task.stop().await;
|
||||
|
||||
info!(target: "minerd", "Shut down successfully");
|
||||
Ok(())
|
||||
}
|
||||
106
script/research/minerd/src/rpc.rs
Normal file
106
script/research/minerd/src/rpc.rs
Normal file
@@ -0,0 +1,106 @@
|
||||
/* This file is part of DarkFi (https://dark.fi)
|
||||
*
|
||||
* Copyright (C) 2020-2024 Dyne.org foundation
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program 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 Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
use std::collections::HashSet;
|
||||
|
||||
use log::{debug, error, info};
|
||||
use num_bigint::BigUint;
|
||||
use smol::lock::MutexGuard;
|
||||
|
||||
use darkfi::{
|
||||
blockchain::BlockInfo,
|
||||
rpc::{
|
||||
jsonrpc::{ErrorCode, JsonError, JsonRequest, JsonResponse, JsonResult},
|
||||
server::RequestHandler,
|
||||
util::JsonValue,
|
||||
},
|
||||
system::StoppableTaskPtr,
|
||||
util::encoding::base64,
|
||||
validator::pow::mine_block,
|
||||
};
|
||||
use darkfi_sdk::num_traits::Num;
|
||||
use darkfi_serial::{async_trait, deserialize, serialize};
|
||||
|
||||
use crate::{
|
||||
error::{server_error, RpcError},
|
||||
Minerd,
|
||||
};
|
||||
|
||||
#[async_trait]
|
||||
impl RequestHandler for Minerd {
|
||||
async fn handle_request(&self, req: JsonRequest) -> JsonResult {
|
||||
debug!(target: "minerd::rpc", "--> {}", req.stringify().unwrap());
|
||||
|
||||
match req.method.as_str() {
|
||||
"ping" => self.pong(req.id, req.params).await,
|
||||
"mine" => self.mine(req.id, req.params).await,
|
||||
_ => JsonError::new(ErrorCode::MethodNotFound, None, req.id).into(),
|
||||
}
|
||||
}
|
||||
|
||||
async fn connections_mut(&self) -> MutexGuard<'_, HashSet<StoppableTaskPtr>> {
|
||||
self.rpc_connections.lock().await
|
||||
}
|
||||
}
|
||||
|
||||
impl Minerd {
|
||||
// RPCAPI:
|
||||
// Mine provided block for requested mine target, and return the corresponding nonce value.
|
||||
//
|
||||
// --> {"jsonrpc": "2.0", "method": "mine", "params": ["target", "block"], "id": 42}
|
||||
// --> {"jsonrpc": "2.0", "result": "nonce", "id": 42}
|
||||
async fn mine(&self, id: u16, params: JsonValue) -> JsonResult {
|
||||
// Verify parameters
|
||||
if !params.is_array() {
|
||||
return JsonError::new(ErrorCode::InvalidParams, None, id).into()
|
||||
}
|
||||
let params = params.get::<Vec<JsonValue>>().unwrap();
|
||||
if params.len() != 2 || !params[0].is_string() || !params[1].is_string() {
|
||||
return JsonError::new(ErrorCode::InvalidParams, None, id).into()
|
||||
}
|
||||
|
||||
// Parse parameters
|
||||
let Ok(target) = BigUint::from_str_radix(params[0].get::<String>().unwrap(), 10) else {
|
||||
error!(target: "minerd::rpc", "Failed to parse target");
|
||||
return server_error(RpcError::TargetParseError, id, None)
|
||||
};
|
||||
let Some(block_bytes) = base64::decode(params[1].get::<String>().unwrap()) else {
|
||||
error!(target: "minerd::rpc", "Failed to parse block bytes");
|
||||
return server_error(RpcError::BlockParseError, id, None)
|
||||
};
|
||||
let Ok(mut block) = deserialize::<BlockInfo>(&block_bytes) else {
|
||||
error!(target: "minerd::rpc", "Failed to parse block");
|
||||
return server_error(RpcError::BlockParseError, id, None)
|
||||
};
|
||||
|
||||
// Mine provided block
|
||||
let Ok(block_hash) = block.hash() else {
|
||||
error!(target: "minerd::rpc", "Failed to hash block");
|
||||
return server_error(RpcError::HashingFailed, id, None)
|
||||
};
|
||||
info!(target: "minerd::rpc", "Mining block {} for target: {}", block_hash, target);
|
||||
if let Err(e) = mine_block(&target, &mut block, self.threads, &self.stop_signal) {
|
||||
error!(target: "minerd::rpc", "Failed mining block {} with error: {}", block_hash, e);
|
||||
return server_error(RpcError::MiningFailed, id, None)
|
||||
}
|
||||
|
||||
// Return block nonce
|
||||
let nonce = base64::encode(&serialize(&block.header.nonce));
|
||||
JsonResponse::new(JsonValue::String(nonce), id).into()
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user