Implement the mining software

This commit is contained in:
2024-03-24 13:09:29 +01:00
parent 2f3216a0c4
commit 825b07bc11
7 changed files with 608 additions and 7 deletions

198
miner/src/main.rs Normal file
View File

@@ -0,0 +1,198 @@
use base64::prelude::*;
use clap::Parser;
use rand::Rng;
use sha2::Digest;
use sha2::Sha256;
use std::io::ErrorKind;
use std::net::Ipv6Addr;
use std::net::UdpSocket;
use std::sync::mpsc;
use std::sync::mpsc::TryRecvError;
use std::sync::Arc;
use std::sync::Mutex;
use std::thread;
use std::time::Duration;
use std::time::Instant;
/// Carrotcoin miner
#[derive(Parser)]
struct Args {
/// Wallet address for the mining reward
#[arg(short, long)]
wallet: String,
/// Message to encode in each block (maximum 32 bytes)
#[arg(short, long)]
message: Option<String>,
/// Number of threads to start
#[arg(short, long)]
thread_count: Option<usize>,
}
struct Task {
prefix: Vec<u8>,
threshold: [u8; 32],
work_counter: usize,
}
fn run_mining_worker(task: Arc<Mutex<Task>>, results: mpsc::Sender<Vec<u8>>) {
let mut rng = rand::thread_rng();
loop {
let mut hasher = Sha256::new();
let mut lock_guard = task.lock().unwrap();
let prefix = lock_guard.prefix.clone();
hasher.update(&prefix);
let threshold = lock_guard.threshold.clone();
lock_guard.work_counter += 1;
drop(lock_guard);
let start = rng.gen_range(0..((1u64 << 50) - 1)) << 14;
for nonce in start..(start + (1 << 14)) {
let mut hasher_cloned = hasher.clone();
let nonce_bytes = nonce.to_be_bytes();
hasher_cloned.update(nonce_bytes);
let result_hash: [u8; 32] = hasher_cloned.finalize().into();
if result_hash[0] == 0 && result_hash <= threshold {
let mut block = Vec::with_capacity(292);
block.extend_from_slice(&prefix);
block.extend_from_slice(&nonce_bytes);
results.send(block).unwrap();
}
}
}
}
fn start_threads(task: &Arc<Mutex<Task>>, results: &mpsc::Sender<Vec<u8>>, thread_count: usize) {
for _ in 0..thread_count {
let task_cloned = task.clone();
let results_cloned = results.clone();
thread::spawn(|| run_mining_worker(task_cloned, results_cloned));
}
}
fn build_message(message_string: &str) -> Option<Vec<u8>> {
let message_bytes = message_string.as_bytes();
if message_bytes.len() > 32 {
return None;
}
let mut message_buffer = Vec::with_capacity(32);
message_buffer.extend_from_slice(&message_bytes);
message_buffer.resize(32, 0);
Some(message_buffer)
}
fn show_statistics(duration: Duration, work_counter: usize, threshold: &[u8]) {
let duration_seconds = duration.as_secs_f64();
let mut hash_numeric = 0f64;
for byte_value in threshold {
hash_numeric = hash_numeric * 256.0 + f64::from(*byte_value);
}
let difficulty = 2.0f64.powi(256) / hash_numeric;
let relative_difficulty = difficulty / 2.0f64.powi(28);
let hashrate = (work_counter as f64) / duration_seconds * 2.0f64.powi(14);
let hashrate_mhs = hashrate / 1e6;
let expected_reward = hashrate * 3600f64 / difficulty;
println!("difficulty: {relative_difficulty:.02}; hashrate: {hashrate_mhs:.02} Mh/s; expected reward: {expected_reward:.04} cc / hour");
}
fn main() {
let args = Args::parse();
let public_key = match BASE64_STANDARD.decode(args.wallet) {
Ok(key_bytes) if key_bytes.len() == 32 => key_bytes,
Ok(_) => {
eprintln!("The wallet address is not exactly 32 bytes, so it is invalid.");
return;
}
Err(_) => {
eprintln!("Failed to base64-decode the wallet address.");
return;
}
};
let message = match build_message(args.message.as_deref().unwrap_or("")) {
Some(message) => message,
None => {
eprintln!("The provided block message ist too long. Must be at most 32 bytes.");
return;
}
};
let thread_count = args.thread_count.unwrap_or_else(num_cpus::get);
let mut last_statistic_time = Instant::now();
let mut statistic_output_delay = Duration::from_secs(5);
let socket = UdpSocket::bind((Ipv6Addr::UNSPECIFIED, 0)).unwrap();
let mut threads_started = false;
socket.connect((Ipv6Addr::LOCALHOST, 62039)).unwrap();
socket.set_nonblocking(true).unwrap();
let task = Arc::new(Mutex::new(Task {
prefix: Vec::new(),
threshold: [0u8; 32],
work_counter: 0,
}));
let (results_tx, results_rx) = mpsc::channel();
let mut request = vec![0u8; 257];
request[4] = 7;
loop {
let mut recv_buffer = vec![0u8; 512];
loop {
match socket.recv(&mut recv_buffer) {
Ok(size) => {
if size == 257 && &recv_buffer[0..5] == &[0, 0, 0, 0, 8] {
let transaction = &recv_buffer[5..153];
let previous_hash = &recv_buffer[153..185];
let timestamp = &recv_buffer[185..193];
let difficulty_sum = &recv_buffer[193..225];
let threshold = &recv_buffer[225..257];
let mut prefix = Vec::with_capacity(292);
prefix.extend_from_slice(transaction);
prefix.extend_from_slice(&message);
prefix.extend_from_slice(&public_key);
prefix.extend_from_slice(previous_hash);
prefix.extend_from_slice(timestamp);
prefix.extend_from_slice(difficulty_sum);
let mut lock = task.lock().unwrap();
lock.prefix = prefix;
lock.threshold = threshold.try_into().unwrap();
let current_time = Instant::now();
let duration = current_time - last_statistic_time;
if duration >= statistic_output_delay {
show_statistics(duration, lock.work_counter, threshold);
last_statistic_time = current_time;
lock.work_counter = 0;
statistic_output_delay = Duration::from_secs(60);
}
if !threads_started {
start_threads(&task, &results_tx, thread_count);
threads_started = true;
}
}
}
Err(e) => {
if e.kind() == ErrorKind::WouldBlock {
break;
} else if e.kind() == ErrorKind::ConnectionRefused {
eprintln!("Node is not running locally!");
return;
} else {
panic!("{e}");
}
}
}
}
socket.send(&request).unwrap();
std::thread::sleep(std::time::Duration::from_secs(1));
let mined_block = match results_rx.try_recv() {
Ok(block) => Some(block),
Err(e) if e == TryRecvError::Empty => None,
Err(e) => panic!("{e}"),
};
if let Some(block) = mined_block {
let mut block_transfer = Vec::with_capacity(297);
block_transfer.extend_from_slice(b"\0\0\0\0\x02");
block_transfer.extend_from_slice(&block);
socket.send(&block_transfer).unwrap();
println!("I found a carrot! \u{1f955}");
}
}
}