Skip to main content

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