diff --git a/Cargo.lock b/Cargo.lock index 75c9d773e..46005dd66 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4312,6 +4312,7 @@ dependencies = [ name = "minerd" version = "0.5.0" dependencies = [ + "bs58", "darkfi", "darkfi-sdk", "darkfi-serial", @@ -4324,6 +4325,7 @@ dependencies = [ "structopt", "structopt-toml", "tinyjson", + "toml 0.9.8", "tracing", "tracing-appender", "tracing-subscriber", diff --git a/bin/darkfid/darkfid_config.toml b/bin/darkfid/darkfid_config.toml index 0f2d1e876..a35e8aad0 100644 --- a/bin/darkfid/darkfid_config.toml +++ b/bin/darkfid/darkfid_config.toml @@ -17,22 +17,9 @@ database = "~/.local/share/darkfi/darkfid/testnet" # Confirmation threshold, denominated by number of blocks threshold = 6 -# minerd JSON-RPC endpoint -#minerd_endpoint = "tcp://127.0.0.1:28467" - # PoW block production target, in seconds pow_target = 120 -# Wallet address to receive mining rewards. -#recipient = "YOUR_WALLET_ADDRESS_HERE" - -# Optional contract spend hook to use in the mining reward -#spend_hook = "YOUR_SPEND_HOOK_HERE" - -# Optional contract user data to use in the mining reward. -# This is not arbitrary data. -#user_data = "YOUR_USER_DATA_HERE" - # Skip syncing process and start node right away skip_sync = false @@ -156,22 +143,9 @@ database = "~/.local/share/darkfi/darkfid/mainnet" # Confirmation threshold, denominated by number of blocks threshold = 11 -# minerd JSON-RPC endpoint -#minerd_endpoint = "tcp://127.0.0.1:28467" - # PoW block production target, in seconds pow_target = 120 -# Wallet address to receive mining rewards. -#recipient = "YOUR_WALLET_ADDRESS_HERE" - -# Optional contract spend hook to use in the mining reward -#spend_hook = "YOUR_SPEND_HOOK_HERE" - -# Optional contract user data to use in the mining reward. -# This is not arbitrary data. -#user_data = "YOUR_USER_DATA_HERE" - # Skip syncing process and start node right away skip_sync = false @@ -297,27 +271,12 @@ database = "~/.local/share/darkfi/darkfid/localnet" # Confirmation threshold, denominated by number of blocks threshold = 3 -# minerd JSON-RPC endpoint -minerd_endpoint = "tcp://127.0.0.1:28467" - # PoW block production target, in seconds pow_target = 10 # Optional fixed PoW difficulty, used for testing pow_fixed_difficulty = 1 -# Wallet address to receive mining rewards. -# This is a dummy one so the miner can start, -# replace with your own one. -recipient = "5ZHfYpt4mpJcwBNxfEyxLzeFJUEeoePs5NQ5jVEgHrMf" - -# Optional contract spend hook to use in the mining reward -#spend_hook = "YOUR_SPEND_HOOK_HERE" - -# Optional contract user data to use in the mining reward. -# This is not arbitrary data. -#user_data = "YOUR_USER_DATA_HERE" - # Skip syncing process and start node right away skip_sync = true diff --git a/bin/darkfid/src/error.rs b/bin/darkfid/src/error.rs index ecc4d78cc..968e5e414 100644 --- a/bin/darkfid/src/error.rs +++ b/bin/darkfid/src/error.rs @@ -37,8 +37,16 @@ pub enum RpcError { ContractStateNotFound = -32201, ContractStateKeyNotFound = -32202, - // Misc errors - PingFailed = -32300, + // Miner errors + MinerMissingHeader = -32300, + MinerInvalidHeader = -32301, + MinerMissingRecipient = -32302, + MinerInvalidRecipient = -32303, + MinerInvalidSpendHook = -32304, + MinerInvalidUserData = -32305, + MinerMissingNonce = -32306, + MinerInvalidNonce = -32307, + MinerUnknownJob = -32308, } fn to_tuple(e: RpcError) -> (i32, String) { @@ -55,8 +63,16 @@ fn to_tuple(e: RpcError) -> (i32, String) { RpcError::ContractZkasDbNotFound => "zkas database not found for given contract", RpcError::ContractStateNotFound => "Records not found for given contract state", RpcError::ContractStateKeyNotFound => "Value not found for given contract state key", - // Misc errors - RpcError::PingFailed => "Miner daemon ping error", + // Miner errors + RpcError::MinerMissingHeader => "Request is missing the Header hash", + RpcError::MinerInvalidHeader => "Request Header hash is invalid", + RpcError::MinerMissingRecipient => "Request is missing the recipient wallet address", + RpcError::MinerInvalidRecipient => "Request recipient wallet address is invalid", + RpcError::MinerInvalidSpendHook => "Request spend hook is invalid", + RpcError::MinerInvalidUserData => "Request user data is invalid", + RpcError::MinerMissingNonce => "Request is missing the Header nonce", + RpcError::MinerInvalidNonce => "Request Header nonce is invalid", + RpcError::MinerUnknownJob => "Request job is unknown", }; (e as i32, msg.to_string()) diff --git a/bin/darkfid/src/lib.rs b/bin/darkfid/src/lib.rs index 5a6460af3..4833e7c75 100644 --- a/bin/darkfid/src/lib.rs +++ b/bin/darkfid/src/lib.rs @@ -22,8 +22,7 @@ use std::{ }; use smol::lock::Mutex; -use tracing::{debug, error, info, warn}; -use url::Url; +use tracing::{debug, error, info}; use darkfi::{ blockchain::BlockInfo, @@ -52,9 +51,11 @@ use error::{server_error, RpcError}; /// JSON-RPC requests handler and methods mod rpc; -use rpc::{DefaultRpcHandler, MinerRpcClient, MmRpcHandler}; +use rpc::{DefaultRpcHandler, MmRpcHandler}; mod rpc_blockchain; +mod rpc_miner; mod rpc_tx; +use rpc_miner::BlockTemplate; mod rpc_xmr; /// Validator async tasks @@ -78,14 +79,14 @@ pub struct DarkfiNode { txs_batch_size: usize, /// A map of various subscribers exporting live info from the blockchain subscribers: HashMap<&'static str, JsonSubscriber>, - /// JSON-RPC connection tracker - rpc_connections: Mutex>, - /// JSON-RPC client to execute requests to the miner daemon - rpc_client: Option>, - /// HTTP JSON-RPC connection tracker - mm_rpc_connections: Mutex>, + /// Native mining block templates + blocktemplates: Mutex, BlockTemplate>>, /// Merge mining block templates mm_blocktemplates: Mutex, (BlockInfo, f64, SecretKey)>>, + /// JSON-RPC connection tracker + rpc_connections: Mutex>, + /// HTTP JSON-RPC connection tracker + mm_rpc_connections: Mutex>, /// PowRewardV1 ZK data powrewardv1_zk: PowRewardV1Zk, } @@ -96,7 +97,6 @@ impl DarkfiNode { validator: ValidatorPtr, txs_batch_size: usize, subscribers: HashMap<&'static str, JsonSubscriber>, - rpc_client: Option>, ) -> Result { let powrewardv1_zk = PowRewardV1Zk::new(validator.clone())?; @@ -105,15 +105,15 @@ impl DarkfiNode { validator, txs_batch_size, subscribers, - rpc_connections: Mutex::new(HashSet::new()), - rpc_client, - mm_rpc_connections: Mutex::new(HashSet::new()), + blocktemplates: Mutex::new(HashMap::new()), mm_blocktemplates: Mutex::new(HashMap::new()), + rpc_connections: Mutex::new(HashSet::new()), + mm_rpc_connections: Mutex::new(HashSet::new()), powrewardv1_zk, })) } - /// Grab best current fork + /// Auxiliary function to grab best current fork. pub async fn best_current_fork(&self) -> Result { let forks = self.validator.consensus.forks.read().await; let index = best_fork_index(&forks)?; @@ -173,7 +173,6 @@ impl Darkfid { sled_db: &sled_overlay::sled::Db, config: &ValidatorConfig, net_settings: &Settings, - minerd_endpoint: &Option, txs_batch_size: &Option, ex: &ExecutorPtr, ) -> Result { @@ -203,17 +202,8 @@ impl Darkfid { subscribers.insert("proposals", JsonSubscriber::new("blockchain.subscribe_proposals")); subscribers.insert("dnet", JsonSubscriber::new("dnet.subscribe_events")); - // Initialize JSON-RPC client to perform requests to minerd - let rpc_client = match minerd_endpoint { - Some(endpoint) => { - Some(Mutex::new(MinerRpcClient::new(endpoint.clone(), ex.clone()).await)) - } - None => None, - }; - // Initialize node - let node = DarkfiNode::new(p2p_handler, validator, txs_batch_size, subscribers, rpc_client) - .await?; + let node = DarkfiNode::new(p2p_handler, validator, txs_batch_size, subscribers).await?; // Generate the background tasks let dnet_task = StoppableTask::new(); @@ -237,13 +227,6 @@ impl Darkfid { ) -> Result<()> { info!(target: "darkfid::Darkfid::start", "Starting Darkfi daemon..."); - // Pinging minerd daemon to verify it listens - if self.node.rpc_client.is_some() { - if let Err(e) = self.node.ping_miner_daemon().await { - warn!(target: "darkfid::Darkfid::start", "Failed to ping miner daemon: {e}"); - } - } - // Start the `dnet` task info!(target: "darkfid::Darkfid::start", "Starting dnet subs task"); let dnet_sub_ = self.node.subscribers.get("dnet").unwrap().clone(); @@ -366,12 +349,6 @@ impl Darkfid { let flushed_bytes = self.node.validator.blockchain.sled_db.flush_async().await?; info!(target: "darkfid::Darkfid::stop", "Flushed {flushed_bytes} bytes"); - // Close the JSON-RPC client, if it was initialized - if let Some(ref rpc_client) = self.node.rpc_client { - info!(target: "darkfid::Darkfid::stop", "Stopping JSON-RPC client..."); - rpc_client.lock().await.stop().await; - }; - info!(target: "darkfid::Darkfid::stop", "Darkfi daemon terminated successfully!"); Ok(()) } diff --git a/bin/darkfid/src/main.rs b/bin/darkfid/src/main.rs index 4b4f5cc6d..27c87a8b9 100644 --- a/bin/darkfid/src/main.rs +++ b/bin/darkfid/src/main.rs @@ -21,7 +21,6 @@ use std::sync::Arc; use smol::{fs::read_to_string, stream::StreamExt}; use structopt_toml::{serde::Deserialize, structopt::StructOpt, StructOptToml}; use tracing::{debug, error, info}; -use url::Url; use darkfi::{ async_daemonize, @@ -99,14 +98,6 @@ pub struct BlockchainNetwork { /// Confirmation threshold, denominated by number of blocks threshold: usize, - #[structopt(long)] - /// minerd JSON-RPC endpoint - minerd_endpoint: Option, - - #[structopt(skip)] - /// Optional JSON-RPC settings for p2pool merge mining requests - mm_rpc: Option, - #[structopt(long, default_value = "120")] /// PoW block production target, in seconds pow_target: u32, @@ -115,19 +106,6 @@ pub struct BlockchainNetwork { /// Optional fixed PoW difficulty, used for testing pow_fixed_difficulty: Option, - #[structopt(long)] - /// Wallet address to receive mining rewards - recipient: Option, - - #[structopt(long)] - /// Optional contract spend hook to use in the mining reward - spend_hook: Option, - - #[structopt(long)] - /// Optional contract user data to use in the mining reward. - /// This is not arbitrary data. - user_data: Option, - #[structopt(long)] /// Skip syncing process and start node right away skip_sync: bool, @@ -159,6 +137,10 @@ pub struct BlockchainNetwork { #[structopt(flatten)] /// JSON-RPC settings rpc: RpcSettingsOpt, + + #[structopt(skip)] + /// Optional JSON-RPC settings for p2pool merge mining requests + mm_rpc: Option, } async_daemonize!(realmain); @@ -255,7 +237,6 @@ async fn realmain(args: Args, ex: Arc>) -> Result<()> { &sled_db, &config, &blockchain_config.net.into(), - &blockchain_config.minerd_endpoint, &blockchain_config.txs_batch_size, &ex, ) @@ -266,10 +247,6 @@ async fn realmain(args: Args, ex: Arc>) -> Result<()> { skip_sync: blockchain_config.skip_sync, checkpoint_height: blockchain_config.checkpoint_height, checkpoint: blockchain_config.checkpoint, - miner: blockchain_config.minerd_endpoint.is_some(), - recipient: blockchain_config.recipient, - spend_hook: blockchain_config.spend_hook, - user_data: blockchain_config.user_data, bootstrap, }; daemon diff --git a/bin/darkfid/src/rpc.rs b/bin/darkfid/src/rpc.rs index 62ba36656..6544518b1 100644 --- a/bin/darkfid/src/rpc.rs +++ b/bin/darkfid/src/rpc.rs @@ -16,65 +16,31 @@ * along with this program. If not, see . */ -use std::{collections::HashSet, time::Instant}; +use std::collections::HashSet; use async_trait::async_trait; use smol::lock::MutexGuard; use tinyjson::JsonValue; -use tracing::{debug, error, info, warn}; -use url::Url; +use tracing::debug; use darkfi::{ net::P2pPtr, rpc::{ - client::RpcChadClient, jsonrpc::{ErrorCode, JsonError, JsonRequest, JsonResponse, JsonResult}, p2p_method::HandlerP2p, server::RequestHandler, }, - system::{sleep, ExecutorPtr, StoppableTaskPtr}, + system::StoppableTaskPtr, util::time::Timestamp, - Error, Result, }; -use crate::{ - error::{server_error, RpcError}, - DarkfiNode, -}; +use crate::DarkfiNode; /// Default JSON-RPC `RequestHandler` type pub struct DefaultRpcHandler; /// HTTP JSON-RPC `RequestHandler` type for p2pool pub struct MmRpcHandler; -/// Structure to hold a JSON-RPC client and its config, -/// so we can recreate it in case of an error. -pub struct MinerRpcClient { - endpoint: Url, - ex: ExecutorPtr, - client: Option, -} - -impl MinerRpcClient { - pub async fn new(endpoint: Url, ex: ExecutorPtr) -> Self { - let client = match RpcChadClient::new(endpoint.clone(), ex.clone()).await { - Ok(c) => Some(c), - Err(_) => { - warn!(target: "darkfid::Darkfid::init", "Failed to initialize miner daemon rpc client, will try later"); - None - } - }; - Self { endpoint, ex, client } - } - - /// Stop the client. - pub async fn stop(&self) { - if let Some(ref client) = self.client { - client.stop().await - } - } -} - #[async_trait] #[rustfmt::skip] impl RequestHandler for DarkfiNode { @@ -87,7 +53,6 @@ impl RequestHandler for DarkfiNode { // ===================== "ping" => >::pong(self, req.id, req.params).await, "clock" => self.clock(req.id, req.params).await, - "ping_miner" => self.ping_miner(req.id, req.params).await, "dnet.switch" => self.dnet_switch(req.id, req.params).await, "dnet.subscribe_events" => self.dnet_subscribe_events(req.id, req.params).await, "p2p.get_info" => self.p2p_get_info(req.id, req.params).await, @@ -116,6 +81,12 @@ impl RequestHandler for DarkfiNode { "tx.clean_pending" => self.tx_clean_pending(req.id, req.params).await, "tx.calculate_fee" => self.tx_calculate_fee(req.id, req.params).await, + // ============= + // Miner methods + // ============= + "miner.get_header" => self.miner_get_header(req.id, req.params).await, + "miner.submit_solution" => self.miner_submit_solution(req.id, req.params).await, + // ============== // Invalid method // ============== @@ -204,83 +175,6 @@ impl DarkfiNode { self.subscribers.get("dnet").unwrap().clone().into() } - - // RPCAPI: - // Pings configured miner daemon for liveness. - // Returns `true` on success. - // - // --> {"jsonrpc": "2.0", "method": "ping_miner", "params": [], "id": 1} - // <-- {"jsonrpc": "2.0", "result": "true", "id": 1} - async fn ping_miner(&self, id: u16, _params: JsonValue) -> JsonResult { - if let Err(e) = self.ping_miner_daemon().await { - error!(target: "darkfid::rpc::ping_miner", "Failed to ping miner daemon: {e}"); - return server_error(RpcError::PingFailed, id, None) - } - JsonResponse::new(JsonValue::Boolean(true), id).into() - } - - /// Ping configured miner daemon JSON-RPC endpoint. - pub async fn ping_miner_daemon(&self) -> Result<()> { - debug!(target: "darkfid::ping_miner_daemon", "Pinging miner daemon..."); - self.miner_daemon_request("ping", &JsonValue::Array(vec![])).await?; - Ok(()) - } - - /// Auxiliary function to execute a request towards the configured miner daemon JSON-RPC endpoint. - pub async fn miner_daemon_request( - &self, - method: &str, - params: &JsonValue, - ) -> Result { - let Some(ref rpc_client) = self.rpc_client else { return Err(Error::RpcClientStopped) }; - debug!(target: "darkfid::rpc::miner_daemon_request", "Executing request {method} with params: {params:?}"); - let latency = Instant::now(); - let req = JsonRequest::new(method, params.clone()); - let lock = rpc_client.lock().await; - let Some(ref client) = lock.client else { return Err(Error::RpcClientStopped) }; - let rep = client.request(req).await?; - drop(lock); - let latency = latency.elapsed(); - debug!(target: "darkfid::rpc::miner_daemon_request", "Got reply: {rep:?}"); - debug!(target: "darkfid::rpc::miner_daemon_request", "Latency: {latency:?}"); - Ok(rep) - } - - /// Auxiliary function to execute a request towards the configured miner daemon JSON-RPC endpoint, - /// but in case of failure, sleep and retry until connection is re-established. - pub async fn miner_daemon_request_with_retry( - &self, - method: &str, - params: &JsonValue, - ) -> JsonValue { - loop { - // Try to execute the request using current client - match self.miner_daemon_request(method, params).await { - Ok(v) => return v, - Err(e) => { - error!(target: "darkfid::rpc::miner_daemon_request_with_retry", "Failed to execute miner daemon request: {e}"); - } - } - loop { - // Sleep a bit before retrying - info!(target: "darkfid::rpc::miner_daemon_request_with_retry", "Sleeping so we can retry later"); - sleep(10).await; - // Create a new client - let mut rpc_client = self.rpc_client.as_ref().unwrap().lock().await; - let Ok(client) = - RpcChadClient::new(rpc_client.endpoint.clone(), rpc_client.ex.clone()).await - else { - error!(target: "darkfid::rpc::miner_daemon_request_with_retry", "Failed to initialize miner daemon rpc client, check if minerd is running"); - drop(rpc_client); - continue - }; - info!(target: "darkfid::rpc::miner_daemon_request_with_retry", "Connection re-established!"); - // Set the new client as the daemon one - rpc_client.client = Some(client); - break; - } - } - } } impl HandlerP2p for DarkfiNode { diff --git a/bin/darkfid/src/rpc_miner.rs b/bin/darkfid/src/rpc_miner.rs new file mode 100644 index 000000000..77449a6e3 --- /dev/null +++ b/bin/darkfid/src/rpc_miner.rs @@ -0,0 +1,540 @@ +/* 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 . + */ + +use std::{collections::HashMap, str::FromStr}; + +use darkfi::{ + blockchain::{BlockInfo, Header, HeaderHash}, + rpc::jsonrpc::{ErrorCode, ErrorCode::InvalidParams, JsonError, JsonResponse, JsonResult}, + tx::{ContractCallLeaf, Transaction, TransactionBuilder}, + util::{encoding::base64, time::Timestamp}, + validator::{ + consensus::{Fork, Proposal}, + pow::{RANDOMX_KEY_CHANGE_DELAY, RANDOMX_KEY_CHANGING_HEIGHT}, + verification::apply_producer_transaction, + }, + zk::ProvingKey, + zkas::ZkBinary, + Error, Result, +}; +use darkfi_money_contract::{client::pow_reward_v1::PoWRewardCallBuilder, MoneyFunction}; +use darkfi_sdk::{ + crypto::{ + pasta_prelude::PrimeField, FuncId, Keypair, MerkleTree, PublicKey, SecretKey, + MONEY_CONTRACT_ID, + }, + pasta::pallas, + ContractCall, +}; +use darkfi_serial::{serialize_async, Encodable}; +use num_bigint::BigUint; +use rand::rngs::OsRng; +use tinyjson::JsonValue; +use tracing::{error, info}; + +use crate::{proto::ProposalMessage, server_error, DarkfiNode, RpcError}; + +/// Auxiliary structure representing node miner rewards recipient configuration. +pub struct MinerRewardsRecipientConfig { + /// Wallet mining address to receive mining rewards + pub recipient: PublicKey, + /// Optional contract spend hook to use in the mining reward + pub spend_hook: Option, + /// Optional contract user data to use in the mining reward. + /// This is not arbitrary data. + pub user_data: Option, +} + +/// Auxiliary structure representing a block template for native mining. +pub struct BlockTemplate { + /// The block that is being mined + pub block: BlockInfo, + /// The base64 encoded RandomX key used + randomx_key: String, + /// The base64 encoded mining target used + target: String, + /// The signing secret for this block + secret: SecretKey, +} + +impl DarkfiNode { + // RPCAPI: + // Queries the validator for the current best fork next header to + // mine. + // Returns the current RandomX key, the mining target and the next + // block header, all encoded as base64 strings. + // + // **Params:** + // * `header` : Mining job Header hash that is currently being polled (as string) + // * `recipient` : Wallet mining address to receive mining rewards (as string) + // * `spend_hook`: Optional contract spend hook to use in the mining reward (as string) + // * `user_data` : Optional contract user data (not arbitrary data) to use in the mining reward (as string) + // + // **Returns:** + // * `String`: Current best fork RandomX key (base64 encoded) + // * `String`: Current best fork mining target (base64 encoded) + // * `String`: Current best fork next block header (base64 encoded) + // + // --> {"jsonrpc": "2.0", "method": "miner.get_header", "params": {"header": "hash", "recipient": "address"}, "id": 1} + // <-- {"jsonrpc": "2.0", "result": ["randomx_key", "target", "header"], "id": 1} + pub async fn miner_get_header(&self, id: u16, params: JsonValue) -> JsonResult { + // Check if node is synced before responding to miner + if !*self.validator.synced.read().await { + return server_error(RpcError::NotSynced, id, None) + } + + // Parse request params + let Some(params) = params.get::>() else { + return JsonError::new(InvalidParams, None, id).into() + }; + if params.len() < 2 || params.len() > 4 { + return JsonError::new(InvalidParams, None, id).into() + } + + // Parse header hash + let Some(header_hash) = params.get("header") else { + return server_error(RpcError::MinerMissingHeader, id, None) + }; + let Some(header_hash) = header_hash.get::() else { + return server_error(RpcError::MinerInvalidHeader, id, None) + }; + let Ok(header_hash) = HeaderHash::from_str(header_hash) else { + return server_error(RpcError::MinerInvalidHeader, id, None) + }; + + // Parse recipient wallet address + let Some(recipient) = params.get("recipient") else { + return server_error(RpcError::MinerMissingRecipient, id, None) + }; + let Some(recipient) = recipient.get::() else { + return server_error(RpcError::MinerInvalidRecipient, id, None) + }; + let Ok(recipient) = PublicKey::from_str(recipient) else { + return server_error(RpcError::MinerInvalidRecipient, id, None) + }; + + // Parse spend hook + let spend_hook = match params.get("spend_hook") { + Some(spend_hook) => { + let Some(spend_hook) = spend_hook.get::() else { + return server_error(RpcError::MinerInvalidSpendHook, id, None) + }; + let Ok(spend_hook) = FuncId::from_str(spend_hook) else { + return server_error(RpcError::MinerInvalidSpendHook, id, None) + }; + Some(spend_hook) + } + None => None, + }; + + // Parse user data + let user_data: Option = match params.get("user_data") { + Some(user_data) => { + let Some(user_data) = user_data.get::() else { + return server_error(RpcError::MinerInvalidUserData, id, None) + }; + let Ok(bytes) = bs58::decode(&user_data).into_vec() else { + return server_error(RpcError::MinerInvalidUserData, id, None) + }; + let bytes: [u8; 32] = match bytes.try_into() { + Ok(b) => b, + Err(_) => return server_error(RpcError::MinerInvalidUserData, id, None), + }; + let Some(user_data) = pallas::Base::from_repr(bytes).into() else { + return server_error(RpcError::MinerInvalidUserData, id, None) + }; + Some(user_data) + } + None => None, + }; + + // Now that method params format is correct, we can check if we + // already have a mining job for this wallet. If we already + // have it, we check if the fork it extends is still the best + // one. If both checks pass, we can just return an empty + // response if the request `aux_hash` matches the job one, + // otherwise return the job block template hash. In case the + // best fork has changed, we drop this job and generate a + // new one. If we don't know this wallet, we create a new job. + // We'll also obtain a lock here to avoid getting polled + // multiple times and potentially missing a job. The lock is + // released when this function exits. + let address_bytes = serialize_async(&(recipient, spend_hook, user_data)).await; + let mut blocktemplates = self.blocktemplates.lock().await; + let mut extended_fork = match self.best_current_fork().await { + Ok(f) => f, + Err(e) => { + error!( + target: "darkfid::rpc::miner_get_header", + "[RPC] Finding best fork index failed: {e}", + ); + return JsonError::new(ErrorCode::InternalError, None, id).into() + } + }; + if let Some(blocktemplate) = blocktemplates.get(&address_bytes) { + let last_proposal = match extended_fork.last_proposal() { + Ok(p) => p, + Err(e) => { + error!( + target: "darkfid::rpc::miner_get_header", + "[RPC] Retrieving best fork last proposal failed: {e}", + ); + return JsonError::new(ErrorCode::InternalError, None, id).into() + } + }; + if last_proposal.hash == blocktemplate.block.header.previous { + return if blocktemplate.block.header.hash() != header_hash { + JsonResponse::new( + JsonValue::Array(vec![ + JsonValue::String(blocktemplate.randomx_key.clone()), + JsonValue::String(blocktemplate.target.clone()), + JsonValue::String(base64::encode( + &serialize_async(&blocktemplate.block.header).await, + )), + ]), + id, + ) + .into() + } else { + JsonResponse::new(JsonValue::Array(vec![]), id).into() + } + } + blocktemplates.remove(&address_bytes); + } + + // At this point, we should query the Validator for a new blocktemplate. + // We first need to construct `MinerRewardsRecipientConfig` from the + // address configuration provided to us through the RPC. + let recipient_str = format!("{recipient}"); + let spend_hook_str = match spend_hook { + Some(spend_hook) => format!("{spend_hook}"), + None => String::from("-"), + }; + let user_data_str = match user_data { + Some(user_data) => bs58::encode(user_data.to_repr()).into_string(), + None => String::from("-"), + }; + let recipient_config = MinerRewardsRecipientConfig { recipient, spend_hook, user_data }; + + // Now let's try to construct the blocktemplate. + let (target, block, secret) = match generate_next_block( + &mut extended_fork, + &recipient_config, + &self.powrewardv1_zk.zkbin, + &self.powrewardv1_zk.provingkey, + self.validator.consensus.module.read().await.target, + self.validator.verify_fees, + ) + .await + { + Ok(v) => v, + Err(e) => { + error!( + target: "darkfid::rpc::miner_get_header", + "[RPC] Failed to generate next blocktemplate: {e}", + ); + return JsonError::new(ErrorCode::InternalError, None, id).into() + } + }; + + // Grab the RandomX key to use. + // We only use the next key when the next block is the + // height changing one. + let randomx_key = if block.header.height > RANDOMX_KEY_CHANGING_HEIGHT && + block.header.height % RANDOMX_KEY_CHANGING_HEIGHT == RANDOMX_KEY_CHANGE_DELAY + { + base64::encode(&serialize_async(&extended_fork.module.darkfi_rx_keys.1).await) + } else { + base64::encode(&serialize_async(&extended_fork.module.darkfi_rx_keys.0).await) + }; + + // Convert the target + let target = base64::encode(&target.to_bytes_le()); + + // Construct the block template + let blocktemplate = BlockTemplate { + block, + randomx_key: randomx_key.clone(), + target: target.clone(), + secret, + }; + + // Now we have the blocktemplate. We'll mark it down in memory, + // and then ship it to RPC. + let header_hash = blocktemplate.block.header.hash().to_string(); + let header = base64::encode(&serialize_async(&blocktemplate.block.header).await); + blocktemplates.insert(address_bytes, blocktemplate); + info!( + target: "darkfid::rpc::miner_get_header", + "[RPC] Created new blocktemplate: address={recipient_str}, spend_hook={spend_hook_str}, user_data={user_data_str}, hash={header_hash}" + ); + + let response = JsonValue::Array(vec![ + JsonValue::String(randomx_key), + JsonValue::String(target), + JsonValue::String(header), + ]); + + JsonResponse::new(response, id).into() + } + + // RPCAPI: + // Submits a PoW solution header nonce for a block. + // Returns the block submittion status. + // + // **Params:** + // * `recipient` : Wallet mining address used (as string) + // * `spend_hook`: Optional contract spend hook used (as string) + // * `user_data` : Optional contract user data (not arbitrary data) used (as string) + // * `nonce` : The solution header nonce (as f64) + // + // **Returns:** + // * `String`: Block submit status + // + // --> {"jsonrpc": "2.0", "method": "miner.submit_solution", "params": {"recipient": "address", "nonce": 42}, "id": 1} + // <-- {"jsonrpc": "2.0", "result": "accepted", "id": 1} + pub async fn miner_submit_solution(&self, id: u16, params: JsonValue) -> JsonResult { + // Check if node is synced before responding to p2pool + if !*self.validator.synced.read().await { + return server_error(RpcError::NotSynced, id, None) + } + + // Parse request params + let Some(params) = params.get::>() else { + return JsonError::new(InvalidParams, None, id).into() + }; + if params.len() < 2 || params.len() > 4 { + return JsonError::new(InvalidParams, None, id).into() + } + + // Parse recipient wallet address + let Some(recipient) = params.get("recipient") else { + return server_error(RpcError::MinerMissingRecipient, id, None) + }; + let Some(recipient) = recipient.get::() else { + return server_error(RpcError::MinerInvalidRecipient, id, None) + }; + let Ok(recipient) = PublicKey::from_str(recipient) else { + return server_error(RpcError::MinerInvalidRecipient, id, None) + }; + + // Parse spend hook + let spend_hook = match params.get("spend_hook") { + Some(spend_hook) => { + let Some(spend_hook) = spend_hook.get::() else { + return server_error(RpcError::MinerInvalidSpendHook, id, None) + }; + let Ok(spend_hook) = FuncId::from_str(spend_hook) else { + return server_error(RpcError::MinerInvalidSpendHook, id, None) + }; + Some(spend_hook) + } + None => None, + }; + + // Parse user data + let user_data: Option = match params.get("user_data") { + Some(user_data) => { + let Some(user_data) = user_data.get::() else { + return server_error(RpcError::MinerInvalidUserData, id, None) + }; + let Ok(bytes) = bs58::decode(&user_data).into_vec() else { + return server_error(RpcError::MinerInvalidUserData, id, None) + }; + let bytes: [u8; 32] = match bytes.try_into() { + Ok(b) => b, + Err(_) => return server_error(RpcError::MinerInvalidUserData, id, None), + }; + let Some(user_data) = pallas::Base::from_repr(bytes).into() else { + return server_error(RpcError::MinerInvalidUserData, id, None) + }; + Some(user_data) + } + None => None, + }; + + // Parse nonce + let Some(nonce) = params.get("nonce") else { + return server_error(RpcError::MinerMissingNonce, id, None) + }; + let Some(nonce) = nonce.get::() else { + return server_error(RpcError::MinerInvalidNonce, id, None) + }; + + // If we don't know about this job, we can just abort here. + let address_bytes = serialize_async(&(recipient, spend_hook, user_data)).await; + let mut blocktemplates = self.blocktemplates.lock().await; + let Some(blocktemplate) = blocktemplates.get(&address_bytes) else { + return server_error(RpcError::MinerUnknownJob, id, None) + }; + + info!( + target: "darkfid::rpc::miner_submit_solution", + "[RPC] Got solution submission for block template: {}", blocktemplate.block.header.hash(), + ); + + // Sign the DarkFi block + let mut block = blocktemplate.block.clone(); + block.header.nonce = *nonce as u64; + block.sign(&blocktemplate.secret); + info!( + target: "darkfid::rpc::miner_submit_solution", + "[RPC] Mined block header hash: {}", blocktemplate.block.header.hash(), + ); + + // At this point we should be able to remove the submitted job. + // We still won't release the lock in hope of proposing the block + // first. + blocktemplates.remove(&address_bytes); + + // Propose the new block + info!( + target: "darkfid::rpc::miner_submit_solution", + "[RPC] Proposing new block to network", + ); + let proposal = Proposal::new(block); + if let Err(e) = self.validator.append_proposal(&proposal).await { + error!( + target: "darkfid::rpc::miner_submit_solution", + "[RPC] Error proposing new block: {e}", + ); + return JsonResponse::new(JsonValue::String(String::from("rejected")), id).into() + } + + let proposals_sub = self.subscribers.get("proposals").unwrap(); + let enc_prop = JsonValue::String(base64::encode(&serialize_async(&proposal).await)); + proposals_sub.notify(vec![enc_prop].into()).await; + + info!( + target: "darkfid::rpc::miner_submit_solution", + "[RPC] Broadcasting new block to network", + ); + let message = ProposalMessage(proposal); + self.p2p_handler.p2p.broadcast(&message).await; + + JsonResponse::new(JsonValue::String(String::from("accepted")), id).into() + } +} + +/// Auxiliary function to generate next block in an atomic manner. +pub async fn generate_next_block( + extended_fork: &mut Fork, + recipient_config: &MinerRewardsRecipientConfig, + zkbin: &ZkBinary, + pk: &ProvingKey, + block_target: u32, + verify_fees: bool, +) -> Result<(BigUint, BlockInfo, SecretKey)> { + // Grab forks' last block proposal(previous) + let last_proposal = extended_fork.last_proposal()?; + + // Grab forks' next block height + let next_block_height = last_proposal.block.header.height + 1; + + // Grab forks' unproposed transactions + let (mut txs, _, fees, overlay) = extended_fork + .unproposed_txs(&extended_fork.blockchain, next_block_height, block_target, verify_fees) + .await?; + + // Create an ephemeral block signing keypair. Its secret key will + // be stored in the PowReward transaction's encrypted note for + // later retrieval. It is encrypted towards the recipient's public + // key. + let block_signing_keypair = Keypair::random(&mut OsRng); + + // Generate reward transaction + let tx = generate_transaction( + next_block_height, + fees, + &block_signing_keypair, + recipient_config, + zkbin, + pk, + )?; + + // Apply producer transaction in the overlay + let _ = apply_producer_transaction( + &overlay, + next_block_height, + block_target, + &tx, + &mut MerkleTree::new(1), + ) + .await?; + txs.push(tx); + + // Grab the updated contracts states root + overlay.lock().unwrap().contracts.update_state_monotree(&mut extended_fork.state_monotree)?; + let Some(state_root) = extended_fork.state_monotree.get_headroot()? else { + return Err(Error::ContractsStatesRootNotFoundError); + }; + + // Drop new trees opened by the unproposed transactions overlay + overlay.lock().unwrap().overlay.lock().unwrap().purge_new_trees()?; + + // Generate the new header + let mut header = + Header::new(last_proposal.hash, next_block_height, Timestamp::current_time(), 0); + header.state_root = state_root; + + // Generate the block + let mut next_block = BlockInfo::new_empty(header); + + // Add transactions to the block + next_block.append_txs(txs); + + // Grab the next mine target + let target = extended_fork.module.next_mine_target()?; + + Ok((target, next_block, block_signing_keypair.secret)) +} + +/// Auxiliary function to generate a Money::PoWReward transaction. +fn generate_transaction( + block_height: u32, + fees: u64, + block_signing_keypair: &Keypair, + recipient_config: &MinerRewardsRecipientConfig, + zkbin: &ZkBinary, + pk: &ProvingKey, +) -> Result { + // Build the transaction debris + let debris = PoWRewardCallBuilder { + signature_keypair: *block_signing_keypair, + block_height, + fees, + recipient: Some(recipient_config.recipient), + spend_hook: recipient_config.spend_hook, + user_data: recipient_config.user_data, + mint_zkbin: zkbin.clone(), + mint_pk: pk.clone(), + } + .build()?; + + // Generate and sign the actual transaction + let mut data = vec![MoneyFunction::PoWRewardV1 as u8]; + debris.params.encode(&mut data)?; + let call = ContractCall { contract_id: *MONEY_CONTRACT_ID, data }; + let mut tx_builder = + TransactionBuilder::new(ContractCallLeaf { call, proofs: debris.proofs }, vec![])?; + let mut tx = tx_builder.build()?; + let sigs = tx.create_sigs(&[block_signing_keypair.secret])?; + tx.signatures = vec![sigs]; + + Ok(tx) +} diff --git a/bin/darkfid/src/rpc_xmr.rs b/bin/darkfid/src/rpc_xmr.rs index 153394a2f..13a09cb5c 100644 --- a/bin/darkfid/src/rpc_xmr.rs +++ b/bin/darkfid/src/rpc_xmr.rs @@ -42,9 +42,8 @@ use tracing::{error, info}; use crate::{ proto::ProposalMessage, - server_error, - task::miner::{generate_next_block, MinerRewardsRecipientConfig}, - DarkfiNode, RpcError, + rpc_miner::{generate_next_block, MinerRewardsRecipientConfig}, + server_error, DarkfiNode, RpcError, }; // https://github.com/SChernykh/p2pool/blob/master/docs/MERGE_MINING.MD diff --git a/bin/darkfid/src/task/consensus.rs b/bin/darkfid/src/task/consensus.rs index 2a2f770e9..6366a566f 100644 --- a/bin/darkfid/src/task/consensus.rs +++ b/bin/darkfid/src/task/consensus.rs @@ -25,28 +25,24 @@ use darkfi::{ util::{encoding::base64, time::Timestamp}, Error, Result, }; -use darkfi_sdk::{ - crypto::{FuncId, PublicKey}, - pasta::{group::ff::PrimeField, pallas}, -}; use darkfi_serial::serialize_async; use tracing::{error, info}; use crate::{ - task::{garbage_collect_task, miner::MinerRewardsRecipientConfig, miner_task, sync_task}, + task::{garbage_collect_task, sync_task}, DarkfiNodePtr, }; -/// Auxiliary structure representing node consensus init task configuration +/// Auxiliary structure representing node consensus init task configuration. #[derive(Clone)] pub struct ConsensusInitTaskConfig { + /// Skip syncing process and start node right away pub skip_sync: bool, + /// Optional sync checkpoint height pub checkpoint_height: Option, + /// Optional sync checkpoint hash pub checkpoint: Option, - pub miner: bool, - pub recipient: Option, - pub spend_hook: Option, - pub user_data: Option, + /// Optional bootstrap timestamp pub bootstrap: u64, } @@ -95,54 +91,9 @@ pub async fn consensus_init_task( None }; - // Grab rewards recipient public key(address) if node is a miner, - // along with configured spend hook and user data. - let recipient_config = if config.miner { - if config.recipient.is_none() { - return Err(Error::ParseFailed("Recipient address missing")) - } - let recipient = match PublicKey::from_str(config.recipient.as_ref().unwrap()) { - Ok(address) => address, - Err(_) => return Err(Error::InvalidAddress), - }; - - let spend_hook = match &config.spend_hook { - Some(s) => match FuncId::from_str(s) { - Ok(s) => Some(s), - Err(_) => return Err(Error::ParseFailed("Invalid spend hook")), - }, - None => None, - }; - - let user_data = match &config.user_data { - Some(u) => { - let bytes: [u8; 32] = match bs58::decode(&u).into_vec()?.try_into() { - Ok(b) => b, - Err(_) => return Err(Error::ParseFailed("Invalid user data")), - }; - - match pallas::Base::from_repr(bytes).into() { - Some(v) => Some(v), - None => return Err(Error::ParseFailed("Invalid user data")), - } - } - None => None, - }; - - Some(MinerRewardsRecipientConfig { recipient, spend_hook, user_data }) - } else { - None - }; - // Gracefully handle network disconnections loop { - let result = if config.miner { - miner_task(&node, recipient_config.as_ref().unwrap(), config.skip_sync, &ex).await - } else { - replicator_task(&node, &ex).await - }; - - match result { + match listen_to_network(&node, &ex).await { Ok(_) => return Ok(()), Err(Error::NetworkNotConnected) => { // Sync node again @@ -160,7 +111,7 @@ pub async fn consensus_init_task( } /// Async task to start the consensus task, while monitoring for a network disconnections. -async fn replicator_task(node: &DarkfiNodePtr, ex: &ExecutorPtr) -> Result<()> { +async fn listen_to_network(node: &DarkfiNodePtr, ex: &ExecutorPtr) -> Result<()> { // Grab proposals subscriber and subscribe to it let proposals_sub = node.subscribers.get("proposals").unwrap(); let prop_subscription = proposals_sub.publisher.clone().subscribe().await; @@ -225,8 +176,8 @@ async fn consensus_task( continue } - if let Err(e) = clean_mm_blocktemplates(node).await { - error!(target: "darkfid", "Failed cleaning merge mining block templates: {e}") + if let Err(e) = clean_blocktemplates(node).await { + error!(target: "darkfid", "Failed cleaning mining block templates: {e}") } let mut notif_blocks = Vec::with_capacity(confirmed.len()); @@ -253,14 +204,15 @@ async fn consensus_task( } } -/// Auxiliary function to drop merge mining block templates not -/// referencing active forks or last confirmed block. -pub async fn clean_mm_blocktemplates(node: &DarkfiNodePtr) -> Result<()> { - // Grab a lock over node merge mining templates +/// Auxiliary function to drop mining block templates not referencing +/// active forks or last confirmed block. +async fn clean_blocktemplates(node: &DarkfiNodePtr) -> Result<()> { + // Grab a lock over node mining templates + let mut blocktemplates = node.blocktemplates.lock().await; let mut mm_blocktemplates = node.mm_blocktemplates.lock().await; - // Early return if no merge mining block templates exist - if mm_blocktemplates.is_empty() { + // Early return if no mining block templates exist + if blocktemplates.is_empty() && mm_blocktemplates.is_empty() { return Ok(()) } @@ -272,6 +224,34 @@ pub async fn clean_mm_blocktemplates(node: &DarkfiNodePtr) -> Result<()> { // Loop through templates to find which can be dropped let mut dropped_templates = vec![]; + 'outer: for (key, blocktemplate) in blocktemplates.iter() { + // Loop through all the forks + for fork in forks.iter() { + // Traverse fork proposals sequence in reverse + for p_hash in fork.proposals.iter().rev() { + // Check if job extends this fork + if &blocktemplate.block.header.previous == p_hash { + continue 'outer + } + } + } + + // Check if it extends last confirmed block + if blocktemplate.block.header.previous == last_confirmed { + continue + } + + // This job doesn't reference something so we drop it + dropped_templates.push(key.clone()); + } + + // Drop jobs not referencing active forks or last confirmed block + for key in dropped_templates { + blocktemplates.remove(&key); + } + + // Loop through merge mining templates to find which can be dropped + let mut dropped_templates = vec![]; 'outer: for (key, (block, _, _)) in mm_blocktemplates.iter() { // Loop through all the forks for fork in forks.iter() { diff --git a/bin/darkfid/src/task/miner.rs b/bin/darkfid/src/task/miner.rs deleted file mode 100644 index 166b9e55f..000000000 --- a/bin/darkfid/src/task/miner.rs +++ /dev/null @@ -1,428 +0,0 @@ -/* 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 . - */ - -use darkfi::{ - blockchain::{BlockInfo, Header, HeaderHash}, - rpc::{jsonrpc::JsonNotification, util::JsonValue}, - system::{ExecutorPtr, StoppableTask, Subscription}, - tx::{ContractCallLeaf, Transaction, TransactionBuilder}, - util::{encoding::base64, time::Timestamp}, - validator::{ - consensus::{Fork, Proposal}, - pow::{RANDOMX_KEY_CHANGE_DELAY, RANDOMX_KEY_CHANGING_HEIGHT}, - utils::best_fork_index, - verification::apply_producer_transaction, - }, - zk::ProvingKey, - zkas::ZkBinary, - Error, Result, -}; -use darkfi_money_contract::{client::pow_reward_v1::PoWRewardCallBuilder, MoneyFunction}; -use darkfi_sdk::{ - crypto::{FuncId, Keypair, MerkleTree, PublicKey, SecretKey, MONEY_CONTRACT_ID}, - pasta::pallas, - ContractCall, -}; -use darkfi_serial::{serialize_async, Encodable}; -use num_bigint::BigUint; -use rand::rngs::OsRng; -use smol::channel::{Receiver, Sender}; -use tracing::{error, info}; - -use crate::{ - proto::ProposalMessage, - task::{consensus::clean_mm_blocktemplates, garbage_collect_task}, - DarkfiNodePtr, -}; - -/// Auxiliary structure representing node miner rewards recipient configuration -pub struct MinerRewardsRecipientConfig { - pub recipient: PublicKey, - pub spend_hook: Option, - pub user_data: Option, -} - -/// Async task used for participating in the PoW block production. -/// -/// Miner initializes their setup and waits for next confirmation, -/// by listening for new proposals from the network, for optimal -/// conditions. After confirmation occurs, they start the actual -/// miner loop, where they first grab the best ranking fork to extend, -/// and start mining procedure for its next block. Additionally, they -/// listen to the network for new proposals, and check if these -/// proposals produce a new best ranking fork. If they do, the stop -/// mining. These two tasks run in parallel, and after one of them -/// finishes, node triggers confirmation check. -pub async fn miner_task( - node: &DarkfiNodePtr, - recipient_config: &MinerRewardsRecipientConfig, - skip_sync: bool, - ex: &ExecutorPtr, -) -> Result<()> { - // Initialize miner configuration - info!(target: "darkfid::task::miner_task", "Starting miner task..."); - - // Grab blocks subscriber - let block_sub = node.subscribers.get("blocks").unwrap(); - - // Grab proposals subscriber and subscribe to it - let proposals_sub = node.subscribers.get("proposals").unwrap(); - let subscription = proposals_sub.publisher.clone().subscribe().await; - - // Create channels so threads can signal each other - let (sender, stop_signal) = smol::channel::bounded(1); - - // Create the garbage collection task using a dummy task - let gc_task = StoppableTask::new(); - gc_task.clone().start( - async { Ok(()) }, - |_| async { /* Do nothing */ }, - Error::GarbageCollectionTaskStopped, - ex.clone(), - ); - - info!(target: "darkfid::task::miner_task", "Miner initialized successfully!"); - - // Start miner loop - loop { - // Grab best current fork - let extended_fork = match node.best_current_fork().await { - Ok(f) => f, - Err(e) => { - error!( - target: "darkfid::task::miner_task", - "Finding best fork index failed: {e}", - ); - continue - } - }; - - // Grab extended fork last proposal hash - let last_proposal_hash = extended_fork.last_proposal()?.hash; - - // Grab zkas proving keys and bin for PoWReward transaction - let zkbin = &node.powrewardv1_zk.zkbin; - let pk = &node.powrewardv1_zk.provingkey; - - // Start listenning for network proposals and mining next block for best fork. - match smol::future::or( - listen_to_network(node, last_proposal_hash, &subscription, &sender), - mine(node, extended_fork, recipient_config, zkbin, pk, &stop_signal, skip_sync), - ) - .await - { - Ok(_) => { /* Do nothing */ } - Err(Error::NetworkNotConnected) => { - error!(target: "darkfid::task::miner_task", "Node disconnected from the network"); - subscription.unsubscribe().await; - return Err(Error::NetworkNotConnected) - } - Err(e) => { - error!( - target: "darkfid::task::miner_task", - "Error during listen_to_network() or mine(): {e}" - ); - continue - } - } - - // Check if we can confirm anything and broadcast them - let confirmed = match node.validator.confirmation().await { - Ok(f) => f, - Err(e) => { - error!( - target: "darkfid::task::miner_task", - "Confirmation failed: {e}" - ); - continue - } - }; - - if confirmed.is_empty() { - continue - } - - if let Err(e) = clean_mm_blocktemplates(node).await { - error!(target: "darkfid", "Failed cleaning merge mining block templates: {e}") - } - - let mut notif_blocks = Vec::with_capacity(confirmed.len()); - for block in confirmed { - notif_blocks.push(JsonValue::String(base64::encode(&serialize_async(&block).await))); - } - block_sub.notify(JsonValue::Array(notif_blocks)).await; - - // Invoke the detached garbage collection task - gc_task.clone().stop().await; - gc_task.clone().start( - garbage_collect_task(node.clone()), - |res| async { - match res { - Ok(()) | Err(Error::GarbageCollectionTaskStopped) => { /* Do nothing */ } - Err(e) => { - error!(target: "darkfid", "Failed starting garbage collection task: {e}") - } - } - }, - Error::GarbageCollectionTaskStopped, - ex.clone(), - ); - } -} - -/// Async task to listen for incoming proposals and check if the best fork has changed. -async fn listen_to_network( - node: &DarkfiNodePtr, - last_proposal_hash: HeaderHash, - subscription: &Subscription, - sender: &Sender<()>, -) -> Result<()> { - loop { - // Wait until a new proposal has been received - subscription.receive().await; - - // Grab a lock over node forks - let forks = node.validator.consensus.forks.read().await; - - // Grab best current fork index - let index = best_fork_index(&forks)?; - - // Verify if proposals sequence has changed - if forks[index].last_proposal()?.hash != last_proposal_hash { - drop(forks); - break - } - - drop(forks); - } - - // Signal miner to abort mining - sender.send(()).await?; - if let Err(e) = node.miner_daemon_request("abort", &JsonValue::Array(vec![])).await { - error!(target: "darkfid::task::miner::listen_to_network", "Failed to execute miner daemon abort request: {e}"); - } - - Ok(()) -} - -/// Async task to generate and mine provided fork index next block, -/// while listening for a stop signal. -#[allow(clippy::too_many_arguments)] -async fn mine( - node: &DarkfiNodePtr, - extended_fork: Fork, - recipient_config: &MinerRewardsRecipientConfig, - zkbin: &ZkBinary, - pk: &ProvingKey, - stop_signal: &Receiver<()>, - skip_sync: bool, -) -> Result<()> { - smol::future::or( - wait_stop_signal(stop_signal), - mine_next_block(node, extended_fork, recipient_config, zkbin, pk, skip_sync), - ) - .await -} - -/// Async task to wait for listener's stop signal. -pub async fn wait_stop_signal(stop_signal: &Receiver<()>) -> Result<()> { - // Clean stop signal channel - if stop_signal.is_full() { - stop_signal.recv().await?; - } - - // Wait for listener signal - stop_signal.recv().await?; - - Ok(()) -} - -/// Async task to generate and mine provided fork index next block. -async fn mine_next_block( - node: &DarkfiNodePtr, - mut extended_fork: Fork, - recipient_config: &MinerRewardsRecipientConfig, - zkbin: &ZkBinary, - pk: &ProvingKey, - skip_sync: bool, -) -> Result<()> { - // Grab next target and block - let (next_target, mut next_block, block_signing_secret) = generate_next_block( - &mut extended_fork, - recipient_config, - zkbin, - pk, - node.validator.consensus.module.read().await.target, - node.validator.verify_fees, - ) - .await?; - - // Execute request to minerd and parse response - let target = JsonValue::String(base64::encode(&next_target.to_bytes_le())); - // Grab the RandomX key to use. - // We only use the next key when the next block is the - // height changing one. - let randomx_key = if next_block.header.height > RANDOMX_KEY_CHANGING_HEIGHT && - next_block.header.height % RANDOMX_KEY_CHANGING_HEIGHT == RANDOMX_KEY_CHANGE_DELAY - { - JsonValue::String(base64::encode( - &serialize_async(&extended_fork.module.darkfi_rx_keys.1).await, - )) - } else { - JsonValue::String(base64::encode( - &serialize_async(&extended_fork.module.darkfi_rx_keys.0).await, - )) - }; - let header = JsonValue::String(base64::encode(&serialize_async(&next_block.header).await)); - let response = node - .miner_daemon_request_with_retry( - "mine", - &JsonValue::Array(vec![target, randomx_key, header]), - ) - .await; - next_block.header.nonce = *response.get::().unwrap() as u64; - - // Sign the mined block - next_block.sign(&block_signing_secret); - - // Verify it - extended_fork.module.verify_current_block(&next_block.header)?; - - // Check if we are connected to the network - if !skip_sync && !node.p2p_handler.p2p.is_connected() { - return Err(Error::NetworkNotConnected) - } - - // Append the mined block as a proposal - let proposal = Proposal::new(next_block); - node.validator.append_proposal(&proposal).await?; - - // Broadcast proposal to the network - let message = ProposalMessage(proposal); - node.p2p_handler.p2p.broadcast(&message).await; - - Ok(()) -} - -/// Auxiliary function to generate next block in an atomic manner. -pub async fn generate_next_block( - extended_fork: &mut Fork, - recipient_config: &MinerRewardsRecipientConfig, - zkbin: &ZkBinary, - pk: &ProvingKey, - block_target: u32, - verify_fees: bool, -) -> Result<(BigUint, BlockInfo, SecretKey)> { - // Grab forks' last block proposal(previous) - let last_proposal = extended_fork.last_proposal()?; - - // Grab forks' next block height - let next_block_height = last_proposal.block.header.height + 1; - - // Grab forks' unproposed transactions - let (mut txs, _, fees, overlay) = extended_fork - .unproposed_txs(&extended_fork.blockchain, next_block_height, block_target, verify_fees) - .await?; - - // Create an ephemeral block signing keypair. Its secret key will - // be stored in the PowReward transaction's encrypted note for - // later retrieval. It is encrypted towards the recipient's public - // key. - let block_signing_keypair = Keypair::random(&mut OsRng); - - // Generate reward transaction - let tx = generate_transaction( - next_block_height, - fees, - &block_signing_keypair, - recipient_config, - zkbin, - pk, - )?; - - // Apply producer transaction in the overlay - let _ = apply_producer_transaction( - &overlay, - next_block_height, - block_target, - &tx, - &mut MerkleTree::new(1), - ) - .await?; - txs.push(tx); - - // Grab the updated contracts states root - overlay.lock().unwrap().contracts.update_state_monotree(&mut extended_fork.state_monotree)?; - let Some(state_root) = extended_fork.state_monotree.get_headroot()? else { - return Err(Error::ContractsStatesRootNotFoundError); - }; - - // Drop new trees opened by the unproposed transactions overlay - overlay.lock().unwrap().overlay.lock().unwrap().purge_new_trees()?; - - // Generate the new header - let mut header = - Header::new(last_proposal.hash, next_block_height, Timestamp::current_time(), 0); - header.state_root = state_root; - - // Generate the block - let mut next_block = BlockInfo::new_empty(header); - - // Add transactions to the block - next_block.append_txs(txs); - - // Grab the next mine target - let target = extended_fork.module.next_mine_target()?; - - Ok((target, next_block, block_signing_keypair.secret)) -} - -/// Auxiliary function to generate a Money::PoWReward transaction. -fn generate_transaction( - block_height: u32, - fees: u64, - block_signing_keypair: &Keypair, - recipient_config: &MinerRewardsRecipientConfig, - zkbin: &ZkBinary, - pk: &ProvingKey, -) -> Result { - // Build the transaction debris - let debris = PoWRewardCallBuilder { - signature_keypair: *block_signing_keypair, - block_height, - fees, - recipient: Some(recipient_config.recipient), - spend_hook: recipient_config.spend_hook, - user_data: recipient_config.user_data, - mint_zkbin: zkbin.clone(), - mint_pk: pk.clone(), - } - .build()?; - - // Generate and sign the actual transaction - let mut data = vec![MoneyFunction::PoWRewardV1 as u8]; - debris.params.encode(&mut data)?; - let call = ContractCall { contract_id: *MONEY_CONTRACT_ID, data }; - let mut tx_builder = - TransactionBuilder::new(ContractCallLeaf { call, proofs: debris.proofs }, vec![])?; - let mut tx = tx_builder.build()?; - let sigs = tx.create_sigs(&[block_signing_keypair.secret])?; - tx.signatures = vec![sigs]; - - Ok(tx) -} diff --git a/bin/darkfid/src/task/mod.rs b/bin/darkfid/src/task/mod.rs index 661d2d326..68ad96996 100644 --- a/bin/darkfid/src/task/mod.rs +++ b/bin/darkfid/src/task/mod.rs @@ -19,9 +19,6 @@ pub mod consensus; pub use consensus::consensus_init_task; -pub mod miner; -pub use miner::{generate_next_block, miner_task}; - pub mod sync; pub use sync::sync_task; diff --git a/bin/darkfid/src/tests/harness.rs b/bin/darkfid/src/tests/harness.rs index 207238215..1e0e94df8 100644 --- a/bin/darkfid/src/tests/harness.rs +++ b/bin/darkfid/src/tests/harness.rs @@ -289,8 +289,7 @@ pub async fn generate_node( let p2p_handler = DarkfidP2pHandler::init(settings, ex).await?; let node = - DarkfiNode::new(p2p_handler.clone(), validator.clone(), 50, subscribers.clone(), None) - .await?; + DarkfiNode::new(p2p_handler.clone(), validator.clone(), 50, subscribers.clone()).await?; p2p_handler.clone().start(ex, &validator, &subscribers).await?; diff --git a/bin/darkfid/src/tests/mod.rs b/bin/darkfid/src/tests/mod.rs index b8476b8f4..e751bf514 100644 --- a/bin/darkfid/src/tests/mod.rs +++ b/bin/darkfid/src/tests/mod.rs @@ -279,10 +279,6 @@ fn darkfid_programmatic_control() -> Result<()> { skip_sync: true, checkpoint_height: None, checkpoint: None, - miner: false, - recipient: None, - spend_hook: None, - user_data: None, bootstrap, }; let rpc_settings = RpcSettings { @@ -296,7 +292,6 @@ fn darkfid_programmatic_control() -> Result<()> { &config, &darkfi::net::Settings::default(), &None, - &None, &ex, ) .await diff --git a/bin/minerd/Cargo.toml b/bin/minerd/Cargo.toml index 6c8e75c48..95754cf3d 100644 --- a/bin/minerd/Cargo.toml +++ b/bin/minerd/Cargo.toml @@ -10,11 +10,12 @@ edition = "2021" [dependencies] # Darkfi -darkfi = {path = "../../", features = ["async-daemonize", "validator", "rpc"]} +darkfi = {path = "../../", features = ["async-daemonize", "validator", "rpc", "bs58"]} darkfi-sdk = {path = "../../src/sdk"} darkfi-serial = {version = "0.5.0", features = ["async"]} # Misc +bs58 = "0.5.1" tracing = "0.1.41" num-bigint = "0.4.6" @@ -34,6 +35,7 @@ smol = "2.0.2" serde = {version = "1.0.228", features = ["derive"]} structopt = "0.3.26" structopt-toml = "0.5.1" +toml = "0.9.8" [lints] workspace = true diff --git a/bin/minerd/minerd.toml b/bin/minerd/minerd.toml index 43d9a934e..661eab54e 100644 --- a/bin/minerd/minerd.toml +++ b/bin/minerd/minerd.toml @@ -9,10 +9,56 @@ # PoW miner number of threads to use #threads = 4 -# JSON-RPC settings -[rpc] -# JSON-RPC listen URL -rpc_listen = "tcp://127.0.0.1:28467" +# Polling rate to ask darkfid for mining jobs +#polling_rate = 2 -# Disabled RPC methods -#rpc_disabled_methods = [] +# Stop mining at given height (0 mines forever) +#stop_at_height = 0 + +# Blockchain network to use +network = "testnet" + +# Localnet blockchain network configuration +[network_config."localnet"] +# Wallet mining address to receive mining rewards +recipient = "YOUR_WALLET_ADDRESS_HERE" + +# Optional contract spend hook to use in the mining reward +#spend_hook = "YOUR_SPEND_HOOK_HERE" + +# Optional contract user data to use in the mining reward. +# This is not arbitrary data. +#user_data = "YOUR_USER_DATA_HERE" + +# darkfid JSON-RPC endpoint +endpoint = "tcp://127.0.0.1:8240" + +# Testnet blockchain network configuration +[network_config."testnet"] +# Wallet mining address to receive mining rewards +recipient = "YOUR_WALLET_ADDRESS_HERE" + +# Optional contract spend hook to use in the mining reward +#spend_hook = "YOUR_SPEND_HOOK_HERE" + +# Optional contract user data to use in the mining reward. +# This is not arbitrary data. +#user_data = "YOUR_USER_DATA_HERE" + +# darkfid JSON-RPC endpoint +endpoint = "tcp://127.0.0.1:8340" + +# Mainnet blockchain network configuration +[network_config."mainnet"] +# Wallet mining address to receive mining rewards +recipient = "YOUR_WALLET_ADDRESS_HERE" + +# Optional contract spend hook to use in the mining reward +#spend_hook = "YOUR_SPEND_HOOK_HERE" + +# Optional contract user data to use in the mining reward. +# This is not arbitrary data. +#user_data = "YOUR_USER_DATA_HERE" + +# darkfid JSON-RPC endpoint +endpoint = "tcp://127.0.0.1:8440" diff --git a/bin/minerd/src/error.rs b/bin/minerd/src/error.rs deleted file mode 100644 index 5dfa8caea..000000000 --- a/bin/minerd/src/error.rs +++ /dev/null @@ -1,54 +0,0 @@ -/* 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 . - */ - -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, - StopFailed = -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::StopFailed => "Failed to stop previous request", - }; - - (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() -} diff --git a/bin/minerd/src/lib.rs b/bin/minerd/src/lib.rs index 58dbd7aaa..93b4f0c6c 100644 --- a/bin/minerd/src/lib.rs +++ b/bin/minerd/src/lib.rs @@ -16,60 +16,116 @@ * along with this program. If not, see . */ -use std::{collections::HashSet, sync::Arc}; +use std::{collections::HashMap, sync::Arc}; use smol::{ channel::{Receiver, Sender}, - lock::Mutex, + lock::RwLock, }; -use tracing::{error, info}; +use tracing::{debug, error, info}; +use url::Url; use darkfi::{ - rpc::{ - server::{listen_and_serve, RequestHandler}, - settings::RpcSettings, - }, + rpc::util::JsonValue, system::{sleep, ExecutorPtr, StoppableTask, StoppableTaskPtr}, - Error, Result, + Error, }; +use darkfi_sdk::crypto::Keypair; -/// Daemon error codes -mod error; - -/// JSON-RPC server methods +/// darkfid JSON-RPC related methods mod rpc; +use rpc::{polling_task, DarkfidRpcClient}; + +/// Auxiliary structure representing miner node configuration. +pub struct MinerNodeConfig { + /// PoW miner number of threads to use + threads: usize, + /// Polling rate to ask darkfid for mining jobs + polling_rate: u64, + /// Stop mining at this height (0 mines forever) + stop_at_height: u32, + /// Wallet mining configuration to receive mining rewards + wallet_config: HashMap, +} + +impl Default for MinerNodeConfig { + fn default() -> Self { + Self::new( + 1, + 5, + 0, + HashMap::from([( + String::from("recipient"), + JsonValue::String(Keypair::default().public.to_string()), + )]), + ) + } +} + +impl MinerNodeConfig { + pub fn new( + threads: usize, + polling_rate: u64, + stop_at_height: u32, + wallet_config: HashMap, + ) -> Self { + Self { threads, polling_rate, stop_at_height, wallet_config } + } +} /// Atomic pointer to the DarkFi mining node pub type MinerNodePtr = Arc; /// Structure representing a DarkFi mining node pub struct MinerNode { - /// PoW miner number of threads to use - threads: usize, - /// Stop mining at this height - stop_at_height: u32, + /// Node configuration + config: MinerNodeConfig, /// Sender to stop miner threads sender: Sender<()>, /// Receiver to stop miner threads stop_signal: Receiver<()>, - /// JSON-RPC connection tracker - rpc_connections: Mutex>, + /// JSON-RPC client to execute requests to darkfid daemon + rpc_client: RwLock, } impl MinerNode { - pub fn new( - threads: usize, - stop_at_height: u32, - sender: Sender<()>, - stop_signal: Receiver<()>, - ) -> MinerNodePtr { - Arc::new(Self { - threads, - stop_at_height, - sender, - stop_signal, - rpc_connections: Mutex::new(HashSet::new()), - }) + pub async fn new(config: MinerNodeConfig, endpoint: Url, ex: &ExecutorPtr) -> MinerNodePtr { + // Initialize the smol channels to send signal between the threads + let (sender, stop_signal) = smol::channel::bounded(1); + + // Initialize JSON-RPC client + let rpc_client = RwLock::new(DarkfidRpcClient::new(endpoint, ex.clone()).await); + + Arc::new(Self { config, sender, stop_signal, rpc_client }) + } + + /// Auxiliary function to abort pending job. + pub async fn abort_pending(&self) { + // Check if a pending request is being processed + debug!(target: "minerd::abort_pending", "Checking if a pending job is being processed..."); + if self.stop_signal.receiver_count() <= 1 { + debug!(target: "minerd::rpc", "No pending job!"); + return + } + + info!(target: "minerd::abort_pending", "Pending job is in progress, sending stop signal..."); + // Send stop signal to worker + if let Err(e) = self.sender.try_send(()) { + error!(target: "minerd::abort_pending", "Failed to stop pending job: {e}"); + return + } + + // Wait for worker to terminate + info!(target: "minerd::abort_pending", "Waiting for job to terminate..."); + while self.stop_signal.receiver_count() > 1 { + sleep(1).await; + } + info!(target: "minerd::abort_pending", "Pending job terminated!"); + + // Consume channel item so its empty again + if let Err(e) = self.stop_signal.try_recv() { + error!(target: "minerd::abort_pending", "Failed to cleanup stop signal channel: {e}"); + } } } @@ -80,77 +136,68 @@ pub type MinerdPtr = Arc; pub struct Minerd { /// Miner node instance conducting the mining operations node: MinerNodePtr, - /// JSON-RPC background task - rpc_task: StoppableTaskPtr, + /// Miner darkfid polling background task + polling_task: StoppableTaskPtr, } impl Minerd { /// Initialize a DarkFi mining daemon. /// - /// Corresponding communication channels are setup to generate a new `MinerNode`, - /// and a new task is generated to handle the JSON-RPC API. - pub fn init(threads: usize, stop_at_height: u32) -> MinerdPtr { + /// Generate a new `MinerNode` and a new task to handle the darkfid + /// polling. + pub async fn init(config: MinerNodeConfig, endpoint: Url, ex: &ExecutorPtr) -> MinerdPtr { info!(target: "minerd::Minerd::init", "Initializing a new mining daemon..."); - // Initialize the smol channels to send signal between the threads - let (sender, stop_signal) = smol::channel::bounded(1); - // Generate the node - let node = MinerNode::new(threads, stop_at_height, sender, stop_signal); + let node = MinerNode::new(config, endpoint, ex).await; - // Generate the JSON-RPC task - let rpc_task = StoppableTask::new(); + // Generate the polling task + let polling_task = StoppableTask::new(); info!(target: "minerd::Minerd::init", "Mining daemon initialized successfully!"); - Arc::new(Self { node, rpc_task }) + Arc::new(Self { node, polling_task }) } - /// Start the DarkFi mining daemon in the given executor, using the provided JSON-RPC listen url. - pub fn start(&self, executor: &ExecutorPtr, rpc_settings: &RpcSettings) { + /// Start the DarkFi mining daemon in the given executor. + pub fn start(&self, ex: &ExecutorPtr) { info!(target: "minerd::Minerd::start", "Starting mining daemon..."); - // Start the JSON-RPC task - let node_ = self.node.clone(); - self.rpc_task.clone().start( - listen_and_serve(rpc_settings.clone(), self.node.clone(), None, executor.clone()), - |res| async move { + // Start the polling task + self.polling_task.clone().start( + polling_task(self.node.clone(), ex.clone()), + |res| async { match res { - Ok(()) | Err(Error::RpcServerStopped) => node_.stop_connections().await, - Err(e) => error!(target: "minerd::Minerd::start", "Failed starting JSON-RPC server: {e}"), + Ok(()) | Err(Error::DetachedTaskStopped) => { /* Do nothing */ } + Err(e) => { + error!(target: "minerd::Minerd::start", "Failed starting polling task: {e}") + } } }, - Error::RpcServerStopped, - executor.clone(), + Error::DetachedTaskStopped, + ex.clone(), ); info!(target: "minerd::Minerd::start", "Mining daemon started successfully!"); } /// Stop the DarkFi mining daemon. - pub async fn stop(&self) -> Result<()> { + pub async fn stop(&self) { info!(target: "minerd::Minerd::stop", "Terminating mining daemon..."); + // Stop the polling task + info!(target: "minerd::Minerd::stop", "Stopping polling task..."); + self.polling_task.stop().await; + // Stop the mining node info!(target: "minerd::Minerd::stop", "Stopping miner threads..."); - if self.node.stop_signal.is_empty() { - self.node.sender.send(()).await?; - } - while self.node.stop_signal.receiver_count() > 1 { - sleep(1).await; - } + self.node.abort_pending().await; - // Stop the JSON-RPC task - info!(target: "minerd::Minerd::stop", "Stopping JSON-RPC server..."); - self.rpc_task.stop().await; - - // Consume channel item so its empty again - if self.node.stop_signal.is_full() { - self.node.stop_signal.recv().await?; - } + // Close the JSON-RPC client + info!(target: "minerd::Minerd::stop", "Stopping JSON-RPC client..."); + self.node.stop_rpc_client().await; info!(target: "minerd::Minerd::stop", "Mining daemon terminated successfully!"); - Ok(()) } } @@ -158,7 +205,6 @@ impl Minerd { use { darkfi::util::logger::{setup_test_logger, Level}, tracing::warn, - url::Url, }; #[test] @@ -166,7 +212,7 @@ use { /// /// First we initialize a daemon, start it and then perform /// couple of restarts to verify everything works as expected. -fn minerd_programmatic_control() -> Result<()> { +fn minerd_programmatic_control() { // We check this error so we can execute same file tests in parallel, // otherwise second one fails to init logger here. if setup_test_logger( @@ -182,74 +228,36 @@ fn minerd_programmatic_control() -> Result<()> { warn!(target: "minerd_programmatic_control", "Logger already initialized"); } - // Daemon configuration - let threads = 4; - let rpc_settings = - RpcSettings { listen: Url::parse("tcp://127.0.0.1:28467")?, ..RpcSettings::default() }; - // Create an executor and communication signals let ex = Arc::new(smol::Executor::new()); let (signal, shutdown) = smol::channel::unbounded::<()>(); - // Generate a dummy mining job - let target = darkfi::rpc::util::JsonValue::String( - num_bigint::BigUint::from_bytes_le(&[0xFF; 32]).to_string(), - ); - let block = darkfi::rpc::util::JsonValue::String(darkfi::util::encoding::base64::encode( - &darkfi_serial::serialize(&darkfi::blockchain::BlockInfo::default()), - )); - let mining_job = darkfi::rpc::jsonrpc::JsonRequest::new( - "mine", - darkfi::rpc::util::JsonValue::Array(vec![target, block]), - ); - - easy_parallel::Parallel::new() - .each(0..threads, |_| smol::block_on(ex.run(shutdown.recv()))) - .finish(|| { + easy_parallel::Parallel::new().each(0..1, |_| smol::block_on(ex.run(shutdown.recv()))).finish( + || { smol::block_on(async { // Initialize a daemon - let daemon = Minerd::init(threads, 0); - - // Start it - daemon.start(&ex, &rpc_settings); - - // Generate a JSON-RPC client to send mining jobs - let mut rpc_client = - darkfi::rpc::client::RpcClient::new(rpc_settings.listen.clone(), ex.clone()) - .await; - while rpc_client.is_err() { - rpc_client = darkfi::rpc::client::RpcClient::new( - rpc_settings.listen.clone(), - ex.clone(), - ) - .await; - } - let rpc_client = rpc_client.unwrap(); - - // Send a mining job but stop the daemon after it starts mining - smol::future::or( - async { - let _ = rpc_client.request(mining_job).await; - }, - async { - // Wait node to start mining - darkfi::system::sleep(2).await; - daemon.stop().await.unwrap(); - }, + let daemon = Minerd::init( + MinerNodeConfig::default(), + Url::parse("tcp://127.0.0.1:12345").unwrap(), + &ex, ) .await; - rpc_client.stop().await; - // Start it again - daemon.start(&ex, &rpc_settings); + // Start it + daemon.start(&ex); // Stop it - daemon.stop().await.unwrap(); + daemon.stop().await; + + // Start it again + daemon.start(&ex); + + // Stop it + daemon.stop().await; // Shutdown entirely drop(signal); }) - }); - - Ok(()) + }, + ); } diff --git a/bin/minerd/src/main.rs b/bin/minerd/src/main.rs index 266a708ed..d80a74be9 100644 --- a/bin/minerd/src/main.rs +++ b/bin/minerd/src/main.rs @@ -16,15 +16,23 @@ * along with this program. If not, see . */ -use std::sync::Arc; +use std::{collections::HashMap, str::FromStr}; -use smol::{stream::StreamExt, Executor}; +use smol::{fs::read_to_string, stream::StreamExt}; use structopt_toml::{serde::Deserialize, structopt::StructOpt, StructOptToml}; -use tracing::info; +use tracing::{debug, error, info}; +use url::Url; -use darkfi::{async_daemonize, cli_desc, rpc::settings::RpcSettingsOpt, Error, Result}; +use darkfi::{ + async_daemonize, cli_desc, rpc::util::JsonValue, system::ExecutorPtr, + util::path::get_config_path, Error, Result, +}; +use darkfi_sdk::{ + crypto::{pasta_prelude::PrimeField, FuncId, PublicKey}, + pasta::pallas, +}; -use minerd::Minerd; +use minerd::{MinerNodeConfig, Minerd}; const CONFIG_FILE: &str = "minerd.toml"; const CONFIG_FILE_CONTENTS: &str = include_str!("../minerd.toml"); @@ -37,18 +45,22 @@ struct Args { /// Configuration file to use config: Option, - #[structopt(flatten)] - /// JSON-RPC settings - rpc: RpcSettingsOpt, - #[structopt(short, long, default_value = "4")] /// PoW miner number of threads to use threads: usize, + #[structopt(short, long, default_value = "2")] + /// Polling rate to ask darkfid for mining jobs + polling_rate: u64, + #[structopt(long, default_value = "0")] - /// Refuse mining at given height (0 mines forever) + /// Stop mining at given height (0 mines forever) stop_at_height: u32, + #[structopt(short, long, default_value = "testnet")] + /// Blockchain network to use + network: String, + #[structopt(short, long)] /// Set log file to ouput into log: Option, @@ -58,18 +70,131 @@ struct Args { verbose: u8, } +#[derive(Clone, Debug, Deserialize, StructOpt, StructOptToml)] +#[structopt()] +/// Defines a blockchain network configuration. +/// Default values correspond to a local network. +pub struct BlockchainNetwork { + #[structopt(short, long, default_value = "tcp://127.0.0.1:8240")] + /// darkfid JSON-RPC endpoint + endpoint: Url, + + #[structopt(long, default_value = "")] + /// Wallet mining address to receive mining rewards + recipient: String, + + #[structopt(long)] + /// Optional contract spend hook to use in the mining reward + spend_hook: Option, + + #[structopt(long)] + /// Optional contract user data to use in the mining reward. + /// This is not arbitrary data. + user_data: Option, +} + +/// Auxiliary function to parse minerd configuration file and extract +/// requested blockchain network config. +pub async fn parse_blockchain_config( + config: Option, + network: &str, +) -> Result { + // Grab config path + let config_path = get_config_path(config, CONFIG_FILE)?; + debug!(target: "minerd", "Parsing configuration file: {config_path:?}"); + + // Parse TOML file contents + let contents = read_to_string(&config_path).await?; + let contents: toml::Value = match toml::from_str(&contents) { + Ok(v) => v, + Err(e) => { + error!(target: "minerd", "Failed parsing TOML config: {e}"); + return Err(Error::ParseFailed("Failed parsing TOML config")) + } + }; + + // Grab requested network config + let Some(table) = contents.as_table() else { return Err(Error::ParseFailed("TOML not a map")) }; + let Some(network_configs) = table.get("network_config") else { + return Err(Error::ParseFailed("TOML does not contain network configurations")) + }; + let Some(network_configs) = network_configs.as_table() else { + return Err(Error::ParseFailed("`network_config` not a map")) + }; + let Some(network_config) = network_configs.get(network) else { + return Err(Error::ParseFailed("TOML does not contain requested network configuration")) + }; + let network_config = toml::to_string(&network_config).unwrap(); + let network_config = + match BlockchainNetwork::from_iter_with_toml::>(&network_config, vec![]) { + Ok(v) => v, + Err(e) => { + error!(target: "minerd", "Failed parsing requested network configuration: {e}"); + return Err(Error::ParseFailed("Failed parsing requested network configuration")) + } + }; + debug!(target: "minerd", "Parsed network configuration: {network_config:?}"); + + Ok(network_config) +} + async_daemonize!(realmain); -async fn realmain(args: Args, ex: Arc>) -> Result<()> { +async fn realmain(args: Args, ex: ExecutorPtr) -> Result<()> { info!(target: "minerd", "Starting DarkFi Mining Daemon..."); - let daemon = Minerd::init(args.threads, args.stop_at_height); - daemon.start(&ex, &args.rpc.into()); + + // Grab blockchain network configuration + let blockchain_config = match args.network.as_str() { + "localnet" => parse_blockchain_config(args.config, "localnet").await?, + "testnet" => parse_blockchain_config(args.config, "testnet").await?, + "mainnet" => parse_blockchain_config(args.config, "mainnet").await?, + _ => { + error!(target: "minerd", "Unsupported chain `{}`", args.network); + return Err(Error::UnsupportedChain) + } + }; + debug!(target: "minerd", "Blockchain config: {blockchain_config:?}"); + + // Parse the network wallet configuration + if PublicKey::from_str(&blockchain_config.recipient).is_err() { + return Err(Error::InvalidAddress) + } + let mut wallet_config = HashMap::from([( + String::from("recipient"), + JsonValue::String(blockchain_config.recipient), + )]); + + if let Some(spend_hook) = &blockchain_config.spend_hook { + if FuncId::from_str(spend_hook).is_err() { + return Err(Error::ParseFailed("Invalid spend hook")) + } + wallet_config.insert(String::from("spend_hook"), JsonValue::String(spend_hook.to_string())); + } + + if let Some(user_data_string) = &blockchain_config.user_data { + let bytes: [u8; 32] = match bs58::decode(&user_data_string).into_vec()?.try_into() { + Ok(b) => b, + Err(_) => return Err(Error::ParseFailed("Invalid user data")), + }; + let user_data: Option = pallas::Base::from_repr(bytes).into(); + if user_data.is_none() { + return Err(Error::ParseFailed("Invalid user data")) + } + wallet_config + .insert(String::from("user_data"), JsonValue::String(user_data_string.to_string())); + } + + // Generate the daemon + let miner_config = + MinerNodeConfig::new(args.threads, args.polling_rate, args.stop_at_height, wallet_config); + let daemon = Minerd::init(miner_config, blockchain_config.endpoint, &ex).await; + daemon.start(&ex); // 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"); - daemon.stop().await?; + daemon.stop().await; info!(target: "minerd", "Shut down successfully"); Ok(()) diff --git a/bin/minerd/src/rpc.rs b/bin/minerd/src/rpc.rs index 64213aca1..57a5c7d4c 100644 --- a/bin/minerd/src/rpc.rs +++ b/bin/minerd/src/rpc.rs @@ -16,162 +16,266 @@ * along with this program. If not, see . */ -use std::collections::HashSet; - use num_bigint::BigUint; -use smol::lock::MutexGuard; use tracing::{debug, error, info}; +use url::Url; use darkfi::{ - blockchain::header_store::{Header, HeaderHash}, - rpc::{ - jsonrpc::{ErrorCode, JsonError, JsonRequest, JsonResponse, JsonResult}, - server::RequestHandler, - util::JsonValue, - }, - system::{sleep, StoppableTaskPtr}, + blockchain::{Header, HeaderHash}, + rpc::{client::RpcClient, jsonrpc::JsonRequest, util::JsonValue}, + system::{sleep, ExecutorPtr, StoppableTask}, util::encoding::base64, validator::pow::mine_block, + Error, Result, }; -use darkfi_serial::{async_trait, deserialize_async}; +use darkfi_serial::deserialize_async; -use crate::{ - error::{server_error, RpcError}, - MinerNode, -}; +use crate::{MinerNode, MinerNodePtr}; -#[async_trait] -impl RequestHandler<()> for MinerNode { - async fn handle_request(&self, req: JsonRequest) -> JsonResult { - debug!(target: "minerd::rpc", "--> {}", req.stringify().unwrap()); +/// 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, +} - 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(), - } +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 } } - async fn connections_mut(&self) -> MutexGuard<'life0, HashSet> { - self.rpc_connections.lock().await + /// Stop the client. + pub async fn stop(&self) { + if let Some(ref client) = self.client { + client.stop().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() + /// 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::>().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::().unwrap()) else { + error!(target: "minerd::rpc::poll", "Failed to parse RandomX key bytes"); + self.sleep().await?; + continue + }; + let Ok(randomx_key) = deserialize_async::(&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::().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::().unwrap()) else { + error!(target: "minerd::rpc::poll", "Failed to parse header bytes"); + self.sleep().await?; + continue + }; + let Ok(header) = deserialize_async::
(&header_bytes).await else { + error!(target: "minerd::rpc::poll", "Failed to parse header"); + self.sleep().await?; + continue + }; + + return Ok((randomx_key, target, header)) + } } - // RPCAPI: - // Mine provided block header for requested mine target, using - // provided RandomX VM key, and return the corresponding nonce - // value. - // - // --> {"jsonrpc": "2.0", "method": "mine", "params": ["target", "randomx_key", "header"], "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() != 3 || - !params[0].is_string() || - !params[1].is_string() || - !params[2].is_string() + /// 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 { - return JsonError::new(ErrorCode::InvalidParams, None, id).into() + Ok(result) => result, + Err(e) => return format!("darkfid submit failed: {e}"), + }; + debug!(target: "minerd::rpc::submit", "Got reply: {result:?}"); + + // Parse response + match result.get::() { + Some(result) => result.clone(), + None => format!("darkfid responded with invalid params: {result:?}"), } - - // Parse parameters - let Some(target_bytes) = base64::decode(params[0].get::().unwrap()) else { - error!(target: "minerd::rpc", "Failed to parse target bytes"); - return server_error(RpcError::TargetParseError, id, None) - }; - let target = BigUint::from_bytes_le(&target_bytes); - let Some(randomx_key_bytes) = base64::decode(params[1].get::().unwrap()) else { - error!(target: "minerd::rpc", "Failed to parse RandomX key bytes"); - return server_error(RpcError::BlockParseError, id, None) - }; - let Ok(randomx_key) = deserialize_async::(&randomx_key_bytes).await else { - error!(target: "minerd::rpc", "Failed to parse RandomX key"); - return server_error(RpcError::BlockParseError, id, None) - }; - let Some(header_bytes) = base64::decode(params[2].get::().unwrap()) else { - error!(target: "minerd::rpc", "Failed to parse header bytes"); - return server_error(RpcError::BlockParseError, id, None) - }; - let Ok(mut header) = deserialize_async::
(&header_bytes).await else { - error!(target: "minerd::rpc", "Failed to parse header"); - return server_error(RpcError::BlockParseError, id, None) - }; - let header_hash = header.hash(); - info!(target: "minerd::rpc", "Received request to mine block header {header_hash} with key {randomx_key} for target: {target}"); - - // If we have a requested mining height, we'll keep dropping here. - if self.stop_at_height > 0 && header.height >= self.stop_at_height { - info!(target: "minerd::rpc", "Reached requested mining height {}", self.stop_at_height); - return server_error(RpcError::MiningFailed, id, None) - } - - // Check if another request is being processed - if let Some(e) = self.abort_pending(id).await { - return e - }; - - // Mine provided block header - info!(target: "minerd::rpc", "Mining block header {header_hash} with key {randomx_key} for target: {target}"); - if let Err(e) = - mine_block(&target, &randomx_key, &mut header, self.threads, &self.stop_signal.clone()) - { - error!(target: "minerd::rpc", "Failed mining block header {header_hash} with error: {e}"); - return server_error(RpcError::MiningFailed, id, None) - } - info!(target: "minerd::rpc", "Mined block header {header_hash} with nonce: {}", header.nonce); - - // Return block header nonce - JsonResponse::new(JsonValue::Number(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() <= 1 { - info!(target: "minerd::rpc", "No pending requests!"); - return None + /// 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 { + 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); + } } - 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)) - } + // 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) + } - // 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!"); + /// 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; + } - // 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)) + /// Auxiliary function to sleep for configured polling rate time. + async fn sleep(&self) -> Result<()> { + // Check if stop signal is received + if self.stop_signal.is_full() { + debug!(target: "minerd::rpc::sleep", "Stop signal received, exiting polling task"); + return Err(Error::DetachedTaskStopped); } - - None + 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<()> { + // Initialize a dummy Header to use on first poll + let mut current_job = Header::default().hash().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 job + miner.abort_pending().await; + + // Detach mining task + StoppableTask::new().start( + mining_task(miner.clone(), randomx_key, 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, + randomx_key: HeaderHash, + 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} with key {randomx_key} for target: 0x{target:064x}"); + if let Err(e) = mine_block( + &randomx_key, + &target, + &mut header, + miner.config.threads, + &miner.stop_signal.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(()) +} diff --git a/contrib/localnet/darkfid-five-nodes/darkfid0.toml b/contrib/localnet/darkfid-five-nodes/darkfid0.toml index e9c9cdc63..c2a2db2e5 100644 --- a/contrib/localnet/darkfid-five-nodes/darkfid0.toml +++ b/contrib/localnet/darkfid-five-nodes/darkfid0.toml @@ -17,26 +17,11 @@ database = "darkfid0" # Confirmation threshold, denominated by number of blocks threshold = 6 -# minerd JSON-RPC endpoint -minerd_endpoint = "tcp://127.0.0.1:48467" - # PoW block production target, in seconds pow_target = 60 -# Participate in block production -miner = true - -# Wallet address to receive mining rewards. -# This is a dummy one so the miner can start, -# replace with your own one. -recipient = "9vw6WznKk7xEFQwwXhJWMMdjUPi3cXL8NrFKQpKifG1U" - -# Optional contract spend hook to use in the mining reward -#spend_hook = "YOUR_SPEND_HOOK_HERE" - -# Optional contract user data to use in the mining reward. -# This is not arbitrary data. -#user_data = "YOUR_USER_DATA_HERE" +# Optional fixed PoW difficulty, used for testing +#pow_fixed_difficulty = 1 # Skip syncing process and start node right away skip_sync = true diff --git a/contrib/localnet/darkfid-five-nodes/darkfid1.toml b/contrib/localnet/darkfid-five-nodes/darkfid1.toml index 932598b93..398c933e4 100644 --- a/contrib/localnet/darkfid-five-nodes/darkfid1.toml +++ b/contrib/localnet/darkfid-five-nodes/darkfid1.toml @@ -17,23 +17,11 @@ database = "darkfid1" # Confirmation threshold, denominated by number of blocks threshold = 6 -# minerd JSON-RPC endpoint -minerd_endpoint = "tcp://127.0.0.1:48567" - # PoW block production target, in seconds pow_target = 60 -# Wallet address to receive mining rewards. -# This is a dummy one so the miner can start, -# replace with your own one. -recipient = "9vw6WznKk7xEFQwwXhJWMMdjUPi3cXL8NrFKQpKifG1U" - -# Optional contract spend hook to use in the mining reward -#spend_hook = "YOUR_SPEND_HOOK_HERE" - -# Optional contract user data to use in the mining reward. -# This is not arbitrary data. -#user_data = "YOUR_USER_DATA_HERE" +# Optional fixed PoW difficulty, used for testing +#pow_fixed_difficulty = 1 # Skip syncing process and start node right away skip_sync = false diff --git a/contrib/localnet/darkfid-five-nodes/darkfid2.toml b/contrib/localnet/darkfid-five-nodes/darkfid2.toml index 73ec30f03..caca07386 100644 --- a/contrib/localnet/darkfid-five-nodes/darkfid2.toml +++ b/contrib/localnet/darkfid-five-nodes/darkfid2.toml @@ -17,23 +17,11 @@ database = "darkfid2" # Confirmation threshold, denominated by number of blocks threshold = 6 -# minerd JSON-RPC endpoint -minerd_endpoint = "tcp://127.0.0.1:48667" - # PoW block production target, in seconds pow_target = 60 -# Wallet address to receive mining rewards. -# This is a dummy one so the miner can start, -# replace with your own one. -recipient = "9vw6WznKk7xEFQwwXhJWMMdjUPi3cXL8NrFKQpKifG1U" - -# Optional contract spend hook to use in the mining reward -#spend_hook = "YOUR_SPEND_HOOK_HERE" - -# Optional contract user data to use in the mining reward. -# This is not arbitrary data. -#user_data = "YOUR_USER_DATA_HERE" +# Optional fixed PoW difficulty, used for testing +#pow_fixed_difficulty = 1 # Skip syncing process and start node right away skip_sync = false diff --git a/contrib/localnet/darkfid-five-nodes/darkfid3.toml b/contrib/localnet/darkfid-five-nodes/darkfid3.toml index 22957b101..785393f66 100644 --- a/contrib/localnet/darkfid-five-nodes/darkfid3.toml +++ b/contrib/localnet/darkfid-five-nodes/darkfid3.toml @@ -17,23 +17,11 @@ database = "darkfid3" # Confirmation threshold, denominated by number of blocks threshold = 6 -# minerd JSON-RPC endpoint -minerd_endpoint = "tcp://127.0.0.1:48767" - # PoW block production target, in seconds pow_target = 60 -# Wallet address to receive mining rewards. -# This is a dummy one so the miner can start, -# replace with your own one. -recipient = "9vw6WznKk7xEFQwwXhJWMMdjUPi3cXL8NrFKQpKifG1U" - -# Optional contract spend hook to use in the mining reward -#spend_hook = "YOUR_SPEND_HOOK_HERE" - -# Optional contract user data to use in the mining reward. -# This is not arbitrary data. -#user_data = "YOUR_USER_DATA_HERE" +# Optional fixed PoW difficulty, used for testing +#pow_fixed_difficulty = 1 # Skip syncing process and start node right away skip_sync = false diff --git a/contrib/localnet/darkfid-five-nodes/darkfid4.toml b/contrib/localnet/darkfid-five-nodes/darkfid4.toml index 72b51a9d5..0dfb5a435 100644 --- a/contrib/localnet/darkfid-five-nodes/darkfid4.toml +++ b/contrib/localnet/darkfid-five-nodes/darkfid4.toml @@ -17,23 +17,11 @@ database = "darkfid4" # Confirmation threshold, denominated by number of blocks threshold = 6 -# minerd JSON-RPC endpoint -minerd_endpoint = "tcp://127.0.0.1:48867" - # PoW block production target, in seconds pow_target = 60 -# Wallet address to receive mining rewards. -# This is a dummy one so the miner can start, -# replace with your own one. -recipient = "9vw6WznKk7xEFQwwXhJWMMdjUPi3cXL8NrFKQpKifG1U" - -# Optional contract spend hook to use in the mining reward -#spend_hook = "YOUR_SPEND_HOOK_HERE" - -# Optional contract user data to use in the mining reward. -# This is not arbitrary data. -#user_data = "YOUR_USER_DATA_HERE" +# Optional fixed PoW difficulty, used for testing +#pow_fixed_difficulty = 1 # Skip syncing process and start node right away skip_sync = false diff --git a/contrib/localnet/darkfid-five-nodes/minerd0.toml b/contrib/localnet/darkfid-five-nodes/minerd0.toml index a0a979221..636791901 100644 --- a/contrib/localnet/darkfid-five-nodes/minerd0.toml +++ b/contrib/localnet/darkfid-five-nodes/minerd0.toml @@ -9,10 +9,28 @@ # PoW miner number of threads to use threads = 1 -# JSON-RPC settings -[rpc] -# JSON-RPC listen URL -rpc_listen = "tcp://127.0.0.1:48467" +# Polling rate to ask darkfid for mining jobs +#polling_rate = 2 -# Disabled RPC methods -#rpc_disabled_methods = [] +# Stop mining at given height (0 mines forever) +#stop_at_height = 0 + +# Blockchain network to use +network = "localnet" + +# Localnet blockchain network configuration +[network_config."localnet"] +# Wallet mining address to receive mining rewards. +# This is a dummy one so the miner can start, +# replace with your own one. +recipient = "9vw6WznKk7xEFQwwXhJWMMdjUPi3cXL8NrFKQpKifG1U" + +# Optional contract spend hook to use in the mining reward +#spend_hook = "YOUR_SPEND_HOOK_HERE" + +# Optional contract user data to use in the mining reward. +# This is not arbitrary data. +#user_data = "YOUR_USER_DATA_HERE" + +# darkfid JSON-RPC endpoint +endpoint = "tcp://127.0.0.1:48240" diff --git a/contrib/localnet/darkfid-five-nodes/minerd1.toml b/contrib/localnet/darkfid-five-nodes/minerd1.toml index eee26be1f..d4b535cb5 100644 --- a/contrib/localnet/darkfid-five-nodes/minerd1.toml +++ b/contrib/localnet/darkfid-five-nodes/minerd1.toml @@ -9,10 +9,28 @@ # PoW miner number of threads to use threads = 1 -# JSON-RPC settings -[rpc] -# JSON-RPC listen URL -rpc_listen = "tcp://127.0.0.1:48567" +# Polling rate to ask darkfid for mining jobs +#polling_rate = 2 -# Disabled RPC methods -#rpc_disabled_methods = [] +# Stop mining at given height (0 mines forever) +#stop_at_height = 0 + +# Blockchain network to use +network = "localnet" + +# Localnet blockchain network configuration +[network_config."localnet"] +# Wallet mining address to receive mining rewards. +# This is a dummy one so the miner can start, +# replace with your own one. +recipient = "9vw6WznKk7xEFQwwXhJWMMdjUPi3cXL8NrFKQpKifG1U" + +# Optional contract spend hook to use in the mining reward +#spend_hook = "YOUR_SPEND_HOOK_HERE" + +# Optional contract user data to use in the mining reward. +# This is not arbitrary data. +#user_data = "YOUR_USER_DATA_HERE" + +# darkfid JSON-RPC endpoint +endpoint = "tcp://127.0.0.1:48340" diff --git a/contrib/localnet/darkfid-five-nodes/minerd2.toml b/contrib/localnet/darkfid-five-nodes/minerd2.toml index 237b4b31b..53936dd69 100644 --- a/contrib/localnet/darkfid-five-nodes/minerd2.toml +++ b/contrib/localnet/darkfid-five-nodes/minerd2.toml @@ -9,10 +9,28 @@ # PoW miner number of threads to use threads = 1 -# JSON-RPC settings -[rpc] -# JSON-RPC listen URL -rpc_listen = "tcp://127.0.0.1:48667" +# Polling rate to ask darkfid for mining jobs +#polling_rate = 2 -# Disabled RPC methods -#rpc_disabled_methods = [] +# Stop mining at given height (0 mines forever) +#stop_at_height = 0 + +# Blockchain network to use +network = "localnet" + +# Localnet blockchain network configuration +[network_config."localnet"] +# Wallet mining address to receive mining rewards. +# This is a dummy one so the miner can start, +# replace with your own one. +recipient = "9vw6WznKk7xEFQwwXhJWMMdjUPi3cXL8NrFKQpKifG1U" + +# Optional contract spend hook to use in the mining reward +#spend_hook = "YOUR_SPEND_HOOK_HERE" + +# Optional contract user data to use in the mining reward. +# This is not arbitrary data. +#user_data = "YOUR_USER_DATA_HERE" + +# darkfid JSON-RPC endpoint +endpoint = "tcp://127.0.0.1:48440" diff --git a/contrib/localnet/darkfid-five-nodes/minerd3.toml b/contrib/localnet/darkfid-five-nodes/minerd3.toml index 9583e67c2..418a3c293 100644 --- a/contrib/localnet/darkfid-five-nodes/minerd3.toml +++ b/contrib/localnet/darkfid-five-nodes/minerd3.toml @@ -9,10 +9,28 @@ # PoW miner number of threads to use threads = 1 -# JSON-RPC settings -[rpc] -# JSON-RPC listen URL -rpc_listen = "tcp://127.0.0.1:48767" +# Polling rate to ask darkfid for mining jobs +#polling_rate = 2 -# Disabled RPC methods -#rpc_disabled_methods = [] +# Stop mining at given height (0 mines forever) +#stop_at_height = 0 + +# Blockchain network to use +network = "localnet" + +# Localnet blockchain network configuration +[network_config."localnet"] +# Wallet mining address to receive mining rewards. +# This is a dummy one so the miner can start, +# replace with your own one. +recipient = "9vw6WznKk7xEFQwwXhJWMMdjUPi3cXL8NrFKQpKifG1U" + +# Optional contract spend hook to use in the mining reward +#spend_hook = "YOUR_SPEND_HOOK_HERE" + +# Optional contract user data to use in the mining reward. +# This is not arbitrary data. +#user_data = "YOUR_USER_DATA_HERE" + +# darkfid JSON-RPC endpoint +endpoint = "tcp://127.0.0.1:48540" diff --git a/contrib/localnet/darkfid-five-nodes/minerd4.toml b/contrib/localnet/darkfid-five-nodes/minerd4.toml index cc182cf2a..aaf992abf 100644 --- a/contrib/localnet/darkfid-five-nodes/minerd4.toml +++ b/contrib/localnet/darkfid-five-nodes/minerd4.toml @@ -9,10 +9,28 @@ # PoW miner number of threads to use threads = 1 -# JSON-RPC settings -[rpc] -# JSON-RPC listen URL -rpc_listen = "tcp://127.0.0.1:48867" +# Polling rate to ask darkfid for mining jobs +#polling_rate = 2 -# Disabled RPC methods -#rpc_disabled_methods = [] +# Stop mining at given height (0 mines forever) +#stop_at_height = 0 + +# Blockchain network to use +network = "localnet" + +# Localnet blockchain network configuration +[network_config."localnet"] +# Wallet mining address to receive mining rewards. +# This is a dummy one so the miner can start, +# replace with your own one. +recipient = "9vw6WznKk7xEFQwwXhJWMMdjUPi3cXL8NrFKQpKifG1U" + +# Optional contract spend hook to use in the mining reward +#spend_hook = "YOUR_SPEND_HOOK_HERE" + +# Optional contract user data to use in the mining reward. +# This is not arbitrary data. +#user_data = "YOUR_USER_DATA_HERE" + +# darkfid JSON-RPC endpoint +endpoint = "tcp://127.0.0.1:48640" diff --git a/contrib/localnet/darkfid-five-nodes/tmux_sessions.sh b/contrib/localnet/darkfid-five-nodes/tmux_sessions.sh index 3e7fd0710..9d6507df4 100755 --- a/contrib/localnet/darkfid-five-nodes/tmux_sessions.sh +++ b/contrib/localnet/darkfid-five-nodes/tmux_sessions.sh @@ -17,32 +17,32 @@ else fi tmux new-session -d -s $session -n "node0" -tmux send-keys -t $session "$MINERD $verbose -c minerd0.toml" Enter +tmux send-keys -t $session "$MINERD -c minerd0.toml $verbose" Enter sleep 1 tmux split-window -t $session -v -l 90% -tmux send-keys -t $session "$DARKFID $verbose -c darkfid0.toml" Enter +tmux send-keys -t $session "$DARKFID -c darkfid0.toml $verbose" Enter sleep 2 tmux new-window -t $session -n "node1" -tmux send-keys -t $session "$MINERD $verbose -c minerd1.toml" Enter +tmux send-keys -t $session "$MINERD -c minerd1.toml $verbose" Enter sleep 1 tmux split-window -t $session -v -l 90% -tmux send-keys -t $session "$DARKFID $verbose -c darkfid1.toml" Enter +tmux send-keys -t $session "$DARKFID -c darkfid1.toml $verbose" Enter sleep 2 tmux new-window -t $session -n "node2" -tmux send-keys -t $session "$MINERD $verbose -c minerd2.toml" Enter +tmux send-keys -t $session "$MINERD -c minerd2.toml $verbose" Enter sleep 1 tmux split-window -t $session -v -l 90% -tmux send-keys -t $session "$DARKFID $verbose -c darkfid2.toml" Enter +tmux send-keys -t $session "$DARKFID -c darkfid2.toml $verbose" Enter sleep 2 tmux new-window -t $session -n "node3" -tmux send-keys -t $session "$MINERD $verbose -c minerd3.toml" Enter +tmux send-keys -t $session "$MINERD -c minerd3.toml $verbose" Enter sleep 1 tmux split-window -t $session -v -l 90% -tmux send-keys -t $session "$DARKFID $verbose -c darkfid3.toml" Enter +tmux send-keys -t $session "$DARKFID -c darkfid3.toml $verbose" Enter sleep 2 tmux new-window -t $session -n "node4" -tmux send-keys -t $session "$MINERD $verbose -c minerd4.toml" Enter +tmux send-keys -t $session "$MINERD -c minerd4.toml $verbose" Enter sleep 1 tmux split-window -t $session -v -l 90% -tmux send-keys -t $session "$DARKFID $verbose -c darkfid4.toml" Enter +tmux send-keys -t $session "$DARKFID -c darkfid4.toml $verbose" Enter tmux attach -t $session diff --git a/contrib/localnet/darkfid-single-node/README.md b/contrib/localnet/darkfid-single-node/README.md index 94a9b5b51..725b826e3 100644 --- a/contrib/localnet/darkfid-single-node/README.md +++ b/contrib/localnet/darkfid-single-node/README.md @@ -9,12 +9,12 @@ a testing wallet and pass its address to the `darkfid` config, so the wallet gets the block rewards the node produces. We generate a wallet, set it as the default and set its address as the `recipient` field in -`darkfid.toml`, using the porvided automated script: +`minerd.toml`, using the porvided automated script: ``` % ./init-wallet.sh ``` -Then start `darkfid` and wait until its initialized: +Then start the daemones and wait until `darkfid` is initialized: ``` % ./tmux_sessions.sh ``` diff --git a/contrib/localnet/darkfid-single-node/clean.sh b/contrib/localnet/darkfid-single-node/clean.sh index d626ffd5f..94ef9e729 100755 --- a/contrib/localnet/darkfid-single-node/clean.sh +++ b/contrib/localnet/darkfid-single-node/clean.sh @@ -1,3 +1,3 @@ #!/bin/sh rm -rf darkfid drk -sed -i -e "s|recipient =.*|recipient = \"9vw6WznKk7xEFQwwXhJWMMdjUPi3cXL8NrFKQpKifG1U\"|g" darkfid.toml +sed -i -e "s|recipient =.*|recipient = \"9vw6WznKk7xEFQwwXhJWMMdjUPi3cXL8NrFKQpKifG1U\"|g" minerd.toml diff --git a/contrib/localnet/darkfid-single-node/darkfid.toml b/contrib/localnet/darkfid-single-node/darkfid.toml index b5114c4e2..c2ce34048 100644 --- a/contrib/localnet/darkfid-single-node/darkfid.toml +++ b/contrib/localnet/darkfid-single-node/darkfid.toml @@ -17,27 +17,12 @@ database = "darkfid" # Confirmation threshold, denominated by number of blocks threshold = 1 -# minerd JSON-RPC endpoint -minerd_endpoint = "tcp://127.0.0.1:48467" - # PoW block production target, in seconds pow_target = 10 # Optional fixed PoW difficulty, used for testing pow_fixed_difficulty = 1 -# Wallet address to receive mining rewards. -# This is a dummy one so the miner can start, -# replace with your own one. -recipient = "9vw6WznKk7xEFQwwXhJWMMdjUPi3cXL8NrFKQpKifG1U" - -# Optional contract spend hook to use in the mining reward -#spend_hook = "6iW9nywZYvyhcM7P1iLwYkh92rvYtREDsC8hgqf2GLuT" - -# Optional contract user data to use in the mining reward. -# This is not arbitrary data. -#user_data = "YOUR_USER_DATA_HERE" - # Skip syncing process and start node right away skip_sync = true diff --git a/contrib/localnet/darkfid-single-node/init-wallet.sh b/contrib/localnet/darkfid-single-node/init-wallet.sh index a94d592dd..24cbe3b83 100755 --- a/contrib/localnet/darkfid-single-node/init-wallet.sh +++ b/contrib/localnet/darkfid-single-node/init-wallet.sh @@ -7,4 +7,4 @@ $DRK wallet initialize $DRK wallet keygen $DRK wallet default-address 1 wallet=$($DRK wallet address) -sed -i -e "s|9vw6WznKk7xEFQwwXhJWMMdjUPi3cXL8NrFKQpKifG1U|$wallet|g" darkfid.toml +sed -i -e "s|9vw6WznKk7xEFQwwXhJWMMdjUPi3cXL8NrFKQpKifG1U|$wallet|g" minerd.toml diff --git a/contrib/localnet/darkfid-single-node/minerd.toml b/contrib/localnet/darkfid-single-node/minerd.toml index 755e7733d..349499ba4 100644 --- a/contrib/localnet/darkfid-single-node/minerd.toml +++ b/contrib/localnet/darkfid-single-node/minerd.toml @@ -9,10 +9,30 @@ # PoW miner number of threads to use #threads = 4 -# JSON-RPC settings -[rpc] -# JSON-RPC listen URL -rpc_listen = "tcp://127.0.0.1:48467" +# Polling rate to ask darkfid for mining jobs +#polling_rate = 2 -# Disabled RPC methods -#rpc_disabled_methods = [] +# Stop mining at given height (0 mines forever) +#stop_at_height = 0 + +# Blockchain network to use +network = "localnet" + +# Localnet blockchain network configuration +[network_config."localnet"] +# Wallet mining address to receive mining rewards. +# This is a dummy one so the miner can start, +# replace with your own one. +recipient = "9vw6WznKk7xEFQwwXhJWMMdjUPi3cXL8NrFKQpKifG1U" + +# Optional contract spend hook to use in the mining reward. +# This is the DAO spend hook set for convinience, +# replace with your own one. +#spend_hook = "6iW9nywZYvyhcM7P1iLwYkh92rvYtREDsC8hgqf2GLuT" + +# Optional contract user data to use in the mining reward. +# This is not arbitrary data. +#user_data = "YOUR_USER_DATA_HERE" + +# darkfid JSON-RPC endpoint +endpoint = "tcp://127.0.0.1:48240" diff --git a/contrib/localnet/darkfid-single-node/run-dao-test.sh b/contrib/localnet/darkfid-single-node/run-dao-test.sh index 45d31f1a6..4327c763e 100755 --- a/contrib/localnet/darkfid-single-node/run-dao-test.sh +++ b/contrib/localnet/darkfid-single-node/run-dao-test.sh @@ -52,30 +52,30 @@ wait_token() { } mint_dao() { - $DRK dao create 20 10 10 0.67 MLDY > $OUTPUT_FOLDER/dao.toml - $DRK dao import MiladyMakerDAO < $OUTPUT_FOLDER/dao.toml + $DRK dao create 20 10 10 0.67 ANON > $OUTPUT_FOLDER/dao.toml + $DRK dao import AnonDAO < $OUTPUT_FOLDER/dao.toml $DRK dao list - $DRK dao list MiladyMakerDAO + $DRK dao list AnonDAO - $DRK dao mint MiladyMakerDAO | tee $OUTPUT_FOLDER/dao-mint.tx | $DRK broadcast + $DRK dao mint AnonDAO | tee $OUTPUT_FOLDER/dao-mint.tx | $DRK broadcast } wait_dao_mint() { - while [ "$($DRK dao list MiladyMakerDAO | grep '^Transaction hash: ' | awk '{print $3}')" = None ]; do + while [ "$($DRK dao list AnonDAO | grep '^Transaction hash: ' | awk '{print $3}')" = None ]; do sleep $SLEEP_TIME sh ./sync-wallet.sh > /dev/null done } fill_treasury() { - PUBKEY="$($DRK dao list MiladyMakerDAO | grep '^Notes Public key: ' | cut -d ' ' -f4)" + PUBKEY="$($DRK dao list AnonDAO | grep '^Notes Public key: ' | cut -d ' ' -f4)" SPEND_HOOK="$($DRK dao spend-hook)" - BULLA="$($DRK dao list MiladyMakerDAO | grep '^Bulla: ' | cut -d' ' -f2)" - $DRK transfer 20 WCKD "$PUBKEY" "$SPEND_HOOK" "$BULLA" | tee $OUTPUT_FOLDER/xfer.tx | $DRK broadcast + BULLA="$($DRK dao list AnonDAO | grep '^Bulla: ' | cut -d' ' -f2)" + $DRK transfer 20 DAWN "$PUBKEY" "$SPEND_HOOK" "$BULLA" | tee $OUTPUT_FOLDER/xfer.tx | $DRK broadcast } dao_balance() { - BALANCE=$($DRK dao balance MiladyMakerDAO 2>/dev/null) + BALANCE=$($DRK dao balance AnonDAO 2>/dev/null) # No tokens received at all yet if echo "$BALANCE" | grep -q "No unspent balances found"; then echo 0 @@ -94,7 +94,7 @@ dao_balance() { } wait_dao_treasury() { - while [ "$(dao_balance WCKD)" = 0 ]; do + while [ "$(dao_balance DAWN)" = 0 ]; do sleep $SLEEP_TIME sh ./sync-wallet.sh > /dev/null done @@ -102,12 +102,12 @@ wait_dao_treasury() { propose() { MY_ADDR=$($DRK wallet address) - PROPOSAL="$($DRK dao propose-transfer MiladyMakerDAO 1 5 WCKD "$MY_ADDR" | cut -d' ' -f3)" + PROPOSAL="$($DRK dao propose-transfer AnonDAO 1 5 DAWN "$MY_ADDR" | cut -d' ' -f3)" $DRK dao proposal "$PROPOSAL" --mint-proposal | tee $OUTPUT_FOLDER/propose.tx | $DRK broadcast } wait_proposal() { - PROPOSAL="$($DRK dao proposals MiladyMakerDAO | cut -d' ' -f2)" + PROPOSAL="$($DRK dao proposals AnonDAO | cut -d' ' -f2)" while [ "$($DRK dao proposal $PROPOSAL | grep '^Proposal transaction hash: ' | awk '{print $4}')" = None ]; do sleep $SLEEP_TIME sh ./sync-wallet.sh > /dev/null @@ -115,12 +115,12 @@ wait_proposal() { } vote() { - PROPOSAL="$($DRK dao proposals MiladyMakerDAO | cut -d' ' -f2)" + PROPOSAL="$($DRK dao proposals AnonDAO | cut -d' ' -f2)" $DRK dao vote "$PROPOSAL" 1 | tee $OUTPUT_FOLDER/dao-vote.tx | $DRK broadcast } wait_vote() { - PROPOSAL="$($DRK dao proposals MiladyMakerDAO | cut -d' ' -f2)" + PROPOSAL="$($DRK dao proposals AnonDAO | cut -d' ' -f2)" while [ "$($DRK dao proposal $PROPOSAL | grep '^Current proposal outcome: ' | awk '{print $4}')" != "Approved" ]; do sleep $SLEEP_TIME sh ./sync-wallet.sh > /dev/null @@ -128,12 +128,12 @@ wait_vote() { } do_exec() { - PROPOSAL="$($DRK dao proposals MiladyMakerDAO | cut -d' ' -f2)" + PROPOSAL="$($DRK dao proposals AnonDAO | cut -d' ' -f2)" $DRK dao exec --early $PROPOSAL | tee $OUTPUT_FOLDER/dao-exec.tx | $DRK broadcast } wait_exec() { - PROPOSAL="$($DRK dao proposals MiladyMakerDAO | cut -d' ' -f2)" + PROPOSAL="$($DRK dao proposals AnonDAO | cut -d' ' -f2)" while [ -z "$($DRK dao proposal $PROPOSAL | grep '^Proposal was executed on transaction: ')" ]; do sleep $SLEEP_TIME sh ./sync-wallet.sh > /dev/null @@ -141,10 +141,10 @@ wait_exec() { } wait_token DRK -mint_token WCKD 42 -wait_token WCKD -mint_token MLDY 20 -wait_token MLDY +mint_token ANON 42 +wait_token ANON +mint_token DAWN 20 +wait_token DAWN mint_dao wait_dao_mint fill_treasury diff --git a/contrib/localnet/darkfid-small/README.md b/contrib/localnet/darkfid-small/README.md index 185734897..95b3399be 100644 --- a/contrib/localnet/darkfid-small/README.md +++ b/contrib/localnet/darkfid-small/README.md @@ -3,6 +3,6 @@ darkfid localnet This will start three `darkfid` node instances in localnet mode. Two of the nodes are activelly -mining, while the other one is just a sync node. +producing blocks, while the other one is just a sync node. We also start two `minerd` daemons to mine blocks, -one for each of the `darkfid` mining nodes. +one for each of the `darkfid` block producing nodes. diff --git a/contrib/localnet/darkfid-small/darkfid0.toml b/contrib/localnet/darkfid-small/darkfid0.toml index e4c0677c1..7bfc230c8 100644 --- a/contrib/localnet/darkfid-small/darkfid0.toml +++ b/contrib/localnet/darkfid-small/darkfid0.toml @@ -17,23 +17,11 @@ database = "darkfid0" # Confirmation threshold, denominated by number of blocks threshold = 6 -# minerd JSON-RPC endpoint -minerd_endpoint = "tcp://127.0.0.1:48467" - # PoW block production target, in seconds pow_target = 20 -# Wallet address to receive mining rewards. -# This is a dummy one so the miner can start, -# replace with your own one. -recipient = "9vw6WznKk7xEFQwwXhJWMMdjUPi3cXL8NrFKQpKifG1U" - -# Optional contract spend hook to use in the mining reward -#spend_hook = "YOUR_SPEND_HOOK_HERE" - -# Optional contract user data to use in the mining reward. -# This is not arbitrary data. -#user_data = "YOUR_USER_DATA_HERE" +# Optional fixed PoW difficulty, used for testing +#pow_fixed_difficulty = 1 # Skip syncing process and start node right away skip_sync = true diff --git a/contrib/localnet/darkfid-small/darkfid1.toml b/contrib/localnet/darkfid-small/darkfid1.toml index 963c6646b..2814d308e 100644 --- a/contrib/localnet/darkfid-small/darkfid1.toml +++ b/contrib/localnet/darkfid-small/darkfid1.toml @@ -25,23 +25,11 @@ database = "darkfid1" # Confirmation threshold, denominated by number of blocks threshold = 6 -# minerd JSON-RPC endpoint -minerd_endpoint = "tcp://127.0.0.1:48567" - # PoW block production target, in seconds pow_target = 20 -# Wallet address to receive mining rewards. -# This is a dummy one so the miner can start, -# replace with your own one. -recipient = "9vw6WznKk7xEFQwwXhJWMMdjUPi3cXL8NrFKQpKifG1U" - -# Optional contract spend hook to use in the mining reward -#spend_hook = "YOUR_SPEND_HOOK_HERE" - -# Optional contract user data to use in the mining reward. -# This is not arbitrary data. -#user_data = "YOUR_USER_DATA_HERE" +# Optional fixed PoW difficulty, used for testing +#pow_fixed_difficulty = 1 # Skip syncing process and start node right away skip_sync = false diff --git a/contrib/localnet/darkfid-small/darkfid2.toml b/contrib/localnet/darkfid-small/darkfid2.toml index 224f94330..cced8118e 100644 --- a/contrib/localnet/darkfid-small/darkfid2.toml +++ b/contrib/localnet/darkfid-small/darkfid2.toml @@ -17,12 +17,12 @@ database = "darkfid2" # Confirmation threshold, denominated by number of blocks threshold = 6 -# minerd JSON-RPC endpoint -#minerd_endpoint = "tcp://127.0.0.1:28467" - # PoW block production target, in seconds pow_target = 20 +# Optional fixed PoW difficulty, used for testing +#pow_fixed_difficulty = 1 + # Skip syncing process and start node right away skip_sync = false diff --git a/contrib/localnet/darkfid-small/minerd0.toml b/contrib/localnet/darkfid-small/minerd0.toml index 6e8738865..13e850bda 100644 --- a/contrib/localnet/darkfid-small/minerd0.toml +++ b/contrib/localnet/darkfid-small/minerd0.toml @@ -9,10 +9,28 @@ # PoW miner number of threads to use threads = 2 -# JSON-RPC settings -[rpc] -# JSON-RPC listen URL -rpc_listen = "tcp://127.0.0.1:48467" +# Polling rate to ask darkfid for mining jobs +#polling_rate = 2 -# Disabled RPC methods -#rpc_disabled_methods = [] +# Stop mining at given height (0 mines forever) +#stop_at_height = 0 + +# Blockchain network to use +network = "localnet" + +# Localnet blockchain network configuration +[network_config."localnet"] +# Wallet mining address to receive mining rewards. +# This is a dummy one so the miner can start, +# replace with your own one. +recipient = "9vw6WznKk7xEFQwwXhJWMMdjUPi3cXL8NrFKQpKifG1U" + +# Optional contract spend hook to use in the mining reward +#spend_hook = "YOUR_SPEND_HOOK_HERE" + +# Optional contract user data to use in the mining reward. +# This is not arbitrary data. +#user_data = "YOUR_USER_DATA_HERE" + +# darkfid JSON-RPC endpoint +endpoint = "tcp://127.0.0.1:48240" diff --git a/contrib/localnet/darkfid-small/minerd1.toml b/contrib/localnet/darkfid-small/minerd1.toml index 558005d02..45e99b550 100644 --- a/contrib/localnet/darkfid-small/minerd1.toml +++ b/contrib/localnet/darkfid-small/minerd1.toml @@ -9,10 +9,28 @@ # PoW miner number of threads to use threads = 2 -# JSON-RPC settings -[rpc] -# JSON-RPC listen URL -rpc_listen = "tcp://127.0.0.1:48567" +# Polling rate to ask darkfid for mining jobs +#polling_rate = 2 -# Disabled RPC methods -#rpc_disabled_methods = [] +# Stop mining at given height (0 mines forever) +#stop_at_height = 0 + +# Blockchain network to use +network = "localnet" + +# Localnet blockchain network configuration +[network_config."localnet"] +# Wallet mining address to receive mining rewards. +# This is a dummy one so the miner can start, +# replace with your own one. +recipient = "9vw6WznKk7xEFQwwXhJWMMdjUPi3cXL8NrFKQpKifG1U" + +# Optional contract spend hook to use in the mining reward +#spend_hook = "YOUR_SPEND_HOOK_HERE" + +# Optional contract user data to use in the mining reward. +# This is not arbitrary data. +#user_data = "YOUR_USER_DATA_HERE" + +# darkfid JSON-RPC endpoint +endpoint = "tcp://127.0.0.1:48340" diff --git a/contrib/localnet/darkfid-small/tmux_sessions.sh b/contrib/localnet/darkfid-small/tmux_sessions.sh index 2bc47ea1c..bf6eb7723 100755 --- a/contrib/localnet/darkfid-small/tmux_sessions.sh +++ b/contrib/localnet/darkfid-small/tmux_sessions.sh @@ -18,17 +18,17 @@ else fi tmux new-session -d -s $session -n "node0" -tmux send-keys -t $session "$MINERD $verbose -c minerd0.toml" Enter +tmux send-keys -t $session "$MINERD -c minerd0.toml $verbose" Enter sleep 1 tmux split-window -t $session -v -l 90% -tmux send-keys -t $session "$DARKFID $verbose -c darkfid0.toml" Enter +tmux send-keys -t $session "$DARKFID -c darkfid0.toml $verbose" Enter sleep 2 tmux new-window -t $session -n "node1" -tmux send-keys -t $session "$MINERD $verbose -c minerd1.toml" Enter +tmux send-keys -t $session "$MINERD -c minerd1.toml $verbose" Enter sleep 1 tmux split-window -t $session -v -l 90% -tmux send-keys -t $session "$DARKFID $verbose -c darkfid1.toml" Enter +tmux send-keys -t $session "$DARKFID -c darkfid1.toml $verbose" Enter sleep 2 tmux new-window -t $session -n "node2" -tmux send-keys -t $session "$DARKFID $verbose -c darkfid2.toml" Enter +tmux send-keys -t $session "$DARKFID -c darkfid2.toml $verbose" Enter tmux attach -t $session diff --git a/doc/src/testnet/dao.md b/doc/src/testnet/dao.md index 8ef560cfe..765dfcfcd 100644 --- a/doc/src/testnet/dao.md +++ b/doc/src/testnet/dao.md @@ -729,7 +729,7 @@ P2Pool wallet address to use: {YOUR_DAO_P2POOL_WALLET_ADDRESS_CONFIGURATION} ``` -Then configure a `darkfid` instance to mine for a DAO, by setting the +Then configure a `minerd` instance to mine for a DAO, by setting the corresponding fields(uncomment if needed) as per retrieved configuration: diff --git a/doc/src/testnet/node.md b/doc/src/testnet/node.md index 43244cc74..a643adfca 100644 --- a/doc/src/testnet/node.md +++ b/doc/src/testnet/node.md @@ -28,27 +28,27 @@ and stays connected to the p2p network. * `drk` is a CLI wallet. It provides an interface to smart contracts such as Money and DAO, manages our keys and coins, and scans the blockchain to update our balances. -* `minerd` is the DarkFi mining daemon. `darkfid` connects to it over -RPC, and triggers commands to mine blocks. +* `minerd` is the DarkFi mining daemon. Connects to `darkfid` over RPC, +and requests new block headers to mine. -The config files for `darkfid` and `drk` are sectioned into three parts, +The config files for all three daemons are sectioned into three parts, each marked `[network_config]`. The sections look like this: * `[network_config."testnet"]` * `[network_config."mainnet"]` * `[network_config."localnet"]` -At the top of the `darkfid` and `drk` config file, we can modify the -network being used by changing the following line: +At the top of each daemon config file, we can modify the network being +used by changing the following line: ```toml # Blockchain network to use network = "testnet" ``` -This enables us to configure `darkfid` and `drk` for different contexts, -namely mainnet, testnet and localnet. Mainnet is not active yet. Localnet -can be setup by following the instructions [here](#local-deployment). The +This enables us to configure the daemons for different contexts, namely +mainnet, testnet and localnet. Mainnet is not active yet. Localnet can +be setup by following the instructions [here](#local-deployment). The rest of this tutorial assumes we are setting up a testnet node. ## Compiling @@ -191,10 +191,8 @@ rest of the tutorial (`darkfid` and `drk` handle this), but if you want to help secure the network, you can participate in the mining process by running the native `minerd` mining daemon. -To mine on DarkFI we need to expose the `minerd` RPC to the `darkfid` -full node, which will initiate the mining process. We'll also need to -add a recipient to `darkfid` that specifies where the mining rewards -will be minted to. +To mine on DarkFI we need to add a recipient to `minerd` that specifies +where the mining rewards will be minted to. First, compile it: @@ -229,8 +227,32 @@ $ ./minerd Config file created in "~/.config/darkfi/minerd_config.toml". Please review it and try again. ``` +You now have to configure `minerd` to use your wallet address as the +rewards recipient, when it retrieves blocks from `darkfid` to mine. + +Open your `minerd` config file with a text editor (the default path +is `~/.config/darkfi/minerd_config.toml`) and replace the +`YOUR_WALLET_ADDRESS_HERE` string with your `drk` wallet address: + +```toml +# Put the address from `./drk wallet address` here +recipient = "YOUR_WALLET_ADDRESS_HERE" +``` + +You can retrieve your `drk` wallet address as follows: + +```shell +$ ./drk wallet address + +CbaqFqGTgn86Zh9AjUeMw3DJyVCshaPSPFtmj6Cyd5yU +``` + +Note: when modifying the `minerd` config file to use with the +testnet, be sure to change the values under the section marked +`[network_config."testnet"]` (not localnet or mainnet!). + Once that's in place, you can run it again and `minerd` will start, -waiting for requests to mine blocks. +polling `darkfid` for new block headers to mine. ```shell $ ./minerd @@ -240,41 +262,15 @@ $ ./minerd 14:20:06 [INFO] Mining daemon initialized successfully! 14:20:06 [INFO] Starting mining daemon... 14:20:06 [INFO] Mining daemon started successfully! +14:20:06 [INFO] Received new job to mine block header beb0...42aa with key 0edc...0679 for target: 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff +14:20:06 [INFO] Mining block header beb0...42aa with key 0edc...0679 for target: 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff +14:20:06 [INFO] Mined block header beb0...42aa with nonce: 1 +14:20:06 [INFO] Mined block header hash: 36fe...753c +14:20:06 [INFO] Submitting solution to darkfid... +14:20:06 [INFO] Submition result: accepted +... ``` -You now have to expose `minerd` RPC to `darkfid`, and configure it -to use your wallet address as the rewards recipient, when submitting -blocks to `minerd` to mine. - -Open your `darkfid` config file with a text editor (the default path -is `~/.config/darkfi/darkfid_config.toml`). Find the `recipient` and -`minerd_endpoint` options under `[network_config."testnet"]`, and -uncomment them by removing the `#` character at the start of line, -like this: - -```toml -# Put your `minerd` endpoint here (default for testnet is in this example) -minerd_endpoint = "tcp://127.0.0.1:28467" -# Put the address from `drk wallet address` here -recipient = "YOUR_WALLET_ADDRESS_HERE" -``` - -Now ensure that `minerd_endpoint` is set to the same value as the -`rpc_listen` address in your `minerd` config (the default path -is `~/.config/darkfi/minerd_config.toml`). Finally, replace the -`YOUR_WALLET_ADDRESS_HERE` string with your `drk` wallet address that -you can retrieve as follows: - -```shell -$ ./drk wallet address - -CbaqFqGTgn86Zh9AjUeMw3DJyVCshaPSPFtmj6Cyd5yU -``` - -Note: when modifying the `darkfid` config file to use with the -testnet, be sure to change the values under the section marked -`[network_config."testnet"]` (not localnet or mainnet!). - ### Darkfid Now that `darkfid` configuration is in place, you can run it again and @@ -307,8 +303,7 @@ As its syncing, you'll see periodic messages like this: This will give you an indication of the current progress. Keep it running, and you should see a `Blockchain synced!` message after some time. -If you're running `minerd`, you should see a notification from the -`minerd` terminal like this: +If you're running `minerd`, you should see a notification like this: ```shell ... @@ -321,36 +316,33 @@ This means that `darkfid` and `minerd` are connected over RPC and ```shell ... -14:23:56 [INFO] Mining block 4abc760a1f1c7198837e91c24d8e045e9fc9cb9fdf3a5fd45e184c25b03b0b51 for target: -115792089237316195423570985008687907853269984665640564039457584007913129639935 -14:24:04 [INFO] Mined block 4abc760a1f1c7198837e91c24d8e045e9fc9cb9fdf3a5fd45e184c25b03b0b51 with nonce: 2 -14:24:06 [INFO] Received request to mine block 17e7428ecb3d911477f8452170d0822c831c6912027abb120e4b4c4cf01d6020 for target: -115792089237316195423570985008687907853269984665640564039457584007913129639935 -14:24:06 [INFO] Checking if a pending request is being processed... +14:23:56 [INFO] [RPC] Created new blocktemplate: address=9vw6...fG1U, spend_hook=-, user_data=-, hash=beb0...42aa +14:24:04 [INFO] [RPC] Got solution submission for block template: beb0...42aa +14:24:06 [INFO] [RPC] Mined block header hash: 36fe...753c +14:24:06 [INFO] [RPC] Proposing new block to network ... ``` When `darkfid` and `minerd` are correctly connected and you get an -error like this: +error on `minerd` like this: ```shell ... -[ERROR] minerd::rpc: Failed mining block f6b4a0f0c8f90905da271ec0add2e856939ef3b0d6cd5b28964d9c2b6d0a0fa9 with error: -Miner task stopped +[ERROR] Failed mining block header b757...5fb1 with error: Miner task stopped ... ``` That's expected behavior. It means your setup is correct and you are -mining blocks. `Failed mining block` happens when a new block was -received by `darkfid`, extending the current best fork, so it sends an -interuption message to `minerd` to stop mining the current block and -start mining the next height one. +mining blocks. `Failed mining block header` happens when a new block +was received by `darkfid`, extending the current best fork, so when +`minerd` polls it again it retrieves the new block header to mine, +interupting current mining workers to start mining the new one. Otherwise, you'll see a notification like this: ```shell ... -[INFO] Mined block b6c7bd3545daa81d0e2e56ee780363beef6eb5b54579f54dca0cdd2a59989b76 with nonce: 266292 +[INFO] Mined block header 36fe...753c with nonce: 266292 ... ``` @@ -430,7 +422,7 @@ $ cd contrib/localnet/darkfid-single-node/ $ ./init-wallet.sh ``` -Then start `darkfid` and wait until its initialized: +Then start the daemones and wait until `darkfid` is initialized: ```shell $ ./tmux_sessions.sh diff --git a/src/validator/pow.rs b/src/validator/pow.rs index e1fdba145..07f7a4707 100644 --- a/src/validator/pow.rs +++ b/src/validator/pow.rs @@ -396,9 +396,6 @@ impl PoWModule { threads: usize, stop_signal: &Receiver<()>, ) -> Result<()> { - // Grab the next mine target - let target = self.next_mine_target()?; - // Grab the RandomX key to use. // We only use the next key when the next block is the // height changing one. @@ -410,7 +407,10 @@ impl PoWModule { &self.darkfi_rx_keys.0 }; - mine_block(&target, randomx_key, header, threads, stop_signal) + // Grab the next mine target + let target = self.next_mine_target()?; + + mine_block(randomx_key, &target, header, threads, stop_signal) } } @@ -435,8 +435,8 @@ fn get_mining_flags() -> RandomXFlags { /// Auxiliary function to mine provided header using a single thread. fn single_thread_mine( - target: &BigUint, input: &HeaderHash, + target: &BigUint, header: &mut Header, stop_signal: &Receiver<()>, ) -> Result<()> { @@ -486,8 +486,8 @@ fn single_thread_mine( /// Auxiliary function to mine provided header using a multiple threads. fn multi_thread_mine( - target: &BigUint, input: &HeaderHash, + target: &BigUint, header: &mut Header, threads: usize, stop_signal: &Receiver<()>, @@ -614,8 +614,8 @@ fn multi_thread_mine( /// Mine provided header, based on provided PoW module next mine target. pub fn mine_block( - target: &BigUint, input: &HeaderHash, + target: &BigUint, header: &mut Header, threads: usize, stop_signal: &Receiver<()>, @@ -634,8 +634,8 @@ pub fn mine_block( error!(target: "validator::pow::mine_block", "[MINER] Can't use 0 threads!"); Err(Error::MinerTaskStopped) } - 1 => single_thread_mine(target, input, header, stop_signal), - _ => multi_thread_mine(target, input, header, threads, stop_signal), + 1 => single_thread_mine(input, target, header, stop_signal), + _ => multi_thread_mine(input, target, header, threads, stop_signal), } }