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