minerd/
rpc.rs

1/* This file is part of DarkFi (https://dark.fi)
2 *
3 * Copyright (C) 2020-2025 Dyne.org foundation
4 *
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU Affero General Public License as
7 * published by the Free Software Foundation, either version 3 of the
8 * License, or (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 * GNU Affero General Public License for more details.
14 *
15 * You should have received a copy of the GNU Affero General Public License
16 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
17 */
18
19use std::collections::HashSet;
20
21use log::{debug, error, info};
22use num_bigint::BigUint;
23use smol::lock::MutexGuard;
24
25use darkfi::{
26    blockchain::BlockInfo,
27    rpc::{
28        jsonrpc::{ErrorCode, JsonError, JsonRequest, JsonResponse, JsonResult},
29        server::RequestHandler,
30        util::JsonValue,
31    },
32    system::{sleep, StoppableTaskPtr},
33    util::encoding::base64,
34    validator::pow::mine_block,
35};
36use darkfi_sdk::num_traits::Num;
37use darkfi_serial::{async_trait, deserialize_async};
38
39use crate::{
40    error::{server_error, RpcError},
41    MinerNode,
42};
43
44#[async_trait]
45impl RequestHandler<()> for MinerNode {
46    async fn handle_request(&self, req: JsonRequest) -> JsonResult {
47        debug!(target: "minerd::rpc", "--> {}", req.stringify().unwrap());
48
49        match req.method.as_str() {
50            "ping" => self.pong(req.id, req.params).await,
51            "abort" => self.abort(req.id, req.params).await,
52            "mine" => self.mine(req.id, req.params).await,
53            _ => JsonError::new(ErrorCode::MethodNotFound, None, req.id).into(),
54        }
55    }
56
57    async fn connections_mut(&self) -> MutexGuard<'life0, HashSet<StoppableTaskPtr>> {
58        self.rpc_connections.lock().await
59    }
60}
61
62impl MinerNode {
63    // RPCAPI:
64    // Signals miner daemon to abort mining pending request.
65    // Returns `true` on success.
66    //
67    // --> {"jsonrpc": "2.0", "method": "abort", "params": [], "id": 42}
68    // <-- {"jsonrpc": "2.0", "result": "true", "id": 42}
69    async fn abort(&self, id: u16, _params: JsonValue) -> JsonResult {
70        if let Some(e) = self.abort_pending(id).await {
71            return e
72        };
73        JsonResponse::new(JsonValue::Boolean(true), id).into()
74    }
75
76    // RPCAPI:
77    // Mine provided block for requested mine target, and return the corresponding nonce value.
78    //
79    // --> {"jsonrpc": "2.0", "method": "mine", "params": ["target", "block"], "id": 42}
80    // --> {"jsonrpc": "2.0", "result": "nonce", "id": 42}
81    async fn mine(&self, id: u16, params: JsonValue) -> JsonResult {
82        // Verify parameters
83        if !params.is_array() {
84            return JsonError::new(ErrorCode::InvalidParams, None, id).into()
85        }
86        let params = params.get::<Vec<JsonValue>>().unwrap();
87        if params.len() != 2 || !params[0].is_string() || !params[1].is_string() {
88            return JsonError::new(ErrorCode::InvalidParams, None, id).into()
89        }
90
91        // Parse parameters
92        let Ok(target) = BigUint::from_str_radix(params[0].get::<String>().unwrap(), 10) else {
93            error!(target: "minerd::rpc", "Failed to parse target");
94            return server_error(RpcError::TargetParseError, id, None)
95        };
96        let Some(block_bytes) = base64::decode(params[1].get::<String>().unwrap()) else {
97            error!(target: "minerd::rpc", "Failed to parse block bytes");
98            return server_error(RpcError::BlockParseError, id, None)
99        };
100        let Ok(mut block) = deserialize_async::<BlockInfo>(&block_bytes).await else {
101            error!(target: "minerd::rpc", "Failed to parse block");
102            return server_error(RpcError::BlockParseError, id, None)
103        };
104        let block_hash = block.hash();
105        info!(target: "minerd::rpc", "Received request to mine block {} for target: {}", block_hash, target);
106
107        // If we have a requested mining height, we'll keep dropping here.
108        if self.stop_at_height > 0 && block.header.height >= self.stop_at_height {
109            info!(target: "minerd::rpc", "Reached requested mining height {}", self.stop_at_height);
110            return server_error(RpcError::MiningFailed, id, None)
111        }
112
113        // Check if another request is being processed
114        if let Some(e) = self.abort_pending(id).await {
115            return e
116        };
117
118        // Mine provided block
119        info!(target: "minerd::rpc", "Mining block {} for target: {}", block_hash, target);
120        if let Err(e) = mine_block(&target, &mut block, self.threads, &self.stop_signal.clone()) {
121            error!(target: "minerd::rpc", "Failed mining block {} with error: {}", block_hash, e);
122            return server_error(RpcError::MiningFailed, id, None)
123        }
124        info!(target: "minerd::rpc", "Mined block {} with nonce: {}", block_hash, block.header.nonce);
125
126        // Return block nonce
127        JsonResponse::new(JsonValue::Number(block.header.nonce as f64), id).into()
128    }
129
130    /// Auxiliary function to abort pending request.
131    async fn abort_pending(&self, id: u16) -> Option<JsonResult> {
132        // Check if a pending request is being processed
133        info!(target: "minerd::rpc", "Checking if a pending request is being processed...");
134        if self.stop_signal.receiver_count() <= 1 {
135            info!(target: "minerd::rpc", "No pending requests!");
136            return None
137        }
138
139        info!(target: "minerd::rpc", "Pending request is in progress, sending stop signal...");
140        // Send stop signal to worker
141        if self.sender.send(()).await.is_err() {
142            error!(target: "minerd::rpc", "Failed to stop pending request");
143            return Some(server_error(RpcError::StopFailed, id, None))
144        }
145
146        // Wait for worker to terminate
147        info!(target: "minerd::rpc", "Waiting for request to terminate...");
148        while self.stop_signal.receiver_count() > 1 {
149            sleep(1).await;
150        }
151        info!(target: "minerd::rpc", "Pending request terminated!");
152
153        // Consume channel item so its empty again
154        if self.stop_signal.recv().await.is_err() {
155            error!(target: "minerd::rpc", "Failed to cleanup stop signal channel");
156            return Some(server_error(RpcError::StopFailed, id, None))
157        }
158
159        None
160    }
161}