cuprate_blockchain/ops/alt_block/
block.rs1use bytemuck::TransparentWrapper;
2use monero_serai::block::{Block, BlockHeader};
3
4use cuprate_database::{DatabaseRo, DatabaseRw, DbResult, StorableVec};
5use cuprate_helper::map::{combine_low_high_bits_to_u128, split_u128_into_low_high_bits};
6use cuprate_types::{AltBlockInformation, Chain, ChainId, ExtendedBlockHeader, HardFork};
7
8use crate::{
9 ops::{
10 alt_block::{add_alt_transaction_blob, get_alt_transaction, update_alt_chain_info},
11 block::get_block_info,
12 macros::doc_error,
13 },
14 tables::{Tables, TablesMut},
15 types::{AltBlockHeight, BlockHash, BlockHeight, CompactAltBlockInfo},
16};
17
18pub fn flush_alt_blocks<'a, E: cuprate_database::EnvInner<'a>>(
22 env_inner: &E,
23 tx_rw: &mut E::Rw<'_>,
24) -> DbResult<()> {
25 use crate::tables::{
26 AltBlockBlobs, AltBlockHeights, AltBlocksInfo, AltChainInfos, AltTransactionBlobs,
27 AltTransactionInfos,
28 };
29
30 env_inner.clear_db::<AltChainInfos>(tx_rw)?;
31 env_inner.clear_db::<AltBlockHeights>(tx_rw)?;
32 env_inner.clear_db::<AltBlocksInfo>(tx_rw)?;
33 env_inner.clear_db::<AltBlockBlobs>(tx_rw)?;
34 env_inner.clear_db::<AltTransactionBlobs>(tx_rw)?;
35 env_inner.clear_db::<AltTransactionInfos>(tx_rw)
36}
37
38#[doc = doc_error!()]
44pub fn add_alt_block(alt_block: &AltBlockInformation, tables: &mut impl TablesMut) -> DbResult<()> {
51 let alt_block_height = AltBlockHeight {
52 chain_id: alt_block.chain_id.into(),
53 height: alt_block.height,
54 };
55
56 tables
57 .alt_block_heights_mut()
58 .put(&alt_block.block_hash, &alt_block_height)?;
59
60 update_alt_chain_info(&alt_block_height, &alt_block.block.header.previous, tables)?;
61
62 let (cumulative_difficulty_low, cumulative_difficulty_high) =
63 split_u128_into_low_high_bits(alt_block.cumulative_difficulty);
64
65 let alt_block_info = CompactAltBlockInfo {
66 block_hash: alt_block.block_hash,
67 pow_hash: alt_block.pow_hash,
68 height: alt_block.height,
69 weight: alt_block.weight,
70 long_term_weight: alt_block.long_term_weight,
71 cumulative_difficulty_low,
72 cumulative_difficulty_high,
73 };
74
75 tables
76 .alt_blocks_info_mut()
77 .put(&alt_block_height, &alt_block_info)?;
78
79 tables.alt_block_blobs_mut().put(
80 &alt_block_height,
81 StorableVec::wrap_ref(&alt_block.block_blob),
82 )?;
83
84 assert_eq!(alt_block.txs.len(), alt_block.block.transactions.len());
85 for tx in &alt_block.txs {
86 add_alt_transaction_blob(tx, tables)?;
87 }
88
89 Ok(())
90}
91
92#[doc = doc_error!()]
97pub fn get_alt_block(
98 alt_block_height: &AltBlockHeight,
99 tables: &impl Tables,
100) -> DbResult<AltBlockInformation> {
101 let block_info = tables.alt_blocks_info().get(alt_block_height)?;
102
103 let block_blob = tables.alt_block_blobs().get(alt_block_height)?.0;
104
105 let block = Block::read(&mut block_blob.as_slice())?;
106
107 let txs = block
108 .transactions
109 .iter()
110 .map(|tx_hash| get_alt_transaction(tx_hash, tables))
111 .collect::<DbResult<_>>()?;
112
113 Ok(AltBlockInformation {
114 block,
115 block_blob,
116 txs,
117 block_hash: block_info.block_hash,
118 pow_hash: block_info.pow_hash,
119 height: block_info.height,
120 weight: block_info.weight,
121 long_term_weight: block_info.long_term_weight,
122 cumulative_difficulty: combine_low_high_bits_to_u128(
123 block_info.cumulative_difficulty_low,
124 block_info.cumulative_difficulty_high,
125 ),
126 chain_id: alt_block_height.chain_id.into(),
127 })
128}
129
130#[doc = doc_error!()]
137pub fn get_alt_block_hash(
138 block_height: &BlockHeight,
139 alt_chain: ChainId,
140 tables: &impl Tables,
141) -> DbResult<BlockHash> {
142 let alt_chains = tables.alt_chain_infos();
143
144 let original_chain = {
146 let mut chain = alt_chain.into();
147 loop {
148 let chain_info = alt_chains.get(&chain)?;
149
150 if chain_info.common_ancestor_height < *block_height {
151 break Chain::Alt(chain.into());
152 }
153
154 match chain_info.parent_chain.into() {
155 Chain::Main => break Chain::Main,
156 Chain::Alt(alt_chain_id) => {
157 chain = alt_chain_id.into();
158 continue;
159 }
160 }
161 }
162 };
163
164 match original_chain {
166 Chain::Main => {
167 get_block_info(block_height, tables.block_infos()).map(|info| info.block_hash)
168 }
169 Chain::Alt(chain_id) => tables
170 .alt_blocks_info()
171 .get(&AltBlockHeight {
172 chain_id: chain_id.into(),
173 height: *block_height,
174 })
175 .map(|info| info.block_hash),
176 }
177}
178
179#[doc = doc_error!()]
185pub fn get_alt_block_extended_header_from_height(
186 height: &AltBlockHeight,
187 table: &impl Tables,
188) -> DbResult<ExtendedBlockHeader> {
189 let block_info = table.alt_blocks_info().get(height)?;
190
191 let block_blob = table.alt_block_blobs().get(height)?.0;
192
193 let block_header = BlockHeader::read(&mut block_blob.as_slice())?;
194
195 Ok(ExtendedBlockHeader {
196 version: HardFork::from_version(block_header.hardfork_version)
197 .expect("Block in DB must have correct version"),
198 vote: block_header.hardfork_version,
199 timestamp: block_header.timestamp,
200 cumulative_difficulty: combine_low_high_bits_to_u128(
201 block_info.cumulative_difficulty_low,
202 block_info.cumulative_difficulty_high,
203 ),
204 block_weight: block_info.weight,
205 long_term_weight: block_info.long_term_weight,
206 })
207}
208
209#[cfg(test)]
210mod tests {
211 use std::num::NonZero;
212
213 use cuprate_database::{Env, EnvInner, TxRw};
214 use cuprate_test_utils::data::{BLOCK_V16_TX0, BLOCK_V1_TX2, BLOCK_V9_TX3};
215 use cuprate_types::{Chain, ChainId};
216
217 use crate::{
218 ops::{
219 alt_block::{
220 add_alt_block, flush_alt_blocks, get_alt_block,
221 get_alt_block_extended_header_from_height, get_alt_block_hash,
222 get_alt_chain_history_ranges,
223 },
224 block::{add_block, pop_block},
225 },
226 tables::{OpenTables, Tables},
227 tests::{assert_all_tables_are_empty, map_verified_block_to_alt, tmp_concrete_env},
228 types::AltBlockHeight,
229 };
230
231 #[expect(clippy::range_plus_one)]
232 #[test]
233 fn all_alt_blocks() {
234 let (env, _tmp) = tmp_concrete_env();
235 let env_inner = env.env_inner();
236 assert_all_tables_are_empty(&env);
237
238 let chain_id = ChainId(NonZero::new(1).unwrap());
239
240 {
242 let tx_rw = env_inner.tx_rw().unwrap();
243 let mut tables = env_inner.open_tables_mut(&tx_rw).unwrap();
244
245 let mut initial_block = BLOCK_V1_TX2.clone();
246 initial_block.height = 0;
247
248 add_block(&initial_block, &mut tables).unwrap();
249
250 drop(tables);
251 TxRw::commit(tx_rw).unwrap();
252 }
253
254 let alt_blocks = [
255 map_verified_block_to_alt(BLOCK_V9_TX3.clone(), chain_id),
256 map_verified_block_to_alt(BLOCK_V16_TX0.clone(), chain_id),
257 ];
258
259 {
261 let tx_rw = env_inner.tx_rw().unwrap();
262 let mut tables = env_inner.open_tables_mut(&tx_rw).unwrap();
263
264 let mut prev_hash = BLOCK_V1_TX2.block_hash;
265 for (i, mut alt_block) in alt_blocks.into_iter().enumerate() {
266 let height = i + 1;
267
268 alt_block.height = height;
269 alt_block.block.header.previous = prev_hash;
270 alt_block.block_blob = alt_block.block.serialize();
271
272 add_alt_block(&alt_block, &mut tables).unwrap();
273
274 let alt_height = AltBlockHeight {
275 chain_id: chain_id.into(),
276 height,
277 };
278
279 let alt_block_2 = get_alt_block(&alt_height, &tables).unwrap();
280 assert_eq!(alt_block.block, alt_block_2.block);
281
282 let headers = get_alt_chain_history_ranges(
283 0..(height + 1),
284 chain_id,
285 tables.alt_chain_infos(),
286 )
287 .unwrap();
288
289 assert_eq!(headers.len(), 2);
290 assert_eq!(headers[1], (Chain::Main, 0..1));
291 assert_eq!(headers[0], (Chain::Alt(chain_id), 1..(height + 1)));
292
293 prev_hash = alt_block.block_hash;
294
295 let header =
296 get_alt_block_extended_header_from_height(&alt_height, &tables).unwrap();
297
298 assert_eq!(header.timestamp, alt_block.block.header.timestamp);
299 assert_eq!(header.block_weight, alt_block.weight);
300 assert_eq!(header.long_term_weight, alt_block.long_term_weight);
301 assert_eq!(
302 header.cumulative_difficulty,
303 alt_block.cumulative_difficulty
304 );
305 assert_eq!(
306 header.version.as_u8(),
307 alt_block.block.header.hardfork_version
308 );
309 assert_eq!(header.vote, alt_block.block.header.hardfork_signal);
310
311 let block_hash = get_alt_block_hash(&height, chain_id, &tables).unwrap();
312
313 assert_eq!(block_hash, alt_block.block_hash);
314 }
315
316 drop(tables);
317 TxRw::commit(tx_rw).unwrap();
318 }
319
320 {
321 let mut tx_rw = env_inner.tx_rw().unwrap();
322
323 flush_alt_blocks(&env_inner, &mut tx_rw).unwrap();
324
325 let mut tables = env_inner.open_tables_mut(&tx_rw).unwrap();
326 pop_block(None, &mut tables).unwrap();
327
328 drop(tables);
329 TxRw::commit(tx_rw).unwrap();
330 }
331
332 assert_all_tables_are_empty(&env);
333 }
334}