1use std::{collections::HashMap, sync::Arc};
6
7use monero_oxide::{block::Block, transaction::Input};
8use tower::{Service, ServiceExt};
9
10use cuprate_consensus_context::{
11 difficulty::DifficultyCache,
12 rx_vms::RandomXVm,
13 weight::{self, BlockWeightsCache},
14 AltChainContextCache, AltChainRequestToken, BLOCKCHAIN_TIMESTAMP_CHECK_WINDOW,
15};
16use cuprate_consensus_rules::{
17 blocks::{
18 check_block_pow, check_block_weight, check_timestamp, randomx_seed_height, BlockError,
19 },
20 miner_tx::MinerTxError,
21 ConsensusError,
22};
23use cuprate_helper::{asynch::rayon_spawn_async, cast::u64_to_usize};
24use cuprate_types::{
25 AltBlockInformation, Chain, ChainId, TransactionVerificationData,
26 VerifiedTransactionInformation,
27};
28
29use crate::{
30 block::{free::pull_ordered_transactions, BlockVerificationError, PreparedBlock},
31 BlockChainContextRequest, BlockChainContextResponse, ExtendedConsensusError,
32};
33
34pub async fn sanity_check_alt_block<C>(
40 block: Block,
41 txs: HashMap<[u8; 32], TransactionVerificationData>,
42 mut context_svc: C,
43) -> Result<AltBlockInformation, BlockVerificationError>
44where
45 C: Service<
46 BlockChainContextRequest,
47 Response = BlockChainContextResponse,
48 Error = tower::BoxError,
49 > + Send
50 + 'static,
51 C::Future: Send + 'static,
52{
53 let BlockChainContextResponse::AltChainContextCache(alt_context_cache) = context_svc
55 .ready()
56 .await
57 .map_err(BlockVerificationError::fatal)?
58 .call(BlockChainContextRequest::AltChainContextCache {
59 prev_id: block.header.previous,
60 _token: AltChainRequestToken,
61 })
62 .await
63 .map_err(BlockVerificationError::fatal)?
64 else {
65 panic!("Context service returned wrong response!");
66 };
67
68 let Some(mut alt_context_cache) = alt_context_cache else {
69 return Err(BlockVerificationError::invalid_pow(ConsensusError::Block(
70 BlockError::PreviousIDIncorrect,
71 )));
72 };
73
74 let [Input::Gen(height)] = &block.miner_transaction().prefix().inputs[..] else {
76 return Err(BlockVerificationError::invalid_pow(ConsensusError::Block(
77 BlockError::MinerTxError(MinerTxError::InputNotOfTypeGen),
78 )));
79 };
80
81 if *height != alt_context_cache.chain_height {
82 return Err(BlockVerificationError::invalid_pow(ConsensusError::Block(
83 BlockError::MinerTxError(MinerTxError::InputsHeightIncorrect),
84 )));
85 }
86
87 let prepped_block = {
89 let rx_vm = alt_rx_vm(
90 alt_context_cache.chain_height,
91 block.header.hardfork_version,
92 alt_context_cache.parent_chain,
93 &mut alt_context_cache,
94 &mut context_svc,
95 )
96 .await
97 .map_err(BlockVerificationError::invalid_pow)?;
98
99 rayon_spawn_async(move || PreparedBlock::new(block, rx_vm.as_deref()))
100 .await
101 .map_err(BlockVerificationError::invalid_pow)?
102 };
103
104 let difficulty_cache = alt_difficulty_cache(
106 prepped_block.block.header.previous,
107 &mut alt_context_cache,
108 &mut context_svc,
109 )
110 .await
111 .map_err(BlockVerificationError::invalid_pow)?;
112
113 let median_timestamp =
117 difficulty_cache.median_timestamp(u64_to_usize(BLOCKCHAIN_TIMESTAMP_CHECK_WINDOW));
118 check_timestamp(&prepped_block.block, median_timestamp)
119 .map_err(ConsensusError::Block)
120 .map_err(BlockVerificationError::invalid_pow)?;
121
122 let next_difficulty = difficulty_cache.next_difficulty(prepped_block.hf_version);
123 check_block_pow(&prepped_block.pow_hash, next_difficulty)
125 .map_err(ConsensusError::Block)
126 .map_err(BlockVerificationError::invalid_pow)?;
127
128 let cumulative_difficulty = difficulty_cache.cumulative_difficulty() + next_difficulty;
131
132 let ordered_txs = pull_ordered_transactions(&prepped_block.block, txs)
133 .map_err(BlockVerificationError::valid_pow)?;
134
135 let block_weight =
136 prepped_block.miner_tx_weight + ordered_txs.iter().map(|tx| tx.tx_weight).sum::<usize>();
137
138 let alt_weight_cache = alt_weight_cache(
139 prepped_block.block.header.previous,
140 &mut alt_context_cache,
141 &mut context_svc,
142 )
143 .await
144 .map_err(BlockVerificationError::valid_pow)?;
145
146 check_block_weight(
148 block_weight,
149 alt_weight_cache.median_for_block_reward(prepped_block.hf_version),
150 )
151 .map_err(ConsensusError::Block)
152 .map_err(BlockVerificationError::valid_pow)?;
153
154 let long_term_weight = weight::calculate_block_long_term_weight(
155 prepped_block.hf_version,
156 block_weight,
157 alt_weight_cache.median_long_term_weight(),
158 );
159
160 let chain_id = *alt_context_cache
162 .chain_id
163 .get_or_insert_with(|| ChainId(rand::random()));
164
165 let block_info = AltBlockInformation {
167 block_hash: prepped_block.block_hash,
168 block: prepped_block.block,
169 block_blob: prepped_block.block_blob,
170 txs: ordered_txs
171 .into_iter()
172 .map(|tx| {
173 let tx_weight = tx.tx_weight;
174 let fee = tx.fee;
175 let tx_hash = tx.tx_hash;
176 let (tx, tx_prunable_blob) = tx.tx.pruned_with_prunable();
177 VerifiedTransactionInformation {
178 tx_prunable_blob,
179 tx_pruned: tx.serialize(),
180 tx_weight,
181 fee,
182 tx_hash,
183 tx,
184 }
185 })
186 .collect(),
187 pow_hash: prepped_block.pow_hash,
188 weight: block_weight,
189 height: alt_context_cache.chain_height,
190 long_term_weight,
191 cumulative_difficulty,
192 chain_id,
193 };
194
195 alt_context_cache.add_new_block(
197 block_info.height,
198 block_info.block_hash,
199 block_info.weight,
200 block_info.long_term_weight,
201 block_info.block.header.timestamp,
202 cumulative_difficulty,
203 );
204
205 context_svc
207 .oneshot(BlockChainContextRequest::AddAltChainContextCache {
208 cache: alt_context_cache,
209 _token: AltChainRequestToken,
210 })
211 .await
212 .map_err(BlockVerificationError::fatal)?;
213
214 Ok(block_info)
215}
216
217async fn alt_rx_vm<C>(
221 block_height: usize,
222 hf: u8,
223 parent_chain: Chain,
224 alt_chain_context: &mut AltChainContextCache,
225 context_svc: C,
226) -> Result<Option<Arc<RandomXVm>>, ExtendedConsensusError>
227where
228 C: Service<
229 BlockChainContextRequest,
230 Response = BlockChainContextResponse,
231 Error = tower::BoxError,
232 > + Send,
233 C::Future: Send + 'static,
234{
235 if hf < 12 {
236 return Ok(None);
237 }
238
239 let seed_height = randomx_seed_height(block_height);
240
241 let cached_vm = match alt_chain_context.cached_rx_vm.take() {
242 Some((cached_seed_height, vm)) if seed_height == cached_seed_height => {
244 (cached_seed_height, vm)
245 }
246 _ => {
248 let BlockChainContextResponse::AltChainRxVM(vm) = context_svc
249 .oneshot(BlockChainContextRequest::AltChainRxVM {
250 height: block_height,
251 chain: parent_chain,
252 _token: AltChainRequestToken,
253 })
254 .await?
255 else {
256 panic!("Context service returned wrong response!");
257 };
258
259 (seed_height, vm)
260 }
261 };
262
263 Ok(Some(Arc::clone(
264 &alt_chain_context.cached_rx_vm.insert(cached_vm).1,
265 )))
266}
267
268async fn alt_difficulty_cache<C>(
270 prev_id: [u8; 32],
271 alt_chain_context: &mut AltChainContextCache,
272 context_svc: C,
273) -> Result<&mut DifficultyCache, ExtendedConsensusError>
274where
275 C: Service<
276 BlockChainContextRequest,
277 Response = BlockChainContextResponse,
278 Error = tower::BoxError,
279 > + Send,
280 C::Future: Send + 'static,
281{
282 match &mut alt_chain_context.difficulty_cache {
284 Some(cache) => Ok(cache),
285 difficulty_cache => {
287 let BlockChainContextResponse::AltChainDifficultyCache(cache) = context_svc
288 .oneshot(BlockChainContextRequest::AltChainDifficultyCache {
289 prev_id,
290 _token: AltChainRequestToken,
291 })
292 .await?
293 else {
294 panic!("Context service returned wrong response!");
295 };
296
297 let Some(cache) = cache else {
298 return Err(ConsensusError::Block(BlockError::PreviousIDIncorrect).into());
299 };
300
301 Ok(difficulty_cache.insert(cache))
302 }
303 }
304}
305
306async fn alt_weight_cache<C>(
308 prev_id: [u8; 32],
309 alt_chain_context: &mut AltChainContextCache,
310 context_svc: C,
311) -> Result<&mut BlockWeightsCache, ExtendedConsensusError>
312where
313 C: Service<
314 BlockChainContextRequest,
315 Response = BlockChainContextResponse,
316 Error = tower::BoxError,
317 > + Send,
318 C::Future: Send + 'static,
319{
320 match &mut alt_chain_context.weight_cache {
322 Some(cache) => Ok(cache),
323 weight_cache => {
325 let BlockChainContextResponse::AltChainWeightCache(cache) = context_svc
326 .oneshot(BlockChainContextRequest::AltChainWeightCache {
327 prev_id,
328 _token: AltChainRequestToken,
329 })
330 .await?
331 else {
332 panic!("Context service returned wrong response!");
333 };
334
335 let Some(cache) = cache else {
336 return Err(ConsensusError::Block(BlockError::PreviousIDIncorrect).into());
337 };
338
339 Ok(weight_cache.insert(cache))
340 }
341 }
342}