cuprated/rpc/service/
blockchain.rs

1//! Functions to send [`BlockchainReadRequest`]s.
2
3use std::{
4    collections::{BTreeSet, HashMap, HashSet},
5    ops::Range,
6};
7
8use anyhow::Error;
9use indexmap::{IndexMap, IndexSet};
10use monero_serai::block::Block;
11use tower::{Service, ServiceExt};
12
13use cuprate_blockchain::service::BlockchainReadHandle;
14use cuprate_helper::cast::{u64_to_usize, usize_to_u64};
15use cuprate_rpc_types::misc::GetOutputsOut;
16use cuprate_types::{
17    blockchain::{BlockchainReadRequest, BlockchainResponse},
18    output_cache::OutputCache,
19    rpc::{
20        ChainInfo, CoinbaseTxSum, KeyImageSpentStatus, OutputDistributionData,
21        OutputHistogramEntry, OutputHistogramInput,
22    },
23    BlockCompleteEntry, Chain, ExtendedBlockHeader, OutputDistributionInput, OutputOnChain,
24    TxInBlockchain,
25};
26
27/// [`BlockchainReadRequest::Block`].
28pub async fn block(
29    blockchain_read: &mut BlockchainReadHandle,
30    height: u64,
31) -> Result<Block, Error> {
32    let BlockchainResponse::Block(block) = blockchain_read
33        .ready()
34        .await?
35        .call(BlockchainReadRequest::Block {
36            height: u64_to_usize(height),
37        })
38        .await?
39    else {
40        unreachable!();
41    };
42
43    Ok(block)
44}
45
46/// [`BlockchainReadRequest::BlockByHash`].
47pub async fn block_by_hash(
48    blockchain_read: &mut BlockchainReadHandle,
49    hash: [u8; 32],
50) -> Result<Block, Error> {
51    let BlockchainResponse::Block(block) = blockchain_read
52        .ready()
53        .await?
54        .call(BlockchainReadRequest::BlockByHash(hash))
55        .await?
56    else {
57        unreachable!();
58    };
59
60    Ok(block)
61}
62
63/// [`BlockchainReadRequest::BlockExtendedHeader`].
64pub async fn block_extended_header(
65    blockchain_read: &mut BlockchainReadHandle,
66    height: u64,
67) -> Result<ExtendedBlockHeader, Error> {
68    let BlockchainResponse::BlockExtendedHeader(header) = blockchain_read
69        .ready()
70        .await?
71        .call(BlockchainReadRequest::BlockExtendedHeader(u64_to_usize(
72            height,
73        )))
74        .await?
75    else {
76        unreachable!();
77    };
78
79    Ok(header)
80}
81
82/// [`BlockchainReadRequest::BlockHash`].
83pub async fn block_hash(
84    blockchain_read: &mut BlockchainReadHandle,
85    height: u64,
86    chain: Chain,
87) -> Result<[u8; 32], Error> {
88    let BlockchainResponse::BlockHash(hash) = blockchain_read
89        .ready()
90        .await?
91        .call(BlockchainReadRequest::BlockHash(
92            u64_to_usize(height),
93            chain,
94        ))
95        .await?
96    else {
97        unreachable!();
98    };
99
100    Ok(hash)
101}
102
103/// [`BlockchainReadRequest::FindBlock`].
104pub async fn find_block(
105    blockchain_read: &mut BlockchainReadHandle,
106    block_hash: [u8; 32],
107) -> Result<Option<(Chain, usize)>, Error> {
108    let BlockchainResponse::FindBlock(option) = blockchain_read
109        .ready()
110        .await?
111        .call(BlockchainReadRequest::FindBlock(block_hash))
112        .await?
113    else {
114        unreachable!();
115    };
116
117    Ok(option)
118}
119
120/// [`BlockchainReadRequest::NextChainEntry`].
121///
122/// Returns only the:
123/// - block IDs
124/// - start height
125/// - current chain height
126pub async fn next_chain_entry(
127    blockchain_read: &mut BlockchainReadHandle,
128    block_hashes: Vec<[u8; 32]>,
129    start_height: u64,
130) -> Result<(Vec<[u8; 32]>, Option<usize>, usize), Error> {
131    let BlockchainResponse::NextChainEntry {
132        block_ids,
133        start_height,
134        chain_height,
135        ..
136    } = blockchain_read
137        .ready()
138        .await?
139        .call(BlockchainReadRequest::NextChainEntry(
140            block_hashes,
141            u64_to_usize(start_height),
142        ))
143        .await?
144    else {
145        unreachable!();
146    };
147
148    Ok((block_ids, start_height, chain_height))
149}
150
151/// [`BlockchainReadRequest::FilterUnknownHashes`].
152pub async fn filter_unknown_hashes(
153    blockchain_read: &mut BlockchainReadHandle,
154    block_hashes: HashSet<[u8; 32]>,
155) -> Result<HashSet<[u8; 32]>, Error> {
156    let BlockchainResponse::FilterUnknownHashes(output) = blockchain_read
157        .ready()
158        .await?
159        .call(BlockchainReadRequest::FilterUnknownHashes(block_hashes))
160        .await?
161    else {
162        unreachable!();
163    };
164
165    Ok(output)
166}
167
168/// [`BlockchainReadRequest::BlockExtendedHeaderInRange`]
169pub async fn block_extended_header_in_range(
170    blockchain_read: &mut BlockchainReadHandle,
171    range: Range<usize>,
172    chain: Chain,
173) -> Result<Vec<ExtendedBlockHeader>, Error> {
174    let BlockchainResponse::BlockExtendedHeaderInRange(output) = blockchain_read
175        .ready()
176        .await?
177        .call(BlockchainReadRequest::BlockExtendedHeaderInRange(
178            range, chain,
179        ))
180        .await?
181    else {
182        unreachable!();
183    };
184
185    Ok(output)
186}
187
188/// [`BlockchainReadRequest::ChainHeight`].
189pub async fn chain_height(
190    blockchain_read: &mut BlockchainReadHandle,
191) -> Result<(u64, [u8; 32]), Error> {
192    let BlockchainResponse::ChainHeight(height, hash) = blockchain_read
193        .ready()
194        .await?
195        .call(BlockchainReadRequest::ChainHeight)
196        .await?
197    else {
198        unreachable!();
199    };
200
201    Ok((usize_to_u64(height), hash))
202}
203
204/// [`BlockchainReadRequest::GeneratedCoins`].
205pub async fn generated_coins(
206    blockchain_read: &mut BlockchainReadHandle,
207    block_height: u64,
208) -> Result<u64, Error> {
209    let BlockchainResponse::GeneratedCoins(generated_coins) = blockchain_read
210        .ready()
211        .await?
212        .call(BlockchainReadRequest::GeneratedCoins(u64_to_usize(
213            block_height,
214        )))
215        .await?
216    else {
217        unreachable!();
218    };
219
220    Ok(generated_coins)
221}
222
223/// [`BlockchainReadRequest::Outputs`]
224pub async fn outputs(
225    blockchain_read: &mut BlockchainReadHandle,
226    outputs: IndexMap<u64, IndexSet<u64>>,
227    get_txid: bool,
228) -> Result<OutputCache, Error> {
229    let BlockchainResponse::Outputs(outputs) = blockchain_read
230        .ready()
231        .await?
232        .call(BlockchainReadRequest::Outputs { outputs, get_txid })
233        .await?
234    else {
235        unreachable!();
236    };
237
238    Ok(outputs)
239}
240
241/// [`BlockchainReadRequest::OutputsVec`]
242pub async fn outputs_vec(
243    blockchain_read: &mut BlockchainReadHandle,
244    outputs: Vec<GetOutputsOut>,
245    get_txid: bool,
246) -> Result<Vec<(u64, Vec<(u64, OutputOnChain)>)>, Error> {
247    let outputs = outputs
248        .into_iter()
249        .map(|output| (output.amount, output.index))
250        .collect();
251
252    let BlockchainResponse::OutputsVec(outputs) = blockchain_read
253        .ready()
254        .await?
255        .call(BlockchainReadRequest::OutputsVec { outputs, get_txid })
256        .await?
257    else {
258        unreachable!();
259    };
260
261    Ok(outputs)
262}
263
264/// [`BlockchainReadRequest::NumberOutputsWithAmount`]
265pub async fn number_outputs_with_amount(
266    blockchain_read: &mut BlockchainReadHandle,
267    output_amounts: Vec<u64>,
268) -> Result<HashMap<u64, usize>, Error> {
269    let BlockchainResponse::NumberOutputsWithAmount(map) = blockchain_read
270        .ready()
271        .await?
272        .call(BlockchainReadRequest::NumberOutputsWithAmount(
273            output_amounts,
274        ))
275        .await?
276    else {
277        unreachable!();
278    };
279
280    Ok(map)
281}
282
283/// [`BlockchainReadRequest::KeyImagesSpent`]
284pub async fn key_images_spent(
285    blockchain_read: &mut BlockchainReadHandle,
286    key_images: HashSet<[u8; 32]>,
287) -> Result<bool, Error> {
288    let BlockchainResponse::KeyImagesSpent(status) = blockchain_read
289        .ready()
290        .await?
291        .call(BlockchainReadRequest::KeyImagesSpent(key_images))
292        .await?
293    else {
294        unreachable!();
295    };
296
297    Ok(status)
298}
299
300/// [`BlockchainReadRequest::KeyImagesSpentVec`]
301pub async fn key_images_spent_vec(
302    blockchain_read: &mut BlockchainReadHandle,
303    key_images: Vec<[u8; 32]>,
304) -> Result<Vec<bool>, Error> {
305    let BlockchainResponse::KeyImagesSpentVec(status) = blockchain_read
306        .ready()
307        .await?
308        .call(BlockchainReadRequest::KeyImagesSpentVec(key_images))
309        .await?
310    else {
311        unreachable!();
312    };
313
314    Ok(status)
315}
316
317/// [`BlockchainReadRequest::CompactChainHistory`]
318pub async fn compact_chain_history(
319    blockchain_read: &mut BlockchainReadHandle,
320) -> Result<(Vec<[u8; 32]>, u128), Error> {
321    let BlockchainResponse::CompactChainHistory {
322        block_ids,
323        cumulative_difficulty,
324    } = blockchain_read
325        .ready()
326        .await?
327        .call(BlockchainReadRequest::CompactChainHistory)
328        .await?
329    else {
330        unreachable!();
331    };
332
333    Ok((block_ids, cumulative_difficulty))
334}
335
336/// [`BlockchainReadRequest::FindFirstUnknown`]
337pub async fn find_first_unknown(
338    blockchain_read: &mut BlockchainReadHandle,
339    hashes: Vec<[u8; 32]>,
340) -> Result<Option<(usize, u64)>, Error> {
341    let BlockchainResponse::FindFirstUnknown(resp) = blockchain_read
342        .ready()
343        .await?
344        .call(BlockchainReadRequest::FindFirstUnknown(hashes))
345        .await?
346    else {
347        unreachable!();
348    };
349
350    Ok(resp.map(|(index, height)| (index, usize_to_u64(height))))
351}
352
353/// [`BlockchainReadRequest::TotalTxCount`]
354pub async fn total_tx_count(blockchain_read: &mut BlockchainReadHandle) -> Result<u64, Error> {
355    let BlockchainResponse::TotalTxCount(tx_count) = blockchain_read
356        .ready()
357        .await?
358        .call(BlockchainReadRequest::TotalTxCount)
359        .await?
360    else {
361        unreachable!();
362    };
363
364    Ok(usize_to_u64(tx_count))
365}
366
367/// [`BlockchainReadRequest::DatabaseSize`]
368pub async fn database_size(
369    blockchain_read: &mut BlockchainReadHandle,
370) -> Result<(u64, u64), Error> {
371    let BlockchainResponse::DatabaseSize {
372        database_size,
373        free_space,
374    } = blockchain_read
375        .ready()
376        .await?
377        .call(BlockchainReadRequest::DatabaseSize)
378        .await?
379    else {
380        unreachable!();
381    };
382
383    Ok((database_size, free_space))
384}
385
386/// [`BlockchainReadRequest::OutputDistribution`]
387pub async fn output_distribution(
388    blockchain_read: &mut BlockchainReadHandle,
389    input: OutputDistributionInput,
390) -> Result<Vec<OutputDistributionData>, Error> {
391    let BlockchainResponse::OutputDistribution(data) = blockchain_read
392        .ready()
393        .await?
394        .call(BlockchainReadRequest::OutputDistribution(input))
395        .await?
396    else {
397        unreachable!();
398    };
399
400    Ok(data)
401}
402
403/// [`BlockchainReadRequest::OutputHistogram`]
404pub async fn output_histogram(
405    blockchain_read: &mut BlockchainReadHandle,
406    input: OutputHistogramInput,
407) -> Result<Vec<OutputHistogramEntry>, Error> {
408    let BlockchainResponse::OutputHistogram(histogram) = blockchain_read
409        .ready()
410        .await?
411        .call(BlockchainReadRequest::OutputHistogram(input))
412        .await?
413    else {
414        unreachable!();
415    };
416
417    Ok(histogram)
418}
419
420/// [`BlockchainReadRequest::CoinbaseTxSum`]
421pub async fn coinbase_tx_sum(
422    blockchain_read: &mut BlockchainReadHandle,
423    height: u64,
424    count: u64,
425) -> Result<CoinbaseTxSum, Error> {
426    let BlockchainResponse::CoinbaseTxSum(sum) = blockchain_read
427        .ready()
428        .await?
429        .call(BlockchainReadRequest::CoinbaseTxSum {
430            height: u64_to_usize(height),
431            count,
432        })
433        .await?
434    else {
435        unreachable!();
436    };
437
438    Ok(sum)
439}
440
441/// [`BlockchainReadRequest::AltChains`]
442pub async fn alt_chains(
443    blockchain_read: &mut BlockchainReadHandle,
444) -> Result<Vec<ChainInfo>, Error> {
445    let BlockchainResponse::AltChains(vec) = blockchain_read
446        .ready()
447        .await?
448        .call(BlockchainReadRequest::AltChains)
449        .await?
450    else {
451        unreachable!();
452    };
453
454    Ok(vec)
455}
456
457/// [`BlockchainReadRequest::AltChainCount`]
458pub async fn alt_chain_count(blockchain_read: &mut BlockchainReadHandle) -> Result<u64, Error> {
459    let BlockchainResponse::AltChainCount(count) = blockchain_read
460        .ready()
461        .await?
462        .call(BlockchainReadRequest::AltChainCount)
463        .await?
464    else {
465        unreachable!();
466    };
467
468    Ok(usize_to_u64(count))
469}
470
471/// [`BlockchainReadRequest::Transactions`].
472pub async fn transactions(
473    blockchain_read: &mut BlockchainReadHandle,
474    tx_hashes: HashSet<[u8; 32]>,
475) -> Result<(Vec<TxInBlockchain>, Vec<[u8; 32]>), Error> {
476    let BlockchainResponse::Transactions { txs, missed_txs } = blockchain_read
477        .ready()
478        .await?
479        .call(BlockchainReadRequest::Transactions { tx_hashes })
480        .await?
481    else {
482        unreachable!();
483    };
484
485    Ok((txs, missed_txs))
486}
487
488/// [`BlockchainReadRequest::TotalRctOutputs`].
489pub async fn total_rct_outputs(blockchain_read: &mut BlockchainReadHandle) -> Result<u64, Error> {
490    let BlockchainResponse::TotalRctOutputs(n) = blockchain_read
491        .ready()
492        .await?
493        .call(BlockchainReadRequest::TotalRctOutputs)
494        .await?
495    else {
496        unreachable!();
497    };
498
499    Ok(n)
500}
501
502/// [`BlockchainReadRequest::BlockCompleteEntries`].
503pub async fn block_complete_entries(
504    blockchain_read: &mut BlockchainReadHandle,
505    block_hashes: Vec<[u8; 32]>,
506) -> Result<(Vec<BlockCompleteEntry>, Vec<[u8; 32]>, usize), Error> {
507    let BlockchainResponse::BlockCompleteEntries {
508        blocks,
509        missing_hashes,
510        blockchain_height,
511    } = blockchain_read
512        .ready()
513        .await?
514        .call(BlockchainReadRequest::BlockCompleteEntries(block_hashes))
515        .await?
516    else {
517        unreachable!();
518    };
519
520    Ok((blocks, missing_hashes, blockchain_height))
521}
522
523/// [`BlockchainReadRequest::BlockCompleteEntriesByHeight`].
524pub async fn block_complete_entries_by_height(
525    blockchain_read: &mut BlockchainReadHandle,
526    block_heights: Vec<u64>,
527) -> Result<Vec<BlockCompleteEntry>, Error> {
528    let BlockchainResponse::BlockCompleteEntriesByHeight(blocks) = blockchain_read
529        .ready()
530        .await?
531        .call(BlockchainReadRequest::BlockCompleteEntriesByHeight(
532            block_heights.into_iter().map(u64_to_usize).collect(),
533        ))
534        .await?
535    else {
536        unreachable!();
537    };
538
539    Ok(blocks)
540}
541
542/// [`BlockchainReadRequest::TxOutputIndexes`].
543pub async fn tx_output_indexes(
544    blockchain_read: &mut BlockchainReadHandle,
545    tx_hash: [u8; 32],
546) -> Result<Vec<u64>, Error> {
547    let BlockchainResponse::TxOutputIndexes(o_indexes) = blockchain_read
548        .ready()
549        .await?
550        .call(BlockchainReadRequest::TxOutputIndexes { tx_hash })
551        .await?
552    else {
553        unreachable!();
554    };
555
556    Ok(o_indexes)
557}