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, 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, ExtendedConsensusError>
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(mut alt_context_cache) = context_svc
55 .ready()
56 .await?
57 .call(BlockChainContextRequest::AltChainContextCache {
58 prev_id: block.header.previous,
59 _token: AltChainRequestToken,
60 })
61 .await?
62 else {
63 panic!("Context service returned wrong response!");
64 };
65
66 let [Input::Gen(height)] = &block.miner_transaction().prefix().inputs[..] else {
68 return Err(ConsensusError::Block(BlockError::MinerTxError(
69 MinerTxError::InputNotOfTypeGen,
70 ))
71 .into());
72 };
73
74 if *height != alt_context_cache.chain_height {
75 return Err(ConsensusError::Block(BlockError::MinerTxError(
76 MinerTxError::InputsHeightIncorrect,
77 ))
78 .into());
79 }
80
81 let prepped_block = {
83 let rx_vm = alt_rx_vm(
84 alt_context_cache.chain_height,
85 block.header.hardfork_version,
86 alt_context_cache.parent_chain,
87 &mut alt_context_cache,
88 &mut context_svc,
89 )
90 .await?;
91
92 rayon_spawn_async(move || PreparedBlock::new(block, rx_vm.as_deref())).await?
93 };
94
95 let difficulty_cache = alt_difficulty_cache(
97 prepped_block.block.header.previous,
98 &mut alt_context_cache,
99 &mut context_svc,
100 )
101 .await?;
102
103 let median_timestamp =
107 difficulty_cache.median_timestamp(u64_to_usize(BLOCKCHAIN_TIMESTAMP_CHECK_WINDOW));
108 check_timestamp(&prepped_block.block, median_timestamp).map_err(ConsensusError::Block)?;
109
110 let next_difficulty = difficulty_cache.next_difficulty(prepped_block.hf_version);
111 check_block_pow(&prepped_block.pow_hash, next_difficulty).map_err(ConsensusError::Block)?;
113
114 let cumulative_difficulty = difficulty_cache.cumulative_difficulty() + next_difficulty;
115
116 let ordered_txs = pull_ordered_transactions(&prepped_block.block, txs)?;
117
118 let block_weight =
119 prepped_block.miner_tx_weight + ordered_txs.iter().map(|tx| tx.tx_weight).sum::<usize>();
120
121 let alt_weight_cache = alt_weight_cache(
122 prepped_block.block.header.previous,
123 &mut alt_context_cache,
124 &mut context_svc,
125 )
126 .await?;
127
128 check_block_weight(
130 block_weight,
131 alt_weight_cache.median_for_block_reward(prepped_block.hf_version),
132 )
133 .map_err(ConsensusError::Block)?;
134
135 let long_term_weight = weight::calculate_block_long_term_weight(
136 prepped_block.hf_version,
137 block_weight,
138 alt_weight_cache.median_long_term_weight(),
139 );
140
141 let chain_id = *alt_context_cache
143 .chain_id
144 .get_or_insert_with(|| ChainId(rand::random()));
145
146 let block_info = AltBlockInformation {
148 block_hash: prepped_block.block_hash,
149 block: prepped_block.block,
150 block_blob: prepped_block.block_blob,
151 txs: ordered_txs
152 .into_iter()
153 .map(|tx| {
154 let tx_weight = tx.tx_weight;
155 let fee = tx.fee;
156 let tx_hash = tx.tx_hash;
157 let (tx, tx_prunable_blob) = tx.tx.pruned_with_prunable();
158 VerifiedTransactionInformation {
159 tx_prunable_blob,
160 tx_pruned: tx.serialize(),
161 tx_weight,
162 fee,
163 tx_hash,
164 tx,
165 }
166 })
167 .collect(),
168 pow_hash: prepped_block.pow_hash,
169 weight: block_weight,
170 height: alt_context_cache.chain_height,
171 long_term_weight,
172 cumulative_difficulty,
173 chain_id,
174 };
175
176 alt_context_cache.add_new_block(
178 block_info.height,
179 block_info.block_hash,
180 block_info.weight,
181 block_info.long_term_weight,
182 block_info.block.header.timestamp,
183 cumulative_difficulty,
184 );
185
186 context_svc
188 .oneshot(BlockChainContextRequest::AddAltChainContextCache {
189 cache: alt_context_cache,
190 _token: AltChainRequestToken,
191 })
192 .await?;
193
194 Ok(block_info)
195}
196
197async fn alt_rx_vm<C>(
201 block_height: usize,
202 hf: u8,
203 parent_chain: Chain,
204 alt_chain_context: &mut AltChainContextCache,
205 context_svc: C,
206) -> Result<Option<Arc<RandomXVm>>, ExtendedConsensusError>
207where
208 C: Service<
209 BlockChainContextRequest,
210 Response = BlockChainContextResponse,
211 Error = tower::BoxError,
212 > + Send,
213 C::Future: Send + 'static,
214{
215 if hf < 12 {
216 return Ok(None);
217 }
218
219 let seed_height = randomx_seed_height(block_height);
220
221 let cached_vm = match alt_chain_context.cached_rx_vm.take() {
222 Some((cached_seed_height, vm)) if seed_height == cached_seed_height => {
224 (cached_seed_height, vm)
225 }
226 _ => {
228 let BlockChainContextResponse::AltChainRxVM(vm) = context_svc
229 .oneshot(BlockChainContextRequest::AltChainRxVM {
230 height: block_height,
231 chain: parent_chain,
232 _token: AltChainRequestToken,
233 })
234 .await?
235 else {
236 panic!("Context service returned wrong response!");
237 };
238
239 (seed_height, vm)
240 }
241 };
242
243 Ok(Some(Arc::clone(
244 &alt_chain_context.cached_rx_vm.insert(cached_vm).1,
245 )))
246}
247
248async fn alt_difficulty_cache<C>(
250 prev_id: [u8; 32],
251 alt_chain_context: &mut AltChainContextCache,
252 context_svc: C,
253) -> Result<&mut DifficultyCache, ExtendedConsensusError>
254where
255 C: Service<
256 BlockChainContextRequest,
257 Response = BlockChainContextResponse,
258 Error = tower::BoxError,
259 > + Send,
260 C::Future: Send + 'static,
261{
262 match &mut alt_chain_context.difficulty_cache {
264 Some(cache) => Ok(cache),
265 difficulty_cache => {
267 let BlockChainContextResponse::AltChainDifficultyCache(cache) = context_svc
268 .oneshot(BlockChainContextRequest::AltChainDifficultyCache {
269 prev_id,
270 _token: AltChainRequestToken,
271 })
272 .await?
273 else {
274 panic!("Context service returned wrong response!");
275 };
276
277 Ok(difficulty_cache.insert(cache))
278 }
279 }
280}
281
282async fn alt_weight_cache<C>(
284 prev_id: [u8; 32],
285 alt_chain_context: &mut AltChainContextCache,
286 context_svc: C,
287) -> Result<&mut BlockWeightsCache, ExtendedConsensusError>
288where
289 C: Service<
290 BlockChainContextRequest,
291 Response = BlockChainContextResponse,
292 Error = tower::BoxError,
293 > + Send,
294 C::Future: Send + 'static,
295{
296 match &mut alt_chain_context.weight_cache {
298 Some(cache) => Ok(cache),
299 weight_cache => {
301 let BlockChainContextResponse::AltChainWeightCache(cache) = context_svc
302 .oneshot(BlockChainContextRequest::AltChainWeightCache {
303 prev_id,
304 _token: AltChainRequestToken,
305 })
306 .await?
307 else {
308 panic!("Context service returned wrong response!");
309 };
310
311 Ok(weight_cache.insert(cache))
312 }
313 }
314}