Skip to main content

cuprate_blockchain/ops/alt_block/
block.rs

1use fjall::Readable;
2use monero_oxide::block::{Block, BlockHeader};
3
4use cuprate_helper::{
5    cast::usize_to_u64,
6    map::{combine_low_high_bits_to_u128, split_u128_into_low_high_bits},
7};
8use cuprate_types::{AltBlockInformation, Chain, ChainId, ExtendedBlockHeader, HardFork};
9
10use crate::{
11    database::reset_fjall_keyspace,
12    error::{BlockchainError, DbResult},
13    ops::alt_block::{add_alt_transaction_blob, get_alt_transaction, update_alt_chain_info},
14    types::{
15        AltBlockHeight, AltChainInfo, BlockHash, BlockHeight, CompactAltBlockInfo, RawChainId,
16    },
17    BlockchainDatabase,
18};
19
20/// Flush all alt-block data from all the alt-block tables.
21///
22/// This function completely empties the alt block tables.
23///
24/// **THIS IS NOT ATOMIC**
25pub fn flush_alt_blocks(db: &BlockchainDatabase) -> DbResult<()> {
26    reset_fjall_keyspace(&db.fjall, &db.alt_chain_infos)?;
27    reset_fjall_keyspace(&db.fjall, &db.alt_block_heights)?;
28    reset_fjall_keyspace(&db.fjall, &db.alt_block_infos)?;
29    reset_fjall_keyspace(&db.fjall, &db.alt_block_blobs)?;
30    reset_fjall_keyspace(&db.fjall, &db.alt_transaction_blobs)?;
31    reset_fjall_keyspace(&db.fjall, &db.alt_transaction_infos)?;
32
33    Ok(())
34}
35
36/// Add a [`AltBlockInformation`] to the database.
37///
38/// This extracts all the data from the input block and
39/// maps/adds them to the appropriate database tables.
40///
41/// # Panics
42/// This function may panic if the given block data is invalid.
43///
44/// **THIS IS NOT ATOMIC**
45pub fn add_alt_block(
46    db: &BlockchainDatabase,
47    alt_block: &AltBlockInformation,
48    tx_rw: &mut fjall::OwnedWriteBatch,
49) -> DbResult<()> {
50    let alt_block_height = AltBlockHeight {
51        chain_id: alt_block.chain_id.into(),
52        height: alt_block.height,
53    };
54
55    tx_rw.insert(
56        &db.alt_block_heights.load(),
57        alt_block.block_hash,
58        bytemuck::bytes_of(&alt_block_height),
59    );
60    update_alt_chain_info(db, &alt_block_height, &alt_block.block.header.previous)?;
61
62    let (cumulative_difficulty_low, cumulative_difficulty_high) =
63        split_u128_into_low_high_bits(alt_block.cumulative_difficulty);
64
65    let alt_block_info = CompactAltBlockInfo {
66        block_hash: alt_block.block_hash,
67        pow_hash: alt_block.pow_hash,
68        height: alt_block.height,
69        weight: alt_block.weight,
70        long_term_weight: alt_block.long_term_weight,
71        cumulative_difficulty_low,
72        cumulative_difficulty_high,
73    };
74
75    tx_rw.insert(
76        &db.alt_block_infos.load(),
77        bytemuck::bytes_of(&alt_block_height),
78        bytemuck::bytes_of(&alt_block_info),
79    );
80    tx_rw.insert(
81        &db.alt_block_blobs.load(),
82        bytemuck::bytes_of(&alt_block_height),
83        &alt_block.block_blob,
84    );
85
86    assert_eq!(alt_block.txs.len(), alt_block.block.transactions.len());
87    for tx in &alt_block.txs {
88        add_alt_transaction_blob(db, tx, tx_rw)?;
89    }
90
91    Ok(())
92}
93
94/// Retrieves an [`AltBlockInformation`] from the database.
95///
96/// This function will look at only the blocks with the given [`AltBlockHeight::chain_id`], no others
97/// even if they are technically part of this chain.
98pub fn get_alt_block_information(
99    db: &BlockchainDatabase,
100    alt_block_height: &AltBlockHeight,
101    tx_ro: &fjall::Snapshot,
102) -> DbResult<AltBlockInformation> {
103    let block_info = tx_ro
104        .get(
105            &**db.alt_block_infos.load(),
106            bytemuck::bytes_of(alt_block_height),
107        )?
108        .ok_or(BlockchainError::NotFound)?;
109
110    let block_info: CompactAltBlockInfo = bytemuck::pod_read_unaligned(block_info.as_ref());
111
112    let block_blob = tx_ro
113        .get(
114            &**db.alt_block_blobs.load(),
115            bytemuck::bytes_of(alt_block_height),
116        )?
117        .ok_or(BlockchainError::NotFound)?;
118
119    let block = Block::read(&mut block_blob.as_ref()).unwrap();
120
121    let txs = block
122        .transactions
123        .iter()
124        .map(|tx_hash| get_alt_transaction(db, tx_hash, tx_ro))
125        .collect::<DbResult<Vec<_>>>()?;
126
127    Ok(AltBlockInformation {
128        block,
129        block_blob: block_blob.to_vec(),
130        txs,
131        block_hash: block_info.block_hash,
132        pow_hash: block_info.pow_hash,
133        height: block_info.height,
134        weight: block_info.weight,
135        long_term_weight: block_info.long_term_weight,
136        cumulative_difficulty: combine_low_high_bits_to_u128(
137            block_info.cumulative_difficulty_low,
138            block_info.cumulative_difficulty_high,
139        ),
140        chain_id: alt_block_height.chain_id.into(),
141    })
142}
143
144/// Retrieve an alt [`Block`] via its [`AltBlockHeight`].
145///
146/// This function will look at only the blocks with the given [`AltBlockHeight::chain_id`], no others
147/// even if they are technically part of this chain.
148pub fn get_alt_block(
149    db: &BlockchainDatabase,
150    alt_block_height: &AltBlockHeight,
151    tx_ro: &fjall::Snapshot,
152) -> DbResult<Block> {
153    let block_blob = tx_ro
154        .get(
155            &**db.alt_block_blobs.load(),
156            bytemuck::bytes_of(alt_block_height),
157        )?
158        .ok_or(BlockchainError::NotFound)?;
159
160    Ok(Block::read(&mut block_blob.as_ref()).unwrap())
161}
162
163/// Retrieves the hash of the block at the given `block_height` on the alt chain with
164/// the given [`ChainId`].
165///
166/// This function will get blocks from the whole chain, for example if you were to ask for height
167/// `0` with any [`ChainId`] (as long that chain actually exists) you will get the main chain genesis.
168///
169pub fn get_alt_block_hash(
170    db: &BlockchainDatabase,
171    block_height: &BlockHeight,
172    alt_chain: ChainId,
173    tx_ro: &fjall::Snapshot,
174    tapes: &impl tapes::TapesRead,
175) -> DbResult<BlockHash> {
176    // First find what [`ChainId`] this block would be stored under.
177    let original_chain = {
178        let mut chain: RawChainId = alt_chain.into();
179        loop {
180            let chain_info = tx_ro
181                .get(&**db.alt_chain_infos.load(), chain.0.to_le_bytes())?
182                .ok_or(BlockchainError::NotFound)?;
183
184            let chain_info: AltChainInfo = bytemuck::pod_read_unaligned(chain_info.as_ref());
185
186            if chain_info.common_ancestor_height < *block_height {
187                break Chain::Alt(chain.into());
188            }
189
190            match chain_info.parent_chain.into() {
191                Chain::Main => break Chain::Main,
192                Chain::Alt(alt_chain_id) => {
193                    chain = alt_chain_id.into();
194                    continue;
195                }
196            }
197        }
198    };
199
200    // Get the block hash.
201    match original_chain {
202        Chain::Main => tapes
203            .read_entry(&db.block_infos, usize_to_u64(*block_height))?
204            .map(|info| info.block_hash)
205            .ok_or(BlockchainError::NotFound),
206        Chain::Alt(chain_id) => tx_ro
207            .get(
208                &**db.alt_block_infos.load(),
209                bytemuck::bytes_of(&AltBlockHeight {
210                    chain_id: chain_id.into(),
211                    height: *block_height,
212                }),
213            )?
214            .map(|info| {
215                let info: CompactAltBlockInfo = bytemuck::pod_read_unaligned(info.as_ref());
216                info.block_hash
217            })
218            .ok_or(BlockchainError::NotFound),
219    }
220}
221
222/// Retrieves the [`ExtendedBlockHeader`] of the alt-block with an exact [`AltBlockHeight`].
223///
224/// This function will look at only the blocks with the given [`AltBlockHeight::chain_id`], no others
225/// even if they are technically part of this chain.
226pub fn get_alt_block_extended_header_from_height(
227    db: &BlockchainDatabase,
228    height: &AltBlockHeight,
229    tx_ro: &fjall::Snapshot,
230) -> DbResult<ExtendedBlockHeader> {
231    let block_info = tx_ro
232        .get(&**db.alt_block_infos.load(), bytemuck::bytes_of(height))?
233        .ok_or(BlockchainError::NotFound)?;
234
235    let block_info: CompactAltBlockInfo = bytemuck::pod_read_unaligned(block_info.as_ref());
236
237    let block_blob = tx_ro
238        .get(&**db.alt_block_blobs.load(), bytemuck::bytes_of(height))?
239        .ok_or(BlockchainError::NotFound)?;
240
241    let block_header = BlockHeader::read(&mut block_blob.as_ref())?;
242
243    Ok(ExtendedBlockHeader {
244        version: HardFork::from_version(block_header.hardfork_version)
245            .expect("Block in DB must have correct version"),
246        vote: block_header.hardfork_signal,
247        timestamp: block_header.timestamp,
248        cumulative_difficulty: combine_low_high_bits_to_u128(
249            block_info.cumulative_difficulty_low,
250            block_info.cumulative_difficulty_high,
251        ),
252        block_weight: block_info.weight,
253        long_term_weight: block_info.long_term_weight,
254    })
255}
256
257/// Returns the [`AltBlockHeight`] of a block from its hash, only if it is in an alt chain.
258pub(crate) fn alt_block_height(
259    db: &BlockchainDatabase,
260    tx_ro: &fjall::Snapshot,
261    hash: &BlockHash,
262) -> DbResult<Option<AltBlockHeight>> {
263    let Some(bytes) = tx_ro.get(&**db.alt_block_heights.load(), hash)? else {
264        return Ok(None);
265    };
266
267    Ok(Some(bytemuck::pod_read_unaligned(bytes.as_ref())))
268}