use crate::{error::ConsensusError, Consensus};
use snarkvm_algorithms::CRH;
use snarkvm_dpc::{
testnet1::{instantiated::*, Record as DPCRecord},
AccountAddress,
Block,
BlockHeader,
DPCScheme,
RecordScheme,
Storage,
TransactionScheme,
Transactions as DPCTransactions,
};
use snarkvm_posw::{txids_to_roots, PoswMarlin};
use snarkvm_utilities::{bytes::ToBytes, to_bytes};
use chrono::Utc;
use rand::{thread_rng, Rng};
use std::sync::Arc;
pub struct Miner<S: Storage> {
address: AccountAddress<Components>,
pub consensus: Arc<Consensus<S>>,
miner: PoswMarlin,
}
impl<S: Storage> Miner<S> {
pub fn new(address: AccountAddress<Components>, consensus: Arc<Consensus<S>>) -> Self {
Self {
address,
consensus,
miner: PoswMarlin::load().expect("could not instantiate the miner"),
}
}
pub fn fetch_memory_pool_transactions(&self) -> Result<DPCTransactions<Tx>, ConsensusError> {
let max_block_size = self.consensus.parameters.max_block_size;
self.consensus
.memory_pool
.get_candidates(&self.consensus.ledger, max_block_size)
}
pub fn add_coinbase_transaction<R: Rng>(
&self,
transactions: &mut DPCTransactions<Tx>,
rng: &mut R,
) -> Result<Vec<DPCRecord<Components>>, ConsensusError> {
let program_vk_hash = to_bytes![ProgramVerificationKeyCRH::hash(
&self
.consensus
.public_parameters
.system_parameters
.program_verification_key_crh,
&to_bytes![
self.consensus
.public_parameters
.noop_program_snark_parameters
.verification_key
]?
)?]?;
let new_birth_programs = vec![program_vk_hash.clone(); NUM_OUTPUT_RECORDS];
let new_death_programs = vec![program_vk_hash.clone(); 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 (records, tx) = self.consensus.create_coinbase_transaction(
self.consensus.ledger.get_current_block_height() + 1,
transactions,
program_vk_hash,
new_birth_programs,
new_death_programs,
self.address.clone(),
rng,
)?;
transactions.push(tx);
Ok(records)
}
#[allow(clippy::type_complexity)]
pub fn establish_block(
&self,
transactions: &DPCTransactions<Tx>,
) -> Result<(BlockHeader, DPCTransactions<Tx>, Vec<DPCRecord<Components>>), ConsensusError> {
let rng = &mut thread_rng();
let mut transactions = transactions.clone();
let coinbase_records = self.add_coinbase_transaction(&mut transactions, rng)?;
assert!(InstantiatedDPC::verify_transactions(
&self.consensus.public_parameters,
&transactions.0,
&*self.consensus.ledger,
)?);
let previous_block_header = self.consensus.ledger.get_latest_block()?.header;
Ok((previous_block_header, transactions, coinbase_records))
}
pub fn find_block<T: TransactionScheme>(
&self,
transactions: &DPCTransactions<T>,
parent_header: &BlockHeader,
) -> Result<BlockHeader, ConsensusError> {
let txids = transactions.to_transaction_ids()?;
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 (nonce, proof) = self.miner.mine(
&subroots,
difficulty_target,
&mut thread_rng(),
self.consensus.parameters.max_nonce,
)?;
Ok(BlockHeader {
previous_block_hash: parent_header.get_hash(),
merkle_root_hash,
pedersen_merkle_root_hash,
time,
difficulty_target,
nonce,
proof: proof.into(),
})
}
pub async fn mine_block(&self) -> Result<(Block<Tx>, Vec<DPCRecord<Components>>), ConsensusError> {
let candidate_transactions = self.fetch_memory_pool_transactions()?;
debug!("The miner is creating a block");
let (previous_block_header, transactions, coinbase_records) = self.establish_block(&candidate_transactions)?;
debug!("The miner generated a coinbase transaction");
for (index, record) in coinbase_records.iter().enumerate() {
let record_commitment = hex::encode(&to_bytes![record.commitment()]?);
debug!("Coinbase record {:?} commitment: {:?}", index, record_commitment);
}
let header = self.find_block(&transactions, &previous_block_header)?;
debug!("The Miner found a block");
let block = Block { header, transactions };
self.consensus.receive_block(&block).await?;
let mut records_to_store = vec![];
for record in &coinbase_records {
if !record.is_dummy() {
records_to_store.push(record.clone());
}
}
self.consensus.ledger.store_records(&records_to_store)?;
Ok((block, coinbase_records))
}
}