cuprate_blockchain/ops/blockchain.rs
1//! Blockchain functions - chain height, generated coins, etc.
2
3use fjall::Readable;
4use tapes::TapesRead;
5
6use cuprate_helper::cast::u64_to_usize;
7
8use crate::{
9 error::{BlockchainError, DbResult},
10 types::{BlockHash, BlockHeight},
11 BlockchainDatabase,
12};
13
14//---------------------------------------------------------------------------------------------------- Free Functions
15/// Retrieve the height of the chain.
16///
17/// This returns the chain-tip, not the [`top_block_height`].
18///
19/// For example:
20/// - The blockchain has 0 blocks => this returns `0`
21/// - The blockchain has 1 block (height 0) => this returns `1`
22/// - The blockchain has 2 blocks (height 1) => this returns `2`
23///
24/// So the height of a new block would be `chain_height()`.
25#[inline]
26pub fn chain_height(db: &BlockchainDatabase, tapes: &impl TapesRead) -> DbResult<BlockHeight> {
27 Ok(u64_to_usize(
28 tapes
29 .fixed_sized_tape_len(&db.block_infos)
30 .expect("Required tape must exist"),
31 ))
32}
33
34/// Retrieve the height of the top block.
35///
36/// This returns the height of the top block, not the [`chain_height`].
37///
38/// For example:
39/// - The blockchain has 0 blocks => this returns `Err(BlockchainError::NotFound)`
40/// - The blockchain has 1 block (height 0) => this returns `Ok(0)`
41/// - The blockchain has 2 blocks (height 1) => this returns `Ok(1)`
42///
43/// Note that in cases where no blocks have been written to the
44/// database yet, an error is returned: `Err(BlockchainError::NotFound)`.
45///
46#[inline]
47pub fn top_block_height(
48 db: &BlockchainDatabase,
49 tapes: &tapes::TapesReadTransaction,
50) -> DbResult<BlockHeight> {
51 match chain_height(db, tapes)? {
52 0 => Err(BlockchainError::NotFound),
53 height => Ok(height - 1),
54 }
55}
56
57/// Find the split point between our chain and a list of [`BlockHash`]s from another chain.
58///
59/// This function accepts chains in chronological and reverse chronological order, however
60/// if the wrong order is specified the return value is meaningless.
61///
62/// For chronologically ordered chains this will return the index of the first unknown, for reverse
63/// chronologically ordered chains this will return the index of the first known.
64///
65/// If all blocks are known for chronologically ordered chains or unknown for reverse chronologically
66/// ordered chains then the length of the `block_ids` will be returned.
67#[inline]
68pub fn find_split_point(
69 db: &BlockchainDatabase,
70 block_ids: &[BlockHash],
71 chronological_order: bool,
72 include_alt_blocks: bool,
73 tx_ro: &fjall::Snapshot,
74) -> DbResult<usize> {
75 let mut err: Option<BlockchainError> = None;
76
77 let block_exists = |block_id| {
78 Ok(tx_ro.contains_key(&db.block_heights, block_id)?
79 || (include_alt_blocks
80 && tx_ro.contains_key(&**db.alt_block_heights.load(), block_id)?))
81 };
82
83 // Do a binary search to find the first unknown/known block in the batch.
84 let idx = block_ids.partition_point(|block_id| {
85 match block_exists(*block_id) {
86 Ok(exists) => exists == chronological_order,
87 Err(e) => {
88 err.get_or_insert(e);
89 // if this happens the search is scrapped, just return `false` back.
90 false
91 }
92 }
93 });
94
95 if let Some(e) = err {
96 return Err(e);
97 }
98
99 Ok(idx)
100}