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::{Chain, 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 =
59            blockchain::block_hash(&mut state.blockchain_read, height, Chain::Main).await?;
60
61        Some(
62            blockchain_context::calculate_pow(
63                &mut state.blockchain_context,
64                hardfork,
65                // TODO: expensive clone
66                block.clone(),
67                seed_hash,
68            )
69            .await?,
70        )
71    } else {
72        None
73    };
74
75    let block_weight = usize_to_u64(header.block_weight);
76    let depth = top_height.saturating_sub(height);
77
78    let (cumulative_difficulty_top64, cumulative_difficulty) =
79        split_u128_into_low_high_bits(header.cumulative_difficulty);
80    let (difficulty_top64, difficulty) = split_u128_into_low_high_bits(difficulty);
81
82    let reward = block
83        .miner_transaction
84        .prefix()
85        .outputs
86        .iter()
87        .map(|o| o.amount.expect("coinbase is transparent"))
88        .sum::<u64>();
89
90    Ok(cuprate_types::rpc::BlockHeader {
91        block_weight,
92        cumulative_difficulty_top64,
93        cumulative_difficulty,
94        depth,
95        difficulty_top64,
96        difficulty,
97        hash: block.hash(),
98        height,
99        long_term_weight: usize_to_u64(header.long_term_weight),
100        major_version: header.version,
101        miner_tx_hash: block.miner_transaction.hash(),
102        minor_version: header.vote,
103        nonce: block.header.nonce,
104        num_txes: usize_to_u64(block.transactions.len()),
105        orphan_status,
106        pow_hash,
107        prev_hash: block.header.previous,
108        reward,
109        timestamp: block.header.timestamp,
110    }
111    .into())
112}
113
114/// Same as [`block_header`] but with the block's hash.
115pub(super) async fn block_header_by_hash(
116    state: &mut CupratedRpcHandler,
117    hash: [u8; 32],
118    fill_pow_hash: bool,
119) -> Result<BlockHeader, Error> {
120    let (_, height) = blockchain::find_block(&mut state.blockchain_read, hash)
121        .await?
122        .ok_or_else(|| anyhow!("Block did not exist."))?;
123
124    let block_header = block_header(state, usize_to_u64(height), fill_pow_hash).await?;
125
126    Ok(block_header)
127}
128
129/// Check if `height` is greater than the [`top_height`].
130///
131/// # Errors
132/// This returns the [`top_height`] on [`Ok`] and
133/// returns [`Error`] if `height` is greater than [`top_height`].
134pub(super) async fn check_height(
135    state: &mut CupratedRpcHandler,
136    height: u64,
137) -> Result<u64, Error> {
138    let (top_height, _) = top_height(state).await?;
139
140    if height > top_height {
141        return Err(anyhow!(
142            "Requested block height: {height} greater than top block height: {top_height}",
143        ));
144    }
145
146    Ok(top_height)
147}
148
149/// Parse a hexadecimal [`String`] as a 32-byte hash.
150#[expect(clippy::needless_pass_by_value)]
151pub(super) fn hex_to_hash(hex: String) -> Result<[u8; 32], Error> {
152    let error = || anyhow!("Failed to parse hex representation of hash. Hex = {hex}.");
153
154    let Ok(bytes) = hex::decode(&hex) else {
155        return Err(error());
156    };
157
158    let Ok(hash) = bytes.try_into() else {
159        return Err(error());
160    };
161
162    Ok(hash)
163}
164
165/// [`cuprate_types::blockchain::BlockchainResponse::ChainHeight`] minus 1.
166pub(super) async fn top_height(state: &mut CupratedRpcHandler) -> Result<(u64, [u8; 32]), Error> {
167    let (chain_height, hash) = blockchain::chain_height(&mut state.blockchain_read).await?;
168    let height = chain_height.checked_sub(1).unwrap();
169    Ok((height, hash))
170}
171
172/// TODO: impl bootstrap
173pub const fn response_base(is_bootstrap: bool) -> ResponseBase {
174    if is_bootstrap {
175        ResponseBase::OK_UNTRUSTED
176    } else {
177        ResponseBase::OK
178    }
179}
180
181/// TODO: impl bootstrap
182pub const fn access_response_base(is_bootstrap: bool) -> AccessResponseBase {
183    if is_bootstrap {
184        AccessResponseBase::OK_UNTRUSTED
185    } else {
186        AccessResponseBase::OK
187    }
188}