1use std::{collections::HashMap, mem};
10
11use monero_oxide::{block::Block, transaction::Input};
12use tower::{Service, ServiceExt};
13
14use cuprate_consensus_context::{
15 BlockChainContextRequest, BlockChainContextResponse, BlockchainContextService,
16};
17use cuprate_helper::asynch::rayon_spawn_async;
18use cuprate_types::{
19 AltBlockInformation, TransactionVerificationData, VerifiedBlockInformation,
20 VerifiedTransactionInformation,
21};
22
23use cuprate_consensus_rules::{
24 blocks::{
25 calculate_pow_hash, check_block, check_block_pow, randomx_seed_height, BlockError, RandomX,
26 },
27 hard_forks::HardForkError,
28 miner_tx::MinerTxError,
29 ConsensusError, HardFork,
30};
31
32use crate::{
33 transactions::start_tx_verification, Database, ExtendedConsensusError, VerificationContext,
34};
35
36mod alt_block;
37mod batch_prepare;
38mod free;
39
40pub use alt_block::sanity_check_alt_block;
41pub use batch_prepare::{batch_prepare_main_chain_blocks, BatchPrepareCache};
42use free::pull_ordered_transactions;
43
44#[derive(Debug, thiserror::Error)]
49#[error("{inner}")]
50pub struct BlockVerificationError {
51 pub pow_valid: bool,
53 #[source]
55 pub inner: ExtendedConsensusError,
56}
57
58impl BlockVerificationError {
59 pub fn valid_pow(inner: impl Into<ExtendedConsensusError>) -> Self {
61 Self {
62 pow_valid: true,
63 inner: inner.into(),
64 }
65 }
66
67 pub fn invalid_pow(inner: impl Into<ExtendedConsensusError>) -> Self {
69 Self {
70 pow_valid: false,
71 inner: inner.into(),
72 }
73 }
74
75 pub fn fatal(inner: tower::BoxError) -> Self {
77 Self {
78 pow_valid: false,
79 inner: ExtendedConsensusError::FatalError(inner),
80 }
81 }
82}
83
84impl From<BlockVerificationError> for ExtendedConsensusError {
85 fn from(inner: BlockVerificationError) -> Self {
86 inner.inner
87 }
88}
89
90#[derive(Debug)]
92pub struct PreparedBlockExPow {
93 pub block: Block,
95 pub block_blob: Vec<u8>,
97
98 pub hf_vote: HardFork,
100 pub hf_version: HardFork,
102
103 pub block_hash: [u8; 32],
105 pub height: usize,
107
108 pub miner_tx_weight: usize,
110}
111
112impl PreparedBlockExPow {
113 pub fn new(block: Block) -> Result<Self, ConsensusError> {
120 let (hf_version, hf_vote) = HardFork::from_block_header(&block.header)
121 .map_err(|_| BlockError::HardForkError(HardForkError::HardForkUnknown))?;
122
123 let Some(Input::Gen(height)) = block.miner_transaction().prefix().inputs.first() else {
124 return Err(ConsensusError::Block(BlockError::MinerTxError(
125 MinerTxError::InputNotOfTypeGen,
126 )));
127 };
128
129 Ok(Self {
130 block_blob: block.serialize(),
131 hf_vote,
132 hf_version,
133
134 block_hash: block.hash(),
135 height: *height,
136
137 miner_tx_weight: block.miner_transaction().weight(),
138 block,
139 })
140 }
141}
142
143#[derive(Debug)]
145pub struct PreparedBlock {
146 pub block: Block,
148 pub block_blob: Vec<u8>,
150
151 pub hf_vote: HardFork,
153 pub hf_version: HardFork,
155
156 pub block_hash: [u8; 32],
158 pub pow_hash: [u8; 32],
160
161 pub miner_tx_weight: usize,
163}
164
165impl PreparedBlock {
166 pub fn new<R: RandomX>(block: Block, randomx_vm: Option<&R>) -> Result<Self, ConsensusError> {
171 let (hf_version, hf_vote) = HardFork::from_block_header(&block.header)
172 .map_err(|_| BlockError::HardForkError(HardForkError::HardForkUnknown))?;
173
174 let [Input::Gen(height)] = &block.miner_transaction().prefix().inputs[..] else {
175 return Err(ConsensusError::Block(BlockError::MinerTxError(
176 MinerTxError::InputNotOfTypeGen,
177 )));
178 };
179
180 Ok(Self {
181 block_blob: block.serialize(),
182 hf_vote,
183 hf_version,
184
185 block_hash: block.hash(),
186 pow_hash: calculate_pow_hash(
187 randomx_vm,
188 &block.serialize_pow_hash(),
189 *height,
190 &hf_version,
191 )?,
192
193 miner_tx_weight: block.miner_transaction().weight(),
194 block,
195 })
196 }
197
198 fn new_prepped<R: RandomX>(
207 block: PreparedBlockExPow,
208 randomx_vm: Option<&R>,
209 ) -> Result<Self, ConsensusError> {
210 Ok(Self {
211 block_blob: block.block_blob,
212 hf_vote: block.hf_vote,
213 hf_version: block.hf_version,
214
215 block_hash: block.block_hash,
216 pow_hash: calculate_pow_hash(
217 randomx_vm,
218 &block.block.serialize_pow_hash(),
219 block.height,
220 &block.hf_version,
221 )?,
222
223 miner_tx_weight: block.block.miner_transaction().weight(),
224 block: block.block,
225 })
226 }
227
228 pub fn new_alt_block(block: AltBlockInformation) -> Result<Self, ConsensusError> {
230 Ok(Self {
231 block_blob: block.block_blob,
232 hf_vote: HardFork::from_vote(block.block.header.hardfork_signal),
233 hf_version: HardFork::from_version(block.block.header.hardfork_version)
234 .map_err(|_| BlockError::HardForkError(HardForkError::HardForkUnknown))?,
235 block_hash: block.block_hash,
236 pow_hash: block.pow_hash,
237 miner_tx_weight: block.block.miner_transaction().weight(),
238 block: block.block,
239 })
240 }
241}
242
243pub async fn verify_main_chain_block<D>(
245 block: Block,
246 txs: HashMap<[u8; 32], TransactionVerificationData>,
247 context_svc: &mut BlockchainContextService,
248 database: D,
249) -> Result<VerifiedBlockInformation, BlockVerificationError>
250where
251 D: Database + Clone + Send + 'static,
252{
253 let context = context_svc.blockchain_context().clone();
254 tracing::debug!("got blockchain context: {:?}", context);
255
256 tracing::debug!(
257 "Preparing block for verification, expected height: {}",
258 context.chain_height
259 );
260
261 let rx_vms = if block.header.hardfork_version < 12 {
265 HashMap::new()
266 } else {
267 let BlockChainContextResponse::RxVms(rx_vms) = context_svc
268 .ready()
269 .await
270 .map_err(BlockVerificationError::fatal)?
271 .call(BlockChainContextRequest::CurrentRxVms)
272 .await
273 .map_err(BlockVerificationError::fatal)?
274 else {
275 panic!("Blockchain context service returned wrong response!");
276 };
277
278 rx_vms
279 };
280
281 let height = context.chain_height;
282 let prepped_block = rayon_spawn_async(move || {
283 PreparedBlock::new(
284 block,
285 rx_vms.get(&randomx_seed_height(height)).map(AsRef::as_ref),
286 )
287 })
288 .await
289 .map_err(BlockVerificationError::invalid_pow)?;
290
291 check_block_pow(&prepped_block.pow_hash, context.next_difficulty)
292 .map_err(ConsensusError::Block)
293 .map_err(BlockVerificationError::invalid_pow)?;
294
295 let ordered_txs = pull_ordered_transactions(&prepped_block.block, txs)
297 .map_err(BlockVerificationError::valid_pow)?;
298
299 verify_prepped_main_chain_block(
300 prepped_block,
301 ordered_txs,
302 context_svc,
303 &mut VerificationContext::Database(database),
304 )
305 .await
306}
307
308pub async fn verify_prepped_main_chain_block<D>(
310 prepped_block: PreparedBlock,
311 mut txs: Vec<TransactionVerificationData>,
312 context_svc: &mut BlockchainContextService,
313 verification_context: &mut VerificationContext<D>,
314) -> Result<VerifiedBlockInformation, BlockVerificationError>
315where
316 D: Database + Clone + Send + 'static,
317{
318 let context = context_svc.blockchain_context();
319
320 tracing::debug!("verifying block: {}", hex::encode(prepped_block.block_hash));
321
322 check_block_pow(&prepped_block.pow_hash, context.next_difficulty)
323 .map_err(ConsensusError::Block)
324 .map_err(BlockVerificationError::invalid_pow)?;
325
326 if prepped_block.block.transactions.len() != txs.len() {
327 return Err(BlockVerificationError::valid_pow(ConsensusError::Block(
328 BlockError::TxsIncludedWithBlockIncorrect,
329 )));
330 }
331
332 if !prepped_block.block.transactions.is_empty() {
333 for (expected_tx_hash, tx) in prepped_block.block.transactions.iter().zip(txs.iter()) {
334 if expected_tx_hash != &tx.tx_hash {
335 return Err(BlockVerificationError::valid_pow(ConsensusError::Block(
336 BlockError::TxsIncludedWithBlockIncorrect,
337 )));
338 }
339 }
340
341 let temp = start_tx_verification()
342 .append_prepped_txs(mem::take(&mut txs))
343 .prepare()
344 .map_err(BlockVerificationError::valid_pow)?
345 .full(
346 context.chain_height,
347 context.top_hash,
348 context.current_adjusted_timestamp_for_time_lock(),
349 context.current_hf,
350 verification_context,
351 )
352 .verify()
353 .await
354 .map_err(BlockVerificationError::valid_pow)?;
355
356 txs = temp;
357 }
358
359 let block_weight =
360 prepped_block.miner_tx_weight + txs.iter().map(|tx| tx.tx_weight).sum::<usize>();
361 let total_fees = txs.iter().map(|tx| tx.fee).sum::<u64>();
362
363 tracing::debug!("Verifying block header.");
364 let (_, generated_coins) = check_block(
365 &prepped_block.block,
366 total_fees,
367 block_weight,
368 prepped_block.block_blob.len(),
369 &context.context_to_verify_block,
370 )
371 .map_err(ConsensusError::Block)
372 .map_err(BlockVerificationError::valid_pow)?;
373
374 let block = VerifiedBlockInformation {
375 block_hash: prepped_block.block_hash,
376 block: prepped_block.block,
377 block_blob: prepped_block.block_blob,
378 txs: txs
379 .into_iter()
380 .map(|tx| {
381 let tx_weight = tx.tx_weight;
382 let fee = tx.fee;
383 let tx_hash = tx.tx_hash;
384 let (tx, tx_prunable_blob) = tx.tx.pruned_with_prunable();
385 VerifiedTransactionInformation {
386 tx_prunable_blob,
387 tx_pruned: tx.serialize(),
388 tx_weight,
389 fee,
390 tx_hash,
391 tx,
392 }
393 })
394 .collect(),
395 pow_hash: prepped_block.pow_hash,
396 generated_coins,
397 weight: block_weight,
398 height: context.chain_height,
399 long_term_weight: context.next_block_long_term_weight(block_weight),
400 cumulative_difficulty: context.cumulative_difficulty + context.next_difficulty,
401 };
402
403 if let VerificationContext::BatchPrepareCache(batch_prep_cache) = verification_context {
404 batch_prep_cache.output_cache.add_block_to_cache(&block);
405 }
406
407 Ok(block)
408}