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}