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