cuprated/rpc/handlers/
other_json.rs

1//! RPC request handler functions (other JSON 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::{
8    borrow::Cow,
9    collections::{BTreeSet, HashMap, HashSet},
10};
11
12use anyhow::{anyhow, Error};
13use monero_serai::transaction::{Input, Timelock, Transaction};
14
15use cuprate_constants::rpc::{
16    MAX_RESTRICTED_GLOBAL_FAKE_OUTS_COUNT, RESTRICTED_SPENT_KEY_IMAGES_COUNT,
17    RESTRICTED_TRANSACTIONS_COUNT,
18};
19use cuprate_helper::cast::usize_to_u64;
20use cuprate_hex::{Hex, HexVec};
21use cuprate_p2p_core::{client::handshaker::builder::DummyAddressBook, ClearNet};
22use cuprate_rpc_interface::RpcHandler;
23use cuprate_rpc_types::{
24    base::{AccessResponseBase, ResponseBase},
25    misc::{Status, TxEntry, TxEntryType},
26    other::{
27        GetAltBlocksHashesRequest, GetAltBlocksHashesResponse, GetHeightRequest, GetHeightResponse,
28        GetLimitRequest, GetLimitResponse, GetNetStatsRequest, GetNetStatsResponse, GetOutsRequest,
29        GetOutsResponse, GetPeerListRequest, GetPeerListResponse, GetPublicNodesRequest,
30        GetPublicNodesResponse, GetTransactionPoolHashesRequest, GetTransactionPoolHashesResponse,
31        GetTransactionPoolRequest, GetTransactionPoolResponse, GetTransactionPoolStatsRequest,
32        GetTransactionPoolStatsResponse, GetTransactionsRequest, GetTransactionsResponse,
33        InPeersRequest, InPeersResponse, IsKeyImageSpentRequest, IsKeyImageSpentResponse,
34        MiningStatusRequest, MiningStatusResponse, OtherRequest, OtherResponse, OutPeersRequest,
35        OutPeersResponse, PopBlocksRequest, PopBlocksResponse, SaveBcRequest, SaveBcResponse,
36        SendRawTransactionRequest, SendRawTransactionResponse, SetBootstrapDaemonRequest,
37        SetBootstrapDaemonResponse, SetLimitRequest, SetLimitResponse, SetLogCategoriesRequest,
38        SetLogCategoriesResponse, SetLogHashRateRequest, SetLogHashRateResponse,
39        SetLogLevelRequest, SetLogLevelResponse, StartMiningRequest, StartMiningResponse,
40        StopDaemonRequest, StopDaemonResponse, StopMiningRequest, StopMiningResponse,
41        UpdateRequest, UpdateResponse,
42    },
43};
44use cuprate_types::{
45    rpc::{KeyImageSpentStatus, PoolInfo, PoolTxInfo, PublicNode},
46    TxInPool, TxRelayChecks,
47};
48
49use crate::{
50    rpc::{
51        constants::UNSUPPORTED_RPC_CALL,
52        handlers::{helper, shared},
53        service::{address_book, blockchain, blockchain_context, blockchain_manager, txpool},
54        CupratedRpcHandler,
55    },
56    statics::START_INSTANT_UNIX,
57};
58
59/// Map a [`OtherRequest`] to the function that will lead to a [`OtherResponse`].
60pub async fn map_request(
61    state: CupratedRpcHandler,
62    request: OtherRequest,
63) -> Result<OtherResponse, Error> {
64    use OtherRequest as Req;
65    use OtherResponse as Resp;
66
67    Ok(match request {
68        Req::GetHeight(r) => Resp::GetHeight(get_height(state, r).await?),
69        Req::GetTransactions(r) => Resp::GetTransactions(get_transactions(state, r).await?),
70        Req::GetAltBlocksHashes(r) => {
71            Resp::GetAltBlocksHashes(get_alt_blocks_hashes(state, r).await?)
72        }
73        Req::IsKeyImageSpent(r) => Resp::IsKeyImageSpent(is_key_image_spent(state, r).await?),
74        Req::SendRawTransaction(r) => {
75            Resp::SendRawTransaction(send_raw_transaction(state, r).await?)
76        }
77        Req::SaveBc(r) => Resp::SaveBc(save_bc(state, r).await?),
78        Req::GetPeerList(r) => Resp::GetPeerList(get_peer_list(state, r).await?),
79        Req::SetLogLevel(r) => Resp::SetLogLevel(set_log_level(state, r).await?),
80        Req::SetLogCategories(r) => Resp::SetLogCategories(set_log_categories(state, r).await?),
81        Req::GetTransactionPool(r) => {
82            Resp::GetTransactionPool(get_transaction_pool(state, r).await?)
83        }
84        Req::GetTransactionPoolStats(r) => {
85            Resp::GetTransactionPoolStats(get_transaction_pool_stats(state, r).await?)
86        }
87        Req::StopDaemon(r) => Resp::StopDaemon(stop_daemon(state, r).await?),
88        Req::GetLimit(r) => Resp::GetLimit(get_limit(state, r).await?),
89        Req::SetLimit(r) => Resp::SetLimit(set_limit(state, r).await?),
90        Req::OutPeers(r) => Resp::OutPeers(out_peers(state, r).await?),
91        Req::InPeers(r) => Resp::InPeers(in_peers(state, r).await?),
92        Req::GetNetStats(r) => Resp::GetNetStats(get_net_stats(state, r).await?),
93        Req::GetOuts(r) => Resp::GetOuts(get_outs(state, r).await?),
94        Req::PopBlocks(r) => Resp::PopBlocks(pop_blocks(state, r).await?),
95        Req::GetTransactionPoolHashes(r) => {
96            Resp::GetTransactionPoolHashes(get_transaction_pool_hashes(state, r).await?)
97        }
98        Req::GetPublicNodes(r) => Resp::GetPublicNodes(get_public_nodes(state, r).await?),
99
100        // Unsupported requests.
101        Req::SetBootstrapDaemon(_)
102        | Req::Update(_)
103        | Req::StartMining(_)
104        | Req::StopMining(_)
105        | Req::MiningStatus(_)
106        | Req::SetLogHashRate(_) => return Err(anyhow!(UNSUPPORTED_RPC_CALL)),
107    })
108}
109
110/// <https://github.com/monero-project/monero/blob/cc73fe71162d564ffda8e549b79a350bca53c454/src/rpc/core_rpc_server.cpp#L486-L499>
111async fn get_height(
112    mut state: CupratedRpcHandler,
113    _: GetHeightRequest,
114) -> Result<GetHeightResponse, Error> {
115    let (height, hash) = helper::top_height(&mut state).await?;
116    let hash = Hex(hash);
117
118    Ok(GetHeightResponse {
119        base: helper::response_base(false),
120        height,
121        hash,
122    })
123}
124
125/// <https://github.com/monero-project/monero/blob/cc73fe71162d564ffda8e549b79a350bca53c454/src/rpc/core_rpc_server.cpp#L979-L1227>
126async fn get_transactions(
127    mut state: CupratedRpcHandler,
128    request: GetTransactionsRequest,
129) -> Result<GetTransactionsResponse, Error> {
130    if state.is_restricted() && request.txs_hashes.len() > RESTRICTED_TRANSACTIONS_COUNT {
131        return Err(anyhow!(
132            "Too many transactions requested in restricted mode"
133        ));
134    }
135
136    let (txs_in_blockchain, missed_txs) = {
137        let requested_txs = request.txs_hashes.into_iter().map(|tx| tx.0).collect();
138        blockchain::transactions(&mut state.blockchain_read, requested_txs).await?
139    };
140
141    let missed_tx = missed_txs.clone().into_iter().map(Hex).collect();
142
143    // Check the txpool for missed transactions.
144    let txs_in_pool = if missed_txs.is_empty() {
145        vec![]
146    } else {
147        let include_sensitive_txs = !state.is_restricted();
148        txpool::txs_by_hash(&mut state.txpool_read, missed_txs, include_sensitive_txs).await?
149    };
150
151    let (txs, txs_as_hex, txs_as_json) = {
152        // Prepare the final JSON output.
153        let len = txs_in_blockchain.len() + txs_in_pool.len();
154        let mut txs = Vec::with_capacity(len);
155        let mut txs_as_hex = Vec::with_capacity(len);
156        let mut txs_as_json = Vec::with_capacity(if request.decode_as_json { len } else { 0 });
157
158        // Map all blockchain transactions.
159        for tx in txs_in_blockchain {
160            let tx_hash = Hex(tx.tx_hash);
161            let prunable_hash = Hex(tx.prunable_hash);
162
163            let (pruned_as_hex, prunable_as_hex) = if tx.pruned_blob.is_empty() {
164                (HexVec::new(), HexVec::new())
165            } else {
166                (HexVec(tx.pruned_blob), HexVec(tx.prunable_blob))
167            };
168
169            let as_hex = if pruned_as_hex.is_empty() {
170                // `monerod` will insert a `""` into the `txs_as_hex` array for pruned transactions.
171                // curl http://127.0.0.1:18081/get_transactions -d '{"txs_hashes":["4c8b98753d1577d225a497a50f453827cff3aa023a4add60ec4ce4f923f75de8"]}' -H 'Content-Type: application/json'
172                HexVec::new()
173            } else {
174                HexVec(tx.tx_blob)
175            };
176
177            txs_as_hex.push(as_hex.clone());
178
179            let as_json = if request.decode_as_json {
180                let tx = Transaction::read(&mut as_hex.as_slice())?;
181                let json_type = cuprate_types::json::tx::Transaction::from(tx);
182                let json = serde_json::to_string(&json_type).unwrap();
183                txs_as_json.push(json.clone());
184                json
185            } else {
186                String::new()
187            };
188
189            let tx_entry_type = TxEntryType::Blockchain {
190                block_height: tx.block_height,
191                block_timestamp: tx.block_timestamp,
192                confirmations: tx.confirmations,
193                output_indices: tx.output_indices,
194                in_pool: false,
195            };
196
197            let tx = TxEntry {
198                as_hex,
199                as_json,
200                double_spend_seen: false,
201                tx_hash,
202                prunable_as_hex,
203                prunable_hash,
204                pruned_as_hex,
205                tx_entry_type,
206            };
207
208            txs.push(tx);
209        }
210
211        // Map all txpool transactions.
212        for tx_in_pool in txs_in_pool {
213            let TxInPool {
214                tx_blob,
215                tx_hash,
216                double_spend_seen,
217                received_timestamp,
218                relayed,
219            } = tx_in_pool;
220
221            let tx_hash = Hex(tx_hash);
222            let tx = Transaction::read(&mut tx_blob.as_slice())?;
223
224            let pruned_as_hex = HexVec::new();
225            let prunable_as_hex = HexVec::new();
226            let prunable_hash = Hex([0; 32]);
227
228            let as_hex = HexVec(tx_blob);
229            txs_as_hex.push(as_hex.clone());
230
231            let as_json = if request.decode_as_json {
232                let json_type = cuprate_types::json::tx::Transaction::from(tx);
233                let json = serde_json::to_string(&json_type).unwrap();
234                txs_as_json.push(json.clone());
235                json
236            } else {
237                String::new()
238            };
239
240            let tx_entry_type = TxEntryType::Pool {
241                relayed,
242                received_timestamp,
243                in_pool: true,
244            };
245
246            let tx = TxEntry {
247                as_hex,
248                as_json,
249                double_spend_seen,
250                tx_hash,
251                prunable_as_hex,
252                prunable_hash,
253                pruned_as_hex,
254                tx_entry_type,
255            };
256
257            txs.push(tx);
258        }
259
260        (txs, txs_as_hex, txs_as_json)
261    };
262
263    Ok(GetTransactionsResponse {
264        base: helper::access_response_base(false),
265        txs_as_hex,
266        txs_as_json,
267        missed_tx,
268        txs,
269    })
270}
271
272/// <https://github.com/monero-project/monero/blob/cc73fe71162d564ffda8e549b79a350bca53c454/src/rpc/core_rpc_server.cpp#L790-L815>
273async fn get_alt_blocks_hashes(
274    mut state: CupratedRpcHandler,
275    _: GetAltBlocksHashesRequest,
276) -> Result<GetAltBlocksHashesResponse, Error> {
277    let blks_hashes = blockchain::alt_chains(&mut state.blockchain_read)
278        .await?
279        .into_iter()
280        .map(|info| Hex(info.block_hash))
281        .collect();
282
283    Ok(GetAltBlocksHashesResponse {
284        base: helper::access_response_base(false),
285        blks_hashes,
286    })
287}
288
289/// <https://github.com/monero-project/monero/blob/cc73fe71162d564ffda8e549b79a350bca53c454/src/rpc/core_rpc_server.cpp#L1229-L1305>
290async fn is_key_image_spent(
291    mut state: CupratedRpcHandler,
292    request: IsKeyImageSpentRequest,
293) -> Result<IsKeyImageSpentResponse, Error> {
294    let restricted = state.is_restricted();
295
296    if restricted && request.key_images.len() > RESTRICTED_SPENT_KEY_IMAGES_COUNT {
297        return Err(anyhow!("Too many key images queried in restricted mode"));
298    }
299
300    let key_images = request
301        .key_images
302        .into_iter()
303        .map(|k| k.0)
304        .collect::<Vec<[u8; 32]>>();
305
306    let mut spent_status = Vec::with_capacity(key_images.len());
307
308    // Check the blockchain for key image spend status.
309    blockchain::key_images_spent_vec(&mut state.blockchain_read, key_images.clone())
310        .await?
311        .into_iter()
312        .for_each(|ki| {
313            if ki {
314                spent_status.push(KeyImageSpentStatus::SpentInBlockchain);
315            } else {
316                spent_status.push(KeyImageSpentStatus::Unspent);
317            }
318        });
319
320    assert_eq!(spent_status.len(), key_images.len(), "key_images_spent() should be returning a Vec with an equal length to the input, the below zip() relies on this.");
321
322    // Filter the remaining unspent key images out from the vector.
323    let key_images = key_images
324        .into_iter()
325        .zip(&spent_status)
326        .filter_map(|(ki, status)| match status {
327            KeyImageSpentStatus::Unspent => Some(ki),
328            KeyImageSpentStatus::SpentInBlockchain => None,
329            KeyImageSpentStatus::SpentInPool => unreachable!(),
330        })
331        .collect::<Vec<[u8; 32]>>();
332
333    // Check if the remaining unspent key images exist in the transaction pool.
334    if !key_images.is_empty() {
335        txpool::key_images_spent_vec(&mut state.txpool_read, key_images, !restricted)
336            .await?
337            .into_iter()
338            .for_each(|ki| {
339                if ki {
340                    spent_status.push(KeyImageSpentStatus::SpentInPool);
341                } else {
342                    spent_status.push(KeyImageSpentStatus::Unspent);
343                }
344            });
345    }
346
347    let spent_status = spent_status
348        .into_iter()
349        .map(KeyImageSpentStatus::to_u8)
350        .collect();
351
352    Ok(IsKeyImageSpentResponse {
353        base: helper::access_response_base(false),
354        spent_status,
355    })
356}
357
358/// <https://github.com/monero-project/monero/blob/cc73fe71162d564ffda8e549b79a350bca53c454/src/rpc/core_rpc_server.cpp#L1307-L1411>
359async fn send_raw_transaction(
360    mut state: CupratedRpcHandler,
361    request: SendRawTransactionRequest,
362) -> Result<SendRawTransactionResponse, Error> {
363    let mut resp = SendRawTransactionResponse {
364        base: helper::access_response_base(false),
365        double_spend: false,
366        fee_too_low: false,
367        invalid_input: false,
368        invalid_output: false,
369        low_mixin: false,
370        nonzero_unlock_time: false,
371        not_relayed: request.do_not_relay,
372        overspend: false,
373        reason: String::new(),
374        sanity_check_failed: false,
375        too_big: false,
376        too_few_outputs: false,
377        tx_extra_too_big: false,
378    };
379
380    let tx = Transaction::read(&mut request.tx_as_hex.as_slice())?;
381
382    if request.do_sanity_checks {
383        /// FIXME: these checks could be defined elsewhere.
384        ///
385        /// <https://github.com/monero-project/monero/blob/893916ad091a92e765ce3241b94e706ad012b62a/src/cryptonote_core/tx_sanity_check.cpp#L42>
386        fn tx_sanity_check(tx: &Transaction, rct_outs_available: u64) -> Result<(), String> {
387            let Some(input) = tx.prefix().inputs.first() else {
388                return Err("No inputs".to_string());
389            };
390
391            let mut rct_indices = vec![];
392            let mut n_indices: usize = 0;
393
394            for input in &tx.prefix().inputs {
395                match input {
396                    Input::Gen(_) => return Err("Transaction is coinbase".to_string()),
397                    Input::ToKey {
398                        amount,
399                        key_offsets,
400                        key_image,
401                    } => {
402                        let Some(amount) = amount else {
403                            continue;
404                        };
405
406                        /// <https://github.com/monero-project/monero/blob/893916ad091a92e765ce3241b94e706ad012b62a/src/cryptonote_basic/cryptonote_format_utils.cpp#L1526>
407                        fn relative_output_offsets_to_absolute(mut offsets: Vec<u64>) -> Vec<u64> {
408                            assert!(!offsets.is_empty());
409
410                            for i in 1..offsets.len() {
411                                offsets[i] += offsets[i - 1];
412                            }
413
414                            offsets
415                        }
416
417                        n_indices += key_offsets.len();
418                        let absolute = relative_output_offsets_to_absolute(key_offsets.clone());
419                        rct_indices.extend(absolute);
420                    }
421                }
422            }
423
424            if n_indices <= 10 {
425                return Ok(());
426            }
427
428            if rct_outs_available < 10_000 {
429                return Ok(());
430            }
431
432            let rct_indices_len = rct_indices.len();
433            if rct_indices_len < n_indices * 8 / 10 {
434                return Err(format!("amount of unique indices is too low (amount of rct indices is {rct_indices_len} out of total {n_indices} indices."));
435            }
436
437            let median = cuprate_helper::num::median(rct_indices);
438            if median < rct_outs_available * 6 / 10 {
439                return Err(format!("median offset index is too low (median is {median} out of total {rct_outs_available} offsets). Transactions should contain a higher fraction of recent outputs."));
440            }
441
442            Ok(())
443        }
444
445        let rct_outs_available = blockchain::total_rct_outputs(&mut state.blockchain_read).await?;
446
447        if let Err(e) = tx_sanity_check(&tx, rct_outs_available) {
448            resp.base.response_base.status = Status::Failed;
449            resp.reason.push_str(&format!("Sanity check failed: {e}"));
450            resp.sanity_check_failed = true;
451            return Ok(resp);
452        }
453    }
454
455    let tx_relay_checks =
456        txpool::check_maybe_relay_local(&mut state.txpool_manager, tx, !request.do_not_relay)
457            .await?;
458
459    if tx_relay_checks.is_empty() {
460        return Ok(resp);
461    }
462
463    // <https://github.com/monero-project/monero/blob/cc73fe71162d564ffda8e549b79a350bca53c454/src/rpc/core_rpc_server.cpp#L124>
464    fn add_reason(reasons: &mut String, reason: &'static str) {
465        if !reasons.is_empty() {
466            reasons.push_str(", ");
467        }
468        reasons.push_str(reason);
469    }
470
471    let mut reasons = String::new();
472
473    #[rustfmt::skip]
474    let array = [
475        (&mut resp.double_spend, TxRelayChecks::DOUBLE_SPEND, "double spend"),
476        (&mut resp.fee_too_low, TxRelayChecks::FEE_TOO_LOW, "fee too low"),
477        (&mut resp.invalid_input, TxRelayChecks::INVALID_INPUT, "invalid input"),
478        (&mut resp.invalid_output, TxRelayChecks::INVALID_OUTPUT, "invalid output"),
479        (&mut resp.low_mixin, TxRelayChecks::LOW_MIXIN, "bad ring size"),
480        (&mut resp.nonzero_unlock_time, TxRelayChecks::NONZERO_UNLOCK_TIME, "tx unlock time is not zero"),
481        (&mut resp.overspend, TxRelayChecks::OVERSPEND, "overspend"),
482        (&mut resp.too_big, TxRelayChecks::TOO_BIG, "too big"),
483        (&mut resp.too_few_outputs, TxRelayChecks::TOO_FEW_OUTPUTS, "too few outputs"),
484        (&mut resp.tx_extra_too_big, TxRelayChecks::TX_EXTRA_TOO_BIG, "tx-extra too big"),
485    ];
486
487    for (field, flag, reason) in array {
488        if tx_relay_checks.contains(flag) {
489            *field = true;
490            add_reason(&mut reasons, reason);
491        }
492    }
493
494    Ok(resp)
495}
496
497/// <https://github.com/monero-project/monero/blob/cc73fe71162d564ffda8e549b79a350bca53c454/src/rpc/core_rpc_server.cpp#L1525-L1535>
498async fn save_bc(mut state: CupratedRpcHandler, _: SaveBcRequest) -> Result<SaveBcResponse, Error> {
499    blockchain_manager::sync(&mut state.blockchain_manager).await?;
500
501    Ok(SaveBcResponse {
502        base: ResponseBase::OK,
503    })
504}
505
506/// <https://github.com/monero-project/monero/blob/cc73fe71162d564ffda8e549b79a350bca53c454/src/rpc/core_rpc_server.cpp#L1537-L1582>
507async fn get_peer_list(
508    mut state: CupratedRpcHandler,
509    request: GetPeerListRequest,
510) -> Result<GetPeerListResponse, Error> {
511    let (white_list, gray_list) = address_book::peerlist::<ClearNet>(&mut DummyAddressBook).await?;
512
513    Ok(GetPeerListResponse {
514        base: helper::response_base(false),
515        white_list,
516        gray_list,
517    })
518}
519
520/// <https://github.com/monero-project/monero/blob/cc73fe71162d564ffda8e549b79a350bca53c454/src/rpc/core_rpc_server.cpp#L1663-L1687>
521async fn get_transaction_pool(
522    mut state: CupratedRpcHandler,
523    _: GetTransactionPoolRequest,
524) -> Result<GetTransactionPoolResponse, Error> {
525    let include_sensitive_txs = !state.is_restricted();
526
527    let (transactions, spent_key_images) =
528        txpool::pool(&mut state.txpool_read, include_sensitive_txs).await?;
529
530    Ok(GetTransactionPoolResponse {
531        base: helper::access_response_base(false),
532        transactions,
533        spent_key_images,
534    })
535}
536
537/// <https://github.com/monero-project/monero/blob/cc73fe71162d564ffda8e549b79a350bca53c454/src/rpc/core_rpc_server.cpp#L1741-L1756>
538async fn get_transaction_pool_stats(
539    mut state: CupratedRpcHandler,
540    _: GetTransactionPoolStatsRequest,
541) -> Result<GetTransactionPoolStatsResponse, Error> {
542    let include_sensitive_txs = !state.is_restricted();
543
544    let pool_stats = txpool::pool_stats(&mut state.txpool_read, include_sensitive_txs).await?;
545
546    Ok(GetTransactionPoolStatsResponse {
547        base: helper::access_response_base(false),
548        pool_stats,
549    })
550}
551
552/// <https://github.com/monero-project/monero/blob/cc73fe71162d564ffda8e549b79a350bca53c454/src/rpc/core_rpc_server.cpp#L1780-L1788>
553async fn stop_daemon(
554    mut state: CupratedRpcHandler,
555    _: StopDaemonRequest,
556) -> Result<StopDaemonResponse, Error> {
557    blockchain_manager::stop(&mut state.blockchain_manager).await?;
558    Ok(StopDaemonResponse { status: Status::Ok })
559}
560
561/// <https://github.com/monero-project/monero/blob/cc73fe71162d564ffda8e549b79a350bca53c454/src/rpc/core_rpc_server.cpp#L3066-L3077>
562async fn get_limit(
563    mut state: CupratedRpcHandler,
564    _: GetLimitRequest,
565) -> Result<GetLimitResponse, Error> {
566    todo!("waiting on p2p service");
567
568    Ok(GetLimitResponse {
569        base: helper::response_base(false),
570        limit_down: todo!(),
571        limit_up: todo!(),
572    })
573}
574
575/// <https://github.com/monero-project/monero/blob/cc73fe71162d564ffda8e549b79a350bca53c454/src/rpc/core_rpc_server.cpp#L3079-L3117>
576async fn set_limit(
577    mut state: CupratedRpcHandler,
578    request: SetLimitRequest,
579) -> Result<SetLimitResponse, Error> {
580    todo!("waiting on p2p service");
581
582    Ok(SetLimitResponse {
583        base: helper::response_base(false),
584        limit_down: todo!(),
585        limit_up: todo!(),
586    })
587}
588
589/// <https://github.com/monero-project/monero/blob/cc73fe71162d564ffda8e549b79a350bca53c454/src/rpc/core_rpc_server.cpp#L3119-L3127>
590async fn out_peers(
591    mut state: CupratedRpcHandler,
592    request: OutPeersRequest,
593) -> Result<OutPeersResponse, Error> {
594    todo!("waiting on p2p service");
595
596    Ok(OutPeersResponse {
597        base: helper::response_base(false),
598        out_peers: todo!(),
599    })
600}
601
602/// <https://github.com/monero-project/monero/blob/cc73fe71162d564ffda8e549b79a350bca53c454/src/rpc/core_rpc_server.cpp#L3129-L3137>
603async fn in_peers(
604    mut state: CupratedRpcHandler,
605    request: InPeersRequest,
606) -> Result<InPeersResponse, Error> {
607    todo!("waiting on p2p service");
608
609    Ok(InPeersResponse {
610        base: helper::response_base(false),
611        in_peers: todo!(),
612    })
613}
614
615/// <https://github.com/monero-project/monero/blob/cc73fe71162d564ffda8e549b79a350bca53c454/src/rpc/core_rpc_server.cpp#L584-L599>
616async fn get_net_stats(
617    mut state: CupratedRpcHandler,
618    _: GetNetStatsRequest,
619) -> Result<GetNetStatsResponse, Error> {
620    todo!("waiting on p2p service");
621
622    Ok(GetNetStatsResponse {
623        base: helper::response_base(false),
624        start_time: *START_INSTANT_UNIX,
625        total_packets_in: todo!(),
626        total_bytes_in: todo!(),
627        total_packets_out: todo!(),
628        total_bytes_out: todo!(),
629    })
630}
631
632/// <https://github.com/monero-project/monero/blob/cc73fe71162d564ffda8e549b79a350bca53c454/src/rpc/core_rpc_server.cpp#L912-L957>
633async fn get_outs(
634    state: CupratedRpcHandler,
635    request: GetOutsRequest,
636) -> Result<GetOutsResponse, Error> {
637    let outs = shared::get_outs(
638        state,
639        cuprate_rpc_types::bin::GetOutsRequest {
640            outputs: request.outputs,
641            get_txid: request.get_txid,
642        },
643    )
644    .await?
645    .outs
646    .into_iter()
647    .map(Into::into)
648    .collect();
649
650    Ok(GetOutsResponse {
651        base: helper::response_base(false),
652        outs,
653    })
654}
655
656/// <https://github.com/monero-project/monero/blob/cc73fe71162d564ffda8e549b79a350bca53c454/src/rpc/core_rpc_server.cpp#L3242-L3252>
657async fn pop_blocks(
658    mut state: CupratedRpcHandler,
659    request: PopBlocksRequest,
660) -> Result<PopBlocksResponse, Error> {
661    let height =
662        blockchain_manager::pop_blocks(&mut state.blockchain_manager, request.nblocks).await?;
663
664    Ok(PopBlocksResponse {
665        base: helper::response_base(false),
666        height,
667    })
668}
669
670/// <https://github.com/monero-project/monero/blob/cc73fe71162d564ffda8e549b79a350bca53c454/src/rpc/core_rpc_server.cpp#L1713-L1739>
671async fn get_transaction_pool_hashes(
672    mut state: CupratedRpcHandler,
673    _: GetTransactionPoolHashesRequest,
674) -> Result<GetTransactionPoolHashesResponse, Error> {
675    Ok(GetTransactionPoolHashesResponse {
676        base: helper::response_base(false),
677        tx_hashes: shared::get_transaction_pool_hashes(state)
678            .await?
679            .into_iter()
680            .map(Hex)
681            .collect(),
682    })
683}
684
685/// <https://github.com/monero-project/monero/blob/cc73fe71162d564ffda8e549b79a350bca53c454/src/rpc/core_rpc_server.cpp#L193-L225>
686async fn get_public_nodes(
687    mut state: CupratedRpcHandler,
688    request: GetPublicNodesRequest,
689) -> Result<GetPublicNodesResponse, Error> {
690    let (white, gray) = address_book::peerlist::<ClearNet>(&mut DummyAddressBook).await?;
691
692    fn map(peers: Vec<cuprate_types::rpc::Peer>) -> Vec<PublicNode> {
693        peers
694            .into_iter()
695            .map(|peer| {
696                let cuprate_types::rpc::Peer {
697                    host,
698                    rpc_port,
699                    rpc_credits_per_hash,
700                    last_seen,
701                    ..
702                } = peer;
703
704                PublicNode {
705                    host,
706                    rpc_port,
707                    rpc_credits_per_hash,
708                    last_seen,
709                }
710            })
711            .collect()
712    }
713
714    let white = map(white);
715    let gray = map(gray);
716
717    Ok(GetPublicNodesResponse {
718        base: helper::response_base(false),
719        white,
720        gray,
721    })
722}
723
724//---------------------------------------------------------------------------------------------------- Unsupported RPC calls (for now)
725
726/// <https://github.com/monero-project/monero/blob/cc73fe71162d564ffda8e549b79a350bca53c454/src/rpc/core_rpc_server.cpp#L1758-L1778>
727async fn set_bootstrap_daemon(
728    state: CupratedRpcHandler,
729    request: SetBootstrapDaemonRequest,
730) -> Result<SetBootstrapDaemonResponse, Error> {
731    todo!();
732}
733
734/// <https://github.com/monero-project/monero/blob/cc73fe71162d564ffda8e549b79a350bca53c454/src/rpc/core_rpc_server.cpp#L3139-L3240>
735async fn update(
736    state: CupratedRpcHandler,
737    request: UpdateRequest,
738) -> Result<UpdateResponse, Error> {
739    todo!();
740}
741
742/// <https://github.com/monero-project/monero/blob/cc73fe71162d564ffda8e549b79a350bca53c454/src/rpc/core_rpc_server.cpp#L1641-L1652>
743async fn set_log_level(
744    state: CupratedRpcHandler,
745    request: SetLogLevelRequest,
746) -> Result<SetLogLevelResponse, Error> {
747    todo!()
748}
749
750/// <https://github.com/monero-project/monero/blob/cc73fe71162d564ffda8e549b79a350bca53c454/src/rpc/core_rpc_server.cpp#L1654-L1661>
751async fn set_log_categories(
752    state: CupratedRpcHandler,
753    request: SetLogCategoriesRequest,
754) -> Result<SetLogCategoriesResponse, Error> {
755    todo!()
756}
757
758//---------------------------------------------------------------------------------------------------- Unsupported RPC calls (forever)
759
760/// <https://github.com/monero-project/monero/blob/cc73fe71162d564ffda8e549b79a350bca53c454/src/rpc/core_rpc_server.cpp#L1413-L1462>
761async fn start_mining(
762    state: CupratedRpcHandler,
763    request: StartMiningRequest,
764) -> Result<StartMiningResponse, Error> {
765    unreachable!()
766}
767
768/// <https://github.com/monero-project/monero/blob/cc73fe71162d564ffda8e549b79a350bca53c454/src/rpc/core_rpc_server.cpp#L1464-L1482>
769async fn stop_mining(
770    state: CupratedRpcHandler,
771    request: StopMiningRequest,
772) -> Result<StopMiningResponse, Error> {
773    unreachable!();
774}
775
776/// <https://github.com/monero-project/monero/blob/cc73fe71162d564ffda8e549b79a350bca53c454/src/rpc/core_rpc_server.cpp#L1484-L1523>
777async fn mining_status(
778    state: CupratedRpcHandler,
779    request: MiningStatusRequest,
780) -> Result<MiningStatusResponse, Error> {
781    unreachable!();
782}
783
784/// <https://github.com/monero-project/monero/blob/cc73fe71162d564ffda8e549b79a350bca53c454/src/rpc/core_rpc_server.cpp#L1626-L1639>
785async fn set_log_hash_rate(
786    state: CupratedRpcHandler,
787    request: SetLogHashRateRequest,
788) -> Result<SetLogHashRateResponse, Error> {
789    unreachable!();
790}