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}