/* 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 .
*/
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::{sleep, StoppableTaskPtr},
util::encoding::base64,
validator::pow::mine_block,
};
use darkfi_sdk::num_traits::Num;
use darkfi_serial::{async_trait, deserialize_async};
use crate::{
error::{server_error, RpcError},
MinerNode,
};
#[async_trait]
impl RequestHandler for MinerNode {
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,
"abort" => self.abort(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<'life0, HashSet> {
self.rpc_connections.lock().await
}
}
impl MinerNode {
// RPCAPI:
// Signals miner daemon to abort mining pending request.
// Returns `true` on success.
//
// --> {"jsonrpc": "2.0", "method": "abort", "params": [], "id": 42}
// <-- {"jsonrpc": "2.0", "result": "true", "id": 42}
async fn abort(&self, id: u16, _params: JsonValue) -> JsonResult {
if let Some(e) = self.abort_pending(id).await {
return e
};
JsonResponse::new(JsonValue::Boolean(true), id).into()
}
// 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::>().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::().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::().unwrap()) else {
error!(target: "minerd::rpc", "Failed to parse block bytes");
return server_error(RpcError::BlockParseError, id, None)
};
let Ok(mut block) = deserialize_async::(&block_bytes).await else {
error!(target: "minerd::rpc", "Failed to parse block");
return server_error(RpcError::BlockParseError, id, None)
};
let block_hash = block.hash();
info!(target: "minerd::rpc", "Received request to mine block {} for target: {}", block_hash, target);
// Check if another request is being processed
if let Some(e) = self.abort_pending(id).await {
return e
};
// Mine provided block
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.clone()) {
error!(target: "minerd::rpc", "Failed mining block {} with error: {}", block_hash, e);
return server_error(RpcError::MiningFailed, id, None)
}
// Return block nonce
JsonResponse::new(JsonValue::Number(block.header.nonce as f64), id).into()
}
/// Auxiliary function to abort pending request.
async fn abort_pending(&self, id: u16) -> Option {
// Check if a pending request is being processed
info!(target: "minerd::rpc", "Checking if a pending request is being processed...");
if self.stop_signal.receiver_count() == 0 {
info!(target: "minerd::rpc", "No pending requests!");
return None
}
info!(target: "minerd::rpc", "Pending request is in progress, sending stop signal...");
// Send stop signal to worker
if self.sender.send(()).await.is_err() {
error!(target: "minerd::rpc", "Failed to stop pending request");
return Some(server_error(RpcError::StopFailed, id, None))
}
// Wait for worker to terminate
info!(target: "minerd::rpc", "Waiting for request to terminate...");
while self.stop_signal.receiver_count() > 1 {
sleep(1).await;
}
info!(target: "minerd::rpc", "Pending request terminated!");
// Consume channel item so its empty again
if self.stop_signal.recv().await.is_err() {
error!(target: "minerd::rpc", "Failed to cleanup stop signal channel");
return Some(server_error(RpcError::StopFailed, id, None))
}
None
}
}