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.
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.
254    ///
255    /// This request flushes all alt-chains from the cache before adding the popped blocks to the
256    /// alt cache.
257    PopBlocks(usize),
258
259    /// A request to flush all alternative blocks.
260    FlushAltBlocks,
261}
262
263//---------------------------------------------------------------------------------------------------- Response
264/// A response from the database.
265///
266/// These are the data types returned when using sending a `Request`.
267///
268/// This pairs with [`BlockchainReadRequest`] and [`BlockchainWriteRequest`],
269/// see those two for more info.
270#[derive(Debug, Clone, PartialEq, Eq)]
271#[expect(clippy::large_enum_variant)]
272pub enum BlockchainResponse {
273    //------------------------------------------------------ Reads
274    /// Response to [`BlockchainReadRequest::BlockCompleteEntries`].
275    BlockCompleteEntries {
276        /// The [`BlockCompleteEntry`]s that we had.
277        blocks: Vec<BlockCompleteEntry>,
278        /// The hashes of blocks that were requested, but we don't have.
279        missing_hashes: Vec<[u8; 32]>,
280        /// Our blockchain height.
281        blockchain_height: usize,
282    },
283
284    /// Response to [`BlockchainReadRequest::BlockCompleteEntriesByHeight`].
285    BlockCompleteEntriesByHeight(Vec<BlockCompleteEntry>),
286
287    /// Response to [`BlockchainReadRequest::BlockCompleteEntriesAboveSplitPoint`].
288    BlockCompleteEntriesAboveSplitPoint {
289        /// The [`BlockCompleteEntry`]s that we had.
290        blocks: Vec<BlockCompleteEntry>,
291        /// The output indices of all transaction outputs across all blocks.
292        ///
293        /// `output_indices[block][tx][output]`, including the miner tx (miner tx will be empty if not requested).
294        output_indices: Vec<Vec<Vec<u64>>>,
295        /// Our blockchain height.
296        blockchain_height: usize,
297        /// The height the returned blocks start from.
298        start_height: usize,
299        /// Hash of the current top block.
300        top_hash: [u8; 32],
301    },
302
303    /// Response to [`BlockchainReadRequest::BlockExtendedHeader`].
304    ///
305    /// Inner value is the extended headed of the requested block.
306    BlockExtendedHeader(ExtendedBlockHeader),
307
308    /// Response to [`BlockchainReadRequest::BlockHash`].
309    ///
310    /// Inner value is the hash of the requested block.
311    BlockHash([u8; 32]),
312
313    /// Response to [`BlockchainReadRequest::BlockHashInRange`].
314    ///
315    /// Inner value is the hashes of the requested blocks, in order.
316    BlockHashInRange(Vec<[u8; 32]>),
317
318    /// Response to [`BlockchainReadRequest::FindBlock`].
319    ///
320    /// Inner value is the chain and height of the block if found.
321    FindBlock(Option<(Chain, usize)>),
322
323    /// Response to [`BlockchainReadRequest::FilterUnknownHashes`].
324    ///
325    /// Inner value is the list of hashes that were in the main chain.
326    FilterUnknownHashes(HashSet<[u8; 32]>),
327
328    /// Response to [`BlockchainReadRequest::BlockExtendedHeaderInRange`].
329    ///
330    /// Inner value is the list of extended header(s) of the requested block(s).
331    BlockExtendedHeaderInRange(Vec<ExtendedBlockHeader>),
332
333    /// Response to [`BlockchainReadRequest::ChainHeight`].
334    ///
335    /// Inner value is the chain height, and the top block's hash.
336    ChainHeight(usize, [u8; 32]),
337
338    /// Response to [`BlockchainReadRequest::GeneratedCoins`].
339    ///
340    /// Inner value is the total amount of generated coins up to and including the chosen height, in atomic units.
341    GeneratedCoins(u64),
342
343    /// Response to [`BlockchainReadRequest::CumulativeRctOutsInRange`].
344    ///
345    /// Inner value is `cumulative_rct_outs` for each block in the requested range.
346    CumulativeRctOutsInRange(Vec<u64>),
347
348    /// Response to [`BlockchainReadRequest::Outputs`].
349    ///
350    /// Inner value is an [`OutputCache`], missing outputs won't trigger an error, they just will not be
351    /// in the cache until the cache is updated with the block containing those outputs.
352    Outputs(OutputCache),
353
354    /// Response to [`BlockchainReadRequest::OutputsVec`].
355    OutputsVec(Vec<(u64, Vec<(u64, OutputOnChain)>)>),
356
357    /// Response to [`BlockchainReadRequest::NumberOutputsWithAmount`].
358    ///
359    /// Inner value is a `HashMap` of all the outputs requested where:
360    /// - Key = output amount
361    /// - Value = count of outputs with the same amount
362    NumberOutputsWithAmount(HashMap<u64, usize>),
363
364    /// Response to [`BlockchainReadRequest::KeyImagesSpent`].
365    ///
366    /// The inner value is `true` if _any_ of the key images
367    /// were spent (existed in the database already).
368    ///
369    /// The inner value is `false` if _none_ of the key images were spent.
370    KeyImagesSpent(bool),
371
372    /// Response to [`BlockchainReadRequest::KeyImagesSpentVec`].
373    ///
374    /// Inner value is a `Vec` the same length as the input.
375    ///
376    /// The index of each entry corresponds with the request.
377    /// `true` means that the key image was spent.
378    KeyImagesSpentVec(Vec<bool>),
379
380    /// Response to [`BlockchainReadRequest::CompactChainHistory`].
381    CompactChainHistory {
382        /// A list of blocks IDs in our chain, starting with the most recent block, all the way to the genesis block.
383        ///
384        /// These blocks should be in reverse chronological order, not every block is needed.
385        block_ids: Vec<[u8; 32]>,
386        /// The current cumulative difficulty of the chain.
387        cumulative_difficulty: u128,
388    },
389
390    /// Response to [`BlockchainReadRequest::NextChainEntry`].
391    ///
392    /// If all blocks were unknown `start_height` will be [`None`], the other fields will be meaningless.
393    NextChainEntry {
394        /// The start height of this entry, [`None`] if we could not find the split point.
395        start_height: Option<usize>,
396        /// The current chain height.
397        chain_height: usize,
398        /// The next block hashes in the entry.
399        block_ids: Vec<[u8; 32]>,
400        /// The block weights of the next blocks.
401        block_weights: Vec<usize>,
402        /// The current cumulative difficulty of our chain.
403        cumulative_difficulty: u128,
404        /// The block blob of the 2nd block in `block_ids`, if there is one.
405        first_block_blob: Option<Vec<u8>>,
406    },
407
408    /// Response to [`BlockchainReadRequest::FindFirstUnknown`].
409    ///
410    /// Contains the index of the first unknown block and its expected height.
411    ///
412    /// This will be [`None`] if all blocks were known.
413    FindFirstUnknown(Option<(usize, usize)>),
414
415    /// The response for [`BlockchainReadRequest::TxsInBlock`].
416    ///
417    /// Will return [`None`] if the request contained an index out of range.
418    TxsInBlock(Option<TxsInBlock>),
419
420    /// The response for [`BlockchainReadRequest::AltBlocksInChain`].
421    ///
422    /// Contains all the alt blocks in the alt-chain in chronological order.
423    AltBlocksInChain(Vec<AltBlockInformation>),
424
425    /// Response to:
426    /// - [`BlockchainReadRequest::Block`].
427    /// - [`BlockchainReadRequest::BlockByHash`].
428    Block(Block),
429
430    /// Response to [`BlockchainReadRequest::TotalTxCount`].
431    TotalTxCount(usize),
432
433    /// Response to [`BlockchainReadRequest::DatabaseSize`].
434    DatabaseSize {
435        /// The size of the database file in bytes.
436        database_size: u64,
437        /// The amount of free bytes there are
438        /// the disk where the database is located.
439        free_space: u64,
440    },
441
442    /// Response to [`BlockchainReadRequest::PreRctOutputDistribution`].
443    PreRctOutputDistribution(Vec<OutputDistributionData>),
444
445    /// Response to [`BlockchainReadRequest::OutputHistogram`].
446    OutputHistogram(Vec<OutputHistogramEntry>),
447
448    /// Response to [`BlockchainReadRequest::CoinbaseTxSum`].
449    CoinbaseTxSum(CoinbaseTxSum),
450
451    /// Response to [`BlockchainReadRequest::AltChains`].
452    AltChains(Vec<ChainInfo>),
453
454    /// Response to [`BlockchainReadRequest::AltChainCount`].
455    AltChainCount(usize),
456
457    /// Response to [`BlockchainReadRequest::Transactions`].
458    Transactions {
459        /// The transaction blobs found.
460        txs: Vec<TxInBlockchain>,
461        /// The hashes of any transactions that could not be found.
462        missed_txs: Vec<[u8; 32]>,
463    },
464
465    /// Response to [`BlockchainReadRequest::TotalRctOutputs`].
466    TotalRctOutputs(u64),
467
468    /// Response to [`BlockchainReadRequest::TxOutputIndexes`].
469    TxOutputIndexes(Vec<u64>),
470
471    //------------------------------------------------------ Writes
472    /// A generic Ok response to indicate a request was successfully handled.
473    ///
474    /// currently the response for:
475    /// - [`BlockchainWriteRequest::WriteBlock`]
476    /// - [`BlockchainWriteRequest::WriteAltBlock`]
477    /// - [`BlockchainWriteRequest::FlushAltBlocks`]
478    Ok,
479
480    /// Response to [`BlockchainWriteRequest::PopBlocks`].
481    ///
482    /// The inner value is the alt-chain ID for the old main chain blocks.
483    PopBlocks(ChainId),
484}
485
486//---------------------------------------------------------------------------------------------------- Tests
487#[cfg(test)]
488mod test {
489    // use super::*;
490}