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