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