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, shared::not_available},
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(not_available()?),
48        Req::GetBlocksByHeight(r) => Resp::GetBlocksByHeight(not_available()?),
49        Req::GetHashes(r) => Resp::GetHashes(not_available()?),
50        Req::GetOutputIndexes(r) => Resp::GetOutputIndexes(not_available()?),
51        Req::GetOuts(r) => Resp::GetOuts(not_available()?),
52        Req::GetTransactionPoolHashes(r) => Resp::GetTransactionPoolHashes(not_available()?),
53        Req::GetOutputDistribution(r) => Resp::GetOutputDistribution(not_available()?),
54    })
55}
56
57/// <https://github.com/monero-project/monero/blob/cc73fe71162d564ffda8e549b79a350bca53c454/src/rpc/core_rpc_server.cpp#L611-L789>
58async fn get_blocks(
59    mut state: CupratedRpcHandler,
60    request: GetBlocksRequest,
61) -> Result<GetBlocksResponse, Error> {
62    // Time should be set early:
63    // <https://github.com/monero-project/monero/blob/cc73fe71162d564ffda8e549b79a350bca53c454/src/rpc/core_rpc_server.cpp#L628-L631>
64    let daemon_time = cuprate_helper::time::current_unix_timestamp();
65
66    let GetBlocksRequest {
67        requested_info,
68        block_ids,
69        start_height,
70        prune,
71        no_miner_tx,
72        pool_info_since,
73    } = request;
74
75    let block_hashes: Vec<[u8; 32]> = (&block_ids).into();
76    drop(block_ids);
77
78    let Some(requested_info) = RequestedInfo::from_u8(request.requested_info) else {
79        return Err(anyhow!("Wrong requested info"));
80    };
81
82    let (get_blocks, get_pool) = match requested_info {
83        RequestedInfo::BlocksOnly => (true, false),
84        RequestedInfo::BlocksAndPool => (true, true),
85        RequestedInfo::PoolOnly => (false, true),
86    };
87
88    let pool_info_extent = PoolInfoExtent::None;
89
90    let pool_info = if get_pool {
91        let is_restricted = state.is_restricted();
92        let include_sensitive_txs = !is_restricted;
93
94        let max_tx_count = if is_restricted {
95            RESTRICTED_TRANSACTIONS_COUNT
96        } else {
97            usize::MAX
98        };
99
100        txpool::pool_info(
101            &mut state.txpool_read,
102            include_sensitive_txs,
103            max_tx_count,
104            NonZero::new(u64_to_usize(request.pool_info_since)),
105        )
106        .await?
107    } else {
108        PoolInfo::None
109    };
110
111    let resp = GetBlocksResponse {
112        base: helper::access_response_base(false),
113        blocks: vec![],
114        start_height: 0,
115        current_height: 0,
116        output_indices: vec![],
117        daemon_time,
118        pool_info,
119    };
120
121    if !get_blocks {
122        return Ok(resp);
123    }
124
125    if let Some(block_id) = block_hashes.first() {
126        let (height, hash) = helper::top_height(&mut state).await?;
127
128        if hash == *block_id {
129            return Ok(GetBlocksResponse {
130                current_height: height + 1,
131                ..resp
132            });
133        }
134    }
135
136    let (block_hashes, start_height, _) =
137        blockchain::next_chain_entry(&mut state.blockchain_read, block_hashes, start_height)
138            .await?;
139
140    if start_height.is_none() {
141        return Err(anyhow!("Block IDs were not sorted properly"));
142    }
143
144    let (blocks, missing_hashes, height) =
145        blockchain::block_complete_entries(&mut state.blockchain_read, block_hashes).await?;
146
147    if !missing_hashes.is_empty() {
148        return Err(anyhow!("Missing blocks"));
149    }
150
151    Ok(GetBlocksResponse {
152        blocks,
153        current_height: usize_to_u64(height),
154        ..resp
155    })
156}
157
158/// <https://github.com/monero-project/monero/blob/cc73fe71162d564ffda8e549b79a350bca53c454/src/rpc/core_rpc_server.cpp#L817-L857>
159async fn get_blocks_by_height(
160    mut state: CupratedRpcHandler,
161    request: GetBlocksByHeightRequest,
162) -> Result<GetBlocksByHeightResponse, Error> {
163    if state.is_restricted() && request.heights.len() > RESTRICTED_BLOCK_COUNT {
164        return Err(anyhow!("Too many blocks requested in restricted mode"));
165    }
166
167    let blocks =
168        blockchain::block_complete_entries_by_height(&mut state.blockchain_read, request.heights)
169            .await?;
170
171    Ok(GetBlocksByHeightResponse {
172        base: helper::access_response_base(false),
173        blocks,
174    })
175}
176
177/// <https://github.com/monero-project/monero/blob/cc73fe71162d564ffda8e549b79a350bca53c454/src/rpc/core_rpc_server.cpp#L859-L880>
178async fn get_hashes(
179    mut state: CupratedRpcHandler,
180    request: GetHashesRequest,
181) -> Result<GetHashesResponse, Error> {
182    let GetHashesRequest {
183        start_height,
184        block_ids,
185    } = request;
186
187    // FIXME: impl `last()`
188    let last = {
189        let len = block_ids.len();
190
191        if len == 0 {
192            return Err(anyhow!("block_ids empty"));
193        }
194
195        block_ids[len - 1]
196    };
197
198    let hashes: Vec<[u8; 32]> = (&block_ids).into();
199
200    let (m_blocks_ids, _, current_height) =
201        blockchain::next_chain_entry(&mut state.blockchain_read, hashes, start_height).await?;
202
203    Ok(GetHashesResponse {
204        base: helper::access_response_base(false),
205        m_blocks_ids: m_blocks_ids.into(),
206        current_height: usize_to_u64(current_height),
207        start_height,
208    })
209}
210
211/// <https://github.com/monero-project/monero/blob/cc73fe71162d564ffda8e549b79a350bca53c454/src/rpc/core_rpc_server.cpp#L959-L977>
212async fn get_output_indexes(
213    mut state: CupratedRpcHandler,
214    request: GetOutputIndexesRequest,
215) -> Result<GetOutputIndexesResponse, Error> {
216    Ok(GetOutputIndexesResponse {
217        base: helper::access_response_base(false),
218        o_indexes: blockchain::tx_output_indexes(&mut state.blockchain_read, request.txid).await?,
219    })
220}
221
222/// <https://github.com/monero-project/monero/blob/cc73fe71162d564ffda8e549b79a350bca53c454/src/rpc/core_rpc_server.cpp#L882-L910>
223async fn get_outs(
224    state: CupratedRpcHandler,
225    request: GetOutsRequest,
226) -> Result<GetOutsResponse, Error> {
227    shared::get_outs(state, request).await
228}
229
230/// <https://github.com/monero-project/monero/blob/cc73fe71162d564ffda8e549b79a350bca53c454/src/rpc/core_rpc_server.cpp#L1689-L1711>
231async fn get_transaction_pool_hashes(
232    mut state: CupratedRpcHandler,
233    _: GetTransactionPoolHashesRequest,
234) -> Result<GetTransactionPoolHashesResponse, Error> {
235    Ok(GetTransactionPoolHashesResponse {
236        base: helper::access_response_base(false),
237        tx_hashes: shared::get_transaction_pool_hashes(state)
238            .await
239            .map(ByteArrayVec::from)?,
240    })
241}
242
243/// <https://github.com/monero-project/monero/blob/cc73fe71162d564ffda8e549b79a350bca53c454/src/rpc/core_rpc_server.cpp#L3352-L3398>
244async fn get_output_distribution(
245    state: CupratedRpcHandler,
246    request: GetOutputDistributionRequest,
247) -> Result<GetOutputDistributionResponse, Error> {
248    shared::get_output_distribution(state, request).await
249}