use crate::{error::ConsensusError, Consensus};
use snarkos_storage::{SerialBlock, SerialBlockHeader, SerialRecord, SerialTransaction};
use snarkvm_algorithms::SNARKError;
use snarkvm_dpc::{testnet1::instantiated::*, Address, BlockHeader, BlockHeaderHash, DPCComponents, ProgramScheme};
use snarkvm_posw::{error::PoswError, txids_to_roots, PoswMarlin};
use snarkvm_utilities::{to_bytes_le, ToBytes};
use chrono::Utc;
use rand::thread_rng;
use std::sync::{
atomic::{AtomicBool, Ordering},
Arc,
};
use tokio::task;
lazy_static::lazy_static! {
static ref POSW: PoswMarlin = {
PoswMarlin::load().expect("could not instantiate the miner")
};
}
#[derive(Clone)]
pub struct MineContext {
address: Address<Components>,
pub consensus: Arc<Consensus>,
}
impl MineContext {
pub async fn prepare(address: Address<Components>, consensus: Arc<Consensus>) -> Result<Self, ConsensusError> {
Ok(Self { address, consensus })
}
async fn add_coinbase_transaction(
&self,
block_number: u32,
transactions: &mut Vec<SerialTransaction>,
) -> Result<Vec<SerialRecord>, ConsensusError> {
let program_vk_hash = self.consensus.dpc.noop_program.id();
let new_birth_programs = vec![program_vk_hash.clone(); Components::NUM_OUTPUT_RECORDS];
let new_death_programs = vec![program_vk_hash.clone(); Components::NUM_OUTPUT_RECORDS];
for transaction in transactions.iter() {
if self.consensus.parameters.network_id != transaction.network {
return Err(ConsensusError::ConflictingNetworkId(
self.consensus.parameters.network_id.id(),
transaction.network.id(),
));
}
}
let response = self
.consensus
.create_coinbase_transaction(
block_number,
transactions.clone(),
program_vk_hash,
new_birth_programs,
new_death_programs,
vec![self.address.clone(); Components::NUM_OUTPUT_RECORDS]
.into_iter()
.map(|x| x.into())
.collect(),
)
.await?;
transactions.push(response.transaction);
Ok(response.records)
}
#[allow(clippy::type_complexity)]
pub async fn establish_block(
&self,
block_number: u32,
mut transactions: Vec<SerialTransaction>,
) -> Result<(Vec<SerialTransaction>, Vec<SerialRecord>), ConsensusError> {
let coinbase_records = self.add_coinbase_transaction(block_number, &mut transactions).await?;
Ok((transactions, coinbase_records))
}
pub fn find_block(
&self,
transactions: &[SerialTransaction],
parent_header: &SerialBlockHeader,
terminator: &AtomicBool,
) -> Result<SerialBlockHeader, ConsensusError> {
let txids = transactions.iter().map(|x| x.id).collect::<Vec<_>>();
let (merkle_root_hash, pedersen_merkle_root_hash, subroots) = txids_to_roots(&txids);
let time = Utc::now().timestamp();
let difficulty_target = self.consensus.parameters.get_block_difficulty(parent_header, time);
let mined = POSW.mine(
&subroots,
difficulty_target,
terminator,
&mut thread_rng(),
self.consensus.parameters.max_nonce,
);
let (nonce, proof) = match mined {
Err(PoswError::SnarkError(SNARKError::Terminated)) => {
debug!("terminated miner due to canon block received");
terminator.store(false, Ordering::SeqCst);
return Err(ConsensusError::PoswError(PoswError::SnarkError(SNARKError::Terminated)));
}
Err(PoswError::SnarkError(SNARKError::Crate("marlin", message)))
if message == "Failed to generate proof - Terminated" =>
{
debug!("terminated miner due to canon block received");
terminator.store(false, Ordering::SeqCst);
return Err(ConsensusError::PoswError(PoswError::SnarkError(SNARKError::Terminated)));
}
Err(e) => return Err(e.into()),
Ok(x) => x,
};
Ok(BlockHeader {
previous_block_hash: BlockHeaderHash(parent_header.hash().bytes().unwrap()),
merkle_root_hash,
pedersen_merkle_root_hash,
time,
difficulty_target,
nonce,
proof: proof.into(),
}
.into())
}
pub async fn mine_block(
&self,
terminator: Arc<AtomicBool>,
) -> Result<(SerialBlock, Vec<SerialRecord>), ConsensusError> {
let candidate_transactions = self.consensus.fetch_memory_pool().await;
let canon = self.consensus.storage.canon().await?;
let canon_header = self.consensus.storage.get_block_header(&canon.hash).await?;
debug!("Miner@{}: creating a block", canon.block_height);
let (transactions, coinbase_records) = self
.establish_block(canon.block_height as u32 + 1, candidate_transactions)
.await?;
debug!("Miner@{}: generated a coinbase transaction", canon.block_height);
for (index, record) in coinbase_records.iter().enumerate() {
let record_commitment = hex::encode(&to_bytes_le![record.commitment]?);
debug!(
"Miner@{}: Coinbase record {:?} commitment: {:?}",
canon.block_height, index, record_commitment
);
}
let ctx = self.clone();
let txs = transactions.clone();
let header = task::spawn_blocking(move || ctx.find_block(&txs, &canon_header, &terminator)).await??;
debug!("Miner@{}: found a block", canon.block_height);
let block = SerialBlock { header, transactions };
self.consensus.receive_block(block.clone()).await;
let mut records_to_store = vec![];
for record in &coinbase_records {
if !record.is_dummy {
records_to_store.push(record.clone());
}
}
self.consensus.storage.store_records(&records_to_store).await?;
Ok((block, coinbase_records))
}
}