mirror of
https://github.com/darkrenaissance/darkfi.git
synced 2026-01-08 22:28:12 -05:00
minerd: changed comms logic so it polls darkfid for new mining jobs
This commit is contained in:
2
Cargo.lock
generated
2
Cargo.lock
generated
@@ -4312,6 +4312,7 @@ dependencies = [
|
|||||||
name = "minerd"
|
name = "minerd"
|
||||||
version = "0.5.0"
|
version = "0.5.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"bs58",
|
||||||
"darkfi",
|
"darkfi",
|
||||||
"darkfi-sdk",
|
"darkfi-sdk",
|
||||||
"darkfi-serial",
|
"darkfi-serial",
|
||||||
@@ -4324,6 +4325,7 @@ dependencies = [
|
|||||||
"structopt",
|
"structopt",
|
||||||
"structopt-toml",
|
"structopt-toml",
|
||||||
"tinyjson",
|
"tinyjson",
|
||||||
|
"toml 0.9.8",
|
||||||
"tracing",
|
"tracing",
|
||||||
"tracing-appender",
|
"tracing-appender",
|
||||||
"tracing-subscriber",
|
"tracing-subscriber",
|
||||||
|
|||||||
@@ -17,22 +17,9 @@ database = "~/.local/share/darkfi/darkfid/testnet"
|
|||||||
# Confirmation threshold, denominated by number of blocks
|
# Confirmation threshold, denominated by number of blocks
|
||||||
threshold = 6
|
threshold = 6
|
||||||
|
|
||||||
# minerd JSON-RPC endpoint
|
|
||||||
#minerd_endpoint = "tcp://127.0.0.1:28467"
|
|
||||||
|
|
||||||
# PoW block production target, in seconds
|
# PoW block production target, in seconds
|
||||||
pow_target = 120
|
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 syncing process and start node right away
|
||||||
skip_sync = false
|
skip_sync = false
|
||||||
|
|
||||||
@@ -156,22 +143,9 @@ database = "~/.local/share/darkfi/darkfid/mainnet"
|
|||||||
# Confirmation threshold, denominated by number of blocks
|
# Confirmation threshold, denominated by number of blocks
|
||||||
threshold = 11
|
threshold = 11
|
||||||
|
|
||||||
# minerd JSON-RPC endpoint
|
|
||||||
#minerd_endpoint = "tcp://127.0.0.1:28467"
|
|
||||||
|
|
||||||
# PoW block production target, in seconds
|
# PoW block production target, in seconds
|
||||||
pow_target = 120
|
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 syncing process and start node right away
|
||||||
skip_sync = false
|
skip_sync = false
|
||||||
|
|
||||||
@@ -297,27 +271,12 @@ database = "~/.local/share/darkfi/darkfid/localnet"
|
|||||||
# Confirmation threshold, denominated by number of blocks
|
# Confirmation threshold, denominated by number of blocks
|
||||||
threshold = 3
|
threshold = 3
|
||||||
|
|
||||||
# minerd JSON-RPC endpoint
|
|
||||||
minerd_endpoint = "tcp://127.0.0.1:28467"
|
|
||||||
|
|
||||||
# PoW block production target, in seconds
|
# PoW block production target, in seconds
|
||||||
pow_target = 10
|
pow_target = 10
|
||||||
|
|
||||||
# Optional fixed PoW difficulty, used for testing
|
# Optional fixed PoW difficulty, used for testing
|
||||||
pow_fixed_difficulty = 1
|
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 syncing process and start node right away
|
||||||
skip_sync = true
|
skip_sync = true
|
||||||
|
|
||||||
|
|||||||
@@ -37,8 +37,16 @@ pub enum RpcError {
|
|||||||
ContractStateNotFound = -32201,
|
ContractStateNotFound = -32201,
|
||||||
ContractStateKeyNotFound = -32202,
|
ContractStateKeyNotFound = -32202,
|
||||||
|
|
||||||
// Misc errors
|
// Miner errors
|
||||||
PingFailed = -32300,
|
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) {
|
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::ContractZkasDbNotFound => "zkas database not found for given contract",
|
||||||
RpcError::ContractStateNotFound => "Records not found for given contract state",
|
RpcError::ContractStateNotFound => "Records not found for given contract state",
|
||||||
RpcError::ContractStateKeyNotFound => "Value not found for given contract state key",
|
RpcError::ContractStateKeyNotFound => "Value not found for given contract state key",
|
||||||
// Misc errors
|
// Miner errors
|
||||||
RpcError::PingFailed => "Miner daemon ping error",
|
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())
|
(e as i32, msg.to_string())
|
||||||
|
|||||||
@@ -22,8 +22,7 @@ use std::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
use smol::lock::Mutex;
|
use smol::lock::Mutex;
|
||||||
use tracing::{debug, error, info, warn};
|
use tracing::{debug, error, info};
|
||||||
use url::Url;
|
|
||||||
|
|
||||||
use darkfi::{
|
use darkfi::{
|
||||||
blockchain::BlockInfo,
|
blockchain::BlockInfo,
|
||||||
@@ -52,9 +51,11 @@ use error::{server_error, RpcError};
|
|||||||
|
|
||||||
/// JSON-RPC requests handler and methods
|
/// JSON-RPC requests handler and methods
|
||||||
mod rpc;
|
mod rpc;
|
||||||
use rpc::{DefaultRpcHandler, MinerRpcClient, MmRpcHandler};
|
use rpc::{DefaultRpcHandler, MmRpcHandler};
|
||||||
mod rpc_blockchain;
|
mod rpc_blockchain;
|
||||||
|
mod rpc_miner;
|
||||||
mod rpc_tx;
|
mod rpc_tx;
|
||||||
|
use rpc_miner::BlockTemplate;
|
||||||
mod rpc_xmr;
|
mod rpc_xmr;
|
||||||
|
|
||||||
/// Validator async tasks
|
/// Validator async tasks
|
||||||
@@ -78,14 +79,14 @@ pub struct DarkfiNode {
|
|||||||
txs_batch_size: usize,
|
txs_batch_size: usize,
|
||||||
/// A map of various subscribers exporting live info from the blockchain
|
/// A map of various subscribers exporting live info from the blockchain
|
||||||
subscribers: HashMap<&'static str, JsonSubscriber>,
|
subscribers: HashMap<&'static str, JsonSubscriber>,
|
||||||
/// JSON-RPC connection tracker
|
/// Native mining block templates
|
||||||
rpc_connections: Mutex<HashSet<StoppableTaskPtr>>,
|
blocktemplates: Mutex<HashMap<Vec<u8>, BlockTemplate>>,
|
||||||
/// JSON-RPC client to execute requests to the miner daemon
|
|
||||||
rpc_client: Option<Mutex<MinerRpcClient>>,
|
|
||||||
/// HTTP JSON-RPC connection tracker
|
|
||||||
mm_rpc_connections: Mutex<HashSet<StoppableTaskPtr>>,
|
|
||||||
/// Merge mining block templates
|
/// Merge mining block templates
|
||||||
mm_blocktemplates: Mutex<HashMap<Vec<u8>, (BlockInfo, f64, SecretKey)>>,
|
mm_blocktemplates: Mutex<HashMap<Vec<u8>, (BlockInfo, f64, SecretKey)>>,
|
||||||
|
/// JSON-RPC connection tracker
|
||||||
|
rpc_connections: Mutex<HashSet<StoppableTaskPtr>>,
|
||||||
|
/// HTTP JSON-RPC connection tracker
|
||||||
|
mm_rpc_connections: Mutex<HashSet<StoppableTaskPtr>>,
|
||||||
/// PowRewardV1 ZK data
|
/// PowRewardV1 ZK data
|
||||||
powrewardv1_zk: PowRewardV1Zk,
|
powrewardv1_zk: PowRewardV1Zk,
|
||||||
}
|
}
|
||||||
@@ -96,7 +97,6 @@ impl DarkfiNode {
|
|||||||
validator: ValidatorPtr,
|
validator: ValidatorPtr,
|
||||||
txs_batch_size: usize,
|
txs_batch_size: usize,
|
||||||
subscribers: HashMap<&'static str, JsonSubscriber>,
|
subscribers: HashMap<&'static str, JsonSubscriber>,
|
||||||
rpc_client: Option<Mutex<MinerRpcClient>>,
|
|
||||||
) -> Result<DarkfiNodePtr> {
|
) -> Result<DarkfiNodePtr> {
|
||||||
let powrewardv1_zk = PowRewardV1Zk::new(validator.clone())?;
|
let powrewardv1_zk = PowRewardV1Zk::new(validator.clone())?;
|
||||||
|
|
||||||
@@ -105,15 +105,15 @@ impl DarkfiNode {
|
|||||||
validator,
|
validator,
|
||||||
txs_batch_size,
|
txs_batch_size,
|
||||||
subscribers,
|
subscribers,
|
||||||
rpc_connections: Mutex::new(HashSet::new()),
|
blocktemplates: Mutex::new(HashMap::new()),
|
||||||
rpc_client,
|
|
||||||
mm_rpc_connections: Mutex::new(HashSet::new()),
|
|
||||||
mm_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,
|
powrewardv1_zk,
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Grab best current fork
|
/// Auxiliary function to grab best current fork.
|
||||||
pub async fn best_current_fork(&self) -> Result<Fork> {
|
pub async fn best_current_fork(&self) -> Result<Fork> {
|
||||||
let forks = self.validator.consensus.forks.read().await;
|
let forks = self.validator.consensus.forks.read().await;
|
||||||
let index = best_fork_index(&forks)?;
|
let index = best_fork_index(&forks)?;
|
||||||
@@ -173,7 +173,6 @@ impl Darkfid {
|
|||||||
sled_db: &sled_overlay::sled::Db,
|
sled_db: &sled_overlay::sled::Db,
|
||||||
config: &ValidatorConfig,
|
config: &ValidatorConfig,
|
||||||
net_settings: &Settings,
|
net_settings: &Settings,
|
||||||
minerd_endpoint: &Option<Url>,
|
|
||||||
txs_batch_size: &Option<usize>,
|
txs_batch_size: &Option<usize>,
|
||||||
ex: &ExecutorPtr,
|
ex: &ExecutorPtr,
|
||||||
) -> Result<DarkfidPtr> {
|
) -> Result<DarkfidPtr> {
|
||||||
@@ -203,17 +202,8 @@ impl Darkfid {
|
|||||||
subscribers.insert("proposals", JsonSubscriber::new("blockchain.subscribe_proposals"));
|
subscribers.insert("proposals", JsonSubscriber::new("blockchain.subscribe_proposals"));
|
||||||
subscribers.insert("dnet", JsonSubscriber::new("dnet.subscribe_events"));
|
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
|
// Initialize node
|
||||||
let node = DarkfiNode::new(p2p_handler, validator, txs_batch_size, subscribers, rpc_client)
|
let node = DarkfiNode::new(p2p_handler, validator, txs_batch_size, subscribers).await?;
|
||||||
.await?;
|
|
||||||
|
|
||||||
// Generate the background tasks
|
// Generate the background tasks
|
||||||
let dnet_task = StoppableTask::new();
|
let dnet_task = StoppableTask::new();
|
||||||
@@ -237,13 +227,6 @@ impl Darkfid {
|
|||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
info!(target: "darkfid::Darkfid::start", "Starting Darkfi daemon...");
|
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
|
// Start the `dnet` task
|
||||||
info!(target: "darkfid::Darkfid::start", "Starting dnet subs task");
|
info!(target: "darkfid::Darkfid::start", "Starting dnet subs task");
|
||||||
let dnet_sub_ = self.node.subscribers.get("dnet").unwrap().clone();
|
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?;
|
let flushed_bytes = self.node.validator.blockchain.sled_db.flush_async().await?;
|
||||||
info!(target: "darkfid::Darkfid::stop", "Flushed {flushed_bytes} bytes");
|
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!");
|
info!(target: "darkfid::Darkfid::stop", "Darkfi daemon terminated successfully!");
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,7 +21,6 @@ use std::sync::Arc;
|
|||||||
use smol::{fs::read_to_string, stream::StreamExt};
|
use smol::{fs::read_to_string, stream::StreamExt};
|
||||||
use structopt_toml::{serde::Deserialize, structopt::StructOpt, StructOptToml};
|
use structopt_toml::{serde::Deserialize, structopt::StructOpt, StructOptToml};
|
||||||
use tracing::{debug, error, info};
|
use tracing::{debug, error, info};
|
||||||
use url::Url;
|
|
||||||
|
|
||||||
use darkfi::{
|
use darkfi::{
|
||||||
async_daemonize,
|
async_daemonize,
|
||||||
@@ -99,14 +98,6 @@ pub struct BlockchainNetwork {
|
|||||||
/// Confirmation threshold, denominated by number of blocks
|
/// Confirmation threshold, denominated by number of blocks
|
||||||
threshold: usize,
|
threshold: usize,
|
||||||
|
|
||||||
#[structopt(long)]
|
|
||||||
/// minerd JSON-RPC endpoint
|
|
||||||
minerd_endpoint: Option<Url>,
|
|
||||||
|
|
||||||
#[structopt(skip)]
|
|
||||||
/// Optional JSON-RPC settings for p2pool merge mining requests
|
|
||||||
mm_rpc: Option<RpcSettingsOpt>,
|
|
||||||
|
|
||||||
#[structopt(long, default_value = "120")]
|
#[structopt(long, default_value = "120")]
|
||||||
/// PoW block production target, in seconds
|
/// PoW block production target, in seconds
|
||||||
pow_target: u32,
|
pow_target: u32,
|
||||||
@@ -115,19 +106,6 @@ pub struct BlockchainNetwork {
|
|||||||
/// Optional fixed PoW difficulty, used for testing
|
/// Optional fixed PoW difficulty, used for testing
|
||||||
pow_fixed_difficulty: Option<usize>,
|
pow_fixed_difficulty: Option<usize>,
|
||||||
|
|
||||||
#[structopt(long)]
|
|
||||||
/// Wallet address to receive mining rewards
|
|
||||||
recipient: Option<String>,
|
|
||||||
|
|
||||||
#[structopt(long)]
|
|
||||||
/// Optional contract spend hook to use in the mining reward
|
|
||||||
spend_hook: Option<String>,
|
|
||||||
|
|
||||||
#[structopt(long)]
|
|
||||||
/// Optional contract user data to use in the mining reward.
|
|
||||||
/// This is not arbitrary data.
|
|
||||||
user_data: Option<String>,
|
|
||||||
|
|
||||||
#[structopt(long)]
|
#[structopt(long)]
|
||||||
/// Skip syncing process and start node right away
|
/// Skip syncing process and start node right away
|
||||||
skip_sync: bool,
|
skip_sync: bool,
|
||||||
@@ -159,6 +137,10 @@ pub struct BlockchainNetwork {
|
|||||||
#[structopt(flatten)]
|
#[structopt(flatten)]
|
||||||
/// JSON-RPC settings
|
/// JSON-RPC settings
|
||||||
rpc: RpcSettingsOpt,
|
rpc: RpcSettingsOpt,
|
||||||
|
|
||||||
|
#[structopt(skip)]
|
||||||
|
/// Optional JSON-RPC settings for p2pool merge mining requests
|
||||||
|
mm_rpc: Option<RpcSettingsOpt>,
|
||||||
}
|
}
|
||||||
|
|
||||||
async_daemonize!(realmain);
|
async_daemonize!(realmain);
|
||||||
@@ -255,7 +237,6 @@ async fn realmain(args: Args, ex: Arc<smol::Executor<'static>>) -> Result<()> {
|
|||||||
&sled_db,
|
&sled_db,
|
||||||
&config,
|
&config,
|
||||||
&blockchain_config.net.into(),
|
&blockchain_config.net.into(),
|
||||||
&blockchain_config.minerd_endpoint,
|
|
||||||
&blockchain_config.txs_batch_size,
|
&blockchain_config.txs_batch_size,
|
||||||
&ex,
|
&ex,
|
||||||
)
|
)
|
||||||
@@ -266,10 +247,6 @@ async fn realmain(args: Args, ex: Arc<smol::Executor<'static>>) -> Result<()> {
|
|||||||
skip_sync: blockchain_config.skip_sync,
|
skip_sync: blockchain_config.skip_sync,
|
||||||
checkpoint_height: blockchain_config.checkpoint_height,
|
checkpoint_height: blockchain_config.checkpoint_height,
|
||||||
checkpoint: blockchain_config.checkpoint,
|
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,
|
bootstrap,
|
||||||
};
|
};
|
||||||
daemon
|
daemon
|
||||||
|
|||||||
@@ -16,65 +16,31 @@
|
|||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
use std::{collections::HashSet, time::Instant};
|
use std::collections::HashSet;
|
||||||
|
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use smol::lock::MutexGuard;
|
use smol::lock::MutexGuard;
|
||||||
use tinyjson::JsonValue;
|
use tinyjson::JsonValue;
|
||||||
use tracing::{debug, error, info, warn};
|
use tracing::debug;
|
||||||
use url::Url;
|
|
||||||
|
|
||||||
use darkfi::{
|
use darkfi::{
|
||||||
net::P2pPtr,
|
net::P2pPtr,
|
||||||
rpc::{
|
rpc::{
|
||||||
client::RpcChadClient,
|
|
||||||
jsonrpc::{ErrorCode, JsonError, JsonRequest, JsonResponse, JsonResult},
|
jsonrpc::{ErrorCode, JsonError, JsonRequest, JsonResponse, JsonResult},
|
||||||
p2p_method::HandlerP2p,
|
p2p_method::HandlerP2p,
|
||||||
server::RequestHandler,
|
server::RequestHandler,
|
||||||
},
|
},
|
||||||
system::{sleep, ExecutorPtr, StoppableTaskPtr},
|
system::StoppableTaskPtr,
|
||||||
util::time::Timestamp,
|
util::time::Timestamp,
|
||||||
Error, Result,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::DarkfiNode;
|
||||||
error::{server_error, RpcError},
|
|
||||||
DarkfiNode,
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Default JSON-RPC `RequestHandler` type
|
/// Default JSON-RPC `RequestHandler` type
|
||||||
pub struct DefaultRpcHandler;
|
pub struct DefaultRpcHandler;
|
||||||
/// HTTP JSON-RPC `RequestHandler` type for p2pool
|
/// HTTP JSON-RPC `RequestHandler` type for p2pool
|
||||||
pub struct MmRpcHandler;
|
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<RpcChadClient>,
|
|
||||||
}
|
|
||||||
|
|
||||||
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]
|
#[async_trait]
|
||||||
#[rustfmt::skip]
|
#[rustfmt::skip]
|
||||||
impl RequestHandler<DefaultRpcHandler> for DarkfiNode {
|
impl RequestHandler<DefaultRpcHandler> for DarkfiNode {
|
||||||
@@ -87,7 +53,6 @@ impl RequestHandler<DefaultRpcHandler> for DarkfiNode {
|
|||||||
// =====================
|
// =====================
|
||||||
"ping" => <DarkfiNode as RequestHandler<DefaultRpcHandler>>::pong(self, req.id, req.params).await,
|
"ping" => <DarkfiNode as RequestHandler<DefaultRpcHandler>>::pong(self, req.id, req.params).await,
|
||||||
"clock" => self.clock(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.switch" => self.dnet_switch(req.id, req.params).await,
|
||||||
"dnet.subscribe_events" => self.dnet_subscribe_events(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,
|
"p2p.get_info" => self.p2p_get_info(req.id, req.params).await,
|
||||||
@@ -116,6 +81,12 @@ impl RequestHandler<DefaultRpcHandler> for DarkfiNode {
|
|||||||
"tx.clean_pending" => self.tx_clean_pending(req.id, req.params).await,
|
"tx.clean_pending" => self.tx_clean_pending(req.id, req.params).await,
|
||||||
"tx.calculate_fee" => self.tx_calculate_fee(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
|
// Invalid method
|
||||||
// ==============
|
// ==============
|
||||||
@@ -204,83 +175,6 @@ impl DarkfiNode {
|
|||||||
|
|
||||||
self.subscribers.get("dnet").unwrap().clone().into()
|
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<JsonValue> {
|
|
||||||
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 {
|
impl HandlerP2p for DarkfiNode {
|
||||||
|
|||||||
540
bin/darkfid/src/rpc_miner.rs
Normal file
540
bin/darkfid/src/rpc_miner.rs
Normal file
@@ -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 <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
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<FuncId>,
|
||||||
|
/// Optional contract user data to use in the mining reward.
|
||||||
|
/// This is not arbitrary data.
|
||||||
|
pub user_data: Option<pallas::Base>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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::<HashMap<String, JsonValue>>() 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::<String>() 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::<String>() 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::<String>() 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<pallas::Base> = match params.get("user_data") {
|
||||||
|
Some(user_data) => {
|
||||||
|
let Some(user_data) = user_data.get::<String>() 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::<HashMap<String, JsonValue>>() 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::<String>() 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::<String>() 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<pallas::Base> = match params.get("user_data") {
|
||||||
|
Some(user_data) => {
|
||||||
|
let Some(user_data) = user_data.get::<String>() 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::<f64>() 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<Transaction> {
|
||||||
|
// 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)
|
||||||
|
}
|
||||||
@@ -42,9 +42,8 @@ use tracing::{error, info};
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
proto::ProposalMessage,
|
proto::ProposalMessage,
|
||||||
server_error,
|
rpc_miner::{generate_next_block, MinerRewardsRecipientConfig},
|
||||||
task::miner::{generate_next_block, MinerRewardsRecipientConfig},
|
server_error, DarkfiNode, RpcError,
|
||||||
DarkfiNode, RpcError,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// https://github.com/SChernykh/p2pool/blob/master/docs/MERGE_MINING.MD
|
// https://github.com/SChernykh/p2pool/blob/master/docs/MERGE_MINING.MD
|
||||||
|
|||||||
@@ -25,28 +25,24 @@ use darkfi::{
|
|||||||
util::{encoding::base64, time::Timestamp},
|
util::{encoding::base64, time::Timestamp},
|
||||||
Error, Result,
|
Error, Result,
|
||||||
};
|
};
|
||||||
use darkfi_sdk::{
|
|
||||||
crypto::{FuncId, PublicKey},
|
|
||||||
pasta::{group::ff::PrimeField, pallas},
|
|
||||||
};
|
|
||||||
use darkfi_serial::serialize_async;
|
use darkfi_serial::serialize_async;
|
||||||
use tracing::{error, info};
|
use tracing::{error, info};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
task::{garbage_collect_task, miner::MinerRewardsRecipientConfig, miner_task, sync_task},
|
task::{garbage_collect_task, sync_task},
|
||||||
DarkfiNodePtr,
|
DarkfiNodePtr,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Auxiliary structure representing node consensus init task configuration
|
/// Auxiliary structure representing node consensus init task configuration.
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct ConsensusInitTaskConfig {
|
pub struct ConsensusInitTaskConfig {
|
||||||
|
/// Skip syncing process and start node right away
|
||||||
pub skip_sync: bool,
|
pub skip_sync: bool,
|
||||||
|
/// Optional sync checkpoint height
|
||||||
pub checkpoint_height: Option<u32>,
|
pub checkpoint_height: Option<u32>,
|
||||||
|
/// Optional sync checkpoint hash
|
||||||
pub checkpoint: Option<String>,
|
pub checkpoint: Option<String>,
|
||||||
pub miner: bool,
|
/// Optional bootstrap timestamp
|
||||||
pub recipient: Option<String>,
|
|
||||||
pub spend_hook: Option<String>,
|
|
||||||
pub user_data: Option<String>,
|
|
||||||
pub bootstrap: u64,
|
pub bootstrap: u64,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -95,54 +91,9 @@ pub async fn consensus_init_task(
|
|||||||
None
|
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
|
// Gracefully handle network disconnections
|
||||||
loop {
|
loop {
|
||||||
let result = if config.miner {
|
match listen_to_network(&node, &ex).await {
|
||||||
miner_task(&node, recipient_config.as_ref().unwrap(), config.skip_sync, &ex).await
|
|
||||||
} else {
|
|
||||||
replicator_task(&node, &ex).await
|
|
||||||
};
|
|
||||||
|
|
||||||
match result {
|
|
||||||
Ok(_) => return Ok(()),
|
Ok(_) => return Ok(()),
|
||||||
Err(Error::NetworkNotConnected) => {
|
Err(Error::NetworkNotConnected) => {
|
||||||
// Sync node again
|
// 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 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
|
// Grab proposals subscriber and subscribe to it
|
||||||
let proposals_sub = node.subscribers.get("proposals").unwrap();
|
let proposals_sub = node.subscribers.get("proposals").unwrap();
|
||||||
let prop_subscription = proposals_sub.publisher.clone().subscribe().await;
|
let prop_subscription = proposals_sub.publisher.clone().subscribe().await;
|
||||||
@@ -225,8 +176,8 @@ async fn consensus_task(
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Err(e) = clean_mm_blocktemplates(node).await {
|
if let Err(e) = clean_blocktemplates(node).await {
|
||||||
error!(target: "darkfid", "Failed cleaning merge mining block templates: {e}")
|
error!(target: "darkfid", "Failed cleaning mining block templates: {e}")
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut notif_blocks = Vec::with_capacity(confirmed.len());
|
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
|
/// Auxiliary function to drop mining block templates not referencing
|
||||||
/// referencing active forks or last confirmed block.
|
/// active forks or last confirmed block.
|
||||||
pub async fn clean_mm_blocktemplates(node: &DarkfiNodePtr) -> Result<()> {
|
async fn clean_blocktemplates(node: &DarkfiNodePtr) -> Result<()> {
|
||||||
// Grab a lock over node merge mining templates
|
// Grab a lock over node mining templates
|
||||||
|
let mut blocktemplates = node.blocktemplates.lock().await;
|
||||||
let mut mm_blocktemplates = node.mm_blocktemplates.lock().await;
|
let mut mm_blocktemplates = node.mm_blocktemplates.lock().await;
|
||||||
|
|
||||||
// Early return if no merge mining block templates exist
|
// Early return if no mining block templates exist
|
||||||
if mm_blocktemplates.is_empty() {
|
if blocktemplates.is_empty() && mm_blocktemplates.is_empty() {
|
||||||
return Ok(())
|
return Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -272,6 +224,34 @@ pub async fn clean_mm_blocktemplates(node: &DarkfiNodePtr) -> Result<()> {
|
|||||||
|
|
||||||
// Loop through templates to find which can be dropped
|
// Loop through templates to find which can be dropped
|
||||||
let mut dropped_templates = vec![];
|
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() {
|
'outer: for (key, (block, _, _)) in mm_blocktemplates.iter() {
|
||||||
// Loop through all the forks
|
// Loop through all the forks
|
||||||
for fork in forks.iter() {
|
for fork in forks.iter() {
|
||||||
|
|||||||
@@ -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 <https://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
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<FuncId>,
|
|
||||||
pub user_data: Option<pallas::Base>,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 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<JsonNotification>,
|
|
||||||
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::<f64>().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<Transaction> {
|
|
||||||
// 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)
|
|
||||||
}
|
|
||||||
@@ -19,9 +19,6 @@
|
|||||||
pub mod consensus;
|
pub mod consensus;
|
||||||
pub use consensus::consensus_init_task;
|
pub use consensus::consensus_init_task;
|
||||||
|
|
||||||
pub mod miner;
|
|
||||||
pub use miner::{generate_next_block, miner_task};
|
|
||||||
|
|
||||||
pub mod sync;
|
pub mod sync;
|
||||||
pub use sync::sync_task;
|
pub use sync::sync_task;
|
||||||
|
|
||||||
|
|||||||
@@ -289,8 +289,7 @@ pub async fn generate_node(
|
|||||||
|
|
||||||
let p2p_handler = DarkfidP2pHandler::init(settings, ex).await?;
|
let p2p_handler = DarkfidP2pHandler::init(settings, ex).await?;
|
||||||
let node =
|
let node =
|
||||||
DarkfiNode::new(p2p_handler.clone(), validator.clone(), 50, subscribers.clone(), None)
|
DarkfiNode::new(p2p_handler.clone(), validator.clone(), 50, subscribers.clone()).await?;
|
||||||
.await?;
|
|
||||||
|
|
||||||
p2p_handler.clone().start(ex, &validator, &subscribers).await?;
|
p2p_handler.clone().start(ex, &validator, &subscribers).await?;
|
||||||
|
|
||||||
|
|||||||
@@ -279,10 +279,6 @@ fn darkfid_programmatic_control() -> Result<()> {
|
|||||||
skip_sync: true,
|
skip_sync: true,
|
||||||
checkpoint_height: None,
|
checkpoint_height: None,
|
||||||
checkpoint: None,
|
checkpoint: None,
|
||||||
miner: false,
|
|
||||||
recipient: None,
|
|
||||||
spend_hook: None,
|
|
||||||
user_data: None,
|
|
||||||
bootstrap,
|
bootstrap,
|
||||||
};
|
};
|
||||||
let rpc_settings = RpcSettings {
|
let rpc_settings = RpcSettings {
|
||||||
@@ -296,7 +292,6 @@ fn darkfid_programmatic_control() -> Result<()> {
|
|||||||
&config,
|
&config,
|
||||||
&darkfi::net::Settings::default(),
|
&darkfi::net::Settings::default(),
|
||||||
&None,
|
&None,
|
||||||
&None,
|
|
||||||
&ex,
|
&ex,
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
|
|||||||
@@ -10,11 +10,12 @@ edition = "2021"
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
# Darkfi
|
# Darkfi
|
||||||
darkfi = {path = "../../", features = ["async-daemonize", "validator", "rpc"]}
|
darkfi = {path = "../../", features = ["async-daemonize", "validator", "rpc", "bs58"]}
|
||||||
darkfi-sdk = {path = "../../src/sdk"}
|
darkfi-sdk = {path = "../../src/sdk"}
|
||||||
darkfi-serial = {version = "0.5.0", features = ["async"]}
|
darkfi-serial = {version = "0.5.0", features = ["async"]}
|
||||||
|
|
||||||
# Misc
|
# Misc
|
||||||
|
bs58 = "0.5.1"
|
||||||
tracing = "0.1.41"
|
tracing = "0.1.41"
|
||||||
num-bigint = "0.4.6"
|
num-bigint = "0.4.6"
|
||||||
|
|
||||||
@@ -34,6 +35,7 @@ smol = "2.0.2"
|
|||||||
serde = {version = "1.0.228", features = ["derive"]}
|
serde = {version = "1.0.228", features = ["derive"]}
|
||||||
structopt = "0.3.26"
|
structopt = "0.3.26"
|
||||||
structopt-toml = "0.5.1"
|
structopt-toml = "0.5.1"
|
||||||
|
toml = "0.9.8"
|
||||||
|
|
||||||
[lints]
|
[lints]
|
||||||
workspace = true
|
workspace = true
|
||||||
|
|||||||
@@ -9,10 +9,56 @@
|
|||||||
# PoW miner number of threads to use
|
# PoW miner number of threads to use
|
||||||
#threads = 4
|
#threads = 4
|
||||||
|
|
||||||
# JSON-RPC settings
|
# Polling rate to ask darkfid for mining jobs
|
||||||
[rpc]
|
#polling_rate = 2
|
||||||
# JSON-RPC listen URL
|
|
||||||
rpc_listen = "tcp://127.0.0.1:28467"
|
|
||||||
|
|
||||||
# Disabled RPC methods
|
# Stop mining at given height (0 mines forever)
|
||||||
#rpc_disabled_methods = []
|
#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"
|
||||||
|
|||||||
@@ -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 <https://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
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()
|
|
||||||
}
|
|
||||||
@@ -16,60 +16,116 @@
|
|||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
use std::{collections::HashSet, sync::Arc};
|
use std::{collections::HashMap, sync::Arc};
|
||||||
|
|
||||||
use smol::{
|
use smol::{
|
||||||
channel::{Receiver, Sender},
|
channel::{Receiver, Sender},
|
||||||
lock::Mutex,
|
lock::RwLock,
|
||||||
};
|
};
|
||||||
use tracing::{error, info};
|
use tracing::{debug, error, info};
|
||||||
|
use url::Url;
|
||||||
|
|
||||||
use darkfi::{
|
use darkfi::{
|
||||||
rpc::{
|
rpc::util::JsonValue,
|
||||||
server::{listen_and_serve, RequestHandler},
|
|
||||||
settings::RpcSettings,
|
|
||||||
},
|
|
||||||
system::{sleep, ExecutorPtr, StoppableTask, StoppableTaskPtr},
|
system::{sleep, ExecutorPtr, StoppableTask, StoppableTaskPtr},
|
||||||
Error, Result,
|
Error,
|
||||||
};
|
};
|
||||||
|
use darkfi_sdk::crypto::Keypair;
|
||||||
|
|
||||||
/// Daemon error codes
|
/// darkfid JSON-RPC related methods
|
||||||
mod error;
|
|
||||||
|
|
||||||
/// JSON-RPC server methods
|
|
||||||
mod rpc;
|
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<String, JsonValue>,
|
||||||
|
}
|
||||||
|
|
||||||
|
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<String, JsonValue>,
|
||||||
|
) -> Self {
|
||||||
|
Self { threads, polling_rate, stop_at_height, wallet_config }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Atomic pointer to the DarkFi mining node
|
/// Atomic pointer to the DarkFi mining node
|
||||||
pub type MinerNodePtr = Arc<MinerNode>;
|
pub type MinerNodePtr = Arc<MinerNode>;
|
||||||
|
|
||||||
/// Structure representing a DarkFi mining node
|
/// Structure representing a DarkFi mining node
|
||||||
pub struct MinerNode {
|
pub struct MinerNode {
|
||||||
/// PoW miner number of threads to use
|
/// Node configuration
|
||||||
threads: usize,
|
config: MinerNodeConfig,
|
||||||
/// Stop mining at this height
|
|
||||||
stop_at_height: u32,
|
|
||||||
/// Sender to stop miner threads
|
/// Sender to stop miner threads
|
||||||
sender: Sender<()>,
|
sender: Sender<()>,
|
||||||
/// Receiver to stop miner threads
|
/// Receiver to stop miner threads
|
||||||
stop_signal: Receiver<()>,
|
stop_signal: Receiver<()>,
|
||||||
/// JSON-RPC connection tracker
|
/// JSON-RPC client to execute requests to darkfid daemon
|
||||||
rpc_connections: Mutex<HashSet<StoppableTaskPtr>>,
|
rpc_client: RwLock<DarkfidRpcClient>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MinerNode {
|
impl MinerNode {
|
||||||
pub fn new(
|
pub async fn new(config: MinerNodeConfig, endpoint: Url, ex: &ExecutorPtr) -> MinerNodePtr {
|
||||||
threads: usize,
|
// Initialize the smol channels to send signal between the threads
|
||||||
stop_at_height: u32,
|
let (sender, stop_signal) = smol::channel::bounded(1);
|
||||||
sender: Sender<()>,
|
|
||||||
stop_signal: Receiver<()>,
|
// Initialize JSON-RPC client
|
||||||
) -> MinerNodePtr {
|
let rpc_client = RwLock::new(DarkfidRpcClient::new(endpoint, ex.clone()).await);
|
||||||
Arc::new(Self {
|
|
||||||
threads,
|
Arc::new(Self { config, sender, stop_signal, rpc_client })
|
||||||
stop_at_height,
|
}
|
||||||
sender,
|
|
||||||
stop_signal,
|
/// Auxiliary function to abort pending job.
|
||||||
rpc_connections: Mutex::new(HashSet::new()),
|
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<Minerd>;
|
|||||||
pub struct Minerd {
|
pub struct Minerd {
|
||||||
/// Miner node instance conducting the mining operations
|
/// Miner node instance conducting the mining operations
|
||||||
node: MinerNodePtr,
|
node: MinerNodePtr,
|
||||||
/// JSON-RPC background task
|
/// Miner darkfid polling background task
|
||||||
rpc_task: StoppableTaskPtr,
|
polling_task: StoppableTaskPtr,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Minerd {
|
impl Minerd {
|
||||||
/// Initialize a DarkFi mining daemon.
|
/// Initialize a DarkFi mining daemon.
|
||||||
///
|
///
|
||||||
/// Corresponding communication channels are setup to generate a new `MinerNode`,
|
/// Generate a new `MinerNode` and a new task to handle the darkfid
|
||||||
/// and a new task is generated to handle the JSON-RPC API.
|
/// polling.
|
||||||
pub fn init(threads: usize, stop_at_height: u32) -> MinerdPtr {
|
pub async fn init(config: MinerNodeConfig, endpoint: Url, ex: &ExecutorPtr) -> MinerdPtr {
|
||||||
info!(target: "minerd::Minerd::init", "Initializing a new mining daemon...");
|
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
|
// 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
|
// Generate the polling task
|
||||||
let rpc_task = StoppableTask::new();
|
let polling_task = StoppableTask::new();
|
||||||
|
|
||||||
info!(target: "minerd::Minerd::init", "Mining daemon initialized successfully!");
|
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.
|
/// Start the DarkFi mining daemon in the given executor.
|
||||||
pub fn start(&self, executor: &ExecutorPtr, rpc_settings: &RpcSettings) {
|
pub fn start(&self, ex: &ExecutorPtr) {
|
||||||
info!(target: "minerd::Minerd::start", "Starting mining daemon...");
|
info!(target: "minerd::Minerd::start", "Starting mining daemon...");
|
||||||
|
|
||||||
// Start the JSON-RPC task
|
// Start the polling task
|
||||||
let node_ = self.node.clone();
|
self.polling_task.clone().start(
|
||||||
self.rpc_task.clone().start(
|
polling_task(self.node.clone(), ex.clone()),
|
||||||
listen_and_serve(rpc_settings.clone(), self.node.clone(), None, executor.clone()),
|
|res| async {
|
||||||
|res| async move {
|
|
||||||
match res {
|
match res {
|
||||||
Ok(()) | Err(Error::RpcServerStopped) => node_.stop_connections().await,
|
Ok(()) | Err(Error::DetachedTaskStopped) => { /* Do nothing */ }
|
||||||
Err(e) => error!(target: "minerd::Minerd::start", "Failed starting JSON-RPC server: {e}"),
|
Err(e) => {
|
||||||
|
error!(target: "minerd::Minerd::start", "Failed starting polling task: {e}")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
Error::RpcServerStopped,
|
Error::DetachedTaskStopped,
|
||||||
executor.clone(),
|
ex.clone(),
|
||||||
);
|
);
|
||||||
|
|
||||||
info!(target: "minerd::Minerd::start", "Mining daemon started successfully!");
|
info!(target: "minerd::Minerd::start", "Mining daemon started successfully!");
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Stop the DarkFi mining daemon.
|
/// Stop the DarkFi mining daemon.
|
||||||
pub async fn stop(&self) -> Result<()> {
|
pub async fn stop(&self) {
|
||||||
info!(target: "minerd::Minerd::stop", "Terminating mining daemon...");
|
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
|
// Stop the mining node
|
||||||
info!(target: "minerd::Minerd::stop", "Stopping miner threads...");
|
info!(target: "minerd::Minerd::stop", "Stopping miner threads...");
|
||||||
if self.node.stop_signal.is_empty() {
|
self.node.abort_pending().await;
|
||||||
self.node.sender.send(()).await?;
|
|
||||||
}
|
|
||||||
while self.node.stop_signal.receiver_count() > 1 {
|
|
||||||
sleep(1).await;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Stop the JSON-RPC task
|
// Close the JSON-RPC client
|
||||||
info!(target: "minerd::Minerd::stop", "Stopping JSON-RPC server...");
|
info!(target: "minerd::Minerd::stop", "Stopping JSON-RPC client...");
|
||||||
self.rpc_task.stop().await;
|
self.node.stop_rpc_client().await;
|
||||||
|
|
||||||
// Consume channel item so its empty again
|
|
||||||
if self.node.stop_signal.is_full() {
|
|
||||||
self.node.stop_signal.recv().await?;
|
|
||||||
}
|
|
||||||
|
|
||||||
info!(target: "minerd::Minerd::stop", "Mining daemon terminated successfully!");
|
info!(target: "minerd::Minerd::stop", "Mining daemon terminated successfully!");
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -158,7 +205,6 @@ impl Minerd {
|
|||||||
use {
|
use {
|
||||||
darkfi::util::logger::{setup_test_logger, Level},
|
darkfi::util::logger::{setup_test_logger, Level},
|
||||||
tracing::warn,
|
tracing::warn,
|
||||||
url::Url,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -166,7 +212,7 @@ use {
|
|||||||
///
|
///
|
||||||
/// First we initialize a daemon, start it and then perform
|
/// First we initialize a daemon, start it and then perform
|
||||||
/// couple of restarts to verify everything works as expected.
|
/// 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,
|
// We check this error so we can execute same file tests in parallel,
|
||||||
// otherwise second one fails to init logger here.
|
// otherwise second one fails to init logger here.
|
||||||
if setup_test_logger(
|
if setup_test_logger(
|
||||||
@@ -182,74 +228,36 @@ fn minerd_programmatic_control() -> Result<()> {
|
|||||||
warn!(target: "minerd_programmatic_control", "Logger already initialized");
|
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
|
// Create an executor and communication signals
|
||||||
let ex = Arc::new(smol::Executor::new());
|
let ex = Arc::new(smol::Executor::new());
|
||||||
let (signal, shutdown) = smol::channel::unbounded::<()>();
|
let (signal, shutdown) = smol::channel::unbounded::<()>();
|
||||||
|
|
||||||
// Generate a dummy mining job
|
easy_parallel::Parallel::new().each(0..1, |_| smol::block_on(ex.run(shutdown.recv()))).finish(
|
||||||
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(|| {
|
|
||||||
smol::block_on(async {
|
smol::block_on(async {
|
||||||
// Initialize a daemon
|
// Initialize a daemon
|
||||||
let daemon = Minerd::init(threads, 0);
|
let daemon = Minerd::init(
|
||||||
|
MinerNodeConfig::default(),
|
||||||
// Start it
|
Url::parse("tcp://127.0.0.1:12345").unwrap(),
|
||||||
daemon.start(&ex, &rpc_settings);
|
&ex,
|
||||||
|
|
||||||
// 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();
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
rpc_client.stop().await;
|
|
||||||
|
|
||||||
// Start it again
|
// Start it
|
||||||
daemon.start(&ex, &rpc_settings);
|
daemon.start(&ex);
|
||||||
|
|
||||||
// Stop it
|
// Stop it
|
||||||
daemon.stop().await.unwrap();
|
daemon.stop().await;
|
||||||
|
|
||||||
|
// Start it again
|
||||||
|
daemon.start(&ex);
|
||||||
|
|
||||||
|
// Stop it
|
||||||
|
daemon.stop().await;
|
||||||
|
|
||||||
// Shutdown entirely
|
// Shutdown entirely
|
||||||
drop(signal);
|
drop(signal);
|
||||||
})
|
})
|
||||||
});
|
},
|
||||||
|
);
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,15 +16,23 @@
|
|||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
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 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: &str = "minerd.toml";
|
||||||
const CONFIG_FILE_CONTENTS: &str = include_str!("../minerd.toml");
|
const CONFIG_FILE_CONTENTS: &str = include_str!("../minerd.toml");
|
||||||
@@ -37,18 +45,22 @@ struct Args {
|
|||||||
/// Configuration file to use
|
/// Configuration file to use
|
||||||
config: Option<String>,
|
config: Option<String>,
|
||||||
|
|
||||||
#[structopt(flatten)]
|
|
||||||
/// JSON-RPC settings
|
|
||||||
rpc: RpcSettingsOpt,
|
|
||||||
|
|
||||||
#[structopt(short, long, default_value = "4")]
|
#[structopt(short, long, default_value = "4")]
|
||||||
/// PoW miner number of threads to use
|
/// PoW miner number of threads to use
|
||||||
threads: usize,
|
threads: usize,
|
||||||
|
|
||||||
|
#[structopt(short, long, default_value = "2")]
|
||||||
|
/// Polling rate to ask darkfid for mining jobs
|
||||||
|
polling_rate: u64,
|
||||||
|
|
||||||
#[structopt(long, default_value = "0")]
|
#[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,
|
stop_at_height: u32,
|
||||||
|
|
||||||
|
#[structopt(short, long, default_value = "testnet")]
|
||||||
|
/// Blockchain network to use
|
||||||
|
network: String,
|
||||||
|
|
||||||
#[structopt(short, long)]
|
#[structopt(short, long)]
|
||||||
/// Set log file to ouput into
|
/// Set log file to ouput into
|
||||||
log: Option<String>,
|
log: Option<String>,
|
||||||
@@ -58,18 +70,131 @@ struct Args {
|
|||||||
verbose: u8,
|
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<String>,
|
||||||
|
|
||||||
|
#[structopt(long)]
|
||||||
|
/// Optional contract user data to use in the mining reward.
|
||||||
|
/// This is not arbitrary data.
|
||||||
|
user_data: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Auxiliary function to parse minerd configuration file and extract
|
||||||
|
/// requested blockchain network config.
|
||||||
|
pub async fn parse_blockchain_config(
|
||||||
|
config: Option<String>,
|
||||||
|
network: &str,
|
||||||
|
) -> Result<BlockchainNetwork> {
|
||||||
|
// 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::<Vec<String>>(&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_daemonize!(realmain);
|
||||||
async fn realmain(args: Args, ex: Arc<Executor<'static>>) -> Result<()> {
|
async fn realmain(args: Args, ex: ExecutorPtr) -> Result<()> {
|
||||||
info!(target: "minerd", "Starting DarkFi Mining Daemon...");
|
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> = 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.
|
// Signal handling for graceful termination.
|
||||||
let (signals_handler, signals_task) = SignalHandler::new(ex)?;
|
let (signals_handler, signals_task) = SignalHandler::new(ex)?;
|
||||||
signals_handler.wait_termination(signals_task).await?;
|
signals_handler.wait_termination(signals_task).await?;
|
||||||
info!(target: "minerd", "Caught termination signal, cleaning up and exiting");
|
info!(target: "minerd", "Caught termination signal, cleaning up and exiting");
|
||||||
|
|
||||||
daemon.stop().await?;
|
daemon.stop().await;
|
||||||
|
|
||||||
info!(target: "minerd", "Shut down successfully");
|
info!(target: "minerd", "Shut down successfully");
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|||||||
@@ -16,162 +16,266 @@
|
|||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
use std::collections::HashSet;
|
|
||||||
|
|
||||||
use num_bigint::BigUint;
|
use num_bigint::BigUint;
|
||||||
use smol::lock::MutexGuard;
|
|
||||||
use tracing::{debug, error, info};
|
use tracing::{debug, error, info};
|
||||||
|
use url::Url;
|
||||||
|
|
||||||
use darkfi::{
|
use darkfi::{
|
||||||
blockchain::header_store::{Header, HeaderHash},
|
blockchain::{Header, HeaderHash},
|
||||||
rpc::{
|
rpc::{client::RpcClient, jsonrpc::JsonRequest, util::JsonValue},
|
||||||
jsonrpc::{ErrorCode, JsonError, JsonRequest, JsonResponse, JsonResult},
|
system::{sleep, ExecutorPtr, StoppableTask},
|
||||||
server::RequestHandler,
|
|
||||||
util::JsonValue,
|
|
||||||
},
|
|
||||||
system::{sleep, StoppableTaskPtr},
|
|
||||||
util::encoding::base64,
|
util::encoding::base64,
|
||||||
validator::pow::mine_block,
|
validator::pow::mine_block,
|
||||||
|
Error, Result,
|
||||||
};
|
};
|
||||||
use darkfi_serial::{async_trait, deserialize_async};
|
use darkfi_serial::deserialize_async;
|
||||||
|
|
||||||
use crate::{
|
use crate::{MinerNode, MinerNodePtr};
|
||||||
error::{server_error, RpcError},
|
|
||||||
MinerNode,
|
|
||||||
};
|
|
||||||
|
|
||||||
#[async_trait]
|
/// Structure to hold a JSON-RPC client and its config,
|
||||||
impl RequestHandler<()> for MinerNode {
|
/// so we can recreate it in case of an error.
|
||||||
async fn handle_request(&self, req: JsonRequest) -> JsonResult {
|
pub struct DarkfidRpcClient {
|
||||||
debug!(target: "minerd::rpc", "--> {}", req.stringify().unwrap());
|
endpoint: Url,
|
||||||
|
ex: ExecutorPtr,
|
||||||
|
client: Option<RpcClient>,
|
||||||
|
}
|
||||||
|
|
||||||
match req.method.as_str() {
|
impl DarkfidRpcClient {
|
||||||
"ping" => self.pong(req.id, req.params).await,
|
pub async fn new(endpoint: Url, ex: ExecutorPtr) -> Self {
|
||||||
"abort" => self.abort(req.id, req.params).await,
|
let client = RpcClient::new(endpoint.clone(), ex.clone()).await.ok();
|
||||||
"mine" => self.mine(req.id, req.params).await,
|
Self { endpoint, ex, client }
|
||||||
_ => JsonError::new(ErrorCode::MethodNotFound, None, req.id).into(),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn connections_mut(&self) -> MutexGuard<'life0, HashSet<StoppableTaskPtr>> {
|
/// Stop the client.
|
||||||
self.rpc_connections.lock().await
|
pub async fn stop(&self) {
|
||||||
|
if let Some(ref client) = self.client {
|
||||||
|
client.stop().await
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MinerNode {
|
impl MinerNode {
|
||||||
// RPCAPI:
|
/// Auxiliary function to poll configured darkfid daemon for a new
|
||||||
// Signals miner daemon to abort mining pending request.
|
/// mining job.
|
||||||
// Returns `true` on success.
|
async fn poll(&self, header: &str) -> Result<(HeaderHash, BigUint, Header)> {
|
||||||
//
|
loop {
|
||||||
// --> {"jsonrpc": "2.0", "method": "abort", "params": [], "id": 42}
|
debug!(target: "minerd::rpc::poll", "Executing poll request to darkfid...");
|
||||||
// <-- {"jsonrpc": "2.0", "result": "true", "id": 42}
|
let mut request_params = self.config.wallet_config.clone();
|
||||||
async fn abort(&self, id: u16, _params: JsonValue) -> JsonResult {
|
request_params.insert(String::from("header"), JsonValue::String(String::from(header)));
|
||||||
if let Some(e) = self.abort_pending(id).await {
|
let params = match self
|
||||||
return e
|
.darkfid_daemon_request("miner.get_header", &JsonValue::from(request_params))
|
||||||
};
|
.await
|
||||||
JsonResponse::new(JsonValue::Boolean(true), id).into()
|
{
|
||||||
|
Ok(params) => params,
|
||||||
|
Err(e) => {
|
||||||
|
error!(target: "minerd::rpc::poll", "darkfid poll failed: {e}");
|
||||||
|
self.sleep().await?;
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
};
|
||||||
|
debug!(target: "minerd::rpc::poll", "Got reply: {params:?}");
|
||||||
|
|
||||||
|
// Verify response parameters
|
||||||
|
if !params.is_array() {
|
||||||
|
error!(target: "minerd::rpc::poll", "darkfid responded with invalid params: {params:?}");
|
||||||
|
self.sleep().await?;
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
let params = params.get::<Vec<JsonValue>>().unwrap();
|
||||||
|
if params.is_empty() {
|
||||||
|
debug!(target: "minerd::rpc::poll", "darkfid response is empty");
|
||||||
|
self.sleep().await?;
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if params.len() != 3 ||
|
||||||
|
!params[0].is_string() ||
|
||||||
|
!params[1].is_string() ||
|
||||||
|
!params[2].is_string()
|
||||||
|
{
|
||||||
|
error!(target: "minerd::rpc::poll", "darkfid responded with invalid params: {params:?}");
|
||||||
|
self.sleep().await?;
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse parameters
|
||||||
|
let Some(randomx_key_bytes) = base64::decode(params[0].get::<String>().unwrap()) else {
|
||||||
|
error!(target: "minerd::rpc::poll", "Failed to parse RandomX key bytes");
|
||||||
|
self.sleep().await?;
|
||||||
|
continue
|
||||||
|
};
|
||||||
|
let Ok(randomx_key) = deserialize_async::<HeaderHash>(&randomx_key_bytes).await else {
|
||||||
|
error!(target: "minerd::rpc::poll", "Failed to parse RandomX key");
|
||||||
|
self.sleep().await?;
|
||||||
|
continue
|
||||||
|
};
|
||||||
|
let Some(target_bytes) = base64::decode(params[1].get::<String>().unwrap()) else {
|
||||||
|
error!(target: "minerd::rpc::poll", "Failed to parse target bytes");
|
||||||
|
self.sleep().await?;
|
||||||
|
continue
|
||||||
|
};
|
||||||
|
let target = BigUint::from_bytes_le(&target_bytes);
|
||||||
|
let Some(header_bytes) = base64::decode(params[2].get::<String>().unwrap()) else {
|
||||||
|
error!(target: "minerd::rpc::poll", "Failed to parse header bytes");
|
||||||
|
self.sleep().await?;
|
||||||
|
continue
|
||||||
|
};
|
||||||
|
let Ok(header) = deserialize_async::<Header>(&header_bytes).await else {
|
||||||
|
error!(target: "minerd::rpc::poll", "Failed to parse header");
|
||||||
|
self.sleep().await?;
|
||||||
|
continue
|
||||||
|
};
|
||||||
|
|
||||||
|
return Ok((randomx_key, target, header))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// RPCAPI:
|
/// Auxiliary function to submit a mining solution to configured
|
||||||
// Mine provided block header for requested mine target, using
|
/// darkfid daemon.
|
||||||
// provided RandomX VM key, and return the corresponding nonce
|
async fn submit(&self, nonce: f64) -> String {
|
||||||
// value.
|
debug!(target: "minerd::rpc::submit", "Executing submit request to darkfid...");
|
||||||
//
|
let mut request_params = self.config.wallet_config.clone();
|
||||||
// --> {"jsonrpc": "2.0", "method": "mine", "params": ["target", "randomx_key", "header"], "id": 42}
|
request_params.insert(String::from("nonce"), JsonValue::Number(nonce));
|
||||||
// --> {"jsonrpc": "2.0", "result": "nonce", "id": 42}
|
let result = match self
|
||||||
async fn mine(&self, id: u16, params: JsonValue) -> JsonResult {
|
.darkfid_daemon_request("miner.submit_solution", &JsonValue::from(request_params))
|
||||||
// Verify parameters
|
.await
|
||||||
if !params.is_array() {
|
|
||||||
return JsonError::new(ErrorCode::InvalidParams, None, id).into()
|
|
||||||
}
|
|
||||||
let params = params.get::<Vec<JsonValue>>().unwrap();
|
|
||||||
if params.len() != 3 ||
|
|
||||||
!params[0].is_string() ||
|
|
||||||
!params[1].is_string() ||
|
|
||||||
!params[2].is_string()
|
|
||||||
{
|
{
|
||||||
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::<String>() {
|
||||||
|
Some(result) => result.clone(),
|
||||||
|
None => format!("darkfid responded with invalid params: {result:?}"),
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse parameters
|
|
||||||
let Some(target_bytes) = base64::decode(params[0].get::<String>().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::<String>().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::<HeaderHash>(&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::<String>().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>(&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.
|
/// Auxiliary function to execute a request towards the configured
|
||||||
async fn abort_pending(&self, id: u16) -> Option<JsonResult> {
|
/// darkfid daemon JSON-RPC endpoint.
|
||||||
// Check if a pending request is being processed
|
async fn darkfid_daemon_request(&self, method: &str, params: &JsonValue) -> Result<JsonValue> {
|
||||||
info!(target: "minerd::rpc", "Checking if a pending request is being processed...");
|
let mut lock = self.rpc_client.write().await;
|
||||||
if self.stop_signal.receiver_count() <= 1 {
|
let req = JsonRequest::new(method, params.clone());
|
||||||
info!(target: "minerd::rpc", "No pending requests!");
|
|
||||||
return None
|
// 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...");
|
// Reset the rpc client in case of an error and try again
|
||||||
// Send stop signal to worker
|
let client = RpcClient::new(lock.endpoint.clone(), lock.ex.clone()).await?;
|
||||||
if self.sender.send(()).await.is_err() {
|
let rep = client.request(req).await?;
|
||||||
error!(target: "minerd::rpc", "Failed to stop pending request");
|
lock.client = Some(client);
|
||||||
return Some(server_error(RpcError::StopFailed, id, None))
|
drop(lock);
|
||||||
}
|
Ok(rep)
|
||||||
|
}
|
||||||
|
|
||||||
// Wait for worker to terminate
|
/// Auxiliary function to stop current JSON-RPC client, if its
|
||||||
info!(target: "minerd::rpc", "Waiting for request to terminate...");
|
/// initialized.
|
||||||
while self.stop_signal.receiver_count() > 1 {
|
pub async fn stop_rpc_client(&self) {
|
||||||
sleep(1).await;
|
self.rpc_client.read().await.stop().await;
|
||||||
}
|
}
|
||||||
info!(target: "minerd::rpc", "Pending request terminated!");
|
|
||||||
|
|
||||||
// Consume channel item so its empty again
|
/// Auxiliary function to sleep for configured polling rate time.
|
||||||
if self.stop_signal.recv().await.is_err() {
|
async fn sleep(&self) -> Result<()> {
|
||||||
error!(target: "minerd::rpc", "Failed to cleanup stop signal channel");
|
// Check if stop signal is received
|
||||||
return Some(server_error(RpcError::StopFailed, id, None))
|
if self.stop_signal.is_full() {
|
||||||
|
debug!(target: "minerd::rpc::sleep", "Stop signal received, exiting polling task");
|
||||||
|
return Err(Error::DetachedTaskStopped);
|
||||||
}
|
}
|
||||||
|
debug!(target: "minerd::rpc::sleep", "Sleeping for {} until next poll...", self.config.polling_rate);
|
||||||
None
|
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(())
|
||||||
|
}
|
||||||
|
|||||||
@@ -17,26 +17,11 @@ database = "darkfid0"
|
|||||||
# Confirmation threshold, denominated by number of blocks
|
# Confirmation threshold, denominated by number of blocks
|
||||||
threshold = 6
|
threshold = 6
|
||||||
|
|
||||||
# minerd JSON-RPC endpoint
|
|
||||||
minerd_endpoint = "tcp://127.0.0.1:48467"
|
|
||||||
|
|
||||||
# PoW block production target, in seconds
|
# PoW block production target, in seconds
|
||||||
pow_target = 60
|
pow_target = 60
|
||||||
|
|
||||||
# Participate in block production
|
# Optional fixed PoW difficulty, used for testing
|
||||||
miner = true
|
#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 = "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 syncing process and start node right away
|
||||||
skip_sync = true
|
skip_sync = true
|
||||||
|
|||||||
@@ -17,23 +17,11 @@ database = "darkfid1"
|
|||||||
# Confirmation threshold, denominated by number of blocks
|
# Confirmation threshold, denominated by number of blocks
|
||||||
threshold = 6
|
threshold = 6
|
||||||
|
|
||||||
# minerd JSON-RPC endpoint
|
|
||||||
minerd_endpoint = "tcp://127.0.0.1:48567"
|
|
||||||
|
|
||||||
# PoW block production target, in seconds
|
# PoW block production target, in seconds
|
||||||
pow_target = 60
|
pow_target = 60
|
||||||
|
|
||||||
# Wallet address to receive mining rewards.
|
# Optional fixed PoW difficulty, used for testing
|
||||||
# This is a dummy one so the miner can start,
|
#pow_fixed_difficulty = 1
|
||||||
# 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"
|
|
||||||
|
|
||||||
# Skip syncing process and start node right away
|
# Skip syncing process and start node right away
|
||||||
skip_sync = false
|
skip_sync = false
|
||||||
|
|||||||
@@ -17,23 +17,11 @@ database = "darkfid2"
|
|||||||
# Confirmation threshold, denominated by number of blocks
|
# Confirmation threshold, denominated by number of blocks
|
||||||
threshold = 6
|
threshold = 6
|
||||||
|
|
||||||
# minerd JSON-RPC endpoint
|
|
||||||
minerd_endpoint = "tcp://127.0.0.1:48667"
|
|
||||||
|
|
||||||
# PoW block production target, in seconds
|
# PoW block production target, in seconds
|
||||||
pow_target = 60
|
pow_target = 60
|
||||||
|
|
||||||
# Wallet address to receive mining rewards.
|
# Optional fixed PoW difficulty, used for testing
|
||||||
# This is a dummy one so the miner can start,
|
#pow_fixed_difficulty = 1
|
||||||
# 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"
|
|
||||||
|
|
||||||
# Skip syncing process and start node right away
|
# Skip syncing process and start node right away
|
||||||
skip_sync = false
|
skip_sync = false
|
||||||
|
|||||||
@@ -17,23 +17,11 @@ database = "darkfid3"
|
|||||||
# Confirmation threshold, denominated by number of blocks
|
# Confirmation threshold, denominated by number of blocks
|
||||||
threshold = 6
|
threshold = 6
|
||||||
|
|
||||||
# minerd JSON-RPC endpoint
|
|
||||||
minerd_endpoint = "tcp://127.0.0.1:48767"
|
|
||||||
|
|
||||||
# PoW block production target, in seconds
|
# PoW block production target, in seconds
|
||||||
pow_target = 60
|
pow_target = 60
|
||||||
|
|
||||||
# Wallet address to receive mining rewards.
|
# Optional fixed PoW difficulty, used for testing
|
||||||
# This is a dummy one so the miner can start,
|
#pow_fixed_difficulty = 1
|
||||||
# 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"
|
|
||||||
|
|
||||||
# Skip syncing process and start node right away
|
# Skip syncing process and start node right away
|
||||||
skip_sync = false
|
skip_sync = false
|
||||||
|
|||||||
@@ -17,23 +17,11 @@ database = "darkfid4"
|
|||||||
# Confirmation threshold, denominated by number of blocks
|
# Confirmation threshold, denominated by number of blocks
|
||||||
threshold = 6
|
threshold = 6
|
||||||
|
|
||||||
# minerd JSON-RPC endpoint
|
|
||||||
minerd_endpoint = "tcp://127.0.0.1:48867"
|
|
||||||
|
|
||||||
# PoW block production target, in seconds
|
# PoW block production target, in seconds
|
||||||
pow_target = 60
|
pow_target = 60
|
||||||
|
|
||||||
# Wallet address to receive mining rewards.
|
# Optional fixed PoW difficulty, used for testing
|
||||||
# This is a dummy one so the miner can start,
|
#pow_fixed_difficulty = 1
|
||||||
# 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"
|
|
||||||
|
|
||||||
# Skip syncing process and start node right away
|
# Skip syncing process and start node right away
|
||||||
skip_sync = false
|
skip_sync = false
|
||||||
|
|||||||
@@ -9,10 +9,28 @@
|
|||||||
# PoW miner number of threads to use
|
# PoW miner number of threads to use
|
||||||
threads = 1
|
threads = 1
|
||||||
|
|
||||||
# JSON-RPC settings
|
# Polling rate to ask darkfid for mining jobs
|
||||||
[rpc]
|
#polling_rate = 2
|
||||||
# JSON-RPC listen URL
|
|
||||||
rpc_listen = "tcp://127.0.0.1:48467"
|
|
||||||
|
|
||||||
# Disabled RPC methods
|
# Stop mining at given height (0 mines forever)
|
||||||
#rpc_disabled_methods = []
|
#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"
|
||||||
|
|||||||
@@ -9,10 +9,28 @@
|
|||||||
# PoW miner number of threads to use
|
# PoW miner number of threads to use
|
||||||
threads = 1
|
threads = 1
|
||||||
|
|
||||||
# JSON-RPC settings
|
# Polling rate to ask darkfid for mining jobs
|
||||||
[rpc]
|
#polling_rate = 2
|
||||||
# JSON-RPC listen URL
|
|
||||||
rpc_listen = "tcp://127.0.0.1:48567"
|
|
||||||
|
|
||||||
# Disabled RPC methods
|
# Stop mining at given height (0 mines forever)
|
||||||
#rpc_disabled_methods = []
|
#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"
|
||||||
|
|||||||
@@ -9,10 +9,28 @@
|
|||||||
# PoW miner number of threads to use
|
# PoW miner number of threads to use
|
||||||
threads = 1
|
threads = 1
|
||||||
|
|
||||||
# JSON-RPC settings
|
# Polling rate to ask darkfid for mining jobs
|
||||||
[rpc]
|
#polling_rate = 2
|
||||||
# JSON-RPC listen URL
|
|
||||||
rpc_listen = "tcp://127.0.0.1:48667"
|
|
||||||
|
|
||||||
# Disabled RPC methods
|
# Stop mining at given height (0 mines forever)
|
||||||
#rpc_disabled_methods = []
|
#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"
|
||||||
|
|||||||
@@ -9,10 +9,28 @@
|
|||||||
# PoW miner number of threads to use
|
# PoW miner number of threads to use
|
||||||
threads = 1
|
threads = 1
|
||||||
|
|
||||||
# JSON-RPC settings
|
# Polling rate to ask darkfid for mining jobs
|
||||||
[rpc]
|
#polling_rate = 2
|
||||||
# JSON-RPC listen URL
|
|
||||||
rpc_listen = "tcp://127.0.0.1:48767"
|
|
||||||
|
|
||||||
# Disabled RPC methods
|
# Stop mining at given height (0 mines forever)
|
||||||
#rpc_disabled_methods = []
|
#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"
|
||||||
|
|||||||
@@ -9,10 +9,28 @@
|
|||||||
# PoW miner number of threads to use
|
# PoW miner number of threads to use
|
||||||
threads = 1
|
threads = 1
|
||||||
|
|
||||||
# JSON-RPC settings
|
# Polling rate to ask darkfid for mining jobs
|
||||||
[rpc]
|
#polling_rate = 2
|
||||||
# JSON-RPC listen URL
|
|
||||||
rpc_listen = "tcp://127.0.0.1:48867"
|
|
||||||
|
|
||||||
# Disabled RPC methods
|
# Stop mining at given height (0 mines forever)
|
||||||
#rpc_disabled_methods = []
|
#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"
|
||||||
|
|||||||
@@ -17,32 +17,32 @@ else
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
tmux new-session -d -s $session -n "node0"
|
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
|
sleep 1
|
||||||
tmux split-window -t $session -v -l 90%
|
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
|
sleep 2
|
||||||
tmux new-window -t $session -n "node1"
|
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
|
sleep 1
|
||||||
tmux split-window -t $session -v -l 90%
|
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
|
sleep 2
|
||||||
tmux new-window -t $session -n "node2"
|
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
|
sleep 1
|
||||||
tmux split-window -t $session -v -l 90%
|
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
|
sleep 2
|
||||||
tmux new-window -t $session -n "node3"
|
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
|
sleep 1
|
||||||
tmux split-window -t $session -v -l 90%
|
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
|
sleep 2
|
||||||
tmux new-window -t $session -n "node4"
|
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
|
sleep 1
|
||||||
tmux split-window -t $session -v -l 90%
|
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
|
tmux attach -t $session
|
||||||
|
|||||||
@@ -9,12 +9,12 @@ a testing wallet and pass its address to the `darkfid`
|
|||||||
config, so the wallet gets the block rewards the node
|
config, so the wallet gets the block rewards the node
|
||||||
produces. We generate a wallet, set it as the default
|
produces. We generate a wallet, set it as the default
|
||||||
and set its address as the `recipient` field in
|
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
|
% ./init-wallet.sh
|
||||||
```
|
```
|
||||||
|
|
||||||
Then start `darkfid` and wait until its initialized:
|
Then start the daemones and wait until `darkfid` is initialized:
|
||||||
```
|
```
|
||||||
% ./tmux_sessions.sh
|
% ./tmux_sessions.sh
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
rm -rf darkfid drk
|
rm -rf darkfid drk
|
||||||
sed -i -e "s|recipient =.*|recipient = \"9vw6WznKk7xEFQwwXhJWMMdjUPi3cXL8NrFKQpKifG1U\"|g" darkfid.toml
|
sed -i -e "s|recipient =.*|recipient = \"9vw6WznKk7xEFQwwXhJWMMdjUPi3cXL8NrFKQpKifG1U\"|g" minerd.toml
|
||||||
|
|||||||
@@ -17,27 +17,12 @@ database = "darkfid"
|
|||||||
# Confirmation threshold, denominated by number of blocks
|
# Confirmation threshold, denominated by number of blocks
|
||||||
threshold = 1
|
threshold = 1
|
||||||
|
|
||||||
# minerd JSON-RPC endpoint
|
|
||||||
minerd_endpoint = "tcp://127.0.0.1:48467"
|
|
||||||
|
|
||||||
# PoW block production target, in seconds
|
# PoW block production target, in seconds
|
||||||
pow_target = 10
|
pow_target = 10
|
||||||
|
|
||||||
# Optional fixed PoW difficulty, used for testing
|
# Optional fixed PoW difficulty, used for testing
|
||||||
pow_fixed_difficulty = 1
|
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 syncing process and start node right away
|
||||||
skip_sync = true
|
skip_sync = true
|
||||||
|
|
||||||
|
|||||||
@@ -7,4 +7,4 @@ $DRK wallet initialize
|
|||||||
$DRK wallet keygen
|
$DRK wallet keygen
|
||||||
$DRK wallet default-address 1
|
$DRK wallet default-address 1
|
||||||
wallet=$($DRK wallet address)
|
wallet=$($DRK wallet address)
|
||||||
sed -i -e "s|9vw6WznKk7xEFQwwXhJWMMdjUPi3cXL8NrFKQpKifG1U|$wallet|g" darkfid.toml
|
sed -i -e "s|9vw6WznKk7xEFQwwXhJWMMdjUPi3cXL8NrFKQpKifG1U|$wallet|g" minerd.toml
|
||||||
|
|||||||
@@ -9,10 +9,30 @@
|
|||||||
# PoW miner number of threads to use
|
# PoW miner number of threads to use
|
||||||
#threads = 4
|
#threads = 4
|
||||||
|
|
||||||
# JSON-RPC settings
|
# Polling rate to ask darkfid for mining jobs
|
||||||
[rpc]
|
#polling_rate = 2
|
||||||
# JSON-RPC listen URL
|
|
||||||
rpc_listen = "tcp://127.0.0.1:48467"
|
|
||||||
|
|
||||||
# Disabled RPC methods
|
# Stop mining at given height (0 mines forever)
|
||||||
#rpc_disabled_methods = []
|
#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"
|
||||||
|
|||||||
@@ -52,30 +52,30 @@ wait_token() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
mint_dao() {
|
mint_dao() {
|
||||||
$DRK dao create 20 10 10 0.67 MLDY > $OUTPUT_FOLDER/dao.toml
|
$DRK dao create 20 10 10 0.67 ANON > $OUTPUT_FOLDER/dao.toml
|
||||||
$DRK dao import MiladyMakerDAO < $OUTPUT_FOLDER/dao.toml
|
$DRK dao import AnonDAO < $OUTPUT_FOLDER/dao.toml
|
||||||
$DRK dao list
|
$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() {
|
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
|
sleep $SLEEP_TIME
|
||||||
sh ./sync-wallet.sh > /dev/null
|
sh ./sync-wallet.sh > /dev/null
|
||||||
done
|
done
|
||||||
}
|
}
|
||||||
|
|
||||||
fill_treasury() {
|
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)"
|
SPEND_HOOK="$($DRK dao spend-hook)"
|
||||||
BULLA="$($DRK dao list MiladyMakerDAO | grep '^Bulla: ' | cut -d' ' -f2)"
|
BULLA="$($DRK dao list AnonDAO | grep '^Bulla: ' | cut -d' ' -f2)"
|
||||||
$DRK transfer 20 WCKD "$PUBKEY" "$SPEND_HOOK" "$BULLA" | tee $OUTPUT_FOLDER/xfer.tx | $DRK broadcast
|
$DRK transfer 20 DAWN "$PUBKEY" "$SPEND_HOOK" "$BULLA" | tee $OUTPUT_FOLDER/xfer.tx | $DRK broadcast
|
||||||
}
|
}
|
||||||
|
|
||||||
dao_balance() {
|
dao_balance() {
|
||||||
BALANCE=$($DRK dao balance MiladyMakerDAO 2>/dev/null)
|
BALANCE=$($DRK dao balance AnonDAO 2>/dev/null)
|
||||||
# No tokens received at all yet
|
# No tokens received at all yet
|
||||||
if echo "$BALANCE" | grep -q "No unspent balances found"; then
|
if echo "$BALANCE" | grep -q "No unspent balances found"; then
|
||||||
echo 0
|
echo 0
|
||||||
@@ -94,7 +94,7 @@ dao_balance() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
wait_dao_treasury() {
|
wait_dao_treasury() {
|
||||||
while [ "$(dao_balance WCKD)" = 0 ]; do
|
while [ "$(dao_balance DAWN)" = 0 ]; do
|
||||||
sleep $SLEEP_TIME
|
sleep $SLEEP_TIME
|
||||||
sh ./sync-wallet.sh > /dev/null
|
sh ./sync-wallet.sh > /dev/null
|
||||||
done
|
done
|
||||||
@@ -102,12 +102,12 @@ wait_dao_treasury() {
|
|||||||
|
|
||||||
propose() {
|
propose() {
|
||||||
MY_ADDR=$($DRK wallet address)
|
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
|
$DRK dao proposal "$PROPOSAL" --mint-proposal | tee $OUTPUT_FOLDER/propose.tx | $DRK broadcast
|
||||||
}
|
}
|
||||||
|
|
||||||
wait_proposal() {
|
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
|
while [ "$($DRK dao proposal $PROPOSAL | grep '^Proposal transaction hash: ' | awk '{print $4}')" = None ]; do
|
||||||
sleep $SLEEP_TIME
|
sleep $SLEEP_TIME
|
||||||
sh ./sync-wallet.sh > /dev/null
|
sh ./sync-wallet.sh > /dev/null
|
||||||
@@ -115,12 +115,12 @@ wait_proposal() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
vote() {
|
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
|
$DRK dao vote "$PROPOSAL" 1 | tee $OUTPUT_FOLDER/dao-vote.tx | $DRK broadcast
|
||||||
}
|
}
|
||||||
|
|
||||||
wait_vote() {
|
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
|
while [ "$($DRK dao proposal $PROPOSAL | grep '^Current proposal outcome: ' | awk '{print $4}')" != "Approved" ]; do
|
||||||
sleep $SLEEP_TIME
|
sleep $SLEEP_TIME
|
||||||
sh ./sync-wallet.sh > /dev/null
|
sh ./sync-wallet.sh > /dev/null
|
||||||
@@ -128,12 +128,12 @@ wait_vote() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
do_exec() {
|
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
|
$DRK dao exec --early $PROPOSAL | tee $OUTPUT_FOLDER/dao-exec.tx | $DRK broadcast
|
||||||
}
|
}
|
||||||
|
|
||||||
wait_exec() {
|
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
|
while [ -z "$($DRK dao proposal $PROPOSAL | grep '^Proposal was executed on transaction: ')" ]; do
|
||||||
sleep $SLEEP_TIME
|
sleep $SLEEP_TIME
|
||||||
sh ./sync-wallet.sh > /dev/null
|
sh ./sync-wallet.sh > /dev/null
|
||||||
@@ -141,10 +141,10 @@ wait_exec() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
wait_token DRK
|
wait_token DRK
|
||||||
mint_token WCKD 42
|
mint_token ANON 42
|
||||||
wait_token WCKD
|
wait_token ANON
|
||||||
mint_token MLDY 20
|
mint_token DAWN 20
|
||||||
wait_token MLDY
|
wait_token DAWN
|
||||||
mint_dao
|
mint_dao
|
||||||
wait_dao_mint
|
wait_dao_mint
|
||||||
fill_treasury
|
fill_treasury
|
||||||
|
|||||||
@@ -3,6 +3,6 @@ darkfid localnet
|
|||||||
|
|
||||||
This will start three `darkfid` node instances
|
This will start three `darkfid` node instances
|
||||||
in localnet mode. Two of the nodes are activelly
|
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,
|
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.
|
||||||
|
|||||||
@@ -17,23 +17,11 @@ database = "darkfid0"
|
|||||||
# Confirmation threshold, denominated by number of blocks
|
# Confirmation threshold, denominated by number of blocks
|
||||||
threshold = 6
|
threshold = 6
|
||||||
|
|
||||||
# minerd JSON-RPC endpoint
|
|
||||||
minerd_endpoint = "tcp://127.0.0.1:48467"
|
|
||||||
|
|
||||||
# PoW block production target, in seconds
|
# PoW block production target, in seconds
|
||||||
pow_target = 20
|
pow_target = 20
|
||||||
|
|
||||||
# Wallet address to receive mining rewards.
|
# Optional fixed PoW difficulty, used for testing
|
||||||
# This is a dummy one so the miner can start,
|
#pow_fixed_difficulty = 1
|
||||||
# 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"
|
|
||||||
|
|
||||||
# Skip syncing process and start node right away
|
# Skip syncing process and start node right away
|
||||||
skip_sync = true
|
skip_sync = true
|
||||||
|
|||||||
@@ -25,23 +25,11 @@ database = "darkfid1"
|
|||||||
# Confirmation threshold, denominated by number of blocks
|
# Confirmation threshold, denominated by number of blocks
|
||||||
threshold = 6
|
threshold = 6
|
||||||
|
|
||||||
# minerd JSON-RPC endpoint
|
|
||||||
minerd_endpoint = "tcp://127.0.0.1:48567"
|
|
||||||
|
|
||||||
# PoW block production target, in seconds
|
# PoW block production target, in seconds
|
||||||
pow_target = 20
|
pow_target = 20
|
||||||
|
|
||||||
# Wallet address to receive mining rewards.
|
# Optional fixed PoW difficulty, used for testing
|
||||||
# This is a dummy one so the miner can start,
|
#pow_fixed_difficulty = 1
|
||||||
# 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"
|
|
||||||
|
|
||||||
# Skip syncing process and start node right away
|
# Skip syncing process and start node right away
|
||||||
skip_sync = false
|
skip_sync = false
|
||||||
|
|||||||
@@ -17,12 +17,12 @@ database = "darkfid2"
|
|||||||
# Confirmation threshold, denominated by number of blocks
|
# Confirmation threshold, denominated by number of blocks
|
||||||
threshold = 6
|
threshold = 6
|
||||||
|
|
||||||
# minerd JSON-RPC endpoint
|
|
||||||
#minerd_endpoint = "tcp://127.0.0.1:28467"
|
|
||||||
|
|
||||||
# PoW block production target, in seconds
|
# PoW block production target, in seconds
|
||||||
pow_target = 20
|
pow_target = 20
|
||||||
|
|
||||||
|
# Optional fixed PoW difficulty, used for testing
|
||||||
|
#pow_fixed_difficulty = 1
|
||||||
|
|
||||||
# Skip syncing process and start node right away
|
# Skip syncing process and start node right away
|
||||||
skip_sync = false
|
skip_sync = false
|
||||||
|
|
||||||
|
|||||||
@@ -9,10 +9,28 @@
|
|||||||
# PoW miner number of threads to use
|
# PoW miner number of threads to use
|
||||||
threads = 2
|
threads = 2
|
||||||
|
|
||||||
# JSON-RPC settings
|
# Polling rate to ask darkfid for mining jobs
|
||||||
[rpc]
|
#polling_rate = 2
|
||||||
# JSON-RPC listen URL
|
|
||||||
rpc_listen = "tcp://127.0.0.1:48467"
|
|
||||||
|
|
||||||
# Disabled RPC methods
|
# Stop mining at given height (0 mines forever)
|
||||||
#rpc_disabled_methods = []
|
#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"
|
||||||
|
|||||||
@@ -9,10 +9,28 @@
|
|||||||
# PoW miner number of threads to use
|
# PoW miner number of threads to use
|
||||||
threads = 2
|
threads = 2
|
||||||
|
|
||||||
# JSON-RPC settings
|
# Polling rate to ask darkfid for mining jobs
|
||||||
[rpc]
|
#polling_rate = 2
|
||||||
# JSON-RPC listen URL
|
|
||||||
rpc_listen = "tcp://127.0.0.1:48567"
|
|
||||||
|
|
||||||
# Disabled RPC methods
|
# Stop mining at given height (0 mines forever)
|
||||||
#rpc_disabled_methods = []
|
#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"
|
||||||
|
|||||||
@@ -18,17 +18,17 @@ else
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
tmux new-session -d -s $session -n "node0"
|
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
|
sleep 1
|
||||||
tmux split-window -t $session -v -l 90%
|
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
|
sleep 2
|
||||||
tmux new-window -t $session -n "node1"
|
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
|
sleep 1
|
||||||
tmux split-window -t $session -v -l 90%
|
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
|
sleep 2
|
||||||
tmux new-window -t $session -n "node2"
|
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
|
tmux attach -t $session
|
||||||
|
|||||||
@@ -729,7 +729,7 @@ P2Pool wallet address to use:
|
|||||||
{YOUR_DAO_P2POOL_WALLET_ADDRESS_CONFIGURATION}
|
{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
|
corresponding fields(uncomment if needed) as per retrieved
|
||||||
configuration:
|
configuration:
|
||||||
|
|
||||||
|
|||||||
@@ -28,27 +28,27 @@ and stays connected to the p2p network.
|
|||||||
* `drk` is a CLI wallet. It provides an interface to smart contracts such
|
* `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
|
as Money and DAO, manages our keys and coins, and scans the blockchain
|
||||||
to update our balances.
|
to update our balances.
|
||||||
* `minerd` is the DarkFi mining daemon. `darkfid` connects to it over
|
* `minerd` is the DarkFi mining daemon. Connects to `darkfid` over RPC,
|
||||||
RPC, and triggers commands to mine blocks.
|
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:
|
each marked `[network_config]`. The sections look like this:
|
||||||
|
|
||||||
* `[network_config."testnet"]`
|
* `[network_config."testnet"]`
|
||||||
* `[network_config."mainnet"]`
|
* `[network_config."mainnet"]`
|
||||||
* `[network_config."localnet"]`
|
* `[network_config."localnet"]`
|
||||||
|
|
||||||
At the top of the `darkfid` and `drk` config file, we can modify the
|
At the top of each daemon config file, we can modify the network being
|
||||||
network being used by changing the following line:
|
used by changing the following line:
|
||||||
|
|
||||||
```toml
|
```toml
|
||||||
# Blockchain network to use
|
# Blockchain network to use
|
||||||
network = "testnet"
|
network = "testnet"
|
||||||
```
|
```
|
||||||
|
|
||||||
This enables us to configure `darkfid` and `drk` for different contexts,
|
This enables us to configure the daemons for different contexts, namely
|
||||||
namely mainnet, testnet and localnet. Mainnet is not active yet. Localnet
|
mainnet, testnet and localnet. Mainnet is not active yet. Localnet can
|
||||||
can be setup by following the instructions [here](#local-deployment). The
|
be setup by following the instructions [here](#local-deployment). The
|
||||||
rest of this tutorial assumes we are setting up a testnet node.
|
rest of this tutorial assumes we are setting up a testnet node.
|
||||||
|
|
||||||
## Compiling
|
## 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
|
to help secure the network, you can participate in the mining process
|
||||||
by running the native `minerd` mining daemon.
|
by running the native `minerd` mining daemon.
|
||||||
|
|
||||||
To mine on DarkFI we need to expose the `minerd` RPC to the `darkfid`
|
To mine on DarkFI we need to add a recipient to `minerd` that specifies
|
||||||
full node, which will initiate the mining process. We'll also need to
|
where the mining rewards will be minted to.
|
||||||
add a recipient to `darkfid` that specifies where the mining rewards
|
|
||||||
will be minted to.
|
|
||||||
|
|
||||||
First, compile it:
|
First, compile it:
|
||||||
|
|
||||||
@@ -229,8 +227,32 @@ $ ./minerd
|
|||||||
Config file created in "~/.config/darkfi/minerd_config.toml". Please review it and try again.
|
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,
|
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
|
```shell
|
||||||
$ ./minerd
|
$ ./minerd
|
||||||
@@ -240,41 +262,15 @@ $ ./minerd
|
|||||||
14:20:06 [INFO] Mining daemon initialized successfully!
|
14:20:06 [INFO] Mining daemon initialized successfully!
|
||||||
14:20:06 [INFO] Starting mining daemon...
|
14:20:06 [INFO] Starting mining daemon...
|
||||||
14:20:06 [INFO] Mining daemon started successfully!
|
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
|
### Darkfid
|
||||||
|
|
||||||
Now that `darkfid` configuration is in place, you can run it again and
|
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,
|
This will give you an indication of the current progress. Keep it running,
|
||||||
and you should see a `Blockchain synced!` message after some time.
|
and you should see a `Blockchain synced!` message after some time.
|
||||||
|
|
||||||
If you're running `minerd`, you should see a notification from the
|
If you're running `minerd`, you should see a notification like this:
|
||||||
`minerd` terminal like this:
|
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
...
|
...
|
||||||
@@ -321,36 +316,33 @@ This means that `darkfid` and `minerd` are connected over RPC and
|
|||||||
|
|
||||||
```shell
|
```shell
|
||||||
...
|
...
|
||||||
14:23:56 [INFO] Mining block 4abc760a1f1c7198837e91c24d8e045e9fc9cb9fdf3a5fd45e184c25b03b0b51 for target:
|
14:23:56 [INFO] [RPC] Created new blocktemplate: address=9vw6...fG1U, spend_hook=-, user_data=-, hash=beb0...42aa
|
||||||
115792089237316195423570985008687907853269984665640564039457584007913129639935
|
14:24:04 [INFO] [RPC] Got solution submission for block template: beb0...42aa
|
||||||
14:24:04 [INFO] Mined block 4abc760a1f1c7198837e91c24d8e045e9fc9cb9fdf3a5fd45e184c25b03b0b51 with nonce: 2
|
14:24:06 [INFO] [RPC] Mined block header hash: 36fe...753c
|
||||||
14:24:06 [INFO] Received request to mine block 17e7428ecb3d911477f8452170d0822c831c6912027abb120e4b4c4cf01d6020 for target:
|
14:24:06 [INFO] [RPC] Proposing new block to network
|
||||||
115792089237316195423570985008687907853269984665640564039457584007913129639935
|
|
||||||
14:24:06 [INFO] Checking if a pending request is being processed...
|
|
||||||
...
|
...
|
||||||
```
|
```
|
||||||
|
|
||||||
When `darkfid` and `minerd` are correctly connected and you get an
|
When `darkfid` and `minerd` are correctly connected and you get an
|
||||||
error like this:
|
error on `minerd` like this:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
...
|
...
|
||||||
[ERROR] minerd::rpc: Failed mining block f6b4a0f0c8f90905da271ec0add2e856939ef3b0d6cd5b28964d9c2b6d0a0fa9 with error:
|
[ERROR] Failed mining block header b757...5fb1 with error: Miner task stopped
|
||||||
Miner task stopped
|
|
||||||
...
|
...
|
||||||
```
|
```
|
||||||
|
|
||||||
That's expected behavior. It means your setup is correct and you are
|
That's expected behavior. It means your setup is correct and you are
|
||||||
mining blocks. `Failed mining block` happens when a new block was
|
mining blocks. `Failed mining block header` happens when a new block
|
||||||
received by `darkfid`, extending the current best fork, so it sends an
|
was received by `darkfid`, extending the current best fork, so when
|
||||||
interuption message to `minerd` to stop mining the current block and
|
`minerd` polls it again it retrieves the new block header to mine,
|
||||||
start mining the next height one.
|
interupting current mining workers to start mining the new one.
|
||||||
|
|
||||||
Otherwise, you'll see a notification like this:
|
Otherwise, you'll see a notification like this:
|
||||||
|
|
||||||
```shell
|
```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
|
$ ./init-wallet.sh
|
||||||
```
|
```
|
||||||
|
|
||||||
Then start `darkfid` and wait until its initialized:
|
Then start the daemones and wait until `darkfid` is initialized:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
$ ./tmux_sessions.sh
|
$ ./tmux_sessions.sh
|
||||||
|
|||||||
@@ -396,9 +396,6 @@ impl PoWModule {
|
|||||||
threads: usize,
|
threads: usize,
|
||||||
stop_signal: &Receiver<()>,
|
stop_signal: &Receiver<()>,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
// Grab the next mine target
|
|
||||||
let target = self.next_mine_target()?;
|
|
||||||
|
|
||||||
// Grab the RandomX key to use.
|
// Grab the RandomX key to use.
|
||||||
// We only use the next key when the next block is the
|
// We only use the next key when the next block is the
|
||||||
// height changing one.
|
// height changing one.
|
||||||
@@ -410,7 +407,10 @@ impl PoWModule {
|
|||||||
&self.darkfi_rx_keys.0
|
&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.
|
/// Auxiliary function to mine provided header using a single thread.
|
||||||
fn single_thread_mine(
|
fn single_thread_mine(
|
||||||
target: &BigUint,
|
|
||||||
input: &HeaderHash,
|
input: &HeaderHash,
|
||||||
|
target: &BigUint,
|
||||||
header: &mut Header,
|
header: &mut Header,
|
||||||
stop_signal: &Receiver<()>,
|
stop_signal: &Receiver<()>,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
@@ -486,8 +486,8 @@ fn single_thread_mine(
|
|||||||
|
|
||||||
/// Auxiliary function to mine provided header using a multiple threads.
|
/// Auxiliary function to mine provided header using a multiple threads.
|
||||||
fn multi_thread_mine(
|
fn multi_thread_mine(
|
||||||
target: &BigUint,
|
|
||||||
input: &HeaderHash,
|
input: &HeaderHash,
|
||||||
|
target: &BigUint,
|
||||||
header: &mut Header,
|
header: &mut Header,
|
||||||
threads: usize,
|
threads: usize,
|
||||||
stop_signal: &Receiver<()>,
|
stop_signal: &Receiver<()>,
|
||||||
@@ -614,8 +614,8 @@ fn multi_thread_mine(
|
|||||||
|
|
||||||
/// Mine provided header, based on provided PoW module next mine target.
|
/// Mine provided header, based on provided PoW module next mine target.
|
||||||
pub fn mine_block(
|
pub fn mine_block(
|
||||||
target: &BigUint,
|
|
||||||
input: &HeaderHash,
|
input: &HeaderHash,
|
||||||
|
target: &BigUint,
|
||||||
header: &mut Header,
|
header: &mut Header,
|
||||||
threads: usize,
|
threads: usize,
|
||||||
stop_signal: &Receiver<()>,
|
stop_signal: &Receiver<()>,
|
||||||
@@ -634,8 +634,8 @@ pub fn mine_block(
|
|||||||
error!(target: "validator::pow::mine_block", "[MINER] Can't use 0 threads!");
|
error!(target: "validator::pow::mine_block", "[MINER] Can't use 0 threads!");
|
||||||
Err(Error::MinerTaskStopped)
|
Err(Error::MinerTaskStopped)
|
||||||
}
|
}
|
||||||
1 => single_thread_mine(target, input, header, stop_signal),
|
1 => single_thread_mine(input, target, header, stop_signal),
|
||||||
_ => multi_thread_mine(target, input, header, threads, stop_signal),
|
_ => multi_thread_mine(input, target, header, threads, stop_signal),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user