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(
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}