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            Some(info)
46        });
47
48    match update {
49        Ok(()) => return Ok(()),
50        Err(RuntimeError::KeyNotFound) => (),
51        Err(e) => return Err(e),
52    }
53
54    // If one doesn't already exist add it.
55
56    tables.alt_chain_infos_mut().put(
57        &alt_block_height.chain_id,
58        &AltChainInfo {
59            parent_chain: parent_chain.into(),
60            common_ancestor_height: alt_block_height.height.checked_sub(1).unwrap(),
61            chain_height: alt_block_height.height + 1,
62        },
63    )
64}
65
66/// Get the height history of an alt-chain in reverse chronological order.
67///
68/// Height history is a list of height ranges with the corresponding [`Chain`] they are stored under.
69/// For example if your range goes from height `0` the last entry in the list will be [`Chain::Main`]
70/// upto the height where the first split occurs.
71#[doc = doc_error!()]
72pub fn get_alt_chain_history_ranges(
73    range: std::ops::Range<BlockHeight>,
74    alt_chain: ChainId,
75    alt_chain_infos: &impl DatabaseRo<AltChainInfos>,
76) -> DbResult<Vec<(Chain, std::ops::Range<BlockHeight>)>> {
77    let mut ranges = Vec::with_capacity(5);
78
79    let mut i = range.end;
80    let mut current_chain_id = alt_chain.into();
81    while i > range.start {
82        let chain_info = alt_chain_infos.get(&current_chain_id)?;
83
84        let start_height = max(range.start, chain_info.common_ancestor_height + 1);
85        let end_height = min(i, chain_info.chain_height);
86
87        ranges.push((
88            Chain::Alt(current_chain_id.into()),
89            start_height..end_height,
90        ));
91        i = chain_info.common_ancestor_height + 1;
92
93        match chain_info.parent_chain.into() {
94            Chain::Main => {
95                ranges.push((Chain::Main, range.start..i));
96                break;
97            }
98            Chain::Alt(alt_chain_id) => {
99                let alt_chain_id = alt_chain_id.into();
100
101                // This shouldn't be possible to hit, however in a test with custom (invalid) block data
102                // this caused an infinite loop.
103                if alt_chain_id == current_chain_id {
104                    return Err(RuntimeError::Io(std::io::Error::other(
105                        "Loop detected in ChainIDs, invalid alt chain.",
106                    )));
107                }
108
109                current_chain_id = alt_chain_id;
110                continue;
111            }
112        }
113    }
114
115    Ok(ranges)
116}