Skip to main content

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}