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