cuprate_types/
blockchain.rs

1//! Database [`BlockchainReadRequest`]s, [`BlockchainWriteRequest`]s, and [`BlockchainResponse`]s.
2//!
3//! Tests that assert particular requests lead to particular
4//! responses are also tested in Cuprate's blockchain database crate.
5//---------------------------------------------------------------------------------------------------- Import
6use std::{
7    collections::{HashMap, HashSet},
8    ops::Range,
9};
10
11use indexmap::{IndexMap, IndexSet};
12use monero_serai::block::Block;
13
14use crate::{
15    output_cache::OutputCache,
16    types::{Chain, ExtendedBlockHeader, TxsInBlock, VerifiedBlockInformation},
17    AltBlockInformation, BlockCompleteEntry, ChainId, ChainInfo, CoinbaseTxSum,
18    OutputHistogramEntry, OutputHistogramInput,
19};
20
21//---------------------------------------------------------------------------------------------------- ReadRequest
22/// A read request to the blockchain database.
23///
24/// This pairs with [`BlockchainResponse`], where each variant here
25/// matches in name with a [`BlockchainResponse`] variant. For example,
26/// the proper response for a [`BlockchainReadRequest::BlockHash`]
27/// would be a [`BlockchainResponse::BlockHash`].
28///
29/// See `Response` for the expected responses per `Request`.
30#[derive(Debug, Clone, PartialEq, Eq)]
31pub enum BlockchainReadRequest {
32    /// Request [`BlockCompleteEntry`]s.
33    ///
34    /// The input is the block hashes.
35    BlockCompleteEntries(Vec<[u8; 32]>),
36
37    /// Request a block's extended header.
38    ///
39    /// The input is the block's height.
40    BlockExtendedHeader(usize),
41
42    /// Request a block's hash.
43    ///
44    /// The input is the block's height and the chain it is on.
45    BlockHash(usize, Chain),
46
47    /// Request a range of block's hashes.
48    ///
49    /// The input is the range of block heights and the chain it is on.
50    BlockHashInRange(Range<usize>, Chain),
51
52    /// Request to check if we have a block and which [`Chain`] it is on.
53    ///
54    /// The input is the block's hash.
55    FindBlock([u8; 32]),
56
57    /// Removes the block hashes that are not in the _main_ chain.
58    ///
59    /// This should filter (remove) hashes in alt-blocks as well.
60    FilterUnknownHashes(HashSet<[u8; 32]>),
61
62    /// Request a range of block extended headers.
63    ///
64    /// The input is a range of block heights.
65    BlockExtendedHeaderInRange(Range<usize>, Chain),
66
67    /// Request the current chain height.
68    ///
69    /// Note that this is not the top-block height.
70    ChainHeight,
71
72    /// Request the total amount of generated coins (atomic units) at this height.
73    GeneratedCoins(usize),
74
75    /// Request data for multiple outputs.
76    ///
77    /// The input is a `HashMap` where:
78    /// - Key = output amount
79    /// - Value = set of amount indices
80    ///
81    /// For pre-RCT outputs, the amount is non-zero,
82    /// and the amount indices represent the wanted
83    /// indices of duplicate amount outputs, i.e.:
84    ///
85    /// ```ignore
86    /// // list of outputs with amount 10
87    /// [0, 1, 2, 3, 4, 5]
88    /// //  ^     ^
89    /// // we only want these two, so we would provide
90    /// // `amount: 10, amount_index: {1, 3}`
91    /// ```
92    ///
93    /// For RCT outputs, the amounts would be `0` and
94    /// the amount indices would represent the global
95    /// RCT output indices.
96    Outputs(IndexMap<u64, IndexSet<u64>>),
97
98    /// Request the amount of outputs with a certain amount.
99    ///
100    /// The input is a list of output amounts.
101    NumberOutputsWithAmount(Vec<u64>),
102
103    /// Check that all key images within a set are not spent.
104    ///
105    /// Input is a set of key images.
106    KeyImagesSpent(HashSet<[u8; 32]>),
107
108    /// A request for the compact chain history.
109    CompactChainHistory,
110
111    /// A request for the next chain entry.
112    ///
113    /// Input is a list of block hashes and the amount of block hashes to return in the next chain entry.
114    ///
115    /// # Invariant
116    /// The [`Vec`] containing the block IDs must be sorted in reverse chronological block
117    /// order, or else the returned response is unspecified and meaningless,
118    /// as this request performs a binary search
119    NextChainEntry(Vec<[u8; 32]>, usize),
120
121    /// A request to find the first unknown block ID in a list of block IDs.
122    ///
123    /// # Invariant
124    /// The [`Vec`] containing the block IDs must be sorted in chronological block
125    /// order, or else the returned response is unspecified and meaningless,
126    /// as this request performs a binary search.
127    FindFirstUnknown(Vec<[u8; 32]>),
128
129    /// A request for transactions from a specific block.
130    TxsInBlock {
131        /// The block to get transactions from.
132        block_hash: [u8; 32],
133        /// The indexes of the transactions from the block.
134        /// This is not the global index of the txs, instead it is the local index as they appear in
135        /// the block.
136        tx_indexes: Vec<u64>,
137    },
138
139    /// A request for all alt blocks in the chain with the given [`ChainId`].
140    AltBlocksInChain(ChainId),
141
142    /// Get a [`Block`] by its height.
143    Block { height: usize },
144
145    /// Get a [`Block`] by its hash.
146    BlockByHash([u8; 32]),
147
148    /// Get the total amount of non-coinbase transactions in the chain.
149    TotalTxCount,
150
151    /// Get the current size of the database.
152    DatabaseSize,
153
154    /// Get an output histogram.
155    ///
156    /// TODO: document fields after impl.
157    OutputHistogram(OutputHistogramInput),
158
159    /// Get the coinbase amount and the fees amount for
160    /// `N` last blocks starting at particular height.
161    ///
162    /// TODO: document fields after impl.
163    CoinbaseTxSum { height: usize, count: u64 },
164
165    /// Get information on all alternative chains.
166    AltChains,
167
168    /// Get the amount of alternative chains that exist.
169    AltChainCount,
170}
171
172//---------------------------------------------------------------------------------------------------- WriteRequest
173/// A write request to the blockchain database.
174#[derive(Debug, Clone, PartialEq, Eq)]
175pub enum BlockchainWriteRequest {
176    /// Request that a block be written to the database.
177    ///
178    /// Input is an already verified block.
179    WriteBlock(VerifiedBlockInformation),
180
181    /// Request that a batch of blocks be written to the database.
182    ///
183    /// Input is an already verified batch of blocks.
184    BatchWriteBlocks(Vec<VerifiedBlockInformation>),
185
186    /// Write an alternative block to the database,
187    ///
188    /// Input is the alternative block.
189    WriteAltBlock(AltBlockInformation),
190
191    /// A request to pop some blocks from the top of the main chain
192    ///
193    /// Input is the amount of blocks to pop.
194    ///
195    /// This request flushes all alt-chains from the cache before adding the popped blocks to the
196    /// alt cache.
197    PopBlocks(usize),
198
199    /// A request to reverse the re-org process.
200    ///
201    /// The inner value is the [`ChainId`] of the old main chain.
202    ///
203    /// # Invariant
204    /// It is invalid to call this with a [`ChainId`] that was not returned from [`BlockchainWriteRequest::PopBlocks`].
205    ReverseReorg(ChainId),
206
207    /// A request to flush all alternative blocks.
208    FlushAltBlocks,
209}
210
211//---------------------------------------------------------------------------------------------------- Response
212/// A response from the database.
213///
214/// These are the data types returned when using sending a `Request`.
215///
216/// This pairs with [`BlockchainReadRequest`] and [`BlockchainWriteRequest`],
217/// see those two for more info.
218#[derive(Debug, Clone, PartialEq, Eq)]
219#[expect(clippy::large_enum_variant)]
220pub enum BlockchainResponse {
221    //------------------------------------------------------ Reads
222    /// Response to [`BlockchainReadRequest::BlockCompleteEntries`].
223    BlockCompleteEntries {
224        /// The [`BlockCompleteEntry`]s that we had.
225        blocks: Vec<BlockCompleteEntry>,
226        /// The hashes of blocks that were requested, but we don't have.
227        missing_hashes: Vec<[u8; 32]>,
228        /// Our blockchain height.
229        blockchain_height: usize,
230    },
231
232    /// Response to [`BlockchainReadRequest::BlockExtendedHeader`].
233    ///
234    /// Inner value is the extended headed of the requested block.
235    BlockExtendedHeader(ExtendedBlockHeader),
236
237    /// Response to [`BlockchainReadRequest::BlockHash`].
238    ///
239    /// Inner value is the hash of the requested block.
240    BlockHash([u8; 32]),
241
242    /// Response to [`BlockchainReadRequest::BlockHashInRange`].
243    ///
244    /// Inner value is the hashes of the requested blocks, in order.
245    BlockHashInRange(Vec<[u8; 32]>),
246
247    /// Response to [`BlockchainReadRequest::FindBlock`].
248    ///
249    /// Inner value is the chain and height of the block if found.
250    FindBlock(Option<(Chain, usize)>),
251
252    /// Response to [`BlockchainReadRequest::FilterUnknownHashes`].
253    ///
254    /// Inner value is the list of hashes that were in the main chain.
255    FilterUnknownHashes(HashSet<[u8; 32]>),
256
257    /// Response to [`BlockchainReadRequest::BlockExtendedHeaderInRange`].
258    ///
259    /// Inner value is the list of extended header(s) of the requested block(s).
260    BlockExtendedHeaderInRange(Vec<ExtendedBlockHeader>),
261
262    /// Response to [`BlockchainReadRequest::ChainHeight`].
263    ///
264    /// Inner value is the chain height, and the top block's hash.
265    ChainHeight(usize, [u8; 32]),
266
267    /// Response to [`BlockchainReadRequest::GeneratedCoins`].
268    ///
269    /// Inner value is the total amount of generated coins up to and including the chosen height, in atomic units.
270    GeneratedCoins(u64),
271
272    /// Response to [`BlockchainReadRequest::Outputs`].
273    ///
274    /// Inner value is an [`OutputCache`], missing outputs won't trigger an error, they just will not be
275    /// in the cache until the cache is updated with the block containing those outputs.
276    Outputs(OutputCache),
277
278    /// Response to [`BlockchainReadRequest::NumberOutputsWithAmount`].
279    ///
280    /// Inner value is a `HashMap` of all the outputs requested where:
281    /// - Key = output amount
282    /// - Value = count of outputs with the same amount
283    NumberOutputsWithAmount(HashMap<u64, usize>),
284
285    /// Response to [`BlockchainReadRequest::KeyImagesSpent`].
286    ///
287    /// The inner value is `true` if _any_ of the key images
288    /// were spent (existed in the database already).
289    ///
290    /// The inner value is `false` if _none_ of the key images were spent.
291    KeyImagesSpent(bool),
292
293    /// Response to [`BlockchainReadRequest::CompactChainHistory`].
294    CompactChainHistory {
295        /// A list of blocks IDs in our chain, starting with the most recent block, all the way to the genesis block.
296        ///
297        /// These blocks should be in reverse chronological order, not every block is needed.
298        block_ids: Vec<[u8; 32]>,
299        /// The current cumulative difficulty of the chain.
300        cumulative_difficulty: u128,
301    },
302
303    /// Response to [`BlockchainReadRequest::NextChainEntry`].
304    ///
305    /// If all blocks were unknown `start_height` will be [`None`], the other fields will be meaningless.
306    NextChainEntry {
307        /// The start height of this entry, [`None`] if we could not find the split point.
308        start_height: Option<usize>,
309        /// The current chain height.
310        chain_height: usize,
311        /// The next block hashes in the entry.
312        block_ids: Vec<[u8; 32]>,
313        /// The block weights of the next blocks.
314        block_weights: Vec<usize>,
315        /// The current cumulative difficulty of our chain.
316        cumulative_difficulty: u128,
317        /// The block blob of the 2nd block in `block_ids`, if there is one.
318        first_block_blob: Option<Vec<u8>>,
319    },
320
321    /// Response to [`BlockchainReadRequest::FindFirstUnknown`].
322    ///
323    /// Contains the index of the first unknown block and its expected height.
324    ///
325    /// This will be [`None`] if all blocks were known.
326    FindFirstUnknown(Option<(usize, usize)>),
327
328    /// The response for [`BlockchainReadRequest::TxsInBlock`].
329    ///
330    /// Will return [`None`] if the request contained an index out of range.
331    TxsInBlock(Option<TxsInBlock>),
332
333    /// The response for [`BlockchainReadRequest::AltBlocksInChain`].
334    ///
335    /// Contains all the alt blocks in the alt-chain in chronological order.
336    AltBlocksInChain(Vec<AltBlockInformation>),
337
338    /// Response to:
339    /// - [`BlockchainReadRequest::Block`].
340    /// - [`BlockchainReadRequest::BlockByHash`].
341    Block(Block),
342
343    /// Response to [`BlockchainReadRequest::TotalTxCount`].
344    TotalTxCount(usize),
345
346    /// Response to [`BlockchainReadRequest::DatabaseSize`].
347    DatabaseSize {
348        /// The size of the database file in bytes.
349        database_size: u64,
350        /// The amount of free bytes there are
351        /// the disk where the database is located.
352        free_space: u64,
353    },
354
355    /// Response to [`BlockchainReadRequest::OutputHistogram`].
356    OutputHistogram(Vec<OutputHistogramEntry>),
357
358    /// Response to [`BlockchainReadRequest::CoinbaseTxSum`].
359    CoinbaseTxSum(CoinbaseTxSum),
360
361    /// Response to [`BlockchainReadRequest::AltChains`].
362    AltChains(Vec<ChainInfo>),
363
364    /// Response to [`BlockchainReadRequest::AltChainCount`].
365    AltChainCount(usize),
366
367    //------------------------------------------------------ Writes
368    /// A generic Ok response to indicate a request was successfully handled.
369    ///
370    /// currently the response for:
371    /// - [`BlockchainWriteRequest::WriteBlock`]
372    /// - [`BlockchainWriteRequest::WriteAltBlock`]
373    /// - [`BlockchainWriteRequest::ReverseReorg`]
374    /// - [`BlockchainWriteRequest::FlushAltBlocks`]
375    Ok,
376
377    /// Response to [`BlockchainWriteRequest::PopBlocks`].
378    ///
379    /// The inner value is the alt-chain ID for the old main chain blocks.
380    PopBlocks(ChainId),
381}
382
383//---------------------------------------------------------------------------------------------------- Tests
384#[cfg(test)]
385mod test {
386    // use super::*;
387}