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}