1use std::{borrow::Cow, cmp::min, collections::HashMap, io};
3
4use bytes::{Buf, Bytes};
5use fjall::Readable;
6use monero_oxide::{
7 block::{Block, BlockHeader},
8 transaction::{Pruned, Transaction},
9};
10use tapes::{BlobTape, TapesAppend, TapesRead, TapesTruncate};
11use tracing::instrument;
12
13use cuprate_constants::block::MAX_BLOCK_HEIGHT_USIZE;
14use cuprate_helper::{
15 cast::{u64_to_usize, usize_to_u64},
16 map::{combine_low_high_bits_to_u128, split_u128_into_low_high_bits},
17 tx::tx_fee,
18};
19use cuprate_pruning::{
20 PruningSeed, CRYPTONOTE_PRUNING_LOG_STRIPES, CRYPTONOTE_PRUNING_STRIPE_SIZE,
21 CRYPTONOTE_PRUNING_TIP_BLOCKS,
22};
23use cuprate_types::{
24 AltBlockInformation, BlockCompleteEntry, ChainId, ExtendedBlockHeader, HardFork,
25 PrunedTxBlobEntry, TransactionBlobs, VerifiedBlockInformation, VerifiedTransactionInformation,
26};
27
28use crate::{
29 database::{PrunableTables, CHAIN_TIP_KEY},
30 error::{BlockchainError, DbResult},
31 ops::{
32 alt_block,
33 blockchain::chain_height,
34 tx::{
35 add_tx_info_to_dynamic_tables, add_tx_info_to_tapes, get_tx_from_id,
36 remove_tx_from_dynamic_tables,
37 },
38 },
39 types::{Amount, BlockHash, BlockHeight, BlockInfo},
40 BlockchainDatabase,
41};
42
43#[instrument(skip_all, level = "info")]
51pub fn add_blocks_to_tapes(
52 blocks: &[VerifiedBlockInformation],
53 db: &BlockchainDatabase,
54 append_tx: &mut tapes::TapesAppendTransaction,
55) -> DbResult<()> {
56 fn write_v2_prunable_data(
58 append_tx: &mut tapes::TapesAppendTransaction,
59 stripe: u32,
60 prunable_tables: &PrunableTables,
61 blocks: &[VerifiedBlockInformation],
62 ) -> io::Result<u64> {
63 fn write_v2_prunable_data_inner(
64 append_tx: &mut tapes::TapesAppendTransaction,
65 tape: &impl BlobTape,
66 blocks: &[VerifiedBlockInformation],
67 ) -> io::Result<u64> {
68 let first_idx = append_tx
69 .blob_tape_len(tape)
70 .expect("Required tape not found");
71 for block in blocks {
72 for tx in &block.txs {
73 if tx.tx.version() != 1 {
74 append_tx.append_bytes(tape, tx.tx_prunable_blob.as_slice())?;
75 }
76 }
77 }
78
79 Ok(first_idx)
80 }
81
82 if let Some(prunable_tape) = prunable_tables.try_get_prunable_tape(stripe) {
83 write_v2_prunable_data_inner(append_tx, prunable_tape, blocks)
84 } else {
85 let PrunableTables::Pruned {
86 prunable_tip_blobs, ..
87 } = prunable_tables
88 else {
89 unreachable!()
90 };
91 write_v2_prunable_data_inner(append_tx, prunable_tip_blobs, blocks)
92 }
93 }
94
95 fn write_v1_prunable_data(
97 append_tx: &mut tapes::TapesAppendTransaction,
98 db: &BlockchainDatabase,
99 blocks: &[VerifiedBlockInformation],
100 ) -> io::Result<u64> {
101 let first_idx = append_tx
102 .blob_tape_len(&db.v1_prunable_blobs)
103 .expect("Required tape not found");
104
105 for block in blocks {
106 for tx in &block.txs {
107 if tx.tx.version() == 1 {
108 append_tx.append_bytes(&db.v1_prunable_blobs, &tx.tx_prunable_blob)?;
109 }
110 }
111 }
112
113 Ok(first_idx)
114 }
115
116 let mut pruned_tape_index = append_tx.blob_tape_len(&db.pruned_blobs).unwrap_or(0);
118 for block in blocks {
119 append_tx.append_bytes(&db.pruned_blobs, &block.block_blob)?;
120
121 for tx in &block.txs {
122 append_tx.append_bytes(&db.pruned_blobs, tx.tx_pruned.as_slice())?;
123
124 let prunable_hash = if tx.tx_prunable_blob.is_empty() || tx.tx.version() == 1 {
125 [0; 32]
126 } else {
127 monero_oxide::primitives::keccak256(&tx.tx_prunable_blob)
128 };
129 append_tx.append_bytes(&db.pruned_blobs, &prunable_hash)?;
130 }
131 }
132
133 tracing::trace!("pruned_tape_index: {}", pruned_tape_index);
134
135 let start_height = blocks[0].height;
137 let first_block_pruning_seed = PruningSeed::new_pruned(
138 cuprate_pruning::get_block_pruning_stripe(
139 start_height,
140 usize::MAX,
141 CRYPTONOTE_PRUNING_LOG_STRIPES,
142 )
143 .unwrap(),
144 CRYPTONOTE_PRUNING_LOG_STRIPES,
145 )
146 .unwrap();
147 let next_stripe_height = first_block_pruning_seed
148 .get_next_pruned_block(start_height, MAX_BLOCK_HEIGHT_USIZE)
149 .unwrap()
150 .unwrap();
151
152 let (first_stripe, next_stripe) =
153 blocks.split_at(min(next_stripe_height - start_height, blocks.len()));
154
155 assert!(
156 next_stripe.len() <= CRYPTONOTE_PRUNING_STRIPE_SIZE,
157 "blocks cross more than 2 pruning stripes"
158 );
159
160 tracing::debug!(
161 start_height,
162 ?first_block_pruning_seed,
163 next_stripe_height,
164 first_stripe_len = first_stripe.len(),
165 next_stripe_len = next_stripe.len()
166 );
167
168 let mut numb_rct_outs = append_tx
169 .fixed_sized_tape_len(&db.rct_outputs)
170 .expect("Required tape not found");
171
172 for blocks in [first_stripe, next_stripe] {
174 if blocks.is_empty() {
175 continue;
176 }
177
178 if let PrunableTables::Pruned {
180 prunable_tip,
181 prunable_tip_blobs,
182 stripe: our_stripe,
183 ..
184 } = &db.prunable_tables
185 {
186 let end_removal_height = blocks
189 .last()
190 .unwrap()
191 .height
192 .saturating_sub(CRYPTONOTE_PRUNING_TIP_BLOCKS - 1);
193
194 let end_removal_stripe = cuprate_pruning::get_block_pruning_stripe(
195 end_removal_height,
196 usize::MAX,
197 CRYPTONOTE_PRUNING_LOG_STRIPES,
198 )
199 .unwrap();
200
201 let new_start_tx_idx = append_tx
202 .read_entry(&db.block_infos, usize_to_u64(end_removal_height))?
203 .map_or(0, |b| b.mining_tx_index);
204
205 append_tx.shift_start_idx_fixed(prunable_tip, new_start_tx_idx)?;
206
207 if end_removal_stripe != *our_stripe {
209 if let Some(new_start_bytes) =
210 append_tx.read_entry(prunable_tip, new_start_tx_idx)?
211 {
212 append_tx.shift_start_idx(prunable_tip_blobs, new_start_bytes)?;
213 }
214 }
215 }
216
217 let stripe = cuprate_pruning::get_block_pruning_stripe(
219 blocks[0].height,
220 usize::MAX,
221 CRYPTONOTE_PRUNING_LOG_STRIPES,
222 )
223 .unwrap();
224
225 let mut v2_prunable_index =
227 write_v2_prunable_data(append_tx, stripe, &db.prunable_tables, blocks)?;
228
229 let mut v1_prunable_index = write_v1_prunable_data(append_tx, db, blocks)?;
231
232 tracing::debug!(
233 chunk_start = blocks[0].height,
234 stripe,
235 v1_prunable_index,
236 v2_prunable_index,
237 numb_rct_outs
238 );
239
240 for block in blocks {
242 let block_pruned_blob_idx = pruned_tape_index;
243 let block_v1_prunable_idx = v1_prunable_index;
244 let block_v2_prunable_idx = v2_prunable_index;
245
246 let header_len = usize_to_u64(block.block.header.serialize().len());
247
248 let mining_tx_index = {
250 let tx = block.block.miner_transaction();
251 add_tx_info_to_tapes(
252 &tx.clone().into(),
253 pruned_tape_index + header_len, v2_prunable_index,
255 tx.serialize().len(),
256 0,
257 &block.height,
258 &mut numb_rct_outs,
259 append_tx,
260 db,
261 )?
262 };
263 pruned_tape_index += usize_to_u64(block.block_blob.len());
265
266 for tx in &block.txs {
268 add_tx_info_to_tapes(
269 &tx.tx,
270 pruned_tape_index,
271 if tx.tx.version() == 1 {
273 v1_prunable_index
274 } else {
275 v2_prunable_index
276 },
277 tx.tx_pruned.len(),
278 tx.tx_prunable_blob.len(),
279 &block.height,
280 &mut numb_rct_outs,
281 append_tx,
282 db,
283 )?;
284
285 pruned_tape_index += usize_to_u64(tx.tx_pruned.len()) + 32;
288 if tx.tx.version() == 1 {
289 v1_prunable_index += usize_to_u64(tx.tx_prunable_blob.len());
290 } else {
291 v2_prunable_index += usize_to_u64(tx.tx_prunable_blob.len());
292 }
293 }
294
295 let cumulative_generated_coins = append_tx
297 .read_entry(
298 &db.block_infos,
299 usize_to_u64(block.height.saturating_sub(1)),
300 )?
301 .map_or(0, |prev| prev.cumulative_generated_coins)
302 .saturating_add(block.generated_coins);
303
304 let (cumulative_difficulty_low, cumulative_difficulty_high) =
305 split_u128_into_low_high_bits(block.cumulative_difficulty);
306
307 append_tx.append_entries(
309 &db.block_infos,
310 &[BlockInfo {
311 cumulative_difficulty_low,
312 cumulative_difficulty_high,
313 cumulative_generated_coins,
314 cumulative_rct_outs: numb_rct_outs,
315 block_hash: block.block_hash,
316 weight: block.weight,
317 long_term_weight: block.long_term_weight,
318 mining_tx_index,
319 pruned_blob_idx: block_pruned_blob_idx,
320 v1_prunable_blob_idx: block_v1_prunable_idx,
321 }],
322 )?;
323
324 tracing::debug!(
325 height = block.height,
326 block_pruned_blob_idx,
327 block_v1_prunable_idx,
328 block_v2_prunable_idx,
329 cumulative_generated_coins,
330 "added block to tapes"
331 );
332 }
333 }
334
335 Ok(())
336}
337
338pub fn add_block_to_dynamic_tables<'a, I: Iterator<Item = Cow<'a, Transaction<Pruned>>>>(
347 db: &BlockchainDatabase,
348 block: &Block,
349 block_hash: &BlockHash,
350 txs: I,
351 numb_transactions: &mut u64,
352 w: &mut fjall::OwnedWriteBatch,
353 pre_rct_numb_outputs_cache: &mut HashMap<Amount, u64>,
354) -> DbResult<()> {
355 assert!(
358 u32::try_from(block.number()).is_ok(),
359 "block.height ({}) > u32::MAX",
360 block.number(),
361 );
362
363 let tx = block.miner_transaction();
365 add_tx_info_to_dynamic_tables(
366 db,
367 &tx.clone().into(),
368 *numb_transactions,
369 &tx.hash(),
370 &block.number(),
371 w,
372 pre_rct_numb_outputs_cache,
373 )?;
374 *numb_transactions += 1;
375
376 for (tx_hash, tx) in block.transactions.iter().zip(txs) {
377 #[cfg(debug_assertions)]
378 {
379 if db.pruning_seed() == PruningSeed::NotPruned {
380 let tx_full = get_tx_from_id(numb_transactions, &db.linear_tapes.reader(), db)?;
382
383 let (pruned, _) = tx_full.pruned_with_prunable();
384
385 assert_eq!(tx.as_ref(), &pruned);
386 }
387 }
388
389 add_tx_info_to_dynamic_tables(
390 db,
391 &tx,
392 *numb_transactions,
393 tx_hash,
394 &block.number(),
395 w,
396 pre_rct_numb_outputs_cache,
397 )?;
398 *numb_transactions += 1;
399 }
400
401 w.insert(&db.block_heights, block_hash, block.number().to_le_bytes());
402 w.insert(&db.chain_tip, CHAIN_TIP_KEY, block_hash);
403
404 Ok(())
405}
406
407pub fn pop_block(
416 db: &BlockchainDatabase,
417 move_to_alt_chain: Option<ChainId>,
418 tx_rw: &mut fjall::OwnedWriteBatch,
419 tapes: &mut tapes::TapesTruncateTransaction,
420) -> DbResult<(BlockHeight, BlockHash, Block, bool)> {
421 let (block_height, block_info) = tapes
423 .pop_fixed_sized_tape(&db.block_infos)?
424 .ok_or(BlockchainError::NotFound)?;
425
426 let block_height = usize::try_from(block_height).unwrap();
427
428 tx_rw.remove(&db.block_heights, block_info.block_hash);
429
430 let new_top = tapes
431 .read_entry(&db.block_infos, usize_to_u64(block_height - 1))?
432 .ok_or(BlockchainError::NotFound)?;
433 tx_rw.insert(&db.chain_tip, CHAIN_TIP_KEY, new_top.block_hash);
434
435 let block = get_block(&block_height, Some(&block_info), tapes, db)?;
437 remove_tx_from_dynamic_tables(db, &block.miner_transaction().hash(), tx_rw, tapes)?;
440
441 let mut add_to_alt_chain = move_to_alt_chain.is_some();
442 let mut txs = Vec::with_capacity(block.transactions.len());
443
444 for tx_hash in &block.transactions {
445 let (tx_id, _) = remove_tx_from_dynamic_tables(db, tx_hash, tx_rw, tapes)?;
446
447 if add_to_alt_chain {
448 match get_tx_from_id(&tx_id, tapes, db) {
450 Ok(tx) => {
451 let tx_weight = tx.weight();
452 let tx_hash = tx.hash();
453 let fee = tx_fee(&tx);
454 let (tx_pruned, prunable) = tx.pruned_with_prunable();
455
456 txs.push(VerifiedTransactionInformation {
457 tx_weight,
458 tx_pruned: tx_pruned.serialize(),
459 tx_prunable_blob: prunable,
460 tx_hash,
461 fee,
462 tx: tx_pruned,
463 });
464 }
465 Err(BlockchainError::NotFound) => {
466 add_to_alt_chain = false;
467 txs.clear();
468 }
469 Err(e) => return Err(e),
470 }
471 }
472 }
473
474 if let Some(chain_id) = move_to_alt_chain.filter(|_| add_to_alt_chain) {
475 alt_block::add_alt_block(
476 db,
477 &AltBlockInformation {
478 block: block.clone(),
479 block_blob: block.serialize(),
480 txs,
481 block_hash: block_info.block_hash,
482 pow_hash: [0; 32],
484 height: block_height,
485 weight: block_info.weight,
486 long_term_weight: block_info.long_term_weight,
487 cumulative_difficulty: combine_low_high_bits_to_u128(
488 block_info.cumulative_difficulty_low,
489 block_info.cumulative_difficulty_high,
490 ),
491 chain_id,
492 },
493 tx_rw,
494 )?;
495 }
496
497 tapes.truncate_blob_tape(&db.pruned_blobs, block_info.pruned_blob_idx)?;
499 tapes.truncate_blob_tape(&db.v1_prunable_blobs, block_info.v1_prunable_blob_idx)?;
500 let stripe = cuprate_pruning::get_block_pruning_stripe(
501 block_height,
502 usize::MAX,
503 CRYPTONOTE_PRUNING_LOG_STRIPES,
504 )
505 .unwrap();
506
507 match &db.prunable_tables {
508 PrunableTables::Full(prunable_blobs) => {
509 let prunable_blob_idx_end = tapes
510 .read_entry(&db.tx_infos, block_info.mining_tx_index)?
511 .unwrap()
512 .prunable_blob_idx;
513
514 tapes
515 .truncate_blob_tape(&prunable_blobs[stripe as usize - 1], prunable_blob_idx_end)?;
516 }
517 PrunableTables::Pruned {
518 prunable_tip,
519 kept_stripe,
520 stripe: our_stripe,
521 ..
522 } if *our_stripe == stripe => {
523 let prunable_blob_idx_end = tapes
524 .read_entry(&db.tx_infos, block_info.mining_tx_index)?
525 .unwrap()
526 .prunable_blob_idx;
527
528 tapes.truncate_blob_tape(kept_stripe, prunable_blob_idx_end)?;
529 tapes.truncate_fixed_sized_tape(prunable_tip, block_info.mining_tx_index)?;
530 }
531 PrunableTables::Pruned {
532 prunable_tip,
533 prunable_tip_blobs,
534 ..
535 } => {
536 let prunable_blob_idx_end =
537 match tapes.read_entry(prunable_tip, block_info.mining_tx_index)? {
538 Some(index) => index,
539 None => tapes
541 .blob_tape_start(prunable_tip_blobs)
542 .expect("required tape not open"),
543 };
544
545 tapes.truncate_blob_tape(prunable_tip_blobs, prunable_blob_idx_end)?;
546 tapes.truncate_fixed_sized_tape(prunable_tip, block_info.mining_tx_index)?;
547 }
548 }
549
550 tapes.truncate_fixed_sized_tape(&db.tx_infos, block_info.mining_tx_index)?;
551
552 let cumulative_rct_outs = tapes
553 .read_entry(&db.block_infos, usize_to_u64(block_height) - 1)?
554 .map_or(0, |info| info.cumulative_rct_outs);
555
556 tapes.truncate_fixed_sized_tape(&db.rct_outputs, cumulative_rct_outs)?;
557
558 Ok((block_height, block_info.block_hash, block, add_to_alt_chain))
559}
560
561pub fn get_block_complete_entry(
565 db: &BlockchainDatabase,
566 block_hash: &BlockHash,
567 pruned: bool,
568 tx_ro: &fjall::Snapshot,
569 tapes: &tapes::TapesReadTransaction,
570) -> DbResult<BlockCompleteEntry> {
571 let block_height = tx_ro
572 .get(&db.block_heights, block_hash)?
573 .ok_or(BlockchainError::NotFound)?;
574
575 get_block_complete_entry_from_height(
576 usize::from_le_bytes(block_height.as_ref().try_into().unwrap()),
577 pruned,
578 tapes,
579 db,
580 )
581}
582
583pub fn get_block_complete_entry_from_height(
586 block_height: BlockHeight,
587 pruned: bool,
588 tapes: &tapes::TapesReadTransaction,
589 db: &BlockchainDatabase,
590) -> DbResult<BlockCompleteEntry> {
591 fn read_blob(
593 tapes: &tapes::TapesReadTransaction,
594 tape: &impl BlobTape,
595 start: u64,
596 len: usize,
597 ) -> DbResult<Bytes> {
598 if len == 0 {
599 return Ok(Bytes::new());
600 }
601
602 let mut buf = vec![0; len];
603 tapes.read_bytes(tape, start, &mut buf)?;
604 Ok(Bytes::from(buf))
605 }
606
607 let block_info = tapes
608 .read_entry(&db.block_infos, usize_to_u64(block_height))?
609 .ok_or(BlockchainError::NotFound)?;
610
611 let chain_height = chain_height(db, tapes)?;
613 if !pruned && !db.pruning_seed().has_full_block(block_height, chain_height) {
614 return Err(BlockchainError::NotFound);
615 }
616
617 let block_blob_start_idx = block_info.pruned_blob_idx;
618 let mut block_blob_end_idx = None;
619
620 let mut txs = Vec::with_capacity(32);
621
622 for tx_info in tapes.iter_from(&db.tx_infos, block_info.mining_tx_index + 1)? {
623 let tx_info = tx_info?;
624
625 if tx_info.height != block_height {
626 break;
627 }
628
629 block_blob_end_idx.get_or_insert(tx_info.pruned_blob_idx);
631
632 txs.push(tx_info);
633 }
634
635 let txs = if txs.is_empty() {
636 TransactionBlobs::None
637 } else {
638 let first_blob_idx = txs.first().unwrap().pruned_blob_idx;
640 let mut bytes = read_blob(
641 tapes,
642 &db.pruned_blobs,
643 first_blob_idx,
644 u64_to_usize(txs.last().unwrap().pruned_blob_idx - first_blob_idx)
645 + txs.last().unwrap().pruned_size
646 + 32,
647 )?;
648
649 if pruned {
650 TransactionBlobs::Pruned(
651 txs.iter()
652 .map(|tx_info| PrunedTxBlobEntry {
653 blob: bytes.split_to(tx_info.pruned_size),
654 prunable_hash: bytes.split_to(32).try_into().unwrap(),
655 })
656 .collect(),
657 )
658 } else {
659 let mut v2_prunable_blob_idx = None;
660 let (mut v1_len, mut v2_len) = (0, 0);
661 for t in &txs {
662 if t.is_v1_tx() {
663 v1_len += t.prunable_size;
664 } else {
665 v2_prunable_blob_idx.get_or_insert(t.prunable_blob_idx);
666 v2_len += t.prunable_size;
667 }
668 }
669 let v2_prunable_blob_idx = v2_prunable_blob_idx.unwrap_or(0);
670
671 let mut v1 = read_blob(
672 tapes,
673 &db.v1_prunable_blobs,
674 block_info.v1_prunable_blob_idx,
675 v1_len,
676 )?;
677 let pruning_stripe = cuprate_pruning::get_block_pruning_stripe(
678 block_height,
679 usize::MAX,
680 CRYPTONOTE_PRUNING_LOG_STRIPES,
681 )
682 .unwrap();
683
684 let mut v2 = match &db.prunable_tables {
685 PrunableTables::Full(prunable_blobs) => read_blob(
686 tapes,
687 &prunable_blobs[usize::try_from(pruning_stripe)
688 .expect("stripe will not exceed usize::MAX")
689 - 1],
690 v2_prunable_blob_idx,
691 v2_len,
692 )?,
693 PrunableTables::Pruned {
694 kept_stripe,
695 stripe,
696 ..
697 } if *stripe == pruning_stripe => {
698 read_blob(tapes, kept_stripe, v2_prunable_blob_idx, v2_len)?
699 }
700 PrunableTables::Pruned {
701 prunable_tip,
702 prunable_tip_blobs,
703 ..
704 } => {
705 let prunable_blob_idx =
706 tapes.read_entry(prunable_tip, block_info.mining_tx_index)?;
707
708 let Some(prunable_blob_idx) = prunable_blob_idx else {
709 return Err(BlockchainError::NotFound);
710 };
711
712 read_blob(tapes, prunable_tip_blobs, prunable_blob_idx, v2_len)?
713 }
714 };
715
716 TransactionBlobs::Normal(
717 txs.iter()
718 .map(|tx_info| {
719 let pruned = bytes.split_to(tx_info.pruned_size);
720 bytes.advance(32); let prunable = if tx_info.is_v1_tx() {
722 v1.split_to(tx_info.prunable_size)
723 } else {
724 v2.split_to(tx_info.prunable_size)
725 };
726 Bytes::from([pruned.as_ref(), prunable.as_ref()].concat())
727 })
728 .collect(),
729 )
730 }
731 };
732
733 let block_blob = {
734 let block_blob_end_idx = block_blob_end_idx.map_or_else(
735 || {
736 let next_block_info =
739 tapes.read_entry(&db.block_infos, usize_to_u64(block_height + 1))?;
740
741 if let Some(info) = next_block_info {
742 return Ok::<_, BlockchainError>(info.pruned_blob_idx);
743 }
744
745 Ok(tapes
746 .blob_tape_len(&db.pruned_blobs)
747 .expect("Required tape not found"))
748 },
749 Ok,
750 )?;
751
752 read_blob(
753 tapes,
754 &db.pruned_blobs,
755 block_blob_start_idx,
756 u64_to_usize(block_blob_end_idx - block_blob_start_idx),
757 )?
758 };
759
760 Ok(BlockCompleteEntry {
761 block: block_blob,
762 txs,
763 pruned,
764 block_weight: if pruned {
765 usize_to_u64(block_info.weight)
766 } else {
767 0
768 },
769 })
770}
771
772#[inline]
781pub fn get_block_extended_header(
782 db: &BlockchainDatabase,
783 block_hash: &BlockHash,
784 tx_ro: &fjall::Snapshot,
785 tapes: &tapes::TapesReadTransaction,
786) -> DbResult<ExtendedBlockHeader> {
787 let block_height = tx_ro
788 .get(&db.block_heights, block_hash)?
789 .ok_or(BlockchainError::NotFound)?;
790
791 get_block_extended_header_from_height(
792 usize::from_le_bytes(block_height.as_ref().try_into().unwrap()),
793 tapes,
794 db,
795 )
796}
797
798#[expect(
800 clippy::missing_panics_doc,
801 reason = "The panic is only possible with a corrupt DB"
802)]
803#[inline]
804pub fn get_block_extended_header_from_height(
805 block_height: BlockHeight,
806 tapes: &tapes::TapesReadTransaction,
807 db: &BlockchainDatabase,
808) -> DbResult<ExtendedBlockHeader> {
809 let block_info = tapes
810 .read_entry(&db.block_infos, usize_to_u64(block_height))?
811 .ok_or(BlockchainError::NotFound)?;
812 let miner_tx_info = tapes
813 .read_entry(&db.tx_infos, block_info.mining_tx_index)?
814 .ok_or(BlockchainError::NotFound)?;
815
816 let mut block_header_blob =
817 vec![0; u64_to_usize(miner_tx_info.pruned_blob_idx - block_info.pruned_blob_idx)];
818
819 tapes.read_bytes(
820 &db.pruned_blobs,
821 block_info.pruned_blob_idx,
822 &mut block_header_blob,
823 )?;
824
825 let block_header = BlockHeader::read(&mut block_header_blob.as_slice()).unwrap();
826
827 let cumulative_difficulty = combine_low_high_bits_to_u128(
828 block_info.cumulative_difficulty_low,
829 block_info.cumulative_difficulty_high,
830 );
831
832 Ok(ExtendedBlockHeader {
833 cumulative_difficulty,
834 version: HardFork::from_version(block_header.hardfork_version)
835 .expect("Stored block must have a valid hard-fork"),
836 vote: block_header.hardfork_signal,
837 timestamp: block_header.timestamp,
838 block_weight: block_info.weight,
839 long_term_weight: block_info.long_term_weight,
840 })
841}
842
843#[inline]
845pub fn get_block_extended_header_top(
846 db: &BlockchainDatabase,
847 tapes: &tapes::TapesReadTransaction,
848) -> DbResult<(ExtendedBlockHeader, BlockHeight)> {
849 let height = u64_to_usize(
850 tapes
851 .fixed_sized_tape_len(&db.block_infos)
852 .expect("Required tape not found")
853 .saturating_sub(1),
854 );
855 let header = get_block_extended_header_from_height(height, tapes, db)?;
856 Ok((header, height))
857}
858
859#[inline]
864pub fn get_block(
865 block_height: &BlockHeight,
866 blocks_info: Option<&BlockInfo>,
867 tapes: &impl TapesRead,
868 db: &BlockchainDatabase,
869) -> DbResult<Block> {
870 let block_info = match blocks_info {
871 Some(blocks_info) => *blocks_info,
872 None => tapes
873 .read_entry(&db.block_infos, usize_to_u64(*block_height))?
874 .ok_or(BlockchainError::NotFound)?,
875 };
876
877 let pruned_end_blob_idx =
878 match tapes.read_entry(&db.tx_infos, block_info.mining_tx_index + 1)? {
879 Some(tx_info) if tx_info.height == *block_height => tx_info.pruned_blob_idx,
881 Some(_) => {
883 tapes
884 .read_entry(&db.block_infos, usize_to_u64(*block_height + 1))?
885 .ok_or(BlockchainError::NotFound)?
886 .pruned_blob_idx
887 }
888 None => tapes
890 .blob_tape_len(&db.pruned_blobs)
891 .expect("Required tape not found"),
892 };
893
894 let mut blob =
895 vec![0; usize::try_from(pruned_end_blob_idx - block_info.pruned_blob_idx).unwrap()];
896
897 tapes.read_bytes(&db.pruned_blobs, block_info.pruned_blob_idx, &mut blob)?;
898
899 Ok(Block::read(&mut blob.as_slice())?)
900}
901
902#[inline]
904pub fn get_block_by_hash(
905 db: &BlockchainDatabase,
906 block_hash: &BlockHash,
907 tx_ro: &fjall::Snapshot,
908 tapes: &tapes::TapesReadTransaction,
909) -> DbResult<Block> {
910 let block_height = tx_ro
911 .get(&db.block_heights, block_hash)?
912 .ok_or(BlockchainError::NotFound)?;
913
914 get_block(
915 &usize::from_le_bytes(block_height.as_ref().try_into().unwrap()),
916 None,
917 tapes,
918 db,
919 )
920}
921
922#[inline]
924pub fn get_block_height(
925 db: &BlockchainDatabase,
926 block_hash: &BlockHash,
927 tx_ro: &fjall::Snapshot,
928) -> DbResult<BlockHeight> {
929 let block_height = tx_ro
930 .get(&db.block_heights, block_hash)?
931 .ok_or(BlockchainError::NotFound)?;
932
933 Ok(usize::from_le_bytes(
934 block_height.as_ref().try_into().unwrap(),
935 ))
936}
937
938#[inline]
941pub fn block_exists(
942 db: &BlockchainDatabase,
943 block_hash: &BlockHash,
944 tx_ro: &fjall::Snapshot,
945) -> DbResult<bool> {
946 Ok(tx_ro.contains_key(&db.block_heights, block_hash)?)
947}
948
949pub(crate) fn block_height(
952 db: &BlockchainDatabase,
953 tx_ro: &fjall::Snapshot,
954 hash: &BlockHash,
955) -> DbResult<Option<usize>> {
956 let Some(block_height) = tx_ro.get(&db.block_heights, hash)? else {
957 return Ok(None);
958 };
959
960 Ok(Some(usize::from_le_bytes(
961 block_height.as_ref().try_into().unwrap(),
962 )))
963}