Skip to main content

cuprate_blockchain/ops/alt_block/
chain.rs

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