1use std::cmp::{max, min};
23use cuprate_database::{DatabaseRo, DatabaseRw, DbResult, RuntimeError};
4use cuprate_types::{Chain, ChainId};
56use crate::{
7 ops::macros::{doc_add_alt_block_inner_invariant, doc_error},
8 tables::{AltChainInfos, TablesMut},
9 types::{AltBlockHeight, AltChainInfo, BlockHash, BlockHeight},
10};
1112/// 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<()> {
25let parent_chain = match tables.alt_block_heights().get(prev_hash) {
26Ok(alt_parent_height) => Chain::Alt(alt_parent_height.chain_id.into()),
27Err(RuntimeError::KeyNotFound) => Chain::Main,
28Err(e) => return Err(e),
29 };
3031// try update the info if one exists for this chain.
32let update = tables
33 .alt_chain_infos_mut()
34 .update(&alt_block_height.chain_id, |mut info| {
35if info.chain_height < alt_block_height.height + 1 {
36// If the chain height is increasing we only need to update the chain height.
37info.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.
41info.common_ancestor_height = alt_block_height.height.checked_sub(1).unwrap();
42 info.parent_chain = parent_chain.into();
43 }
4445Some(info)
46 });
4748match update {
49Ok(()) => return Ok(()),
50Err(RuntimeError::KeyNotFound) => (),
51Err(e) => return Err(e),
52 }
5354// If one doesn't already exist add it.
5556tables.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}
6566/// 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>)>> {
77let mut ranges = Vec::with_capacity(5);
7879let mut i = range.end;
80let mut current_chain_id = alt_chain.into();
81while i > range.start {
82let chain_info = alt_chain_infos.get(¤t_chain_id)?;
8384let start_height = max(range.start, chain_info.common_ancestor_height + 1);
85let end_height = min(i, chain_info.chain_height);
8687 ranges.push((
88 Chain::Alt(current_chain_id.into()),
89 start_height..end_height,
90 ));
91 i = chain_info.common_ancestor_height + 1;
9293match chain_info.parent_chain.into() {
94 Chain::Main => {
95 ranges.push((Chain::Main, range.start..i));
96break;
97 }
98 Chain::Alt(alt_chain_id) => {
99let alt_chain_id = alt_chain_id.into();
100101// This shouldn't be possible to hit, however in a test with custom (invalid) block data
102 // this caused an infinite loop.
103if alt_chain_id == current_chain_id {
104return Err(RuntimeError::Io(std::io::Error::other(
105"Loop detected in ChainIDs, invalid alt chain.",
106 )));
107 }
108109 current_chain_id = alt_chain_id;
110continue;
111 }
112 }
113 }
114115Ok(ranges)
116}