| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361 |
- /* This file is part of DarkFi (https://dark.fi)
- *
- * Copyright (C) 2020-2025 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::sync::Arc;
- use num_bigint::BigUint;
- use randomx::RandomXVM;
- use tracing::{debug, error, info};
- use url::Url;
- use darkfi::{
- blockchain::{Header, HeaderHash},
- rpc::{client::RpcClient, jsonrpc::JsonRequest, util::JsonValue},
- system::{sleep, ExecutorPtr, StoppableTask},
- util::encoding::base64,
- validator::pow::{generate_mining_vms, get_mining_flags, mine_block},
- Error, Result,
- };
- use darkfi_serial::deserialize_async;
- use crate::{MinerNode, MinerNodePtr};
- /// Structure to hold a JSON-RPC client and its config,
- /// so we can recreate it in case of an error.
- pub struct DarkfidRpcClient {
- endpoint: Url,
- ex: ExecutorPtr,
- client: Option<RpcClient>,
- }
- impl DarkfidRpcClient {
- pub async fn new(endpoint: Url, ex: ExecutorPtr) -> Self {
- let client = RpcClient::new(endpoint.clone(), ex.clone()).await.ok();
- Self { endpoint, ex, client }
- }
- /// Stop the client.
- pub async fn stop(&self) {
- if let Some(ref client) = self.client {
- client.stop().await
- }
- }
- }
- impl MinerNode {
- /// Auxiliary function to request configured darkfid daemon for
- /// its current mining RandomX key.
- async fn randomx_key(&self) -> Result<HeaderHash> {
- loop {
- debug!(target: "minerd::rpc::randomx_key", "Executing mining RandomX key request to darkfid...");
- let params = match self
- .darkfid_daemon_request(
- "miner.get_current_mining_randomx_key",
- &JsonValue::Array(vec![]),
- )
- .await
- {
- Ok(params) => params,
- Err(e) => {
- error!(target: "minerd::rpc::randomx_key", "darkfid request failed: {e}");
- self.sleep().await?;
- continue
- }
- };
- debug!(target: "minerd::rpc::randomx_key", "Got reply: {params:?}");
- // Verify response parameters
- if !params.is_array() {
- error!(target: "minerd::rpc::randomx_key", "darkfid responded with invalid params: {params:?}");
- self.sleep().await?;
- continue
- }
- let params = params.get::<Vec<JsonValue>>().unwrap();
- if params.is_empty() {
- debug!(target: "minerd::rpc::randomx_key", "darkfid response is empty");
- self.sleep().await?;
- continue
- }
- if params.len() != 1 || !params[0].is_string() {
- error!(target: "minerd::rpc::randomx_key", "darkfid responded with invalid params: {params:?}");
- self.sleep().await?;
- continue
- }
- // Parse parameters
- let Some(randomx_key_bytes) = base64::decode(params[0].get::<String>().unwrap()) else {
- error!(target: "minerd::rpc::randomx_key", "Failed to parse RandomX key bytes");
- self.sleep().await?;
- continue
- };
- let Ok(randomx_key) = deserialize_async::<HeaderHash>(&randomx_key_bytes).await else {
- error!(target: "minerd::rpc::randomx_key", "Failed to parse RandomX key");
- self.sleep().await?;
- continue
- };
- return Ok(randomx_key)
- }
- }
- /// Auxiliary function to poll configured darkfid daemon for a new
- /// mining job.
- async fn poll(&self, header: &str) -> Result<(HeaderHash, BigUint, Header)> {
- loop {
- debug!(target: "minerd::rpc::poll", "Executing poll request to darkfid...");
- let mut request_params = self.config.wallet_config.clone();
- request_params.insert(String::from("header"), JsonValue::String(String::from(header)));
- let params = match self
- .darkfid_daemon_request("miner.get_header", &JsonValue::from(request_params))
- .await
- {
- Ok(params) => params,
- Err(e) => {
- error!(target: "minerd::rpc::poll", "darkfid poll failed: {e}");
- self.sleep().await?;
- continue
- }
- };
- debug!(target: "minerd::rpc::poll", "Got reply: {params:?}");
- // Verify response parameters
- if !params.is_array() {
- error!(target: "minerd::rpc::poll", "darkfid responded with invalid params: {params:?}");
- self.sleep().await?;
- continue
- }
- let params = params.get::<Vec<JsonValue>>().unwrap();
- if params.is_empty() {
- debug!(target: "minerd::rpc::poll", "darkfid response is empty");
- self.sleep().await?;
- continue
- }
- if params.len() != 3 ||
- !params[0].is_string() ||
- !params[1].is_string() ||
- !params[2].is_string()
- {
- error!(target: "minerd::rpc::poll", "darkfid responded with invalid params: {params:?}");
- self.sleep().await?;
- continue
- }
- // Parse parameters
- let Some(randomx_key_bytes) = base64::decode(params[0].get::<String>().unwrap()) else {
- error!(target: "minerd::rpc::poll", "Failed to parse RandomX key bytes");
- self.sleep().await?;
- continue
- };
- let Ok(randomx_key) = deserialize_async::<HeaderHash>(&randomx_key_bytes).await else {
- error!(target: "minerd::rpc::poll", "Failed to parse RandomX key");
- self.sleep().await?;
- continue
- };
- let Some(target_bytes) = base64::decode(params[1].get::<String>().unwrap()) else {
- error!(target: "minerd::rpc::poll", "Failed to parse target bytes");
- self.sleep().await?;
- continue
- };
- let target = BigUint::from_bytes_le(&target_bytes);
- let Some(header_bytes) = base64::decode(params[2].get::<String>().unwrap()) else {
- error!(target: "minerd::rpc::poll", "Failed to parse header bytes");
- self.sleep().await?;
- continue
- };
- let Ok(header) = deserialize_async::<Header>(&header_bytes).await else {
- error!(target: "minerd::rpc::poll", "Failed to parse header");
- self.sleep().await?;
- continue
- };
- return Ok((randomx_key, target, header))
- }
- }
- /// Auxiliary function to submit a mining solution to configured
- /// darkfid daemon.
- async fn submit(&self, nonce: f64) -> String {
- debug!(target: "minerd::rpc::submit", "Executing submit request to darkfid...");
- let mut request_params = self.config.wallet_config.clone();
- request_params.insert(String::from("nonce"), JsonValue::Number(nonce));
- let result = match self
- .darkfid_daemon_request("miner.submit_solution", &JsonValue::from(request_params))
- .await
- {
- Ok(result) => result,
- Err(e) => return format!("darkfid submit failed: {e}"),
- };
- debug!(target: "minerd::rpc::submit", "Got reply: {result:?}");
- // Parse response
- match result.get::<String>() {
- Some(result) => result.clone(),
- None => format!("darkfid responded with invalid params: {result:?}"),
- }
- }
- /// Auxiliary function to execute a request towards the configured
- /// darkfid daemon JSON-RPC endpoint.
- async fn darkfid_daemon_request(&self, method: &str, params: &JsonValue) -> Result<JsonValue> {
- let mut lock = self.rpc_client.write().await;
- let req = JsonRequest::new(method, params.clone());
- // Check the client is initialized
- if let Some(ref client) = lock.client {
- // Execute request
- if let Ok(rep) = client.request(req.clone()).await {
- drop(lock);
- return Ok(rep);
- }
- }
- // Reset the rpc client in case of an error and try again
- let client = RpcClient::new(lock.endpoint.clone(), lock.ex.clone()).await?;
- let rep = client.request(req).await?;
- lock.client = Some(client);
- drop(lock);
- Ok(rep)
- }
- /// Auxiliary function to stop current JSON-RPC client, if its
- /// initialized.
- pub async fn stop_rpc_client(&self) {
- self.rpc_client.read().await.stop().await;
- }
- /// Auxiliary function to sleep for configured polling rate time.
- async fn sleep(&self) -> Result<()> {
- // Check if stop signal is received
- if self.mining_channel.1.is_full() {
- debug!(target: "minerd::rpc::sleep", "Stop signal received, exiting polling task");
- return Err(Error::DetachedTaskStopped);
- }
- debug!(target: "minerd::rpc::sleep", "Sleeping for {} until next poll...", self.config.polling_rate);
- sleep(self.config.polling_rate).await;
- Ok(())
- }
- }
- /// Async task to poll darkfid for new mining jobs. Once a new job is
- /// received, spawns a mining task in the background.
- pub async fn polling_task(miner: MinerNodePtr, ex: ExecutorPtr) -> Result<()> {
- // Cache current RandomX key and its VMs
- let mut current_randomx_key = miner.randomx_key().await?;
- info!(target: "minerd::rpc::mining_task", "Initializing {} mining VMs for key: {current_randomx_key}", miner.config.threads);
- let mining_flags =
- get_mining_flags(miner.config.fast_mode, miner.config.large_pages, miner.config.secure);
- let mut _current_vms = Arc::new(generate_mining_vms(
- mining_flags,
- ¤t_randomx_key,
- miner.config.threads,
- &miner.mining_channel.1.clone(),
- )?);
- // Use the dummy Header on first poll
- let mut current_job = current_randomx_key.to_string();
- loop {
- // Poll darkfid for a mining job
- let (randomx_key, target, header) = miner.poll(¤t_job).await?;
- let header_hash = header.hash().to_string();
- debug!(target: "minerd::rpc::polling_task", "Received job:");
- debug!(target: "minerd::rpc::polling_task", "\tRandomX key - {randomx_key}");
- debug!(target: "minerd::rpc::polling_task", "\tTarget - {target}");
- debug!(target: "minerd::rpc::polling_task", "\tHeader - {header_hash}");
- // Check if we are already processing this job
- if header_hash == current_job {
- debug!(target: "minerd::rpc::polling_task", "Already received job, skipping...");
- miner.sleep().await?;
- continue
- }
- // Check if we reached the stop height
- if miner.config.stop_at_height > 0 && header.height > miner.config.stop_at_height {
- info!(target: "minerd::rpc::polling_task", "Reached requested mining height: {}", miner.config.stop_at_height);
- info!(target: "minerd::rpc::polling_task", "Daemon can be safely terminated now!");
- break
- }
- info!(target: "minerd::rpc::polling_task", "Received new job to mine block header {header_hash} with key {randomx_key} for target: 0x{target:064x}");
- // Abord pending mining job
- miner.abort_mining().await;
- // Check if the current RandomX key has changed
- if randomx_key != current_randomx_key {
- // Drop previous VMs
- _current_vms = Arc::new(vec![]);
- // Generate the RandomX VMs for the key
- info!(target: "minerd::rpc::mining_task", "Initializing {} mining VMs for key: {randomx_key}", miner.config.threads);
- _current_vms = Arc::new(generate_mining_vms(
- mining_flags,
- &randomx_key,
- miner.config.threads,
- &miner.mining_channel.1.clone(),
- )?);
- current_randomx_key = randomx_key;
- }
- // Detach mining task
- StoppableTask::new().start(
- mining_task(miner.clone(), _current_vms.clone(), target, header),
- |res| async {
- match res {
- Ok(()) | Err(Error::DetachedTaskStopped) => { /* Do nothing */ }
- Err(e) => error!(target: "minerd::rpc::polling_task", "Failed starting mining task: {e}"),
- }
- },
- Error::DetachedTaskStopped,
- ex.clone(),
- );
- // Update current job
- current_job = header_hash;
- // Sleep until next poll
- miner.sleep().await?;
- }
- Ok(())
- }
- /// Async task to mine provided header and submit solution to darkfid.
- async fn mining_task(
- miner: MinerNodePtr,
- vms: Arc<Vec<Arc<RandomXVM>>>,
- target: BigUint,
- mut header: Header,
- ) -> Result<()> {
- // Mine provided block header
- let header_hash = header.hash().to_string();
- info!(target: "minerd::rpc::mining_task", "Mining block header {header_hash} for target: 0x{target:064x}");
- if let Err(e) = mine_block(&vms, &target, &mut header, &miner.mining_channel.1.clone()) {
- error!(target: "minerd::rpc::mining_task", "Failed mining block header {header_hash} with error: {e}");
- return Err(Error::DetachedTaskStopped)
- }
- info!(target: "minerd::rpc::mining_task", "Mined block header {header_hash} with nonce: {}", header.nonce);
- info!(target: "minerd::rpc::mining_task", "Mined block header hash: {}", header.hash());
- // Submit solution to darkfid
- info!(target: "minerd::rpc::submit", "Submitting solution to darkfid...");
- let result = miner.submit(header.nonce as f64).await;
- info!(target: "minerd::rpc::submit", "Submition result: {result}");
- Ok(())
- }
|