cuprated/rpc/handlers/
json_rpc.rs

1//! RPC request handler functions (JSON-RPC).
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    collections::HashMap,
9    net::{IpAddr, Ipv4Addr, SocketAddr, SocketAddrV4},
10    num::NonZero,
11    time::{Duration, Instant},
12};
13
14use anyhow::{anyhow, Error};
15use monero_serai::block::Block;
16use strum::{EnumCount, VariantArray};
17
18use cuprate_constants::{
19    build::RELEASE,
20    rpc::{RESTRICTED_BLOCK_COUNT, RESTRICTED_BLOCK_HEADER_RANGE},
21};
22use cuprate_helper::{
23    cast::{u32_to_usize, u64_to_usize, usize_to_u64},
24    fmt::HexPrefix,
25    map::split_u128_into_low_high_bits,
26    time::current_unix_timestamp,
27};
28use cuprate_hex::{Hex, HexVec};
29use cuprate_p2p_core::{client::handshaker::builder::DummyAddressBook, ClearNet, Network};
30use cuprate_rpc_interface::RpcHandler;
31use cuprate_rpc_types::{
32    base::{AccessResponseBase, ResponseBase},
33    json::{
34        AddAuxPowRequest, AddAuxPowResponse, BannedRequest, BannedResponse, CalcPowRequest,
35        CalcPowResponse, FlushCacheRequest, FlushCacheResponse, FlushTransactionPoolRequest,
36        FlushTransactionPoolResponse, GenerateBlocksRequest, GenerateBlocksResponse,
37        GetAlternateChainsRequest, GetAlternateChainsResponse, GetBansRequest, GetBansResponse,
38        GetBlockCountRequest, GetBlockCountResponse, GetBlockHeaderByHashRequest,
39        GetBlockHeaderByHashResponse, GetBlockHeaderByHeightRequest,
40        GetBlockHeaderByHeightResponse, GetBlockHeadersRangeRequest, GetBlockHeadersRangeResponse,
41        GetBlockRequest, GetBlockResponse, GetBlockTemplateRequest, GetBlockTemplateResponse,
42        GetCoinbaseTxSumRequest, GetCoinbaseTxSumResponse, GetConnectionsRequest,
43        GetConnectionsResponse, GetFeeEstimateRequest, GetFeeEstimateResponse, GetInfoRequest,
44        GetInfoResponse, GetLastBlockHeaderRequest, GetLastBlockHeaderResponse,
45        GetMinerDataRequest, GetMinerDataResponse, GetOutputDistributionRequest,
46        GetOutputDistributionResponse, GetOutputHistogramRequest, GetOutputHistogramResponse,
47        GetTransactionPoolBacklogRequest, GetTransactionPoolBacklogResponse, GetTxIdsLooseRequest,
48        GetTxIdsLooseResponse, GetVersionRequest, GetVersionResponse, HardForkInfoRequest,
49        HardForkInfoResponse, JsonRpcRequest, JsonRpcResponse, OnGetBlockHashRequest,
50        OnGetBlockHashResponse, PruneBlockchainRequest, PruneBlockchainResponse, RelayTxRequest,
51        RelayTxResponse, SetBansRequest, SetBansResponse, SubmitBlockRequest, SubmitBlockResponse,
52        SyncInfoRequest, SyncInfoResponse,
53    },
54    misc::{BlockHeader, ChainInfo, Distribution, GetBan, HistogramEntry, Status, SyncInfoPeer},
55    CORE_RPC_VERSION,
56};
57use cuprate_types::{
58    rpc::{AuxPow, CoinbaseTxSum, GetMinerDataTxBacklogEntry, HardForkEntry, TxBacklogEntry},
59    BlockTemplate, Chain, HardFork,
60};
61
62use crate::{
63    blockchain::interface as blockchain_interface,
64    constants::VERSION_BUILD,
65    rpc::{
66        constants::{FIELD_NOT_SUPPORTED, UNSUPPORTED_RPC_CALL},
67        handlers::{helper, shared, shared::not_available},
68        service::{address_book, blockchain, blockchain_context, blockchain_manager, txpool},
69        CupratedRpcHandler,
70    },
71    statics::START_INSTANT_UNIX,
72};
73
74/// Map a [`JsonRpcRequest`] to the function that will lead to a [`JsonRpcResponse`].
75pub async fn map_request(
76    state: CupratedRpcHandler,
77    request: JsonRpcRequest,
78) -> Result<JsonRpcResponse, Error> {
79    use JsonRpcRequest as Req;
80    use JsonRpcResponse as Resp;
81
82    Ok(match request {
83        Req::GetBlockTemplate(r) => Resp::GetBlockTemplate(not_available()?),
84        Req::GetBlockCount(r) => Resp::GetBlockCount(get_block_count(state, r).await?),
85        Req::OnGetBlockHash(r) => Resp::OnGetBlockHash(on_get_block_hash(state, r).await?),
86        Req::SubmitBlock(r) => Resp::SubmitBlock(submit_block(state, r).await?),
87        Req::GenerateBlocks(r) => Resp::GenerateBlocks(not_available()?),
88        Req::GetLastBlockHeader(r) => {
89            Resp::GetLastBlockHeader(get_last_block_header(state, r).await?)
90        }
91        Req::GetBlockHeaderByHash(r) => {
92            Resp::GetBlockHeaderByHash(get_block_header_by_hash(state, r).await?)
93        }
94        Req::GetBlockHeaderByHeight(r) => {
95            Resp::GetBlockHeaderByHeight(get_block_header_by_height(state, r).await?)
96        }
97        Req::GetBlockHeadersRange(r) => {
98            Resp::GetBlockHeadersRange(get_block_headers_range(state, r).await?)
99        }
100        Req::GetBlock(r) => Resp::GetBlock(get_block(state, r).await?),
101        Req::GetConnections(r) => Resp::GetConnections(not_available()?),
102        Req::GetInfo(r) => Resp::GetInfo(not_available()?),
103        Req::HardForkInfo(r) => Resp::HardForkInfo(not_available()?),
104        Req::SetBans(r) => Resp::SetBans(not_available()?),
105        Req::GetBans(r) => Resp::GetBans(not_available()?),
106        Req::Banned(r) => Resp::Banned(not_available()?),
107        Req::FlushTransactionPool(r) => Resp::FlushTransactionPool(not_available()?),
108        Req::GetOutputHistogram(r) => Resp::GetOutputHistogram(not_available()?),
109        Req::GetCoinbaseTxSum(r) => Resp::GetCoinbaseTxSum(not_available()?),
110        Req::GetVersion(r) => Resp::GetVersion(not_available()?),
111        Req::GetFeeEstimate(r) => Resp::GetFeeEstimate(not_available()?),
112        Req::GetAlternateChains(r) => Resp::GetAlternateChains(not_available()?),
113        Req::RelayTx(r) => Resp::RelayTx(not_available()?),
114        Req::SyncInfo(r) => Resp::SyncInfo(not_available()?),
115        Req::GetTransactionPoolBacklog(r) => Resp::GetTransactionPoolBacklog(not_available()?),
116        Req::GetMinerData(r) => Resp::GetMinerData(not_available()?),
117        Req::PruneBlockchain(r) => Resp::PruneBlockchain(not_available()?),
118        Req::CalcPow(r) => Resp::CalcPow(not_available()?),
119        Req::AddAuxPow(r) => Resp::AddAuxPow(not_available()?),
120
121        // Unsupported RPC calls.
122        Req::GetTxIdsLoose(_) | Req::FlushCache(_) => return Err(anyhow!(UNSUPPORTED_RPC_CALL)),
123    })
124}
125
126/// <https://github.com/monero-project/monero/blob/cc73fe71162d564ffda8e549b79a350bca53c454/src/rpc/core_rpc_server.cpp#L1911-L2005>
127async fn get_block_template(
128    mut state: CupratedRpcHandler,
129    request: GetBlockTemplateRequest,
130) -> Result<GetBlockTemplateResponse, Error> {
131    if request.reserve_size > 255 {
132        return Err(anyhow!("Too big reserved size, maximum 255"));
133    }
134
135    if request.reserve_size != 0 && !request.extra_nonce.is_empty() {
136        return Err(anyhow!(
137            "Cannot specify both a reserve_size and an extra_nonce"
138        ));
139    }
140
141    if request.extra_nonce.len() > 510 {
142        return Err(anyhow!("Too big extra_nonce size"));
143    }
144
145    // TODO: This should be `cuprated`'s active network.
146    let network = match Network::Mainnet {
147        Network::Mainnet => monero_address::Network::Mainnet,
148        Network::Stagenet => monero_address::Network::Stagenet,
149        Network::Testnet => monero_address::Network::Testnet,
150    };
151
152    let address = monero_address::MoneroAddress::from_str(network, &request.wallet_address)?;
153
154    if *address.kind() != monero_address::AddressType::Legacy {
155        return Err(anyhow!("Incorrect address type"));
156    }
157
158    let prev_block = request.prev_block.try_into().unwrap_or([0; 32]);
159
160    let BlockTemplate {
161        block,
162        reserved_offset,
163        difficulty,
164        height,
165        expected_reward,
166        seed_height,
167        seed_hash,
168        next_seed_hash,
169    } = *blockchain_manager::create_block_template(
170        todo!(),
171        prev_block,
172        request.wallet_address,
173        request.extra_nonce.0,
174    )
175    .await?;
176
177    let blockhashing_blob = HexVec(block.serialize_pow_hash());
178    let blocktemplate_blob = HexVec(block.serialize());
179    let (difficulty, difficulty_top64) = split_u128_into_low_high_bits(difficulty);
180    let next_seed_hash = HexVec::empty_if_zeroed(next_seed_hash);
181    let prev_hash = Hex(block.header.previous);
182    let seed_hash = Hex(seed_hash);
183    let wide_difficulty = (difficulty, difficulty_top64).hex_prefix();
184
185    Ok(GetBlockTemplateResponse {
186        base: helper::response_base(false),
187        blockhashing_blob,
188        blocktemplate_blob,
189        difficulty_top64,
190        difficulty,
191        expected_reward,
192        height,
193        next_seed_hash,
194        prev_hash,
195        reserved_offset,
196        seed_hash,
197        seed_height,
198        wide_difficulty,
199    })
200}
201
202/// <https://github.com/monero-project/monero/blob/cc73fe71162d564ffda8e549b79a350bca53c454/src/rpc/core_rpc_server.cpp#L1790-L1804>
203async fn get_block_count(
204    mut state: CupratedRpcHandler,
205    _: GetBlockCountRequest,
206) -> Result<GetBlockCountResponse, Error> {
207    Ok(GetBlockCountResponse {
208        base: helper::response_base(false),
209        // Block count starts at 1
210        count: blockchain::chain_height(&mut state.blockchain_read)
211            .await?
212            .0,
213    })
214}
215
216/// <https://github.com/monero-project/monero/blob/cc73fe71162d564ffda8e549b79a350bca53c454/src/rpc/core_rpc_server.cpp#L1806-L1831>
217async fn on_get_block_hash(
218    mut state: CupratedRpcHandler,
219    request: OnGetBlockHashRequest,
220) -> Result<OnGetBlockHashResponse, Error> {
221    let [height] = request.block_height;
222    let hash = blockchain::block_hash(&mut state.blockchain_read, height, Chain::Main).await?;
223
224    Ok(OnGetBlockHashResponse {
225        block_hash: Hex(hash),
226    })
227}
228
229/// <https://github.com/monero-project/monero/blob/cc73fe71162d564ffda8e549b79a350bca53c454/src/rpc/core_rpc_server.cpp#L2209-L2266>
230async fn submit_block(
231    mut state: CupratedRpcHandler,
232    request: SubmitBlockRequest,
233) -> Result<SubmitBlockResponse, Error> {
234    // Parse hex into block.
235    let [blob] = request.block_blob;
236    let block = Block::read(&mut blob.as_slice())?;
237    let block_id = Hex(block.hash());
238
239    // Attempt to relay the block.
240    blockchain_interface::handle_incoming_block(
241        block,
242        HashMap::new(), // this function reads the txpool
243        &mut state.blockchain_read,
244        &mut state.txpool_read,
245    )
246    .await?;
247
248    Ok(SubmitBlockResponse {
249        base: helper::response_base(false),
250        block_id,
251    })
252}
253
254/// <https://github.com/monero-project/monero/blob/cc73fe71162d564ffda8e549b79a350bca53c454/src/rpc/core_rpc_server.cpp#L2268-L2340>
255async fn generate_blocks(
256    state: CupratedRpcHandler,
257    request: GenerateBlocksRequest,
258) -> Result<GenerateBlocksResponse, Error> {
259    if todo!("active cuprated chain") != todo!("regtest chain") {
260        return Err(anyhow!("Regtest required when generating blocks"));
261    }
262
263    // FIXME:
264    // is this field only used as a local variable in the handler in `monerod`?
265    // It may not be needed in the request type.
266    let prev_block = if request.prev_block.is_empty() {
267        None
268    } else {
269        Some(request.prev_block.try_into()?)
270    };
271
272    let (blocks, height) = blockchain_manager::generate_blocks(
273        todo!(),
274        request.amount_of_blocks,
275        prev_block,
276        request.starting_nonce,
277        request.wallet_address,
278    )
279    .await?;
280
281    let blocks = blocks.into_iter().map(Hex).collect();
282
283    Ok(GenerateBlocksResponse {
284        base: helper::response_base(false),
285        blocks,
286        height,
287    })
288}
289
290/// <https://github.com/monero-project/monero/blob/cc73fe71162d564ffda8e549b79a350bca53c454/src/rpc/core_rpc_server.cpp#L2468-L2498>
291async fn get_last_block_header(
292    mut state: CupratedRpcHandler,
293    request: GetLastBlockHeaderRequest,
294) -> Result<GetLastBlockHeaderResponse, Error> {
295    let (height, _) = helper::top_height(&mut state).await?;
296    let block_header = helper::block_header(&mut state, height, request.fill_pow_hash).await?;
297
298    Ok(GetLastBlockHeaderResponse {
299        base: helper::access_response_base(false),
300        block_header,
301    })
302}
303
304/// <https://github.com/monero-project/monero/blob/cc73fe71162d564ffda8e549b79a350bca53c454/src/rpc/core_rpc_server.cpp#L2500-L2567>
305async fn get_block_header_by_hash(
306    mut state: CupratedRpcHandler,
307    request: GetBlockHeaderByHashRequest,
308) -> Result<GetBlockHeaderByHashResponse, Error> {
309    if state.is_restricted() && request.hashes.len() > RESTRICTED_BLOCK_COUNT {
310        return Err(anyhow!(
311            "Too many block headers requested in restricted mode"
312        ));
313    }
314
315    let block_header =
316        helper::block_header_by_hash(&mut state, request.hash.0, request.fill_pow_hash).await?;
317
318    // FIXME PERF: could make a `Vec` on await on all tasks at the same time.
319    let mut block_headers = Vec::with_capacity(request.hashes.len());
320    for hash in request.hashes {
321        let hash = helper::block_header_by_hash(&mut state, hash.0, request.fill_pow_hash).await?;
322        block_headers.push(hash);
323    }
324
325    Ok(GetBlockHeaderByHashResponse {
326        base: helper::access_response_base(false),
327        block_header,
328        block_headers,
329    })
330}
331
332/// <https://github.com/monero-project/monero/blob/cc73fe71162d564ffda8e549b79a350bca53c454/src/rpc/core_rpc_server.cpp#L2629-L2662>
333async fn get_block_header_by_height(
334    mut state: CupratedRpcHandler,
335    request: GetBlockHeaderByHeightRequest,
336) -> Result<GetBlockHeaderByHeightResponse, Error> {
337    helper::check_height(&mut state, request.height).await?;
338    let block_header =
339        helper::block_header(&mut state, request.height, request.fill_pow_hash).await?;
340
341    Ok(GetBlockHeaderByHeightResponse {
342        base: helper::access_response_base(false),
343        block_header,
344    })
345}
346
347/// <https://github.com/monero-project/monero/blob/cc73fe71162d564ffda8e549b79a350bca53c454/src/rpc/core_rpc_server.cpp#L2569-L2627>
348async fn get_block_headers_range(
349    mut state: CupratedRpcHandler,
350    request: GetBlockHeadersRangeRequest,
351) -> Result<GetBlockHeadersRangeResponse, Error> {
352    let (top_height, _) = helper::top_height(&mut state).await?;
353
354    if request.start_height >= top_height
355        || request.end_height >= top_height
356        || request.start_height > request.end_height
357    {
358        return Err(anyhow!("Invalid start/end heights"));
359    }
360
361    let too_many_blocks =
362        || request.end_height - request.start_height + 1 > RESTRICTED_BLOCK_HEADER_RANGE;
363
364    if state.is_restricted() && too_many_blocks() {
365        return Err(anyhow!("Too many block headers requested."));
366    }
367
368    // FIXME:
369    // This code currently:
370    // 1. requests a specific `(Block, BlockHeader)`
371    // 2. maps them to the RPC type
372    // 3. pushes them to the a `Vec` sequentially
373    //
374    // It could be more efficient by:
375    // 1. requesting all `(Block, Header)`s in the range at once
376    // 2. mapping all at once and collect into a `Vec`
377    //
378    // This isn't currently possible because there
379    // is no internal request for a range of blocks.
380
381    let (range, expected_len) = {
382        let start = u64_to_usize(request.start_height);
383        let end = u64_to_usize(request.end_height);
384        (start..=end, end - start + 1)
385    };
386
387    let mut headers = Vec::with_capacity(expected_len);
388
389    for height in range {
390        let height = usize_to_u64(height);
391        let header = helper::block_header(&mut state, height, request.fill_pow_hash).await?;
392        headers.push(header);
393    }
394
395    Ok(GetBlockHeadersRangeResponse {
396        base: helper::access_response_base(false),
397        headers,
398    })
399}
400
401/// <https://github.com/monero-project/monero/blob/cc73fe71162d564ffda8e549b79a350bca53c454/src/rpc/core_rpc_server.cpp#L2664-L2727>
402async fn get_block(
403    mut state: CupratedRpcHandler,
404    request: GetBlockRequest,
405) -> Result<GetBlockResponse, Error> {
406    let (block, block_header) = if request.hash.is_empty() {
407        helper::check_height(&mut state, request.height).await?;
408        let block = blockchain::block(&mut state.blockchain_read, request.height).await?;
409        let block_header =
410            helper::block_header(&mut state, request.height, request.fill_pow_hash).await?;
411        (block, block_header)
412    } else {
413        let hash: [u8; 32] = request.hash.try_into()?;
414        let block = blockchain::block_by_hash(&mut state.blockchain_read, hash).await?;
415        let block_header =
416            helper::block_header_by_hash(&mut state, hash, request.fill_pow_hash).await?;
417        (block, block_header)
418    };
419
420    let blob = HexVec(block.serialize());
421    let miner_tx_hash = Hex(block.miner_transaction.hash());
422    let tx_hashes = block.transactions.iter().copied().map(Hex).collect();
423    let json = {
424        let block = cuprate_types::json::block::Block::from(block);
425        serde_json::to_string_pretty(&block)?
426    };
427
428    Ok(GetBlockResponse {
429        base: helper::access_response_base(false),
430        blob,
431        json,
432        miner_tx_hash,
433        tx_hashes,
434        block_header,
435    })
436}
437
438/// <https://github.com/monero-project/monero/blob/cc73fe71162d564ffda8e549b79a350bca53c454/src/rpc/core_rpc_server.cpp#L2729-L2738>
439async fn get_connections(
440    state: CupratedRpcHandler,
441    _: GetConnectionsRequest,
442) -> Result<GetConnectionsResponse, Error> {
443    let connections = address_book::connection_info::<ClearNet>(&mut DummyAddressBook).await?;
444
445    Ok(GetConnectionsResponse {
446        base: helper::response_base(false),
447        connections,
448    })
449}
450
451/// <https://github.com/monero-project/monero/blob/cc73fe71162d564ffda8e549b79a350bca53c454/src/rpc/core_rpc_server.cpp#L501-L582>
452async fn get_info(
453    mut state: CupratedRpcHandler,
454    _: GetInfoRequest,
455) -> Result<GetInfoResponse, Error> {
456    let restricted = state.is_restricted();
457    let c = state.blockchain_context.blockchain_context();
458
459    let cumulative_difficulty = c.cumulative_difficulty;
460    let adjusted_time = c.current_adjusted_timestamp_for_time_lock();
461    let c = &c.context_to_verify_block;
462
463    let alt_blocks_count = if restricted {
464        0
465    } else {
466        blockchain::alt_chain_count(&mut state.blockchain_read).await?
467    };
468
469    // TODO: these values are incorrectly calculated in `monerod` and copied in `cuprated`
470    // <https://github.com/Cuprate/cuprate/pull/355#discussion_r1906273007>
471    let block_weight_limit = usize_to_u64(c.effective_median_weight * 2);
472    let block_weight_median = usize_to_u64(c.effective_median_weight);
473    let block_size_limit = block_weight_limit;
474    let block_size_median = block_weight_median;
475
476    #[expect(clippy::if_same_then_else, reason = "TODO: support bootstrap")]
477    let (bootstrap_daemon_address, was_bootstrap_ever_used) = if restricted {
478        (String::new(), false)
479    } else {
480        (String::new(), false)
481    };
482
483    let busy_syncing = blockchain_manager::syncing(todo!()).await?;
484
485    let (cumulative_difficulty, cumulative_difficulty_top64) =
486        split_u128_into_low_high_bits(cumulative_difficulty);
487
488    let (database_size, free_space) = blockchain::database_size(&mut state.blockchain_read).await?;
489    let (database_size, free_space) = if restricted {
490        // <https://github.com/monero-project/monero/blob/cc73fe71162d564ffda8e549b79a350bca53c454/src/rpc/core_rpc_server.cpp#L131-L134>
491        let database_size = database_size.div_ceil(5 * 1024 * 1024 * 1024);
492        (database_size, u64::MAX)
493    } else {
494        (database_size, free_space)
495    };
496
497    let (difficulty, difficulty_top64) = split_u128_into_low_high_bits(c.next_difficulty);
498
499    let height = usize_to_u64(c.chain_height);
500    let height_without_bootstrap = if restricted { 0 } else { height };
501
502    let (incoming_connections_count, outgoing_connections_count) = if restricted {
503        (0, 0)
504    } else {
505        address_book::connection_count::<ClearNet>(&mut DummyAddressBook).await?
506    };
507
508    // TODO: This should be `cuprated`'s active network.
509    let network = Network::Mainnet;
510
511    let (mainnet, testnet, stagenet) = match network {
512        Network::Mainnet => (true, false, false),
513        Network::Testnet => (false, true, false),
514        Network::Stagenet => (false, false, true),
515    };
516
517    let nettype = network.to_string();
518    // TODO: access to CLI/config's `--offline`
519    let offline = false;
520
521    #[expect(
522        clippy::if_same_then_else,
523        reason = "TODO: implement a connection counter in axum/RPC"
524    )]
525    let rpc_connections_count = if restricted { 0 } else { 0 };
526
527    let start_time = if restricted { 0 } else { *START_INSTANT_UNIX };
528    let synchronized = blockchain_manager::synced(todo!()).await?;
529
530    let target_height = blockchain_manager::target_height(todo!()).await?;
531    let target = blockchain_manager::target(todo!()).await?.as_secs();
532    let top_block_hash = Hex(c.top_hash);
533
534    let tx_count = blockchain::total_tx_count(&mut state.blockchain_read).await?;
535    let tx_pool_size = txpool::size(&mut state.txpool_read, !restricted).await?;
536
537    #[expect(
538        clippy::if_same_then_else,
539        clippy::needless_bool,
540        reason = "TODO: implement an update checker for `cuprated`?"
541    )]
542    let update_available = if restricted { false } else { false };
543
544    let version = if restricted {
545        String::new()
546    } else {
547        VERSION_BUILD.to_string()
548    };
549
550    let (white_peerlist_size, grey_peerlist_size) = if restricted {
551        (0, 0)
552    } else {
553        address_book::peerlist_size::<ClearNet>(&mut DummyAddressBook).await?
554    };
555
556    let wide_cumulative_difficulty = cumulative_difficulty.hex_prefix();
557    let wide_difficulty = c.next_difficulty.hex_prefix();
558
559    Ok(GetInfoResponse {
560        base: helper::access_response_base(false),
561        adjusted_time,
562        alt_blocks_count,
563        block_size_limit,
564        block_size_median,
565        block_weight_limit,
566        block_weight_median,
567        bootstrap_daemon_address,
568        busy_syncing,
569        cumulative_difficulty_top64,
570        cumulative_difficulty,
571        database_size,
572        difficulty_top64,
573        difficulty,
574        free_space,
575        grey_peerlist_size,
576        height,
577        height_without_bootstrap,
578        incoming_connections_count,
579        mainnet,
580        nettype,
581        offline,
582        outgoing_connections_count,
583        restricted,
584        rpc_connections_count,
585        stagenet,
586        start_time,
587        synchronized,
588        target_height,
589        target,
590        testnet,
591        top_block_hash,
592        tx_count,
593        tx_pool_size,
594        update_available,
595        version,
596        was_bootstrap_ever_used,
597        white_peerlist_size,
598        wide_cumulative_difficulty,
599        wide_difficulty,
600    })
601}
602
603/// <https://github.com/monero-project/monero/blob/cc73fe71162d564ffda8e549b79a350bca53c454/src/rpc/core_rpc_server.cpp#L2751-L2766>
604async fn hard_fork_info(
605    mut state: CupratedRpcHandler,
606    request: HardForkInfoRequest,
607) -> Result<HardForkInfoResponse, Error> {
608    let hard_fork = if request.version > 0 {
609        HardFork::from_version(request.version)?
610    } else {
611        state.blockchain_context.blockchain_context().current_hf
612    };
613
614    let hard_fork_info =
615        blockchain_context::hard_fork_info(&mut state.blockchain_context, hard_fork).await?;
616
617    Ok(HardForkInfoResponse {
618        base: helper::access_response_base(false),
619        hard_fork_info,
620    })
621}
622
623/// <https://github.com/monero-project/monero/blob/cc73fe71162d564ffda8e549b79a350bca53c454/src/rpc/core_rpc_server.cpp#L2832-L2878>
624async fn set_bans(
625    state: CupratedRpcHandler,
626    request: SetBansRequest,
627) -> Result<SetBansResponse, Error> {
628    for peer in request.bans {
629        // TODO: support non-clearnet addresses.
630
631        // <https://architecture.cuprate.org/oddities/le-ipv4.html>
632        let ip = Ipv4Addr::from(peer.ip.to_le_bytes());
633        let address = SocketAddr::V4(SocketAddrV4::new(ip, 0));
634
635        let ban = if peer.ban {
636            Some(Duration::from_secs(peer.seconds.into()))
637        } else {
638            None
639        };
640
641        let set_ban = cuprate_p2p_core::types::SetBan { address, ban };
642
643        address_book::set_ban::<ClearNet>(&mut DummyAddressBook, set_ban).await?;
644    }
645
646    Ok(SetBansResponse {
647        base: helper::response_base(false),
648    })
649}
650
651/// <https://github.com/monero-project/monero/blob/cc73fe71162d564ffda8e549b79a350bca53c454/src/rpc/core_rpc_server.cpp#L2768-L2801>
652async fn get_bans(state: CupratedRpcHandler, _: GetBansRequest) -> Result<GetBansResponse, Error> {
653    let now = Instant::now();
654
655    // TODO: support non-clearnet addresses.
656
657    let bans = address_book::get_bans::<ClearNet>(&mut DummyAddressBook)
658        .await?
659        .into_iter()
660        .filter_map(|ban| {
661            let seconds = if let Some(instant) = ban.unban_instant {
662                instant
663                    .checked_duration_since(now)
664                    .unwrap_or_default()
665                    .as_secs()
666                    .try_into()
667                    .unwrap_or(0)
668            } else {
669                0
670            };
671
672            // <https://architecture.cuprate.org/oddities/le-ipv4.html>
673            let ip = match ban.address.ip() {
674                IpAddr::V4(v4) => u32::from_le_bytes(v4.octets()),
675                IpAddr::V6(v6) => return None,
676            };
677
678            Some(GetBan {
679                host: ban.address.to_string(),
680                ip,
681                seconds,
682            })
683        })
684        .collect();
685
686    Ok(GetBansResponse {
687        base: helper::response_base(false),
688        bans,
689    })
690}
691
692/// <https://github.com/monero-project/monero/blob/cc73fe71162d564ffda8e549b79a350bca53c454/src/rpc/core_rpc_server.cpp#L2803-L2830>
693async fn banned(
694    state: CupratedRpcHandler,
695    request: BannedRequest,
696) -> Result<BannedResponse, Error> {
697    let peer = match request.address.parse::<SocketAddr>() {
698        Ok(p) => p,
699        Err(e) => {
700            return Err(anyhow!(
701                "Failed to parse address: {} ({e})",
702                request.address
703            ))
704        }
705    };
706
707    let ban = address_book::get_ban::<ClearNet>(&mut DummyAddressBook, peer).await?;
708
709    let (banned, seconds) = if let Some(instant) = ban {
710        let seconds = instant
711            .checked_duration_since(Instant::now())
712            .unwrap_or_default()
713            .as_secs()
714            .try_into()
715            .unwrap_or(0);
716
717        (true, seconds)
718    } else {
719        (false, 0)
720    };
721
722    Ok(BannedResponse {
723        banned,
724        seconds,
725        status: Status::Ok,
726    })
727}
728
729/// <https://github.com/monero-project/monero/blob/cc73fe71162d564ffda8e549b79a350bca53c454/src/rpc/core_rpc_server.cpp#L2880-L2932>
730async fn flush_transaction_pool(
731    mut state: CupratedRpcHandler,
732    request: FlushTransactionPoolRequest,
733) -> Result<FlushTransactionPoolResponse, Error> {
734    let tx_hashes = request
735        .txids
736        .into_iter()
737        .map(|h| h.0)
738        .collect::<Vec<[u8; 32]>>();
739
740    txpool::flush(todo!(), tx_hashes).await?;
741
742    Ok(FlushTransactionPoolResponse { status: Status::Ok })
743}
744
745/// <https://github.com/monero-project/monero/blob/cc73fe71162d564ffda8e549b79a350bca53c454/src/rpc/core_rpc_server.cpp#L2934-L2979>
746async fn get_output_histogram(
747    mut state: CupratedRpcHandler,
748    request: GetOutputHistogramRequest,
749) -> Result<GetOutputHistogramResponse, Error> {
750    let input = cuprate_types::rpc::OutputHistogramInput {
751        amounts: request.amounts,
752        min_count: request.min_count,
753        max_count: request.max_count,
754        unlocked: request.unlocked,
755        recent_cutoff: request.recent_cutoff,
756    };
757
758    let histogram = blockchain::output_histogram(&mut state.blockchain_read, input)
759        .await?
760        .into_iter()
761        .map(|entry| HistogramEntry {
762            amount: entry.amount,
763            total_instances: entry.total_instances,
764            unlocked_instances: entry.unlocked_instances,
765            recent_instances: entry.recent_instances,
766        })
767        .collect();
768
769    Ok(GetOutputHistogramResponse {
770        base: helper::access_response_base(false),
771        histogram,
772    })
773}
774
775/// <https://github.com/monero-project/monero/blob/cc73fe71162d564ffda8e549b79a350bca53c454/src/rpc/core_rpc_server.cpp#L2998-L3013>
776async fn get_coinbase_tx_sum(
777    mut state: CupratedRpcHandler,
778    request: GetCoinbaseTxSumRequest,
779) -> Result<GetCoinbaseTxSumResponse, Error> {
780    let CoinbaseTxSum {
781        emission_amount_top64,
782        emission_amount,
783        fee_amount_top64,
784        fee_amount,
785    } = blockchain::coinbase_tx_sum(&mut state.blockchain_read, request.height, request.count)
786        .await?;
787
788    // Formats `u128` as hexadecimal strings.
789    let wide_emission_amount = fee_amount.hex_prefix();
790    let wide_fee_amount = emission_amount.hex_prefix();
791
792    Ok(GetCoinbaseTxSumResponse {
793        base: helper::access_response_base(false),
794        emission_amount,
795        emission_amount_top64,
796        fee_amount,
797        fee_amount_top64,
798        wide_emission_amount,
799        wide_fee_amount,
800    })
801}
802
803/// <https://github.com/monero-project/monero/blob/cc73fe71162d564ffda8e549b79a350bca53c454/src/rpc/core_rpc_server.cpp#L2981-L2996>
804async fn get_version(
805    mut state: CupratedRpcHandler,
806    _: GetVersionRequest,
807) -> Result<GetVersionResponse, Error> {
808    let current_height = helper::top_height(&mut state).await?.0;
809    let target_height = blockchain_manager::target_height(todo!()).await?;
810
811    let mut hard_forks = Vec::with_capacity(HardFork::COUNT);
812
813    // FIXME: use an async iterator `collect()` version.
814    for hf in HardFork::VARIANTS {
815        if let Ok(hf) = blockchain_context::hard_fork_info(&mut state.blockchain_context, *hf).await
816        {
817            let entry = HardForkEntry {
818                height: hf.earliest_height,
819                hf_version: HardFork::from_version(hf.version)
820                    .expect("blockchain context should not be responding with invalid hardforks"),
821            };
822
823            hard_forks.push(entry);
824        }
825    }
826
827    Ok(GetVersionResponse {
828        base: helper::response_base(false),
829        version: CORE_RPC_VERSION,
830        release: RELEASE,
831        current_height,
832        target_height,
833        hard_forks,
834    })
835}
836
837/// <https://github.com/monero-project/monero/blob/cc73fe71162d564ffda8e549b79a350bca53c454/src/rpc/core_rpc_server.cpp#L3015-L3031>
838async fn get_fee_estimate(
839    mut state: CupratedRpcHandler,
840    request: GetFeeEstimateRequest,
841) -> Result<GetFeeEstimateResponse, Error> {
842    let estimate =
843        blockchain_context::fee_estimate(&mut state.blockchain_context, request.grace_blocks)
844            .await?;
845
846    Ok(GetFeeEstimateResponse {
847        base: helper::access_response_base(false),
848        fee: estimate.fee,
849        fees: estimate.fees,
850        quantization_mask: estimate.quantization_mask,
851    })
852}
853
854/// <https://github.com/monero-project/monero/blob/cc73fe71162d564ffda8e549b79a350bca53c454/src/rpc/core_rpc_server.cpp#L3033-L3064>
855async fn get_alternate_chains(
856    mut state: CupratedRpcHandler,
857    _: GetAlternateChainsRequest,
858) -> Result<GetAlternateChainsResponse, Error> {
859    let chains = blockchain::alt_chains(&mut state.blockchain_read)
860        .await?
861        .into_iter()
862        .map(Into::into)
863        .collect();
864
865    Ok(GetAlternateChainsResponse {
866        base: helper::response_base(false),
867        chains,
868    })
869}
870
871/// <https://github.com/monero-project/monero/blob/cc73fe71162d564ffda8e549b79a350bca53c454/src/rpc/core_rpc_server.cpp#L3254-L3304>
872async fn relay_tx(
873    mut state: CupratedRpcHandler,
874    request: RelayTxRequest,
875) -> Result<RelayTxResponse, Error> {
876    let tx_hashes = request
877        .txids
878        .into_iter()
879        .map(|h| h.0)
880        .collect::<Vec<[u8; 32]>>();
881
882    txpool::relay(todo!(), tx_hashes).await?;
883
884    Ok(RelayTxResponse { status: Status::Ok })
885}
886
887/// <https://github.com/monero-project/monero/blob/cc73fe71162d564ffda8e549b79a350bca53c454/src/rpc/core_rpc_server.cpp#L3306-L3330>
888async fn sync_info(
889    mut state: CupratedRpcHandler,
890    _: SyncInfoRequest,
891) -> Result<SyncInfoResponse, Error> {
892    let height = usize_to_u64(state.blockchain_context.blockchain_context().chain_height);
893
894    let target_height = blockchain_manager::target_height(todo!()).await?;
895
896    let peers = address_book::connection_info::<ClearNet>(&mut DummyAddressBook)
897        .await?
898        .into_iter()
899        .map(|info| SyncInfoPeer { info })
900        .collect();
901
902    let next_needed_pruning_seed = blockchain_manager::next_needed_pruning_seed(todo!())
903        .await?
904        .compress();
905
906    let spans = blockchain_manager::spans::<ClearNet>(todo!()).await?;
907
908    // <https://github.com/Cuprate/cuprate/pull/320#discussion_r1811063772>
909    let overview = String::from(FIELD_NOT_SUPPORTED);
910
911    Ok(SyncInfoResponse {
912        base: helper::access_response_base(false),
913        height,
914        next_needed_pruning_seed,
915        overview,
916        peers,
917        spans,
918        target_height,
919    })
920}
921
922/// <https://github.com/monero-project/monero/blob/cc73fe71162d564ffda8e549b79a350bca53c454/src/rpc/core_rpc_server.cpp#L3332-L3350>
923async fn get_transaction_pool_backlog(
924    mut state: CupratedRpcHandler,
925    _: GetTransactionPoolBacklogRequest,
926) -> Result<GetTransactionPoolBacklogResponse, Error> {
927    let now = current_unix_timestamp();
928
929    let backlog = txpool::backlog(&mut state.txpool_read)
930        .await?
931        .into_iter()
932        .map(|entry| TxBacklogEntry {
933            weight: usize_to_u64(entry.weight),
934            fee: entry.fee,
935            time_in_pool: now - entry.received_at,
936        })
937        .collect();
938
939    Ok(GetTransactionPoolBacklogResponse {
940        base: helper::response_base(false),
941        backlog,
942    })
943}
944
945/// <https://github.com/monero-project/monero/blob/cc73fe71162d564ffda8e549b79a350bca53c454/src/rpc/core_rpc_server.cpp#L3352-L3398>
946async fn get_output_distribution(
947    state: CupratedRpcHandler,
948    request: GetOutputDistributionRequest,
949) -> Result<GetOutputDistributionResponse, Error> {
950    shared::get_output_distribution(state, request).await
951}
952
953/// <https://github.com/monero-project/monero/blob/cc73fe71162d564ffda8e549b79a350bca53c454/src/rpc/core_rpc_server.cpp#L1998-L2033>
954async fn get_miner_data(
955    mut state: CupratedRpcHandler,
956    _: GetMinerDataRequest,
957) -> Result<GetMinerDataResponse, Error> {
958    let c = state.blockchain_context.blockchain_context();
959
960    let major_version = c.current_hf.as_u8();
961    let height = usize_to_u64(c.chain_height);
962    let prev_id = Hex(c.top_hash);
963    // TODO: RX seed hash <https://github.com/Cuprate/cuprate/pull/355#discussion_r1911814320>.
964    let seed_hash = Hex([0; 32]);
965    let difficulty = c.next_difficulty.hex_prefix();
966    // TODO: <https://github.com/Cuprate/cuprate/pull/355#discussion_r1911821515>
967    let median_weight = usize_to_u64(c.effective_median_weight);
968    let already_generated_coins = c.already_generated_coins;
969    let tx_backlog = txpool::backlog(&mut state.txpool_read)
970        .await?
971        .into_iter()
972        .map(|entry| GetMinerDataTxBacklogEntry {
973            id: Hex(entry.id),
974            weight: usize_to_u64(entry.weight),
975            fee: entry.fee,
976        })
977        .collect();
978
979    Ok(GetMinerDataResponse {
980        base: helper::response_base(false),
981        major_version,
982        height,
983        prev_id,
984        seed_hash,
985        difficulty,
986        median_weight,
987        already_generated_coins,
988        tx_backlog,
989    })
990}
991
992/// <https://github.com/monero-project/monero/blob/cc73fe71162d564ffda8e549b79a350bca53c454/src/rpc/core_rpc_server.cpp#L3453-L3476>
993async fn prune_blockchain(
994    mut state: CupratedRpcHandler,
995    request: PruneBlockchainRequest,
996) -> Result<PruneBlockchainResponse, Error> {
997    let pruned = blockchain_manager::pruned(todo!()).await?;
998    let pruning_seed = blockchain_manager::prune(todo!()).await?.compress();
999
1000    Ok(PruneBlockchainResponse {
1001        base: helper::response_base(false),
1002        pruned,
1003        pruning_seed,
1004    })
1005}
1006
1007/// <https://github.com/monero-project/monero/blob/cc73fe71162d564ffda8e549b79a350bca53c454/src/rpc/core_rpc_server.cpp#L2035-L2070>
1008async fn calc_pow(
1009    mut state: CupratedRpcHandler,
1010    request: CalcPowRequest,
1011) -> Result<CalcPowResponse, Error> {
1012    let hardfork = HardFork::from_version(request.major_version)?;
1013    let block = Block::read(&mut request.block_blob.as_slice())?;
1014    let seed_hash = request.seed_hash.0;
1015
1016    // let block_weight = todo!();
1017
1018    // let median_for_block_reward = blockchain_context::context(&mut state.blockchain_context)
1019    //     .await?
1020    //     .unchecked_blockchain_context()
1021    //     .context_to_verify_block
1022    //     .median_weight_for_block_reward;
1023
1024    // if cuprate_consensus_rules::blocks::check_block_weight(block_weight, median_for_block_reward)
1025    //     .is_err()
1026    // {
1027    //     return Err(anyhow!("Block blob size is too big, rejecting block"));
1028    // }
1029
1030    // TODO: will `CalculatePow` do the above checks?
1031
1032    let pow_hash = blockchain_context::calculate_pow(
1033        &mut state.blockchain_context,
1034        hardfork,
1035        block,
1036        seed_hash,
1037    )
1038    .await?;
1039
1040    Ok(CalcPowResponse {
1041        pow_hash: Hex(pow_hash),
1042    })
1043}
1044
1045/// An async-friendly wrapper for [`add_aux_pow_inner`].
1046async fn add_aux_pow(
1047    state: CupratedRpcHandler,
1048    request: AddAuxPowRequest,
1049) -> Result<AddAuxPowResponse, Error> {
1050    // This method can be a bit heavy, so rate-limit restricted use.
1051    //
1052    // FIXME: Add rate-limiting with `Semaphore` or impl
1053    // rate-limiting across the entire RPC system.
1054    // <https://github.com/Cuprate/cuprate/pull/355#discussion_r1986155415>
1055    if state.is_restricted() {
1056        tokio::time::sleep(Duration::from_millis(100)).await;
1057    }
1058
1059    cuprate_helper::asynch::rayon_spawn_async(|| add_aux_pow_inner(state, request)).await
1060}
1061
1062/// <https://github.com/monero-project/monero/blob/cc73fe71162d564ffda8e549b79a350bca53c454/src/rpc/core_rpc_server.cpp#L2072-L2207>
1063fn add_aux_pow_inner(
1064    state: CupratedRpcHandler,
1065    request: AddAuxPowRequest,
1066) -> Result<AddAuxPowResponse, Error> {
1067    let Some(non_zero_len) = NonZero::<usize>::new(request.aux_pow.len()) else {
1068        return Err(anyhow!("Empty `aux_pow` vector"));
1069    };
1070
1071    // Some of the code below requires that the
1072    // `.len()` of certain containers are the same.
1073    // Boxed slices are used over `Vec` to slightly
1074    // safe-guard against accidentally pushing to it.
1075    let aux_pow = request.aux_pow.into_boxed_slice();
1076
1077    // TODO: why is this here? it does nothing:
1078    // <https://github.com/monero-project/monero/blob/cc73fe71162d564ffda8e549b79a350bca53c454/src/rpc/core_rpc_server.cpp#L2110-L2112>
1079    // let mut path_domain = 1_usize;
1080    // while 1 << path_domain < len {
1081    //     path_domain += 1;
1082    // }
1083
1084    fn find_nonce(
1085        aux_pow: &[AuxPow],
1086        non_zero_len: NonZero<usize>,
1087        aux_pow_len: usize,
1088    ) -> Result<(u32, Box<[u32]>), Error> {
1089        /// <https://github.com/monero-project/monero/blob/893916ad091a92e765ce3241b94e706ad012b62a/src/cryptonote_basic/merge_mining.cpp#L48>
1090        fn get_aux_slot(id: &[u8; 32], nonce: u32, n_aux_chains: NonZero<u32>) -> u32 {
1091            const HASH_SIZE: usize = 32;
1092
1093            let mut buf = [0; HASH_SIZE + size_of::<u32>() + 1];
1094            buf[..HASH_SIZE].copy_from_slice(id);
1095
1096            let v: [u8; 4] = nonce.to_le_bytes();
1097            buf[HASH_SIZE..HASH_SIZE + size_of::<u32>()].copy_from_slice(&v);
1098
1099            const HASH_KEY_MM_SLOT: u8 = b'm';
1100            buf[HASH_SIZE + size_of::<u32>()] = HASH_KEY_MM_SLOT;
1101
1102            fn sha256sum(buf: &[u8]) -> [u8; 32] {
1103                todo!()
1104            }
1105            let res = sha256sum(&buf);
1106
1107            let v = u32::from_le_bytes(res[..4].try_into().unwrap());
1108
1109            v % n_aux_chains.get()
1110        }
1111
1112        // INVARIANT: this must be the same `.len()` as `aux_pow`
1113        let mut slots: Box<[u32]> = vec![u32::MAX; aux_pow_len].into_boxed_slice();
1114        let mut slot_seen: Box<[bool]> = vec![false; aux_pow_len].into_boxed_slice();
1115
1116        const MAX_NONCE: u32 = 65535;
1117
1118        for nonce in 0..=MAX_NONCE {
1119            for i in &mut slots {
1120                let slot_u32 = get_aux_slot(
1121                    &aux_pow[u32_to_usize(*i)].id.0,
1122                    nonce,
1123                    non_zero_len.try_into().unwrap(),
1124                );
1125
1126                let slot = u32_to_usize(slot_u32);
1127
1128                if slot >= aux_pow_len {
1129                    return Err(anyhow!("Computed slot is out of range"));
1130                }
1131
1132                if slot_seen[slot] {
1133                    return Ok((nonce, slots));
1134                }
1135
1136                slot_seen[slot] = true;
1137                *i = slot_u32;
1138            }
1139
1140            slots.fill(u32::MAX);
1141        }
1142
1143        Err(anyhow!("Failed to find a suitable nonce"))
1144    }
1145
1146    let len = non_zero_len.get();
1147    let (nonce, slots) = find_nonce(&aux_pow, non_zero_len, len)?;
1148
1149    // FIXME: use iterator version.
1150    let (aux_pow_id_raw, aux_pow_raw) = {
1151        let mut aux_pow_id_raw = Vec::<[u8; 32]>::with_capacity(len);
1152        let mut aux_pow_raw = Vec::<[u8; 32]>::with_capacity(len);
1153
1154        assert_eq!(
1155            aux_pow.len(),
1156            slots.len(),
1157            "these need to be the same or else the below .zip() doesn't make sense"
1158        );
1159
1160        for (aux_pow, slot) in aux_pow.iter().zip(&slots) {
1161            if u32_to_usize(*slot) >= len {
1162                return Err(anyhow!("Slot value out of range"));
1163            }
1164
1165            aux_pow_id_raw.push(aux_pow.id.0);
1166            aux_pow_raw.push(aux_pow.hash.0);
1167        }
1168
1169        assert_eq!(
1170            slots.len(),
1171            aux_pow_raw.len(),
1172            "these need to be the same or else the below .zip() doesn't make sense"
1173        );
1174        assert_eq!(
1175            aux_pow_raw.len(),
1176            aux_pow_id_raw.len(),
1177            "these need to be the same or else the below .zip() doesn't make sense"
1178        );
1179
1180        for (slot, aux_pow) in slots.iter().zip(&aux_pow) {
1181            let slot = u32_to_usize(*slot);
1182
1183            if slot >= len {
1184                return Err(anyhow!("Slot value out of range"));
1185            }
1186
1187            aux_pow_raw[slot] = aux_pow.hash.0;
1188            aux_pow_id_raw[slot] = aux_pow.id.0;
1189        }
1190
1191        (
1192            aux_pow_id_raw.into_boxed_slice(),
1193            aux_pow_raw.into_boxed_slice(),
1194        )
1195    };
1196
1197    fn tree_hash(aux_pow_raw: &[[u8; 32]]) -> [u8; 32] {
1198        todo!("https://github.com/serai-dex/serai/pull/629")
1199    }
1200
1201    fn encode_mm_depth(aux_pow_len: usize, nonce: u32) -> u64 {
1202        todo!("https://github.com/monero-project/monero/blob/893916ad091a92e765ce3241b94e706ad012b62a/src/cryptonote_basic/merge_mining.cpp#L74")
1203    }
1204
1205    let merkle_root = tree_hash(aux_pow_raw.as_ref());
1206    let merkle_tree_depth = encode_mm_depth(len, nonce);
1207
1208    let block_template = Block::read(&mut request.blocktemplate_blob.as_slice())?;
1209
1210    fn remove_field_from_tx_extra() -> Result<(), ()> {
1211        todo!("https://github.com/monero-project/monero/blob/master/src/cryptonote_basic/cryptonote_format_utils.cpp#L767")
1212    }
1213
1214    if remove_field_from_tx_extra().is_err() {
1215        return Err(anyhow!("Error removing existing merkle root"));
1216    }
1217
1218    fn add_mm_merkle_root_to_tx_extra() -> Result<(), ()> {
1219        todo!("https://github.com/monero-project/monero/blob/cc73fe71162d564ffda8e549b79a350bca53c454/src/rpc/core_rpc_server.cpp#L2189
1220        ")
1221    }
1222
1223    if add_mm_merkle_root_to_tx_extra().is_err() {
1224        return Err(anyhow!("Error adding merkle root"));
1225    }
1226
1227    fn invalidate_hashes() {
1228        // block_template.invalidate_hashes();
1229        // block_template.miner_tx.invalidate_hashes();
1230        // <https://github.com/monero-project/monero/blob/cc73fe71162d564ffda8e549b79a350bca53c454/src/rpc/core_rpc_server.cpp#L2195-L2196>
1231        todo!();
1232    }
1233
1234    invalidate_hashes();
1235
1236    let blocktemplate_blob = block_template.serialize();
1237    let blockhashing_blob = block_template.serialize_pow_hash();
1238
1239    let blocktemplate_blob = HexVec(blocktemplate_blob);
1240    let blockhashing_blob = HexVec(blockhashing_blob);
1241    let merkle_root = Hex(merkle_root);
1242    let aux_pow = aux_pow.into_vec();
1243
1244    Ok(AddAuxPowResponse {
1245        base: helper::response_base(false),
1246        blocktemplate_blob,
1247        blockhashing_blob,
1248        merkle_root,
1249        merkle_tree_depth,
1250        aux_pow,
1251    })
1252}
1253
1254//---------------------------------------------------------------------------------------------------- Unsupported RPC calls (for now)
1255
1256/// <https://github.com/monero-project/monero/blob/cc73fe71162d564ffda8e549b79a350bca53c454/src/rpc/core_rpc_server.cpp#L3553-L3627>
1257async fn get_tx_ids_loose(
1258    state: CupratedRpcHandler,
1259    request: GetTxIdsLooseRequest,
1260) -> Result<GetTxIdsLooseResponse, Error> {
1261    Ok(GetTxIdsLooseResponse {
1262        base: helper::response_base(false),
1263        txids: todo!("this RPC call is not yet in the v0.18 branch."),
1264    })
1265}
1266
1267//---------------------------------------------------------------------------------------------------- Unsupported RPC calls (forever)
1268
1269/// <https://github.com/monero-project/monero/blob/cc73fe71162d564ffda8e549b79a350bca53c454/src/rpc/core_rpc_server.cpp#L3542-L3551>
1270async fn flush_cache(
1271    state: CupratedRpcHandler,
1272    request: FlushCacheRequest,
1273) -> Result<FlushCacheResponse, Error> {
1274    unreachable!()
1275}