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