Skip to main content

cuprate_consensus_context/
alt_chains.rs

1use std::{collections::HashMap, sync::Arc};
2
3use tower::ServiceExt;
4
5use cuprate_types::{
6    blockchain::{BlockchainReadRequest, BlockchainResponse},
7    Chain, ChainId,
8};
9
10use crate::{
11    __private::Database, difficulty::DifficultyCache, rx_vms::RandomXVm, weight::BlockWeightsCache,
12    ContextCacheError,
13};
14
15pub(crate) mod sealed {
16    /// A token that should be hard to create from outside this crate.
17    ///
18    /// It is currently possible to safely create this from outside this crate, **DO NOT** rely on this
19    /// as it will be broken once we find a way to completely seal this.
20    #[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq)]
21    pub struct AltChainRequestToken;
22}
23
24/// The context cache of an alternative chain.
25#[derive(Debug, Clone)]
26pub struct AltChainContextCache {
27    /// The alt chain weight cache, [`None`] if it has not been built yet.
28    pub weight_cache: Option<BlockWeightsCache>,
29    /// The alt chain difficulty cache, [`None`] if it has not been built yet.
30    pub difficulty_cache: Option<DifficultyCache>,
31
32    /// A cached RX VM.
33    pub cached_rx_vm: Option<(usize, Arc<RandomXVm>)>,
34
35    /// The chain height of the alt chain.
36    pub chain_height: usize,
37    /// The top hash of the alt chain.
38    pub top_hash: [u8; 32],
39    /// The [`ChainId`] of the alt chain.
40    pub chain_id: Option<ChainId>,
41    /// The parent [`Chain`] of this alt chain.
42    pub parent_chain: Chain,
43}
44
45impl AltChainContextCache {
46    /// Add a new block to the cache.
47    pub fn add_new_block(
48        &mut self,
49        height: usize,
50        block_hash: [u8; 32],
51        block_weight: usize,
52        long_term_block_weight: usize,
53        timestamp: u64,
54        cumulative_difficulty: u128,
55    ) {
56        if let Some(difficulty_cache) = &mut self.difficulty_cache {
57            difficulty_cache.new_block(height, timestamp, cumulative_difficulty);
58        }
59
60        if let Some(weight_cache) = &mut self.weight_cache {
61            weight_cache.new_block(height, block_weight, long_term_block_weight);
62        }
63
64        self.chain_height += 1;
65        self.top_hash = block_hash;
66    }
67}
68
69/// A map of top IDs to alt chains.
70pub(crate) struct AltChainMap {
71    alt_cache_map: HashMap<[u8; 32], Box<AltChainContextCache>>,
72}
73
74impl AltChainMap {
75    pub(crate) fn new() -> Self {
76        Self {
77            alt_cache_map: HashMap::new(),
78        }
79    }
80
81    pub(crate) fn clear(&mut self) {
82        self.alt_cache_map.clear();
83    }
84
85    /// Add an alt chain cache to the map.
86    pub(crate) fn add_alt_cache(&mut self, alt_cache: Box<AltChainContextCache>) {
87        self.alt_cache_map.insert(alt_cache.top_hash, alt_cache);
88    }
89
90    /// Attempts to take an [`AltChainContextCache`] from the map, building it from the database if no
91    /// cache is present.
92    ///
93    /// Returns [`None`] if `prev_id` is not a block we know about.
94    pub(crate) async fn get_alt_chain_context<D: Database>(
95        &mut self,
96        prev_id: [u8; 32],
97        database: D,
98    ) -> Result<Option<Box<AltChainContextCache>>, ContextCacheError> {
99        if let Some(cache) = self.alt_cache_map.remove(&prev_id) {
100            return Ok(Some(cache));
101        }
102
103        // find the block with hash == prev_id.
104        let BlockchainResponse::FindBlock(res) = database
105            .oneshot(BlockchainReadRequest::FindBlock(prev_id))
106            .await?
107        else {
108            panic!("Database returned wrong response");
109        };
110
111        let Some((parent_chain, top_height)) = res else {
112            // Couldn't find prev_id
113            return Ok(None);
114        };
115
116        Ok(Some(Box::new(AltChainContextCache {
117            weight_cache: None,
118            difficulty_cache: None,
119            cached_rx_vm: None,
120            chain_height: top_height + 1,
121            top_hash: prev_id,
122            chain_id: None,
123            parent_chain,
124        })))
125    }
126}
127
128/// Builds a [`DifficultyCache`] for an alt chain.
129pub(crate) async fn get_alt_chain_difficulty_cache<D: Database + Clone>(
130    prev_id: [u8; 32],
131    main_chain_difficulty_cache: &DifficultyCache,
132    mut database: D,
133) -> Result<Option<DifficultyCache>, ContextCacheError> {
134    // find the block with hash == prev_id.
135    let BlockchainResponse::FindBlock(res) = database
136        .ready()
137        .await?
138        .call(BlockchainReadRequest::FindBlock(prev_id))
139        .await?
140    else {
141        panic!("Database returned wrong response");
142    };
143
144    let Some((chain, top_height)) = res else {
145        // Can't find prev_id
146        return Ok(None);
147    };
148
149    Ok(Some(match chain {
150        Chain::Main => {
151            // prev_id is in main chain, we can use the fast path and clone the main chain cache.
152            let mut difficulty_cache = main_chain_difficulty_cache.clone();
153            difficulty_cache
154                .pop_blocks_main_chain(
155                    difficulty_cache.last_accounted_height - top_height,
156                    database,
157                )
158                .await?;
159
160            difficulty_cache
161        }
162        Chain::Alt(_) => {
163            // prev_id is in an alt chain, completely rebuild the cache.
164            DifficultyCache::init_from_chain_height(
165                top_height + 1,
166                main_chain_difficulty_cache.config,
167                database,
168                chain,
169            )
170            .await?
171        }
172    }))
173}
174
175/// Builds a [`BlockWeightsCache`] for an alt chain.
176pub(crate) async fn get_alt_chain_weight_cache<D: Database + Clone>(
177    prev_id: [u8; 32],
178    main_chain_weight_cache: &BlockWeightsCache,
179    mut database: D,
180) -> Result<Option<BlockWeightsCache>, ContextCacheError> {
181    // find the block with hash == prev_id.
182    let BlockchainResponse::FindBlock(res) = database
183        .ready()
184        .await?
185        .call(BlockchainReadRequest::FindBlock(prev_id))
186        .await?
187    else {
188        panic!("Database returned wrong response");
189    };
190
191    let Some((chain, top_height)) = res else {
192        // Can't find prev_id
193        return Ok(None);
194    };
195
196    Ok(Some(match chain {
197        Chain::Main => {
198            // prev_id is in main chain, we can use the fast path and clone the main chain cache.
199            let mut weight_cache = main_chain_weight_cache.clone();
200            weight_cache
201                .pop_blocks_main_chain(weight_cache.tip_height - top_height, database)
202                .await?;
203
204            weight_cache
205        }
206        Chain::Alt(_) => {
207            // prev_id is in an alt chain, completely rebuild the cache.
208            BlockWeightsCache::init_from_chain_height(
209                top_height + 1,
210                main_chain_weight_cache.config,
211                database,
212                chain,
213            )
214            .await?
215        }
216    }))
217}