use sha2::{Digest, Sha256};
use crate::error::{Error, Result};
use crate::hd::ChainCode;
use crate::server::{Legalese, MiningReportRequest};
use crate::wallet::Wallet;
use crate::webcash::SecretWebcash;
use crate::Amount;
pub struct MineResult {
pub webcash: String,
pub subsidy: Option<String>,
pub preimage: String,
pub hash: String,
pub difficulty: u32,
pub amount: Amount,
}
fn leading_zero_bits(hash: &[u8]) -> u32 {
let mut count = 0u32;
for byte in hash {
if *byte == 0 {
count += 8;
} else {
count += byte.leading_zeros();
break;
}
}
count
}
pub async fn mine(wallet: &Wallet) -> Result<MineResult> {
let target = {
let server = wallet.server_client.lock().await;
server.get_target().await?
};
let difficulty = target.difficulty_target_bits;
let mining_amount_str = &target.mining_amount;
let subsidy_amount_str = &target.mining_subsidy_amount;
let mining_amount_f64: f64 = mining_amount_str
.parse()
.map_err(|_| Error::wallet(format!("invalid mining amount: {}", mining_amount_str)))?;
let mining_amount = Amount::from_webcash(mining_amount_f64)?;
let (mining_secret_hex, _mining_depth) = wallet.derive_next_secret(ChainCode::Mining)?;
let webcash_str = format!("e{}:secret:{}", mining_amount_str, mining_secret_hex);
let subsidy_amount_f64: f64 = subsidy_amount_str.parse().unwrap_or(0.0);
let subsidy_amount = Amount::from_webcash(subsidy_amount_f64)?;
let subsidy_str = if !subsidy_amount.is_zero() {
let (subsidy_secret_hex, _) = wallet.derive_next_secret(ChainCode::Mining)?;
Some(format!(
"e{}:secret:{}",
subsidy_amount_str, subsidy_secret_hex
))
} else {
None
};
let mut nonce: u64 = 0;
let preimage_str;
let hash_hex;
loop {
let timestamp = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.map_err(|e| Error::wallet(format!("system time error: {}", e)))?
.as_secs();
let subsidy_array = match &subsidy_str {
Some(s) => format!(r#"["{}"]"#, s),
None => "[]".to_string(),
};
let candidate = format!(
r#"{{"webcash":["{}"],"subsidy":{},"timestamp":{},"difficulty":{},"nonce":{}}}"#,
webcash_str, subsidy_array, timestamp, difficulty, nonce
);
let hash = Sha256::digest(candidate.as_bytes());
if leading_zero_bits(&hash) >= difficulty {
preimage_str = candidate;
hash_hex = hex::encode(hash);
break;
}
nonce += 1;
if nonce % 10_000 == 0 {
tokio::task::yield_now().await;
}
}
let report = MiningReportRequest {
preimage: preimage_str.clone(),
legalese: Legalese { terms: true },
};
{
let server = wallet.server_client.lock().await;
server.submit_mining_report(&report).await?;
}
let secret = SecretWebcash::parse(&webcash_str)?;
wallet.insert(secret).await?;
Ok(MineResult {
webcash: webcash_str,
subsidy: subsidy_str,
preimage: preimage_str,
hash: hash_hex,
difficulty,
amount: mining_amount,
})
}