/ bin / minerd / src / rpc.rs
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  
 19  use std::collections::HashSet;
 20  
 21  use num_bigint::BigUint;
 22  use smol::lock::MutexGuard;
 23  use tracing::{debug, error, info};
 24  
 25  use 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  };
 36  use darkfi_sdk::num_traits::Num;
 37  use darkfi_serial::{async_trait, deserialize_async};
 38  
 39  use crate::{
 40      error::{server_error, RpcError},
 41      MinerNode,
 42  };
 43  
 44  #[async_trait]
 45  impl 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  
 62  impl 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 {block_hash} for target: {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 {block_hash} for target: {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 {block_hash} with error: {e}");
122              return server_error(RpcError::MiningFailed, id, None)
123          }
124          info!(target: "minerd::rpc", "Mined block {block_hash} with nonce: {}", 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  }