Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
68 changes: 68 additions & 0 deletions miner/src/core/args.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
use clap::Parser;
use nyks_protocol::consensus::network::Network;
use nyks_rpc_client::RpcApi;
use nyks_rpc_client::http::HttpClient;
use nyks_standards::wallet::keys::address::Address;
use nyks_standards::wallet::keys::address::Recipient;

#[derive(Parser)]
#[command(name = "nyks-miner")]
#[command(about = "A nyks CPU miner")]
pub struct Args {
/// RPC URL to use (JSON/HTTP)
#[arg(long, default_value = "http://localhost:9797")]
pub rpc_url: String,

/// Address to mine for (coinbase reward receiver)
#[arg(long)]
pub address: String,

/// Network we are going to mine on.
#[arg(long, default_value_t = Network::Main)]
pub network: Network,

/// Minimum guesser reward as a percentage of total block reward (0-100), default 10.
#[arg(long, default_value_t = 10.0)]
pub min_percentage_reward: f64,
}

impl Args {
/// Validates the percentage is in range and converts it to a fraction
/// (e.g. 10.0 -> 0.10) for internal use.
pub fn min_reward_fraction(&self) -> f64 {
assert!(
(0.0..=100.0).contains(&self.min_percentage_reward),
"min-percentage-reward must be between 0 and 100, got {}",
self.min_percentage_reward
);

self.min_percentage_reward / 100.0
}

/// Creates an RPC client and verifies that the connected node is on the
/// expected network.
pub async fn rpc_client(&self) -> HttpClient {
let client = HttpClient::new(self.rpc_url.clone());

let remote_network = client
.network()
.await
.expect("Failed to connect to RPC node")
.network
.parse::<Network>()
.unwrap();

assert_eq!(
self.network, remote_network,
"Network mismatch: expected {:?}, connected node is on {:?}",
self.network, remote_network,
);

client
}

/// Validates that `address` is a well-formed address for the selected network.
pub fn validate_address(&self) {
Address::from_bech32m(&self.address, self.network).expect("Invalid address");
}
}
1 change: 1 addition & 0 deletions miner/src/core/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
pub mod args;
70 changes: 0 additions & 70 deletions miner/src/flow.rs

This file was deleted.

28 changes: 9 additions & 19 deletions miner/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,26 +2,14 @@ use std::panic;

use anyhow::Result;
use clap::Parser;
use nyks_rpc_client::http::HttpClient;
use tracing::info;
use tracing_subscriber::EnvFilter;

use crate::flow::Miner;

pub mod flow;
pub mod guesser;

#[derive(Parser)]
#[command(name = "nyks-miner")]
#[command(about = "A nyks CPU miner")]
struct Args {
/// Address to mine for (coinbase reward receiver)
#[arg(long)]
address: String,
/// RPC URL to use (JSON/HTTP)
#[arg(long)]
rpc_url: String,
}
use crate::core::args::Args;
use crate::miner::flow::Miner;

pub mod core;
pub mod miner;

#[tokio::main]
async fn main() -> Result<()> {
Expand All @@ -37,8 +25,10 @@ async fn main() -> Result<()> {

info!("Initializing nyks-miner, the operator of nyks blocks...");

let client = HttpClient::new(args.rpc_url);
let miner = Miner::new(client, args.address);
args.validate_address();

let client = args.rpc_client().await;
let miner = Miner::new(client, args.address.clone(), args.min_reward_fraction());

miner.main_loop().await;

Expand Down
110 changes: 110 additions & 0 deletions miner/src/miner/flow.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
use std::sync::Arc;
use std::time::{Duration, Instant};

use num_traits::Zero;
use nyks_protocol::consensus::block::Block;
use nyks_protocol::consensus::type_scripts::native_currency_amount::NativeCurrencyAmount;
use nyks_rpc_client::RpcApi;
use nyks_rpc_client::http::HttpClient;
use tokio::sync::RwLock;
use tracing::debug;
use tracing::info;

use crate::miner::guesser::Guesser;

#[derive(Clone, Debug)]
pub struct Miner {
client: HttpClient,
address: String,
min_reward_fraction: f64,
guesser_reward: Arc<RwLock<NativeCurrencyAmount>>,
guesser: Guesser,
composing_since: Arc<RwLock<Option<Instant>>>,
}

impl Miner {
pub fn new(client: HttpClient, address: String, min_reward_fraction: f64) -> Self {
Miner {
client: client.clone(),
address,
min_reward_fraction,
guesser_reward: Arc::new(RwLock::new(NativeCurrencyAmount::zero())),
guesser: Guesser::new(client),
composing_since: Arc::new(RwLock::new(None)),
}
}

pub async fn main_loop(&self) {
let mut interval = tokio::time::interval(Duration::from_secs(5));
loop {
interval.tick().await;
self.scan_templates().await;
}
}

pub async fn scan_templates(&self) {
let template = self
.client
.get_block_template(Some(self.address.clone()))
.await
.unwrap()
.template;

if let Some(template) = template {
// If we were waiting on a template, capture how long composing took
let composing_time = {
let mut composing_since = self.composing_since.write().await;
composing_since
.take()
.map(|start| start.elapsed().as_secs_f64())
};

let new_guesser_reward = template.metadata.total_guesser_reward.0;
let total_reward = Block::block_subsidy(template.block.kernel.header.height);
let guesser_share = new_guesser_reward.to_nau() as f64 / total_reward.to_nau() as f64;

if guesser_share < self.min_reward_fraction {
debug!(
"Skipping template: guesser share {:.2}% below minimum {:.2}%.",
guesser_share * 100.0,
self.min_reward_fraction * 100.0
);
return;
}

let mut current_guesser_reward = self.guesser_reward.write().await;

if new_guesser_reward > *current_guesser_reward {
match composing_time {
Some(secs) => info!(
"Switching to mining of new template with {} NYKS reward (composed in {:.2}s).",
new_guesser_reward, secs
),
None => info!(
"Switching to mining of new template with {} NYKS reward.",
new_guesser_reward
),
}
*current_guesser_reward = new_guesser_reward;

self.guesser
.override_task(template.metadata.prev_block, template)
.await;
}
} else {
{
let mut composing_since = self.composing_since.write().await;
if composing_since.is_none() {
info!("Waiting for a template...");
*composing_since = Some(Instant::now());
}
}

if self.guesser.is_running().await {
info!("New tip is found, waiting for a composed template...");
*self.guesser_reward.write().await = NativeCurrencyAmount::zero();
self.guesser.stop().await;
}
}
}
}
Loading
Loading