1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
use std::cmp::{max, min};

use cuprate_database::{DatabaseRo, DatabaseRw, RuntimeError};
use cuprate_types::{Chain, ChainId};

use crate::{
    ops::macros::{doc_add_alt_block_inner_invariant, doc_error},
    tables::{AltChainInfos, TablesMut},
    types::{AltBlockHeight, AltChainInfo, BlockHash, BlockHeight},
};

/// Updates the [`AltChainInfo`] with information on a new alt-block.
///
#[doc = doc_add_alt_block_inner_invariant!()]
#[doc = doc_error!()]
///
/// # Panics
///
/// This will panic if [`AltBlockHeight::height`] == `0`.
pub fn update_alt_chain_info(
    alt_block_height: &AltBlockHeight,
    prev_hash: &BlockHash,
    tables: &mut impl TablesMut,
) -> Result<(), RuntimeError> {
    let parent_chain = match tables.alt_block_heights().get(prev_hash) {
        Ok(alt_parent_height) => Chain::Alt(alt_parent_height.chain_id.into()),
        Err(RuntimeError::KeyNotFound) => Chain::Main,
        Err(e) => return Err(e),
    };

    // try update the info if one exists for this chain.
    let update = tables
        .alt_chain_infos_mut()
        .update(&alt_block_height.chain_id, |mut info| {
            if info.chain_height < alt_block_height.height + 1 {
                // If the chain height is increasing we only need to update the chain height.
                info.chain_height = alt_block_height.height + 1;
            } else {
                // If the chain height is not increasing we are popping blocks and need to update the
                // split point.
                info.common_ancestor_height = alt_block_height.height.checked_sub(1).unwrap();
                info.parent_chain = parent_chain.into();
            }

            info.chain_height = alt_block_height.height + 1;
            Some(info)
        });

    match update {
        Ok(()) => return Ok(()),
        Err(RuntimeError::KeyNotFound) => (),
        Err(e) => return Err(e),
    }

    // If one doesn't already exist add it.

    tables.alt_chain_infos_mut().put(
        &alt_block_height.chain_id,
        &AltChainInfo {
            parent_chain: parent_chain.into(),
            common_ancestor_height: alt_block_height.height.checked_sub(1).unwrap(),
            chain_height: alt_block_height.height + 1,
        },
    )
}

/// Get the height history of an alt-chain in reverse chronological order.
///
/// Height history is a list of height ranges with the corresponding [`Chain`] they are stored under.
/// For example if your range goes from height `0` the last entry in the list will be [`Chain::Main`]
/// upto the height where the first split occurs.
#[doc = doc_error!()]
pub fn get_alt_chain_history_ranges(
    range: std::ops::Range<BlockHeight>,
    alt_chain: ChainId,
    alt_chain_infos: &impl DatabaseRo<AltChainInfos>,
) -> Result<Vec<(Chain, std::ops::Range<BlockHeight>)>, RuntimeError> {
    let mut ranges = Vec::with_capacity(5);

    let mut i = range.end;
    let mut current_chain_id = alt_chain.into();
    while i > range.start {
        let chain_info = alt_chain_infos.get(&current_chain_id)?;

        let start_height = max(range.start, chain_info.common_ancestor_height + 1);
        let end_height = min(i, chain_info.chain_height);

        ranges.push((
            Chain::Alt(current_chain_id.into()),
            start_height..end_height,
        ));
        i = chain_info.common_ancestor_height + 1;

        match chain_info.parent_chain.into() {
            Chain::Main => {
                ranges.push((Chain::Main, range.start..i));
                break;
            }
            Chain::Alt(alt_chain_id) => {
                let alt_chain_id = alt_chain_id.into();

                // This shouldn't be possible to hit, however in a test with custom (invalid) block data
                // this caused an infinite loop.
                if alt_chain_id == current_chain_id {
                    return Err(RuntimeError::Io(std::io::Error::other(
                        "Loop detected in ChainIDs, invalid alt chain.",
                    )));
                }

                current_chain_id = alt_chain_id;
                continue;
            }
        }
    }

    Ok(ranges)
}