//! Block Verifier Service. use std::{ collections::HashSet, future::Future, pin::Pin, sync::Arc, task::{Context, Poll}, }; use cuprate_helper::asynch::rayon_spawn_async; use futures::FutureExt; use monero_serai::{block::Block, transaction::Input}; use tower::{Service, ServiceExt}; use cuprate_consensus_rules::{ blocks::{calculate_pow_hash, check_block, check_block_pow, BlockError, RandomX}, miner_tx::MinerTxError, ConsensusError, HardFork, }; use crate::{ context::{BlockChainContextRequest, BlockChainContextResponse}, transactions::{TransactionVerificationData, VerifyTxRequest, VerifyTxResponse}, Database, ExtendedConsensusError, }; /// A pre-prepared block with all data needed to verify it. #[derive(Debug)] pub struct PrePreparedBlock { /// The block pub block: Block, /// The serialised blocks bytes pub block_blob: Vec, /// The blocks hf vote pub hf_vote: HardFork, /// The blocks hf version pub hf_version: HardFork, /// The blocks hash pub block_hash: [u8; 32], /// The blocks POW hash. pub pow_hash: [u8; 32], /// The weight of the blocks miner transaction. pub miner_tx_weight: usize, } impl PrePreparedBlock { /// Creates a new [`PrePreparedBlock`]. /// /// The randomX VM must be Some if RX is needed or this will panic. /// The randomX VM must also be initialised with the correct seed. fn new( block: Block, randomx_vm: Option<&R>, ) -> Result { let (hf_version, hf_vote) = HardFork::from_block_header(&block.header).map_err(BlockError::HardForkError)?; let Some(Input::Gen(height)) = block.miner_tx.prefix.inputs.first() else { Err(ConsensusError::Block(BlockError::MinerTxError( MinerTxError::InputNotOfTypeGen, )))? }; Ok(PrePreparedBlock { block_blob: block.serialize(), hf_vote, hf_version, block_hash: block.hash(), pow_hash: calculate_pow_hash( randomx_vm, &block.serialize_hashable(), *height, &hf_version, )?, miner_tx_weight: block.miner_tx.weight(), block, }) } } /// Information about a verified block. #[derive(Debug)] pub struct VerifiedBlockInformation { /// The block that has been verified. pub block: Block, /// The block's hard-fork vote. pub hf_vote: HardFork, /// The txs in this block. pub txs: Arc<[Arc]>, /// The blocks hash. pub block_hash: [u8; 32], /// the blocks POW hash. pub pow_hash: [u8; 32], /// The blocks height. pub height: u64, /// The amount of coins generated by this block. pub generated_coins: u64, /// This blocks wight. pub weight: usize, /// This blocks long term weight. pub long_term_weight: usize, /// The cumulative difficulty of the chain including this block. pub cumulative_difficulty: u128, } /// A request to verify a block. pub enum VerifyBlockRequest { /// A request to verify a block. MainChain { block: Block, prepared_txs: Arc<[Arc]>, }, } /// A response from a verify block request. pub enum VerifyBlockResponse { /// This block is valid. MainChain(VerifiedBlockInformation), } /// The block verifier service. pub struct BlockVerifierService { /// The context service. context_svc: C, /// The tx verifier service. tx_verifier_svc: TxV, /// The database. // Not use yet but will be. _database: D, } impl BlockVerifierService where C: Service + Clone + Send + 'static, TxV: Service + Clone + Send + 'static, D: Database + Clone + Send + Sync + 'static, D::Future: Send + 'static, { /// Creates a new block verifier. pub(crate) fn new( context_svc: C, tx_verifier_svc: TxV, database: D, ) -> BlockVerifierService { BlockVerifierService { context_svc, tx_verifier_svc, _database: database, } } } impl Service for BlockVerifierService where C: Service< BlockChainContextRequest, Response = BlockChainContextResponse, Error = tower::BoxError, > + Clone + Send + 'static, C::Future: Send + 'static, TxV: Service + Clone + Send + 'static, TxV::Future: Send + 'static, D: Database + Clone + Send + Sync + 'static, D::Future: Send + 'static, { type Response = VerifyBlockResponse; type Error = ExtendedConsensusError; type Future = Pin> + Send + 'static>>; fn poll_ready(&mut self, _: &mut Context<'_>) -> Poll> { Poll::Ready(Ok(())) } fn call(&mut self, req: VerifyBlockRequest) -> Self::Future { let context_svc = self.context_svc.clone(); let tx_verifier_svc = self.tx_verifier_svc.clone(); async move { match req { VerifyBlockRequest::MainChain { block, prepared_txs, } => { verify_main_chain_block(block, prepared_txs, context_svc, tx_verifier_svc).await } } } .boxed() } } /// Verifies a prepared block. async fn verify_main_chain_block( block: Block, txs: Arc<[Arc]>, context_svc: C, tx_verifier_svc: TxV, ) -> Result where C: Service< BlockChainContextRequest, Response = BlockChainContextResponse, Error = tower::BoxError, > + Send + 'static, C::Future: Send + 'static, TxV: Service, { tracing::debug!("getting blockchain context"); let BlockChainContextResponse::Context(checked_context) = context_svc .oneshot(BlockChainContextRequest::GetContext) .await .map_err(Into::::into)? else { panic!("Context service returned wrong response!"); }; let context = checked_context.unchecked_blockchain_context().clone(); tracing::debug!("got blockchain context: {:?}", context); // Set up the block and just pass it to [`verify_main_chain_block_prepared`] let rx_vms = context.rx_vms.clone(); let height = context.chain_height; let prepped_block = rayon_spawn_async(move || { PrePreparedBlock::new(block, rx_vms.get(&height).map(AsRef::as_ref)) }) .await?; tracing::debug!("verifying block: {}", hex::encode(prepped_block.block_hash)); check_block_pow(&prepped_block.pow_hash, context.next_difficulty) .map_err(ConsensusError::Block)?; // Check that the txs included are what we need and that there are not any extra. let mut tx_hashes = txs.iter().map(|tx| &tx.tx_hash).collect::>(); tracing::debug!("Checking we have correct transactions for block."); if tx_hashes.len() != txs.len() { return Err(ExtendedConsensusError::TxsIncludedWithBlockIncorrect); } for tx_hash in &prepped_block.block.txs { if !tx_hashes.remove(tx_hash) { return Err(ExtendedConsensusError::TxsIncludedWithBlockIncorrect); } } if !tx_hashes.is_empty() { return Err(ExtendedConsensusError::TxsIncludedWithBlockIncorrect); } tracing::debug!("Verifying transactions for block."); tx_verifier_svc .oneshot(VerifyTxRequest::Prepped { txs: txs.clone(), current_chain_height: context.chain_height, top_hash: context.top_hash, time_for_time_lock: context.current_adjusted_timestamp_for_time_lock(), hf: context.current_hf, }) .await?; let block_weight = prepped_block.miner_tx_weight + txs.iter().map(|tx| tx.tx_weight).sum::(); let total_fees = txs.iter().map(|tx| tx.fee).sum::(); tracing::debug!("Verifying block header."); let (hf_vote, generated_coins) = check_block( &prepped_block.block, total_fees, block_weight, prepped_block.block_blob.len(), &context.context_to_verify_block, ) .map_err(ConsensusError::Block)?; Ok(VerifyBlockResponse::MainChain(VerifiedBlockInformation { block_hash: prepped_block.block_hash, block: prepped_block.block, txs, pow_hash: prepped_block.pow_hash, generated_coins, weight: block_weight, height: context.chain_height, long_term_weight: context.next_block_long_term_weight(block_weight), hf_vote, cumulative_difficulty: context.cumulative_difficulty + context.next_difficulty, })) }