darkfid; Minor cleanups

This commit is contained in:
x
2025-12-28 11:07:43 +00:00
parent d1b94095a8
commit 0fcd8c96d3
4 changed files with 7 additions and 463 deletions

View File

@@ -42,10 +42,6 @@ mod rpc_blockchain;
/// Transactions related methods
mod rpc_tx;
// TODO: drop
/// minerd related methods
mod rpc_miner;
/// Stratum JSON-RPC related methods for native mining
pub mod rpc_stratum;

View File

@@ -1,434 +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 crate::DarkfiNode;
impl DarkfiNode {
/*
// RPCAPI:
// Queries the validator for the current mining RandomX key,
// based on next block height.
// If no forks exist, returns the canonical key.
// Returns the current mining RandomX key encoded as base64 string.
//
// **Params:**
// * `None`
//
// **Returns:**
// * `String`: Current mining RandomX key (base64 encoded)
//
// --> {"jsonrpc": "2.0", "method": "miner.get_current_mining_randomx_key", "params": [], "id": 1}
// <-- {"jsonrpc": "2.0", "result": ["randomx_key"], "id": 1}
pub async fn miner_get_current_mining_randomx_key(
&self,
id: u16,
params: JsonValue,
) -> JsonResult {
// Verify request params
let Some(params) = params.get::<Vec<JsonValue>>() else {
return JsonError::new(InvalidParams, None, id).into()
};
if !params.is_empty() {
return JsonError::new(InvalidParams, None, id).into()
}
// Grab current mining RandomX key
let randomx_key = match self.validator.current_mining_randomx_key().await {
Ok(key) => key,
Err(e) => {
error!(
target: "darkfid::rpc::current_mining_randomx_key",
"[RPC] Retrieving current mining RandomX key failed: {e}",
);
return JsonError::new(ErrorCode::InternalError, None, id).into()
}
};
// Encode them and build response
let response = JsonValue::Array(vec![JsonValue::String(base64::encode(
&serialize_async(&randomx_key).await,
))]);
JsonResponse::new(response, id).into()
}
// 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_str) = recipient.get::<String>() else {
return server_error(RpcError::MinerInvalidRecipient, id, None)
};
let Ok(recipient) = Address::from_str(recipient_str) else {
return server_error(RpcError::MinerInvalidRecipient, id, None)
};
if recipient.network() != self.network {
return server_error(RpcError::MinerInvalidRecipientPrefix, 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_str.clone().into_bytes(), spend_hook, user_data)).await;
let mut blocktemplates = self.blocktemplates.lock().await;
let mut extended_fork = match self.validator.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 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_str) = recipient.get::<String>() else {
return server_error(RpcError::MinerInvalidRecipient, id, None)
};
let Ok(recipient) = Address::from_str(recipient_str) else {
return server_error(RpcError::MinerInvalidRecipient, id, None)
};
if recipient.network() != self.network {
return server_error(RpcError::MinerInvalidRecipientPrefix, 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_str.clone().into_bytes(), 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 u32;
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()
}
*/
}

View File

@@ -86,6 +86,7 @@ impl DarkfiNode {
// * `target` : Current mining target
// * `algo` : The mining algorithm - RandomX
// * `seed_hash` : Current RandomX key
// * `next_seed_hash`: (optional) Next RandomX key if it is known
//
// --> {"jsonrpc":"2.0", "method": "login", "id": 1, "params": {"login": "MINING_CONFIG", "pass": "", "agent": "XMRig", "algo": ["rx/0"]}}
// <-- {"jsonrpc":"2.0", "id": 1, "result": {"id": "1be0b7b6-b15a-47be-a17d-46b2911cf7d0", "job": { ... }, "status": "OK"}}
@@ -99,9 +100,6 @@ impl DarkfiNode {
let Some(params) = params.get::<HashMap<String, JsonValue>>() else {
return JsonError::new(InvalidParams, None, id).into()
};
if params.len() != 4 {
return JsonError::new(InvalidParams, None, id).into()
}
// Parse login mining configuration
let Some(wallet) = params.get("login") else {
@@ -120,12 +118,9 @@ impl DarkfiNode {
let Some(pass) = params.get("pass") else {
return server_error(RpcError::MinerMissingPassword, id, None)
};
let Some(pass) = pass.get::<String>() else {
let Some(_pass) = pass.get::<String>() else {
return server_error(RpcError::MinerInvalidPassword, id, None)
};
if pass != "x" {
return server_error(RpcError::MinerInvalidPassword, id, None)
}
// Parse agent
let Some(agent) = params.get("agent") else {
@@ -160,7 +155,10 @@ impl DarkfiNode {
}
// Register the new miner
info!(target: "darkfid::rpc::rpc_stratum::stratum_login","[RPC-STRATUM] Got login from {wallet} ({agent})");
info!(
target: "darkfid::rpc::rpc_stratum::stratum_login",
"[RPC-STRATUM] Got login from {wallet} ({agent})",
);
let (client_id, block_template, publisher) =
match self.registry.register_miner(&self.validator, wallet, &config).await {
Ok(p) => p,
@@ -214,9 +212,6 @@ impl DarkfiNode {
let Some(params) = params.get::<HashMap<String, JsonValue>>() else {
return JsonError::new(InvalidParams, None, id).into()
};
if params.len() != 4 {
return JsonError::new(InvalidParams, None, id).into()
}
// Parse client id
let Some(client_id) = params.get("id") else {
@@ -309,7 +304,7 @@ impl DarkfiNode {
{
error!(
target: "darkfid::rpc::rpc_xmr::xmr_merge_submit_solution",
"[RPC-XMR] Error submitting new block: {e}",
"[RPC-STRATUM] Error submitting new block: {e}",
);
return JsonResponse::new(
JsonValue::from(HashMap::from([(
@@ -360,9 +355,6 @@ impl DarkfiNode {
let Some(params) = params.get::<HashMap<String, JsonValue>>() else {
return JsonError::new(InvalidParams, None, id).into()
};
if params.len() != 1 {
return JsonError::new(InvalidParams, None, id).into()
}
// Parse client id
let Some(client_id) = params.get("id") else {

View File

@@ -65,10 +65,6 @@ impl RequestHandler<MmRpcHandler> for DarkfiNode {
"merge_mining_get_chain_id" => self.xmr_merge_mining_get_chain_id(req.id, req.params).await,
"merge_mining_get_aux_block" => self.xmr_merge_mining_get_aux_block(req.id, req.params).await,
"merge_mining_submit_solution" => self.xmr_merge_mining_submit_solution(req.id, req.params).await,
// ==============
// Invalid method
// ==============
_ => JsonError::new(ErrorCode::MethodNotFound, None, req.id).into(),
}
}
@@ -146,9 +142,6 @@ impl DarkfiNode {
let Some(params) = params.get::<HashMap<String, JsonValue>>() else {
return JsonError::new(InvalidParams, None, id).into()
};
if params.len() != 4 {
return JsonError::new(InvalidParams, None, id).into()
}
// Parse aux_hash
let Some(aux_hash) = params.get("aux_hash") else {
@@ -261,9 +254,6 @@ impl DarkfiNode {
let Some(params) = params.get::<HashMap<String, JsonValue>>() else {
return JsonError::new(InvalidParams, None, id).into()
};
if params.len() != 6 {
return JsonError::new(InvalidParams, None, id).into()
}
// Parse aux_hash
let Some(aux_hash) = params.get("aux_hash") else {