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