cuprated/rpc/handlers/
bin.rs

1//! RPC request handler functions (binary endpoints).
2//!
3//! TODO:
4//! Some handlers have `todo!()`s for other Cuprate internals that must be completed, see:
5//! <https://github.com/Cuprate/cuprate/pull/355>
6
7use std::num::NonZero;
8
9use anyhow::{anyhow, Error};
10use bytes::Bytes;
11
12use cuprate_constants::rpc::{RESTRICTED_BLOCK_COUNT, RESTRICTED_TRANSACTIONS_COUNT};
13use cuprate_fixed_bytes::ByteArrayVec;
14use cuprate_helper::cast::{u64_to_usize, usize_to_u64};
15use cuprate_rpc_interface::RpcHandler;
16use cuprate_rpc_types::{
17    base::{AccessResponseBase, ResponseBase},
18    bin::{
19        BinRequest, BinResponse, GetBlocksByHeightRequest, GetBlocksByHeightResponse,
20        GetBlocksRequest, GetBlocksResponse, GetHashesRequest, GetHashesResponse,
21        GetOutputIndexesRequest, GetOutputIndexesResponse, GetOutsRequest, GetOutsResponse,
22        GetTransactionPoolHashesRequest, GetTransactionPoolHashesResponse,
23    },
24    json::{GetOutputDistributionRequest, GetOutputDistributionResponse},
25    misc::RequestedInfo,
26};
27use cuprate_types::{
28    rpc::{PoolInfo, PoolInfoExtent},
29    BlockCompleteEntry,
30};
31
32use crate::rpc::{
33    handlers::{helper, shared},
34    service::{blockchain, txpool},
35    CupratedRpcHandler,
36};
37
38/// Map a [`BinRequest`] to the function that will lead to a [`BinResponse`].
39pub async fn map_request(
40    state: CupratedRpcHandler,
41    request: BinRequest,
42) -> Result<BinResponse, Error> {
43    use BinRequest as Req;
44    use BinResponse as Resp;
45
46    Ok(match request {
47        Req::GetBlocks(r) => Resp::GetBlocks(get_blocks(state, r).await?),
48        Req::GetBlocksByHeight(r) => Resp::GetBlocksByHeight(get_blocks_by_height(state, r).await?),
49        Req::GetHashes(r) => Resp::GetHashes(get_hashes(state, r).await?),
50        Req::GetOutputIndexes(r) => Resp::GetOutputIndexes(get_output_indexes(state, r).await?),
51        Req::GetOuts(r) => Resp::GetOuts(get_outs(state, r).await?),
52        Req::GetTransactionPoolHashes(r) => {
53            Resp::GetTransactionPoolHashes(get_transaction_pool_hashes(state, r).await?)
54        }
55        Req::GetOutputDistribution(r) => {
56            Resp::GetOutputDistribution(get_output_distribution(state, r).await?)
57        }
58    })
59}
60
61/// <https://github.com/monero-project/monero/blob/cc73fe71162d564ffda8e549b79a350bca53c454/src/rpc/core_rpc_server.cpp#L611-L789>
62async fn get_blocks(
63    mut state: CupratedRpcHandler,
64    request: GetBlocksRequest,
65) -> Result<GetBlocksResponse, Error> {
66    // Time should be set early:
67    // <https://github.com/monero-project/monero/blob/cc73fe71162d564ffda8e549b79a350bca53c454/src/rpc/core_rpc_server.cpp#L628-L631>
68    let daemon_time = cuprate_helper::time::current_unix_timestamp();
69
70    let GetBlocksRequest {
71        requested_info,
72        block_ids,
73        start_height,
74        prune,
75        no_miner_tx,
76        pool_info_since,
77    } = request;
78
79    let block_hashes: Vec<[u8; 32]> = (&block_ids).into();
80    drop(block_ids);
81
82    let Some(requested_info) = RequestedInfo::from_u8(request.requested_info) else {
83        return Err(anyhow!("Wrong requested info"));
84    };
85
86    let (get_blocks, get_pool) = match requested_info {
87        RequestedInfo::BlocksOnly => (true, false),
88        RequestedInfo::BlocksAndPool => (true, true),
89        RequestedInfo::PoolOnly => (false, true),
90    };
91
92    let pool_info_extent = PoolInfoExtent::None;
93
94    let pool_info = if get_pool {
95        let is_restricted = state.is_restricted();
96        let include_sensitive_txs = !is_restricted;
97
98        let max_tx_count = if is_restricted {
99            RESTRICTED_TRANSACTIONS_COUNT
100        } else {
101            usize::MAX
102        };
103
104        txpool::pool_info(
105            &mut state.txpool_read,
106            include_sensitive_txs,
107            max_tx_count,
108            NonZero::new(u64_to_usize(request.pool_info_since)),
109        )
110        .await?
111    } else {
112        PoolInfo::None
113    };
114
115    let resp = GetBlocksResponse {
116        base: helper::access_response_base(false),
117        blocks: vec![],
118        start_height: 0,
119        current_height: 0,
120        output_indices: vec![],
121        daemon_time,
122        pool_info,
123    };
124
125    if !get_blocks {
126        return Ok(resp);
127    }
128
129    if let Some(block_id) = block_hashes.first() {
130        let (height, hash) = helper::top_height(&mut state).await?;
131
132        if hash == *block_id {
133            return Ok(GetBlocksResponse {
134                current_height: height + 1,
135                ..resp
136            });
137        }
138    }
139
140    let (block_hashes, start_height, _) =
141        blockchain::next_chain_entry(&mut state.blockchain_read, block_hashes, start_height)
142            .await?;
143
144    if start_height.is_none() {
145        return Err(anyhow!("Block IDs were not sorted properly"));
146    }
147
148    let (blocks, missing_hashes, height) =
149        blockchain::block_complete_entries(&mut state.blockchain_read, block_hashes).await?;
150
151    if !missing_hashes.is_empty() {
152        return Err(anyhow!("Missing blocks"));
153    }
154
155    Ok(GetBlocksResponse {
156        blocks,
157        current_height: usize_to_u64(height),
158        ..resp
159    })
160}
161
162/// <https://github.com/monero-project/monero/blob/cc73fe71162d564ffda8e549b79a350bca53c454/src/rpc/core_rpc_server.cpp#L817-L857>
163async fn get_blocks_by_height(
164    mut state: CupratedRpcHandler,
165    request: GetBlocksByHeightRequest,
166) -> Result<GetBlocksByHeightResponse, Error> {
167    if state.is_restricted() && request.heights.len() > RESTRICTED_BLOCK_COUNT {
168        return Err(anyhow!("Too many blocks requested in restricted mode"));
169    }
170
171    let blocks =
172        blockchain::block_complete_entries_by_height(&mut state.blockchain_read, request.heights)
173            .await?;
174
175    Ok(GetBlocksByHeightResponse {
176        base: helper::access_response_base(false),
177        blocks,
178    })
179}
180
181/// <https://github.com/monero-project/monero/blob/cc73fe71162d564ffda8e549b79a350bca53c454/src/rpc/core_rpc_server.cpp#L859-L880>
182async fn get_hashes(
183    mut state: CupratedRpcHandler,
184    request: GetHashesRequest,
185) -> Result<GetHashesResponse, Error> {
186    let GetHashesRequest {
187        start_height,
188        block_ids,
189    } = request;
190
191    // FIXME: impl `last()`
192    let last = {
193        let len = block_ids.len();
194
195        if len == 0 {
196            return Err(anyhow!("block_ids empty"));
197        }
198
199        block_ids[len - 1]
200    };
201
202    let hashes: Vec<[u8; 32]> = (&block_ids).into();
203
204    let (m_blocks_ids, _, current_height) =
205        blockchain::next_chain_entry(&mut state.blockchain_read, hashes, start_height).await?;
206
207    Ok(GetHashesResponse {
208        base: helper::access_response_base(false),
209        m_blocks_ids: m_blocks_ids.into(),
210        current_height: usize_to_u64(current_height),
211        start_height,
212    })
213}
214
215/// <https://github.com/monero-project/monero/blob/cc73fe71162d564ffda8e549b79a350bca53c454/src/rpc/core_rpc_server.cpp#L959-L977>
216async fn get_output_indexes(
217    mut state: CupratedRpcHandler,
218    request: GetOutputIndexesRequest,
219) -> Result<GetOutputIndexesResponse, Error> {
220    Ok(GetOutputIndexesResponse {
221        base: helper::access_response_base(false),
222        o_indexes: blockchain::tx_output_indexes(&mut state.blockchain_read, request.txid).await?,
223    })
224}
225
226/// <https://github.com/monero-project/monero/blob/cc73fe71162d564ffda8e549b79a350bca53c454/src/rpc/core_rpc_server.cpp#L882-L910>
227async fn get_outs(
228    state: CupratedRpcHandler,
229    request: GetOutsRequest,
230) -> Result<GetOutsResponse, Error> {
231    shared::get_outs(state, request).await
232}
233
234/// <https://github.com/monero-project/monero/blob/cc73fe71162d564ffda8e549b79a350bca53c454/src/rpc/core_rpc_server.cpp#L1689-L1711>
235async fn get_transaction_pool_hashes(
236    mut state: CupratedRpcHandler,
237    _: GetTransactionPoolHashesRequest,
238) -> Result<GetTransactionPoolHashesResponse, Error> {
239    Ok(GetTransactionPoolHashesResponse {
240        base: helper::access_response_base(false),
241        tx_hashes: shared::get_transaction_pool_hashes(state)
242            .await
243            .map(ByteArrayVec::from)?,
244    })
245}
246
247/// <https://github.com/monero-project/monero/blob/cc73fe71162d564ffda8e549b79a350bca53c454/src/rpc/core_rpc_server.cpp#L3352-L3398>
248async fn get_output_distribution(
249    state: CupratedRpcHandler,
250    request: GetOutputDistributionRequest,
251) -> Result<GetOutputDistributionResponse, Error> {
252    shared::get_output_distribution(state, request).await
253}