// set all the variable for mining a new block and start the creation and verification loop.
pub async fn mine_block(db: &Db, memory_data: mpsc::Sender<String>, verified_transactions: Arc<Mutex<mpsc::Receiver<Vec<String>>>>) -> Result<(), Box<dyn Error>> {
// begin the nonce at 0
let mut nonce: u32 = 0;
// start mining new block at true
let mut new_block_starting = true;
// the block to be mined is current block + 1
let mut expected_block_height = get_height(db) + 1;
// begin the loop for mining
loop {
// each iteration of the loop check if the current height matches the starting height
// if not set the new expected height and reset starting a new block this allows reseting
// the nonce and starting new blocks when new blocks are submitted without exiting the
// loop.
let current_block_number = get_height(db) + 1;
if current_block_number != expected_block_height {
expected_block_height = current_block_number;
new_block_starting = true;
}
// if starting a new block, reset the nonce and set new_block starting to false so the
// nonce will be increased each iteration
if new_block_starting == true {
nonce = 0;
new_block_starting = false;
}
// begin the interals for finding a new block
let result = mine_block_internal(&db, nonce, current_block_number, memory_data.clone(), verified_transactions.clone()).await?;
// when a new block is found we have to get the current timestamp for output reasons, reset the
// new block_starting flag so that the nonce is again reset and then send the block to be
// saved.
if let Some((new_block, current_block_number, hashed)) = result {
let empty_vec: Vec<String> = Vec::new();
let current_time = Utc::now().format("%H:%M:%S").to_string();
println!("Block {} mined successfully at {}!", current_block_number, current_time);
new_block_starting = true;
save_block(to_string(&new_block).map_err(|e| e.to_string())?, db, hashed, empty_vec, "mining".to_string()).await.expect("Unable to write file");
}
// increase the nonce. If the new_block_starting is set to true this won't matter because
// the next loop will still reset back to 0.
nonce = nonce.wrapping_add(1);
}
}
//create block and check difficulty
async fn mine_block_internal(db: &Db, nonce: u32, current_block_number: u32, memory_data: mpsc::Sender<String>, verified_transactions: Arc<Mutex<mpsc::Receiver<Vec<String>>>>) -> Result<Option<(Block, u32, String)>, Box<dyn Error>> {
// load settings. TYhis is required to sign the rewards transaction. Miners must sign a rewards
// transaction.
let settings = Settings::load().expect("Failed to load settings");
// get the current timestamp
let timestamp = Utc::now().timestamp() as u32;
// get the previous block number
let previous_block = current_block_number as u32 - 1;
// load the previous block header
let previous_block_json_result = load_block_header(previous_block);
let previous_block_json = match previous_block_json_result {
Ok(json) => json,
Err(err) => {
eprintln!("Error loading block: {}", err);
return Ok(None);
}
};
// hash the previous block header
let previous_hash = hash_data(&previous_block_json).await;
// turn previous block header into json Value
let parsed_data: Value = serde_json::from_str(&previous_block_json).map_err(|e| e.to_string())?;
// get the previous block timestamp required for calculating difficulty
let previous_timestamp = parsed_data["timestamp"].as_u64().unwrap_or_default() as u32;
// get the current block difficulty from the previous block. We always store the next block
// difficulty in the current block and get the current difficulty from the previous hash.
let difficulty = calculate_difficulty(&previous_block_json).await?;
// calculate the next block difficulty
let new_difficulty = Block::adjust_difficulty(previous_timestamp, timestamp, difficulty);
// get the wallet information for signing transaction
let wallet_file_name = ".clc.wallet";
let wallet_path = Path::new(&settings.wallet_path).join(wallet_file_name);
let wallet = load_wallet(wallet_path.as_path());
let receiver = &wallet.address;
// create the reward transaction
let previous_hash_clone = previous_hash.clone();
let rewards_transaction = create_rewards_transaction(timestamp, &receiver, &wallet, &db).await;
// hash the header of the current block
let mut data = json!({
"timestamp": timestamp,
"previous_hash": previous_hash_clone,
"next_block_difficulty": new_difficulty,
"nonce": nonce,
});
// convert header json to string
let block_json = to_string(&data).map_err(|e| e.to_string())?;
// hash the header string
let hashed = hash_data(&block_json).await;
// convert the hash to u32
let hash = hex_to_u32(&hashed).await;
// check if hash is less than difficulty. If so its a valid block
if hash < Ok(difficulty) {
data["transactions"] = json![vec![Transaction::Rewards(rewards_transaction.clone())]];
let new_block = Block {
timestamp,
previous_hash: previous_hash.clone(),
next_block_difficulty: new_difficulty,
nonce,
transactions: vec![Transaction::Rewards(rewards_transaction)],
};
// send block to be validated. We ALWAYS validate and never assume it is valid.
if let Some(new_block) = verify_and_save_block(new_block, &db, memory_data, verified_transactions).await {
Ok(Some((new_block, current_block_number, hashed)))
} else {
Ok(None)
}
} else {
Ok(None)
}
}
// send the block to be verified. If an error occurs we continue mining with the next nonce.
async fn verify_and_save_block(new_block: Block, db: &Db, memory_data: mpsc::Sender<String>, verified_transactions: Arc<Mutex<mpsc::Receiver<Vec<String>>>>) -> Option<Block> {
let new_block_json = to_string(&new_block).ok()?;
match verify_block_data(&new_block_json, db, memory_data.clone(), verified_transactions.clone()).await {
Ok(_) => {
Some(new_block)
}
Err(err_msg) => {
println!("Error during block verification: {}. Mining again...", err_msg);
None
}
}
}
// calculate the next block difficulty
async fn calculate_difficulty(previous_block_json: &str) -> Result<u32, Box<dyn Error>> {
let parsed_data: Value = serde_json::from_str(&previous_block_json).map_err(|e| e.to_string())?;
let difficulty_u64 = parsed_data["next_block_difficulty"].as_u64().ok_or("Missing or invalid difficulty value.")?;
let difficulty = difficulty_u64 as u32;
Ok(difficulty)
}
// create and sign the rewards transaction
async fn create_rewards_transaction(timestamp: u32, receiver: &str, wallet: &Wallet, db: &Db,) -> RewardsTransaction {
let txtype = 1;
let block_height = get_height(&db);
let reward_value = calculate_block_reward(block_height);
let receiver = receiver.to_string();
let data = json!({
"txtype": txtype,
"timestamp": timestamp,
"value": reward_value,
"receiver": receiver,
});
let json_data = to_string(&data).unwrap();
let hash = hash_data(&json_data).await;
let signature = sign_transaction(&hash, &wallet.private_key);
RewardsTransaction {
txtype,
timestamp,
value: reward_value,
receiver,
hash,
signature,
}