1use 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
71pub 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 Req::GetTxIdsLoose(_) | Req::FlushCache(_) => return Err(anyhow!(UNSUPPORTED_RPC_CALL)),
120 })
121}
122
123async 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 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
199async fn get_block_count(
201 mut state: CupratedRpcHandler,
202 _: GetBlockCountRequest,
203) -> Result<GetBlockCountResponse, Error> {
204 Ok(GetBlockCountResponse {
205 base: helper::response_base(false),
206 count: blockchain::chain_height(&mut state.blockchain_read)
208 .await?
209 .0,
210 })
211}
212
213async 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
226async fn submit_block(
228 mut state: CupratedRpcHandler,
229 request: SubmitBlockRequest,
230) -> Result<SubmitBlockResponse, Error> {
231 let [blob] = request.block_blob;
233 let block = Block::read(&mut blob.as_slice())?;
234 let block_id = Hex(block.hash());
235
236 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
245async 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 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
281async 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
295async 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 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
323async 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
338async 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 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
392async 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
429async 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
442async 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 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 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 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 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
594async 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
614async fn set_bans(
616 state: CupratedRpcHandler,
617 request: SetBansRequest,
618) -> Result<SetBansResponse, Error> {
619 for peer in request.bans {
620 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
642async fn get_bans(state: CupratedRpcHandler, _: GetBansRequest) -> Result<GetBansResponse, Error> {
644 let now = Instant::now();
645
646 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 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
683async 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
720async 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
736async 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
766async 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 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
794async 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 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
828async 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
845async 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
862async 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
878async 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 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
913async 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
934async fn get_output_distribution(
936 state: CupratedRpcHandler,
937 request: GetOutputDistributionRequest,
938) -> Result<GetOutputDistributionResponse, Error> {
939 shared::get_output_distribution(state, request).await
940}
941
942async 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 let seed_hash = Hex([0; 32]);
954 let difficulty = c.next_difficulty.hex_prefix();
955 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
981async 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
996async 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 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
1034async fn add_aux_pow(
1036 state: CupratedRpcHandler,
1037 request: AddAuxPowRequest,
1038) -> Result<AddAuxPowResponse, Error> {
1039 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
1051fn 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 let aux_pow = request.aux_pow.into_boxed_slice();
1065
1066 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 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 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 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 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
1243async 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
1256async fn flush_cache(
1260 state: CupratedRpcHandler,
1261 request: FlushCacheRequest,
1262) -> Result<FlushCacheResponse, Error> {
1263 unreachable!()
1264}