script/research/minerd: miner daemon skeleton

This commit is contained in:
aggstam
2024-01-11 16:35:11 +02:00
parent dc882d256b
commit a27725b58f
6 changed files with 331 additions and 0 deletions

3
script/research/minerd/.gitignore vendored Normal file
View File

@@ -0,0 +1,3 @@
/target
Cargo.lock
rustfmt.toml

View 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"}

View 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"

View 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()
}

View 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(())
}

View 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()
}
}