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},
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(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 Req::GetTxIdsLoose(_) | Req::FlushCache(_) => return Err(anyhow!(UNSUPPORTED_RPC_CALL)),
128 })
129}
130
131async 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 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
207async fn get_block_count(
209 mut state: CupratedRpcHandler,
210 _: GetBlockCountRequest,
211) -> Result<GetBlockCountResponse, Error> {
212 Ok(GetBlockCountResponse {
213 base: helper::response_base(false),
214 count: blockchain::chain_height(&mut state.blockchain_read)
216 .await?
217 .0,
218 })
219}
220
221async 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
234async fn submit_block(
236 mut state: CupratedRpcHandler,
237 request: SubmitBlockRequest,
238) -> Result<SubmitBlockResponse, Error> {
239 let [blob] = request.block_blob;
241 let block = Block::read(&mut blob.as_slice())?;
242 let block_id = Hex(block.hash());
243
244 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
253async 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 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
289async 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
303async 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 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
331async 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
346async 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 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
400async 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
437async 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
450async 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 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 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 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 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
604async 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
624async fn set_bans(
626 state: CupratedRpcHandler,
627 request: SetBansRequest,
628) -> Result<SetBansResponse, Error> {
629 for peer in request.bans {
630 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
652async fn get_bans(state: CupratedRpcHandler, _: GetBansRequest) -> Result<GetBansResponse, Error> {
654 let now = Instant::now();
655
656 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 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
693async 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
730async 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
746async 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
776async 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 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
804async 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 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
838async 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
855async 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
872async 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
888async 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 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
924async 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
945async fn get_output_distribution(
947 state: CupratedRpcHandler,
948 request: GetOutputDistributionRequest,
949) -> Result<GetOutputDistributionResponse, Error> {
950 shared::get_output_distribution(state, request).await
951}
952
953async 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 let seed_hash = Hex([0; 32]);
965 let difficulty = c.next_difficulty.hex_prefix();
966 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
992async 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
1009async 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 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
1047async fn add_aux_pow(
1049 state: CupratedRpcHandler,
1050 request: AddAuxPowRequest,
1051) -> Result<AddAuxPowResponse, Error> {
1052 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
1064fn 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 let aux_pow = request.aux_pow.into_boxed_slice();
1078
1079 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 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 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 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 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
1256async 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
1269async fn flush_cache(
1273 state: CupratedRpcHandler,
1274 request: FlushCacheRequest,
1275) -> Result<FlushCacheResponse, Error> {
1276 unreachable!()
1277}