1use std::{collections::HashMap, mem};
10
11use monero_serai::{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::{transactions::start_tx_verification, Database, ExtendedConsensusError};
33
34mod alt_block;
35mod batch_prepare;
36mod free;
37
38pub use alt_block::sanity_check_alt_block;
39pub use batch_prepare::{batch_prepare_main_chain_blocks, BatchPrepareCache};
40use free::pull_ordered_transactions;
41
42#[derive(Debug)]
44pub struct PreparedBlockExPow {
45 pub block: Block,
47 pub block_blob: Vec<u8>,
49
50 pub hf_vote: HardFork,
52 pub hf_version: HardFork,
54
55 pub block_hash: [u8; 32],
57 pub height: usize,
59
60 pub miner_tx_weight: usize,
62}
63
64impl PreparedBlockExPow {
65 pub fn new(block: Block) -> Result<Self, ConsensusError> {
72 let (hf_version, hf_vote) = HardFork::from_block_header(&block.header)
73 .map_err(|_| BlockError::HardForkError(HardForkError::HardForkUnknown))?;
74
75 let Some(Input::Gen(height)) = block.miner_transaction.prefix().inputs.first() else {
76 return Err(ConsensusError::Block(BlockError::MinerTxError(
77 MinerTxError::InputNotOfTypeGen,
78 )));
79 };
80
81 Ok(Self {
82 block_blob: block.serialize(),
83 hf_vote,
84 hf_version,
85
86 block_hash: block.hash(),
87 height: *height,
88
89 miner_tx_weight: block.miner_transaction.weight(),
90 block,
91 })
92 }
93}
94
95#[derive(Debug)]
97pub struct PreparedBlock {
98 pub block: Block,
100 pub block_blob: Vec<u8>,
102
103 pub hf_vote: HardFork,
105 pub hf_version: HardFork,
107
108 pub block_hash: [u8; 32],
110 pub pow_hash: [u8; 32],
112
113 pub miner_tx_weight: usize,
115}
116
117impl PreparedBlock {
118 pub fn new<R: RandomX>(block: Block, randomx_vm: Option<&R>) -> Result<Self, ConsensusError> {
123 let (hf_version, hf_vote) = HardFork::from_block_header(&block.header)
124 .map_err(|_| BlockError::HardForkError(HardForkError::HardForkUnknown))?;
125
126 let [Input::Gen(height)] = &block.miner_transaction.prefix().inputs[..] else {
127 return Err(ConsensusError::Block(BlockError::MinerTxError(
128 MinerTxError::InputNotOfTypeGen,
129 )));
130 };
131
132 Ok(Self {
133 block_blob: block.serialize(),
134 hf_vote,
135 hf_version,
136
137 block_hash: block.hash(),
138 pow_hash: calculate_pow_hash(
139 randomx_vm,
140 &block.serialize_pow_hash(),
141 *height,
142 &hf_version,
143 )?,
144
145 miner_tx_weight: block.miner_transaction.weight(),
146 block,
147 })
148 }
149
150 fn new_prepped<R: RandomX>(
159 block: PreparedBlockExPow,
160 randomx_vm: Option<&R>,
161 ) -> Result<Self, ConsensusError> {
162 Ok(Self {
163 block_blob: block.block_blob,
164 hf_vote: block.hf_vote,
165 hf_version: block.hf_version,
166
167 block_hash: block.block_hash,
168 pow_hash: calculate_pow_hash(
169 randomx_vm,
170 &block.block.serialize_pow_hash(),
171 block.height,
172 &block.hf_version,
173 )?,
174
175 miner_tx_weight: block.block.miner_transaction.weight(),
176 block: block.block,
177 })
178 }
179
180 pub fn new_alt_block(block: AltBlockInformation) -> Result<Self, ConsensusError> {
182 Ok(Self {
183 block_blob: block.block_blob,
184 hf_vote: HardFork::from_version(block.block.header.hardfork_version)
185 .map_err(|_| BlockError::HardForkError(HardForkError::HardForkUnknown))?,
186 hf_version: HardFork::from_vote(block.block.header.hardfork_signal),
187 block_hash: block.block_hash,
188 pow_hash: block.pow_hash,
189 miner_tx_weight: block.block.miner_transaction.weight(),
190 block: block.block,
191 })
192 }
193}
194
195pub async fn verify_main_chain_block<D>(
197 block: Block,
198 txs: HashMap<[u8; 32], TransactionVerificationData>,
199 context_svc: &mut BlockchainContextService,
200 database: D,
201) -> Result<VerifiedBlockInformation, ExtendedConsensusError>
202where
203 D: Database + Clone + Send + 'static,
204{
205 let context = context_svc.blockchain_context().clone();
206 tracing::debug!("got blockchain context: {:?}", context);
207
208 tracing::debug!(
209 "Preparing block for verification, expected height: {}",
210 context.chain_height
211 );
212
213 let rx_vms = if block.header.hardfork_version < 12 {
217 HashMap::new()
218 } else {
219 let BlockChainContextResponse::RxVms(rx_vms) = context_svc
220 .ready()
221 .await?
222 .call(BlockChainContextRequest::CurrentRxVms)
223 .await?
224 else {
225 panic!("Blockchain context service returned wrong response!");
226 };
227
228 rx_vms
229 };
230
231 let height = context.chain_height;
232 let prepped_block = rayon_spawn_async(move || {
233 PreparedBlock::new(
234 block,
235 rx_vms.get(&randomx_seed_height(height)).map(AsRef::as_ref),
236 )
237 })
238 .await?;
239
240 check_block_pow(&prepped_block.pow_hash, context.next_difficulty)
241 .map_err(ConsensusError::Block)?;
242
243 let ordered_txs = pull_ordered_transactions(&prepped_block.block, txs)?;
245
246 verify_prepped_main_chain_block(prepped_block, ordered_txs, context_svc, database, None).await
247}
248
249pub async fn verify_prepped_main_chain_block<D>(
251 prepped_block: PreparedBlock,
252 mut txs: Vec<TransactionVerificationData>,
253 context_svc: &mut BlockchainContextService,
254 database: D,
255 batch_prep_cache: Option<&mut BatchPrepareCache>,
256) -> Result<VerifiedBlockInformation, ExtendedConsensusError>
257where
258 D: Database + Clone + Send + 'static,
259{
260 let context = context_svc.blockchain_context();
261
262 tracing::debug!("verifying block: {}", hex::encode(prepped_block.block_hash));
263
264 check_block_pow(&prepped_block.pow_hash, context.next_difficulty)
265 .map_err(ConsensusError::Block)?;
266
267 if prepped_block.block.transactions.len() != txs.len() {
268 return Err(ExtendedConsensusError::TxsIncludedWithBlockIncorrect);
269 }
270
271 if !prepped_block.block.transactions.is_empty() {
272 for (expected_tx_hash, tx) in prepped_block.block.transactions.iter().zip(txs.iter()) {
273 if expected_tx_hash != &tx.tx_hash {
274 return Err(ExtendedConsensusError::TxsIncludedWithBlockIncorrect);
275 }
276 }
277
278 let temp = start_tx_verification()
279 .append_prepped_txs(mem::take(&mut txs))
280 .prepare()?
281 .full(
282 context.chain_height,
283 context.top_hash,
284 context.current_adjusted_timestamp_for_time_lock(),
285 context.current_hf,
286 database,
287 batch_prep_cache.as_deref(),
288 )
289 .verify()
290 .await?;
291
292 txs = temp;
293 }
294
295 let block_weight =
296 prepped_block.miner_tx_weight + txs.iter().map(|tx| tx.tx_weight).sum::<usize>();
297 let total_fees = txs.iter().map(|tx| tx.fee).sum::<u64>();
298
299 tracing::debug!("Verifying block header.");
300 let (_, generated_coins) = check_block(
301 &prepped_block.block,
302 total_fees,
303 block_weight,
304 prepped_block.block_blob.len(),
305 &context.context_to_verify_block,
306 )
307 .map_err(ConsensusError::Block)?;
308
309 let block = VerifiedBlockInformation {
310 block_hash: prepped_block.block_hash,
311 block: prepped_block.block,
312 block_blob: prepped_block.block_blob,
313 txs: txs
314 .into_iter()
315 .map(|tx| VerifiedTransactionInformation {
316 tx_blob: tx.tx_blob,
317 tx_weight: tx.tx_weight,
318 fee: tx.fee,
319 tx_hash: tx.tx_hash,
320 tx: tx.tx,
321 })
322 .collect(),
323 pow_hash: prepped_block.pow_hash,
324 generated_coins,
325 weight: block_weight,
326 height: context.chain_height,
327 long_term_weight: context.next_block_long_term_weight(block_weight),
328 cumulative_difficulty: context.cumulative_difficulty + context.next_difficulty,
329 };
330
331 if let Some(batch_prep_cache) = batch_prep_cache {
332 batch_prep_cache.output_cache.add_block_to_cache(&block);
333 }
334
335 Ok(block)
336}