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