cuprated/rpc/handlers/
helper.rs

1//! These are internal helper functions used by the actual RPC handlers.
2//!
3//! Many of the handlers have bodies with only small differences,
4//! the identical code is extracted and reused here in these functions.
5//!
6//! These build on-top of [`crate::rpc::service`] functions.
7
8use anyhow::{anyhow, Error};
9
10use cuprate_helper::{
11    cast::{u64_to_usize, usize_to_u64},
12    map::split_u128_into_low_high_bits,
13};
14use cuprate_rpc_types::{
15    base::{AccessResponseBase, ResponseBase},
16    misc::BlockHeader,
17};
18use cuprate_types::HardFork;
19use monero_serai::transaction::Timelock;
20
21use crate::rpc::{
22    service::{blockchain, blockchain_context},
23    CupratedRpcHandler,
24};
25
26/// Map some data into a [`BlockHeader`].
27///
28/// Sort of equivalent to:
29/// <https://github.com/monero-project/monero/blob/893916ad091a92e765ce3241b94e706ad012b62a/src/rpc/core_rpc_server.cpp#L2361>.
30pub(super) async fn block_header(
31    state: &mut CupratedRpcHandler,
32    height: u64,
33    fill_pow_hash: bool,
34) -> Result<BlockHeader, Error> {
35    let block = blockchain::block(&mut state.blockchain_read, height).await?;
36    let header = blockchain::block_extended_header(&mut state.blockchain_read, height).await?;
37    let hardfork = HardFork::from_vote(header.vote);
38    let (top_height, _) = top_height(state).await?;
39
40    // TODO: if the request block is not on the main chain,
41    // we must get the alt block and this variable will be `true`.
42    let orphan_status = false;
43
44    // TODO: impl cheaper way to get this.
45    // <https://github.com/Cuprate/cuprate/pull/355#discussion_r1904508934>
46    let difficulty = blockchain_context::batch_get_difficulties(
47        &mut state.blockchain_context,
48        vec![(height, hardfork)],
49    )
50    .await?
51    .first()
52    .copied()
53    .ok_or_else(|| anyhow!("Failed to get block difficulty"))?;
54
55    let pow_hash = if fill_pow_hash {
56        let seed_height =
57            cuprate_consensus_rules::blocks::randomx_seed_height(u64_to_usize(height));
58        let seed_hash = blockchain::block_hash(
59            &mut state.blockchain_read,
60            height,
61            todo!("access to `cuprated`'s Chain"),
62        )
63        .await?;
64
65        Some(
66            blockchain_context::calculate_pow(
67                &mut state.blockchain_context,
68                hardfork,
69                block,
70                seed_hash,
71            )
72            .await?,
73        )
74    } else {
75        None
76    };
77
78    let block_weight = usize_to_u64(header.block_weight);
79    let depth = top_height.saturating_sub(height);
80
81    let (cumulative_difficulty_top64, cumulative_difficulty) =
82        split_u128_into_low_high_bits(header.cumulative_difficulty);
83    let (difficulty_top64, difficulty) = split_u128_into_low_high_bits(difficulty);
84
85    let reward = block
86        .miner_transaction
87        .prefix()
88        .outputs
89        .iter()
90        .map(|o| o.amount.expect("coinbase is transparent"))
91        .sum::<u64>();
92
93    Ok(cuprate_types::rpc::BlockHeader {
94        block_weight,
95        cumulative_difficulty_top64,
96        cumulative_difficulty,
97        depth,
98        difficulty_top64,
99        difficulty,
100        hash: block.hash(),
101        height,
102        long_term_weight: usize_to_u64(header.long_term_weight),
103        major_version: header.version,
104        miner_tx_hash: block.miner_transaction.hash(),
105        minor_version: header.vote,
106        nonce: block.header.nonce,
107        num_txes: usize_to_u64(block.transactions.len()),
108        orphan_status,
109        pow_hash,
110        prev_hash: block.header.previous,
111        reward,
112        timestamp: block.header.timestamp,
113    }
114    .into())
115}
116
117/// Same as [`block_header`] but with the block's hash.
118pub(super) async fn block_header_by_hash(
119    state: &mut CupratedRpcHandler,
120    hash: [u8; 32],
121    fill_pow_hash: bool,
122) -> Result<BlockHeader, Error> {
123    let (_, height) = blockchain::find_block(&mut state.blockchain_read, hash)
124        .await?
125        .ok_or_else(|| anyhow!("Block did not exist."))?;
126
127    let block_header = block_header(state, usize_to_u64(height), fill_pow_hash).await?;
128
129    Ok(block_header)
130}
131
132/// Check if `height` is greater than the [`top_height`].
133///
134/// # Errors
135/// This returns the [`top_height`] on [`Ok`] and
136/// returns [`Error`] if `height` is greater than [`top_height`].
137pub(super) async fn check_height(
138    state: &mut CupratedRpcHandler,
139    height: u64,
140) -> Result<u64, Error> {
141    let (top_height, _) = top_height(state).await?;
142
143    if height > top_height {
144        return Err(anyhow!(
145            "Requested block height: {height} greater than top block height: {top_height}",
146        ));
147    }
148
149    Ok(top_height)
150}
151
152/// Parse a hexadecimal [`String`] as a 32-byte hash.
153#[expect(clippy::needless_pass_by_value)]
154pub(super) fn hex_to_hash(hex: String) -> Result<[u8; 32], Error> {
155    let error = || anyhow!("Failed to parse hex representation of hash. Hex = {hex}.");
156
157    let Ok(bytes) = hex::decode(&hex) else {
158        return Err(error());
159    };
160
161    let Ok(hash) = bytes.try_into() else {
162        return Err(error());
163    };
164
165    Ok(hash)
166}
167
168/// [`cuprate_types::blockchain::BlockchainResponse::ChainHeight`] minus 1.
169pub(super) async fn top_height(state: &mut CupratedRpcHandler) -> Result<(u64, [u8; 32]), Error> {
170    let (chain_height, hash) = blockchain::chain_height(&mut state.blockchain_read).await?;
171    let height = chain_height.checked_sub(1).unwrap();
172    Ok((height, hash))
173}
174
175/// TODO: impl bootstrap
176pub const fn response_base(is_bootstrap: bool) -> ResponseBase {
177    if is_bootstrap {
178        ResponseBase::OK_UNTRUSTED
179    } else {
180        ResponseBase::OK
181    }
182}
183
184/// TODO: impl bootstrap
185pub const fn access_response_base(is_bootstrap: bool) -> AccessResponseBase {
186    if is_bootstrap {
187        AccessResponseBase::OK_UNTRUSTED
188    } else {
189        AccessResponseBase::OK
190    }
191}