use std::{ future::Future, pin::Pin, sync::Arc, task::{Context, Poll}, }; use futures::FutureExt; use monero_serai::{block::Block, transaction::Transaction}; use tower::{Service, ServiceExt}; use crate::{ context::{BlockChainContext, BlockChainContextRequest}, transactions::{TransactionVerificationData, VerifyTxRequest, VerifyTxResponse}, ConsensusError, HardFork, }; mod hash_worker; mod miner_tx; #[derive(Debug)] pub struct VerifiedBlockInformation { pub block: Block, pub hf_vote: HardFork, pub txs: Vec>, pub block_hash: [u8; 32], pub pow_hash: [u8; 32], pub height: u64, pub generated_coins: u64, pub weight: usize, pub long_term_weight: usize, } pub enum VerifyBlockRequest { MainChainBatchSetupVerify(Block, Vec), MainChain(Block, Vec>), } pub enum VerifyBlockResponse { MainChainBatchSetupVerify(), } // TODO: it is probably a bad idea for this to derive clone, if 2 places (RPC, P2P) receive valid but different blocks // then they will both get approved but only one should go to main chain. #[derive(Clone)] pub struct BlockVerifierService { context_svc: C, tx_verifier_svc: Tx, } impl BlockVerifierService where C: Service + Clone + Send + 'static, Tx: Service + Clone + Send + 'static, { pub fn new(context_svc: C, tx_verifier_svc: Tx) -> BlockVerifierService { BlockVerifierService { context_svc, tx_verifier_svc, } } } impl Service for BlockVerifierService where C: Service + Clone + Send + 'static, C::Future: Send + 'static, Tx: Service + Clone + Send + 'static, Tx::Future: Send + 'static, { type Response = VerifiedBlockInformation; type Error = ConsensusError; type Future = Pin> + Send + 'static>>; fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll> { futures::ready!(self.context_svc.poll_ready(cx)).map(Into::into)?; self.tx_verifier_svc.poll_ready(cx) } 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::MainChainBatchSetupVerify(block, txs) => { batch_setup_verify_main_chain_block(block, txs, context_svc, tx_verifier_svc) .await } _ => todo!(), } } .boxed() } } async fn batch_setup_verify_main_chain_block( block: Block, txs: Vec, context_svc: C, tx_verifier_svc: Tx, ) -> Result where C: Service + Send + 'static, C::Future: Send + 'static, Tx: Service, { tracing::info!("getting blockchain context"); let context = context_svc .oneshot(BlockChainContextRequest) .await .map_err(Into::::into)?; tracing::info!("got blockchain context: {:?}", context); let txs = if !txs.is_empty() { let VerifyTxResponse::BatchSetupOk(txs) = tx_verifier_svc .oneshot(VerifyTxRequest::BatchSetupVerifyBlock { txs, current_chain_height: context.chain_height, hf: context.current_hard_fork, }) .await? else { panic!("tx verifier sent incorrect response!"); }; txs } else { vec![] }; let block_weight = block.miner_tx.weight() + txs.iter().map(|tx| tx.tx_weight).sum::(); let total_fees = txs.iter().map(|tx| tx.fee).sum::(); let generated_coins = miner_tx::check_miner_tx( &block.miner_tx, total_fees, context.chain_height, block_weight, context.median_weight_for_block_reward, context.already_generated_coins, &context.current_hard_fork, )?; let hashing_blob = block.serialize_hashable(); let pow_hash = tokio::task::spawn_blocking(move || { hash_worker::calculate_pow_hash( &hashing_blob, context.chain_height, &context.current_hard_fork, ) }) .await .unwrap()?; Ok(VerifiedBlockInformation { block_hash: block.hash(), block, txs, pow_hash, generated_coins, weight: block_weight, height: context.chain_height, long_term_weight: 0, hf_vote: HardFork::V1, }) }