cuprate/consensus/src/block.rs

314 lines
9.2 KiB
Rust
Raw Normal View History

//! Block Verifier Service.
2023-10-23 18:14:40 +00:00
use std::{
collections::HashSet,
2023-10-23 18:14:40 +00:00
future::Future,
pin::Pin,
sync::Arc,
task::{Context, Poll},
};
use cuprate_helper::asynch::rayon_spawn_async;
2023-10-23 18:14:40 +00:00
use futures::FutureExt;
use monero_serai::{block::Block, transaction::Input};
2023-10-23 18:14:40 +00:00
use tower::{Service, ServiceExt};
use cuprate_consensus_rules::{
blocks::{calculate_pow_hash, check_block, check_block_pow, BlockError, RandomX},
miner_tx::MinerTxError,
ConsensusError, HardFork,
};
2023-10-23 18:14:40 +00:00
use crate::{
context::{BlockChainContextRequest, BlockChainContextResponse},
transactions::{TransactionVerificationData, VerifyTxRequest, VerifyTxResponse},
Database, ExtendedConsensusError,
2023-10-23 18:14:40 +00:00
};
2023-10-05 16:54:19 +00:00
/// 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<u8>,
/// 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<R: RandomX>(
block: Block,
randomx_vm: Option<&R>,
) -> Result<PrePreparedBlock, ConsensusError> {
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.
2023-10-23 18:14:40 +00:00
#[derive(Debug)]
pub struct VerifiedBlockInformation {
/// The block that has been verified.
2023-10-23 18:14:40 +00:00
pub block: Block,
/// The block's hard-fork vote.
2023-10-23 18:14:40 +00:00
pub hf_vote: HardFork,
/// The txs in this block.
pub txs: Arc<[Arc<TransactionVerificationData>]>,
/// The blocks hash.
2023-10-23 18:14:40 +00:00
pub block_hash: [u8; 32],
/// the blocks POW hash.
2023-10-23 18:14:40 +00:00
pub pow_hash: [u8; 32],
/// The blocks height.
2023-10-23 18:14:40 +00:00
pub height: u64,
/// The amount of coins generated by this block.
2023-10-23 18:14:40 +00:00
pub generated_coins: u64,
/// This blocks wight.
2023-10-23 18:14:40 +00:00
pub weight: usize,
/// This blocks long term weight.
2023-10-23 18:14:40 +00:00
pub long_term_weight: usize,
/// The cumulative difficulty of the chain including this block.
2023-10-24 01:25:11 +00:00
pub cumulative_difficulty: u128,
2023-10-23 18:14:40 +00:00
}
/// A request to verify a block.
2023-10-23 18:14:40 +00:00
pub enum VerifyBlockRequest {
/// A request to verify a block.
MainChain {
block: Block,
prepared_txs: Arc<[Arc<TransactionVerificationData>]>,
},
2023-11-11 01:55:15 +00:00
}
/// A response from a verify block request.
2023-11-11 01:55:15 +00:00
pub enum VerifyBlockResponse {
/// This block is valid.
2023-11-11 01:55:15 +00:00
MainChain(VerifiedBlockInformation),
}
/// The block verifier service.
pub struct BlockVerifierService<C, TxV, D> {
/// The context service.
2023-10-23 18:14:40 +00:00
context_svc: C,
/// The tx verifier service.
tx_verifier_svc: TxV,
/// The database.
// Not use yet but will be.
_database: D,
}
impl<C, TxV, D> BlockVerifierService<C, TxV, D>
2023-10-23 18:14:40 +00:00
where
C: Service<BlockChainContextRequest, Response = BlockChainContextResponse>
+ Clone
+ Send
+ 'static,
TxV: Service<VerifyTxRequest, Response = VerifyTxResponse, Error = ExtendedConsensusError>
+ Clone
+ Send
+ 'static,
D: Database + Clone + Send + Sync + 'static,
D::Future: Send + 'static,
2023-10-23 18:14:40 +00:00
{
/// Creates a new block verifier.
pub(crate) fn new(
context_svc: C,
tx_verifier_svc: TxV,
database: D,
) -> BlockVerifierService<C, TxV, D> {
2023-10-23 18:14:40 +00:00
BlockVerifierService {
context_svc,
tx_verifier_svc,
_database: database,
2023-10-23 18:14:40 +00:00
}
}
}
impl<C, TxV, D> Service<VerifyBlockRequest> for BlockVerifierService<C, TxV, D>
2023-10-23 18:14:40 +00:00
where
C: Service<
BlockChainContextRequest,
Response = BlockChainContextResponse,
Error = tower::BoxError,
> + Clone
2023-10-23 18:14:40 +00:00
+ Send
+ 'static,
C::Future: Send + 'static,
TxV: Service<VerifyTxRequest, Response = VerifyTxResponse, Error = ExtendedConsensusError>
2023-10-23 18:14:40 +00:00
+ Clone
+ Send
+ 'static,
TxV::Future: Send + 'static,
D: Database + Clone + Send + Sync + 'static,
D::Future: Send + 'static,
2023-10-23 18:14:40 +00:00
{
2023-11-11 01:55:15 +00:00
type Response = VerifyBlockResponse;
type Error = ExtendedConsensusError;
2023-10-23 18:14:40 +00:00
type Future =
Pin<Box<dyn Future<Output = Result<Self::Response, Self::Error>> + Send + 'static>>;
fn poll_ready(&mut self, _: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
Poll::Ready(Ok(()))
2023-10-23 18:14:40 +00:00
}
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
}
2023-10-23 18:14:40 +00:00
}
}
.boxed()
}
}
/// Verifies a prepared block.
async fn verify_main_chain_block<C, TxV>(
block: Block,
txs: Arc<[Arc<TransactionVerificationData>]>,
context_svc: C,
tx_verifier_svc: TxV,
) -> Result<VerifyBlockResponse, ExtendedConsensusError>
where
C: Service<
BlockChainContextRequest,
Response = BlockChainContextResponse,
Error = tower::BoxError,
> + Send
+ 'static,
C::Future: Send + 'static,
TxV: Service<VerifyTxRequest, Response = VerifyTxResponse, Error = ExtendedConsensusError>,
{
tracing::debug!("getting blockchain context");
let BlockChainContextResponse::Context(checked_context) = context_svc
.oneshot(BlockChainContextRequest::GetContext)
.await
.map_err(Into::<ExtendedConsensusError>::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::<HashSet<_>>();
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::<usize>();
let total_fees = txs.iter().map(|tx| tx.fee).sum::<u64>();
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,
}))
}