cuprate_blockchain/ops/alt_block/
chain.rs

1use std::cmp::{max, min};
2
3use cuprate_database::{DatabaseRo, DatabaseRw, DbResult, RuntimeError};
4use cuprate_types::{Chain, ChainId};
5
6use crate::{
7    ops::macros::{doc_add_alt_block_inner_invariant, doc_error},
8    tables::{AltChainInfos, TablesMut},
9    types::{AltBlockHeight, AltChainInfo, BlockHash, BlockHeight},
10};
11
12/// Updates the [`AltChainInfo`] with information on a new alt-block.
13///
14#[doc = doc_add_alt_block_inner_invariant!()]
15#[doc = doc_error!()]
16///
17/// # Panics
18///
19/// This will panic if [`AltBlockHeight::height`] == `0`.
20pub fn update_alt_chain_info(
21    alt_block_height: &AltBlockHeight,
22    prev_hash: &BlockHash,
23    tables: &mut impl TablesMut,
24) -> DbResult<()> {
25    let parent_chain = match tables.alt_block_heights().get(prev_hash) {
26        Ok(alt_parent_height) => Chain::Alt(alt_parent_height.chain_id.into()),
27        Err(RuntimeError::KeyNotFound) => Chain::Main,
28        Err(e) => return Err(e),
29    };
30
31    // try update the info if one exists for this chain.
32    let update = tables
33        .alt_chain_infos_mut()
34        .update(&alt_block_height.chain_id, |mut info| {
35            if info.chain_height < alt_block_height.height + 1 {
36                // If the chain height is increasing we only need to update the chain height.
37                info.chain_height = alt_block_height.height + 1;
38            } else {
39                // If the chain height is not increasing we are popping blocks and need to update the
40                // split point.
41                info.common_ancestor_height = alt_block_height.height.checked_sub(1).unwrap();
42                info.parent_chain = parent_chain.into();
43            }
44
45            info.chain_height = alt_block_height.height + 1;
46            Some(info)
47        });
48
49    match update {
50        Ok(()) => return Ok(()),
51        Err(RuntimeError::KeyNotFound) => (),
52        Err(e) => return Err(e),
53    }
54
55    // If one doesn't already exist add it.
56
57    tables.alt_chain_infos_mut().put(
58        &alt_block_height.chain_id,
59        &AltChainInfo {
60            parent_chain: parent_chain.into(),
61            common_ancestor_height: alt_block_height.height.checked_sub(1).unwrap(),
62            chain_height: alt_block_height.height + 1,
63        },
64    )
65}
66
67/// Get the height history of an alt-chain in reverse chronological order.
68///
69/// Height history is a list of height ranges with the corresponding [`Chain`] they are stored under.
70/// For example if your range goes from height `0` the last entry in the list will be [`Chain::Main`]
71/// upto the height where the first split occurs.
72#[doc = doc_error!()]
73pub fn get_alt_chain_history_ranges(
74    range: std::ops::Range<BlockHeight>,
75    alt_chain: ChainId,
76    alt_chain_infos: &impl DatabaseRo<AltChainInfos>,
77) -> DbResult<Vec<(Chain, std::ops::Range<BlockHeight>)>> {
78    let mut ranges = Vec::with_capacity(5);
79
80    let mut i = range.end;
81    let mut current_chain_id = alt_chain.into();
82    while i > range.start {
83        let chain_info = alt_chain_infos.get(&current_chain_id)?;
84
85        let start_height = max(range.start, chain_info.common_ancestor_height + 1);
86        let end_height = min(i, chain_info.chain_height);
87
88        ranges.push((
89            Chain::Alt(current_chain_id.into()),
90            start_height..end_height,
91        ));
92        i = chain_info.common_ancestor_height + 1;
93
94        match chain_info.parent_chain.into() {
95            Chain::Main => {
96                ranges.push((Chain::Main, range.start..i));
97                break;
98            }
99            Chain::Alt(alt_chain_id) => {
100                let alt_chain_id = alt_chain_id.into();
101
102                // This shouldn't be possible to hit, however in a test with custom (invalid) block data
103                // this caused an infinite loop.
104                if alt_chain_id == current_chain_id {
105                    return Err(RuntimeError::Io(std::io::Error::other(
106                        "Loop detected in ChainIDs, invalid alt chain.",
107                    )));
108                }
109
110                current_chain_id = alt_chain_id;
111                continue;
112            }
113        }
114    }
115
116    Ok(ranges)
117}