1use std::{
8 borrow::Cow,
9 collections::{BTreeSet, HashMap, HashSet},
10};
11
12use anyhow::{anyhow, Error};
13use monero_serai::transaction::{Input, Timelock, Transaction};
14
15use cuprate_constants::rpc::{
16 MAX_RESTRICTED_GLOBAL_FAKE_OUTS_COUNT, RESTRICTED_SPENT_KEY_IMAGES_COUNT,
17 RESTRICTED_TRANSACTIONS_COUNT,
18};
19use cuprate_helper::cast::usize_to_u64;
20use cuprate_hex::{Hex, HexVec};
21use cuprate_p2p_core::{client::handshaker::builder::DummyAddressBook, ClearNet};
22use cuprate_rpc_interface::RpcHandler;
23use cuprate_rpc_types::{
24 base::{AccessResponseBase, ResponseBase},
25 misc::{Status, TxEntry, TxEntryType},
26 other::{
27 GetAltBlocksHashesRequest, GetAltBlocksHashesResponse, GetHeightRequest, GetHeightResponse,
28 GetLimitRequest, GetLimitResponse, GetNetStatsRequest, GetNetStatsResponse, GetOutsRequest,
29 GetOutsResponse, GetPeerListRequest, GetPeerListResponse, GetPublicNodesRequest,
30 GetPublicNodesResponse, GetTransactionPoolHashesRequest, GetTransactionPoolHashesResponse,
31 GetTransactionPoolRequest, GetTransactionPoolResponse, GetTransactionPoolStatsRequest,
32 GetTransactionPoolStatsResponse, GetTransactionsRequest, GetTransactionsResponse,
33 InPeersRequest, InPeersResponse, IsKeyImageSpentRequest, IsKeyImageSpentResponse,
34 MiningStatusRequest, MiningStatusResponse, OtherRequest, OtherResponse, OutPeersRequest,
35 OutPeersResponse, PopBlocksRequest, PopBlocksResponse, SaveBcRequest, SaveBcResponse,
36 SendRawTransactionRequest, SendRawTransactionResponse, SetBootstrapDaemonRequest,
37 SetBootstrapDaemonResponse, SetLimitRequest, SetLimitResponse, SetLogCategoriesRequest,
38 SetLogCategoriesResponse, SetLogHashRateRequest, SetLogHashRateResponse,
39 SetLogLevelRequest, SetLogLevelResponse, StartMiningRequest, StartMiningResponse,
40 StopDaemonRequest, StopDaemonResponse, StopMiningRequest, StopMiningResponse,
41 UpdateRequest, UpdateResponse,
42 },
43};
44use cuprate_types::{
45 rpc::{KeyImageSpentStatus, PoolInfo, PoolTxInfo, PublicNode},
46 TxInPool, TxRelayChecks,
47};
48
49use crate::{
50 rpc::{
51 constants::UNSUPPORTED_RPC_CALL,
52 handlers::{helper, shared, shared::not_available},
53 service::{address_book, blockchain, blockchain_context, blockchain_manager, txpool},
54 CupratedRpcHandler,
55 },
56 statics::START_INSTANT_UNIX,
57};
58
59pub async fn map_request(
61 state: CupratedRpcHandler,
62 request: OtherRequest,
63) -> Result<OtherResponse, Error> {
64 use OtherRequest as Req;
65 use OtherResponse as Resp;
66
67 Ok(match request {
68 Req::GetHeight(r) => Resp::GetHeight(get_height(state, r).await?),
69 Req::GetTransactions(r) => Resp::GetTransactions(not_available()?),
70 Req::GetAltBlocksHashes(r) => Resp::GetAltBlocksHashes(not_available()?),
71 Req::IsKeyImageSpent(r) => Resp::IsKeyImageSpent(not_available()?),
72 Req::SendRawTransaction(r) => Resp::SendRawTransaction(not_available()?),
73 Req::SaveBc(r) => Resp::SaveBc(not_available()?),
74 Req::GetPeerList(r) => Resp::GetPeerList(not_available()?),
75 Req::SetLogLevel(r) => Resp::SetLogLevel(not_available()?),
76 Req::SetLogCategories(r) => Resp::SetLogCategories(not_available()?),
77 Req::GetTransactionPool(r) => Resp::GetTransactionPool(not_available()?),
78 Req::GetTransactionPoolStats(r) => Resp::GetTransactionPoolStats(not_available()?),
79 Req::StopDaemon(r) => Resp::StopDaemon(not_available()?),
80 Req::GetLimit(r) => Resp::GetLimit(not_available()?),
81 Req::SetLimit(r) => Resp::SetLimit(not_available()?),
82 Req::OutPeers(r) => Resp::OutPeers(not_available()?),
83 Req::InPeers(r) => Resp::InPeers(not_available()?),
84 Req::GetNetStats(r) => Resp::GetNetStats(not_available()?),
85 Req::GetOuts(r) => Resp::GetOuts(not_available()?),
86 Req::PopBlocks(r) => Resp::PopBlocks(not_available()?),
87 Req::GetTransactionPoolHashes(r) => Resp::GetTransactionPoolHashes(not_available()?),
88 Req::GetPublicNodes(r) => Resp::GetPublicNodes(not_available()?),
89
90 Req::SetBootstrapDaemon(_)
92 | Req::Update(_)
93 | Req::StartMining(_)
94 | Req::StopMining(_)
95 | Req::MiningStatus(_)
96 | Req::SetLogHashRate(_) => return Err(anyhow!(UNSUPPORTED_RPC_CALL)),
97 })
98}
99
100async fn get_height(
102 mut state: CupratedRpcHandler,
103 _: GetHeightRequest,
104) -> Result<GetHeightResponse, Error> {
105 let (height, hash) = helper::top_height(&mut state).await?;
106 let hash = Hex(hash);
107
108 Ok(GetHeightResponse {
109 base: helper::response_base(false),
110 height,
111 hash,
112 })
113}
114
115async fn get_transactions(
117 mut state: CupratedRpcHandler,
118 request: GetTransactionsRequest,
119) -> Result<GetTransactionsResponse, Error> {
120 if state.is_restricted() && request.txs_hashes.len() > RESTRICTED_TRANSACTIONS_COUNT {
121 return Err(anyhow!(
122 "Too many transactions requested in restricted mode"
123 ));
124 }
125
126 let (txs_in_blockchain, missed_txs) = {
127 let requested_txs = request.txs_hashes.into_iter().map(|tx| tx.0).collect();
128 blockchain::transactions(&mut state.blockchain_read, requested_txs).await?
129 };
130
131 let missed_tx = missed_txs.clone().into_iter().map(Hex).collect();
132
133 let txs_in_pool = if missed_txs.is_empty() {
135 vec![]
136 } else {
137 let include_sensitive_txs = !state.is_restricted();
138 txpool::txs_by_hash(&mut state.txpool_read, missed_txs, include_sensitive_txs).await?
139 };
140
141 let (txs, txs_as_hex, txs_as_json) = {
142 let len = txs_in_blockchain.len() + txs_in_pool.len();
144 let mut txs = Vec::with_capacity(len);
145 let mut txs_as_hex = Vec::with_capacity(len);
146 let mut txs_as_json = Vec::with_capacity(if request.decode_as_json { len } else { 0 });
147
148 for tx in txs_in_blockchain {
150 let tx_hash = Hex(tx.tx_hash);
151 let prunable_hash = Hex(tx.prunable_hash);
152
153 let (pruned_as_hex, prunable_as_hex) = if tx.pruned_blob.is_empty() {
154 (HexVec::new(), HexVec::new())
155 } else {
156 (HexVec(tx.pruned_blob), HexVec(tx.prunable_blob))
157 };
158
159 let as_hex = if pruned_as_hex.is_empty() {
160 HexVec::new()
163 } else {
164 HexVec(tx.tx_blob)
165 };
166
167 txs_as_hex.push(as_hex.clone());
168
169 let as_json = if request.decode_as_json {
170 let tx = Transaction::read(&mut as_hex.as_slice())?;
171 let json_type = cuprate_types::json::tx::Transaction::from(tx);
172 let json = serde_json::to_string(&json_type).unwrap();
173 txs_as_json.push(json.clone());
174 json
175 } else {
176 String::new()
177 };
178
179 let tx_entry_type = TxEntryType::Blockchain {
180 block_height: tx.block_height,
181 block_timestamp: tx.block_timestamp,
182 confirmations: tx.confirmations,
183 output_indices: tx.output_indices,
184 in_pool: false,
185 };
186
187 let tx = TxEntry {
188 as_hex,
189 as_json,
190 double_spend_seen: false,
191 tx_hash,
192 prunable_as_hex,
193 prunable_hash,
194 pruned_as_hex,
195 tx_entry_type,
196 };
197
198 txs.push(tx);
199 }
200
201 for tx_in_pool in txs_in_pool {
203 let TxInPool {
204 tx_blob,
205 tx_hash,
206 double_spend_seen,
207 received_timestamp,
208 relayed,
209 } = tx_in_pool;
210
211 let tx_hash = Hex(tx_hash);
212 let tx = Transaction::read(&mut tx_blob.as_slice())?;
213
214 let pruned_as_hex = HexVec::new();
215 let prunable_as_hex = HexVec::new();
216 let prunable_hash = Hex([0; 32]);
217
218 let as_hex = HexVec(tx_blob);
219 txs_as_hex.push(as_hex.clone());
220
221 let as_json = if request.decode_as_json {
222 let json_type = cuprate_types::json::tx::Transaction::from(tx);
223 let json = serde_json::to_string(&json_type).unwrap();
224 txs_as_json.push(json.clone());
225 json
226 } else {
227 String::new()
228 };
229
230 let tx_entry_type = TxEntryType::Pool {
231 relayed,
232 received_timestamp,
233 in_pool: true,
234 };
235
236 let tx = TxEntry {
237 as_hex,
238 as_json,
239 double_spend_seen,
240 tx_hash,
241 prunable_as_hex,
242 prunable_hash,
243 pruned_as_hex,
244 tx_entry_type,
245 };
246
247 txs.push(tx);
248 }
249
250 (txs, txs_as_hex, txs_as_json)
251 };
252
253 Ok(GetTransactionsResponse {
254 base: helper::access_response_base(false),
255 txs_as_hex,
256 txs_as_json,
257 missed_tx,
258 txs,
259 })
260}
261
262async fn get_alt_blocks_hashes(
264 mut state: CupratedRpcHandler,
265 _: GetAltBlocksHashesRequest,
266) -> Result<GetAltBlocksHashesResponse, Error> {
267 let blks_hashes = blockchain::alt_chains(&mut state.blockchain_read)
268 .await?
269 .into_iter()
270 .map(|info| Hex(info.block_hash))
271 .collect();
272
273 Ok(GetAltBlocksHashesResponse {
274 base: helper::access_response_base(false),
275 blks_hashes,
276 })
277}
278
279async fn is_key_image_spent(
281 mut state: CupratedRpcHandler,
282 request: IsKeyImageSpentRequest,
283) -> Result<IsKeyImageSpentResponse, Error> {
284 let restricted = state.is_restricted();
285
286 if restricted && request.key_images.len() > RESTRICTED_SPENT_KEY_IMAGES_COUNT {
287 return Err(anyhow!("Too many key images queried in restricted mode"));
288 }
289
290 let key_images = request
291 .key_images
292 .into_iter()
293 .map(|k| k.0)
294 .collect::<Vec<[u8; 32]>>();
295
296 let mut spent_status = Vec::with_capacity(key_images.len());
297
298 blockchain::key_images_spent_vec(&mut state.blockchain_read, key_images.clone())
300 .await?
301 .into_iter()
302 .for_each(|ki| {
303 if ki {
304 spent_status.push(KeyImageSpentStatus::SpentInBlockchain);
305 } else {
306 spent_status.push(KeyImageSpentStatus::Unspent);
307 }
308 });
309
310 assert_eq!(spent_status.len(), key_images.len(), "key_images_spent() should be returning a Vec with an equal length to the input, the below zip() relies on this.");
311
312 let key_images = key_images
314 .into_iter()
315 .zip(&spent_status)
316 .filter_map(|(ki, status)| match status {
317 KeyImageSpentStatus::Unspent => Some(ki),
318 KeyImageSpentStatus::SpentInBlockchain => None,
319 KeyImageSpentStatus::SpentInPool => unreachable!(),
320 })
321 .collect::<Vec<[u8; 32]>>();
322
323 if !key_images.is_empty() {
325 txpool::key_images_spent_vec(&mut state.txpool_read, key_images, !restricted)
326 .await?
327 .into_iter()
328 .for_each(|ki| {
329 if ki {
330 spent_status.push(KeyImageSpentStatus::SpentInPool);
331 } else {
332 spent_status.push(KeyImageSpentStatus::Unspent);
333 }
334 });
335 }
336
337 let spent_status = spent_status
338 .into_iter()
339 .map(KeyImageSpentStatus::to_u8)
340 .collect();
341
342 Ok(IsKeyImageSpentResponse {
343 base: helper::access_response_base(false),
344 spent_status,
345 })
346}
347
348async fn send_raw_transaction(
350 mut state: CupratedRpcHandler,
351 request: SendRawTransactionRequest,
352) -> Result<SendRawTransactionResponse, Error> {
353 let mut resp = SendRawTransactionResponse {
354 base: helper::access_response_base(false),
355 double_spend: false,
356 fee_too_low: false,
357 invalid_input: false,
358 invalid_output: false,
359 low_mixin: false,
360 nonzero_unlock_time: false,
361 not_relayed: request.do_not_relay,
362 overspend: false,
363 reason: String::new(),
364 sanity_check_failed: false,
365 too_big: false,
366 too_few_outputs: false,
367 tx_extra_too_big: false,
368 };
369
370 let tx = Transaction::read(&mut request.tx_as_hex.as_slice())?;
371
372 if request.do_sanity_checks {
373 fn tx_sanity_check(tx: &Transaction, rct_outs_available: u64) -> Result<(), String> {
377 let Some(input) = tx.prefix().inputs.first() else {
378 return Err("No inputs".to_string());
379 };
380
381 let mut rct_indices = vec![];
382 let mut n_indices: usize = 0;
383
384 for input in &tx.prefix().inputs {
385 match input {
386 Input::Gen(_) => return Err("Transaction is coinbase".to_string()),
387 Input::ToKey {
388 amount,
389 key_offsets,
390 key_image,
391 } => {
392 let Some(amount) = amount else {
393 continue;
394 };
395
396 fn relative_output_offsets_to_absolute(mut offsets: Vec<u64>) -> Vec<u64> {
398 assert!(!offsets.is_empty());
399
400 for i in 1..offsets.len() {
401 offsets[i] += offsets[i - 1];
402 }
403
404 offsets
405 }
406
407 n_indices += key_offsets.len();
408 let absolute = relative_output_offsets_to_absolute(key_offsets.clone());
409 rct_indices.extend(absolute);
410 }
411 }
412 }
413
414 if n_indices <= 10 {
415 return Ok(());
416 }
417
418 if rct_outs_available < 10_000 {
419 return Ok(());
420 }
421
422 let rct_indices_len = rct_indices.len();
423 if rct_indices_len < n_indices * 8 / 10 {
424 return Err(format!("amount of unique indices is too low (amount of rct indices is {rct_indices_len} out of total {n_indices} indices."));
425 }
426
427 let median = cuprate_helper::num::median(rct_indices);
428 if median < rct_outs_available * 6 / 10 {
429 return Err(format!("median offset index is too low (median is {median} out of total {rct_outs_available} offsets). Transactions should contain a higher fraction of recent outputs."));
430 }
431
432 Ok(())
433 }
434
435 let rct_outs_available = blockchain::total_rct_outputs(&mut state.blockchain_read).await?;
436
437 if let Err(e) = tx_sanity_check(&tx, rct_outs_available) {
438 resp.base.response_base.status = Status::Failed;
439 resp.reason.push_str(&format!("Sanity check failed: {e}"));
440 resp.sanity_check_failed = true;
441 return Ok(resp);
442 }
443 }
444
445 let tx_relay_checks =
447 txpool::check_maybe_relay_local(todo!(), tx, !request.do_not_relay).await?;
448
449 if tx_relay_checks.is_empty() {
450 return Ok(resp);
451 }
452
453 fn add_reason(reasons: &mut String, reason: &'static str) {
455 if !reasons.is_empty() {
456 reasons.push_str(", ");
457 }
458 reasons.push_str(reason);
459 }
460
461 let mut reasons = String::new();
462
463 #[rustfmt::skip]
464 let array = [
465 (&mut resp.double_spend, TxRelayChecks::DOUBLE_SPEND, "double spend"),
466 (&mut resp.fee_too_low, TxRelayChecks::FEE_TOO_LOW, "fee too low"),
467 (&mut resp.invalid_input, TxRelayChecks::INVALID_INPUT, "invalid input"),
468 (&mut resp.invalid_output, TxRelayChecks::INVALID_OUTPUT, "invalid output"),
469 (&mut resp.low_mixin, TxRelayChecks::LOW_MIXIN, "bad ring size"),
470 (&mut resp.nonzero_unlock_time, TxRelayChecks::NONZERO_UNLOCK_TIME, "tx unlock time is not zero"),
471 (&mut resp.overspend, TxRelayChecks::OVERSPEND, "overspend"),
472 (&mut resp.too_big, TxRelayChecks::TOO_BIG, "too big"),
473 (&mut resp.too_few_outputs, TxRelayChecks::TOO_FEW_OUTPUTS, "too few outputs"),
474 (&mut resp.tx_extra_too_big, TxRelayChecks::TX_EXTRA_TOO_BIG, "tx-extra too big"),
475 ];
476
477 for (field, flag, reason) in array {
478 if tx_relay_checks.contains(flag) {
479 *field = true;
480 add_reason(&mut reasons, reason);
481 }
482 }
483
484 Ok(resp)
485}
486
487async fn save_bc(mut state: CupratedRpcHandler, _: SaveBcRequest) -> Result<SaveBcResponse, Error> {
489 blockchain_manager::sync(todo!()).await?;
490
491 Ok(SaveBcResponse {
492 base: ResponseBase::OK,
493 })
494}
495
496async fn get_peer_list(
498 mut state: CupratedRpcHandler,
499 request: GetPeerListRequest,
500) -> Result<GetPeerListResponse, Error> {
501 let (white_list, gray_list) = address_book::peerlist::<ClearNet>(&mut DummyAddressBook).await?;
502
503 Ok(GetPeerListResponse {
504 base: helper::response_base(false),
505 white_list,
506 gray_list,
507 })
508}
509
510async fn get_transaction_pool(
512 mut state: CupratedRpcHandler,
513 _: GetTransactionPoolRequest,
514) -> Result<GetTransactionPoolResponse, Error> {
515 let include_sensitive_txs = !state.is_restricted();
516
517 let (transactions, spent_key_images) =
518 txpool::pool(&mut state.txpool_read, include_sensitive_txs).await?;
519
520 Ok(GetTransactionPoolResponse {
521 base: helper::access_response_base(false),
522 transactions,
523 spent_key_images,
524 })
525}
526
527async fn get_transaction_pool_stats(
529 mut state: CupratedRpcHandler,
530 _: GetTransactionPoolStatsRequest,
531) -> Result<GetTransactionPoolStatsResponse, Error> {
532 let include_sensitive_txs = !state.is_restricted();
533
534 let pool_stats = txpool::pool_stats(&mut state.txpool_read, include_sensitive_txs).await?;
535
536 Ok(GetTransactionPoolStatsResponse {
537 base: helper::access_response_base(false),
538 pool_stats,
539 })
540}
541
542async fn stop_daemon(
544 mut state: CupratedRpcHandler,
545 _: StopDaemonRequest,
546) -> Result<StopDaemonResponse, Error> {
547 blockchain_manager::stop(todo!()).await?;
548 Ok(StopDaemonResponse { status: Status::Ok })
549}
550
551async fn get_limit(
553 mut state: CupratedRpcHandler,
554 _: GetLimitRequest,
555) -> Result<GetLimitResponse, Error> {
556 todo!("waiting on p2p service");
557
558 Ok(GetLimitResponse {
559 base: helper::response_base(false),
560 limit_down: todo!(),
561 limit_up: todo!(),
562 })
563}
564
565async fn set_limit(
567 mut state: CupratedRpcHandler,
568 request: SetLimitRequest,
569) -> Result<SetLimitResponse, Error> {
570 todo!("waiting on p2p service");
571
572 Ok(SetLimitResponse {
573 base: helper::response_base(false),
574 limit_down: todo!(),
575 limit_up: todo!(),
576 })
577}
578
579async fn out_peers(
581 mut state: CupratedRpcHandler,
582 request: OutPeersRequest,
583) -> Result<OutPeersResponse, Error> {
584 todo!("waiting on p2p service");
585
586 Ok(OutPeersResponse {
587 base: helper::response_base(false),
588 out_peers: todo!(),
589 })
590}
591
592async fn in_peers(
594 mut state: CupratedRpcHandler,
595 request: InPeersRequest,
596) -> Result<InPeersResponse, Error> {
597 todo!("waiting on p2p service");
598
599 Ok(InPeersResponse {
600 base: helper::response_base(false),
601 in_peers: todo!(),
602 })
603}
604
605async fn get_net_stats(
607 mut state: CupratedRpcHandler,
608 _: GetNetStatsRequest,
609) -> Result<GetNetStatsResponse, Error> {
610 todo!("waiting on p2p service");
611
612 Ok(GetNetStatsResponse {
613 base: helper::response_base(false),
614 start_time: *START_INSTANT_UNIX,
615 total_packets_in: todo!(),
616 total_bytes_in: todo!(),
617 total_packets_out: todo!(),
618 total_bytes_out: todo!(),
619 })
620}
621
622async fn get_outs(
624 state: CupratedRpcHandler,
625 request: GetOutsRequest,
626) -> Result<GetOutsResponse, Error> {
627 let outs = shared::get_outs(
628 state,
629 cuprate_rpc_types::bin::GetOutsRequest {
630 outputs: request.outputs,
631 get_txid: request.get_txid,
632 },
633 )
634 .await?
635 .outs
636 .into_iter()
637 .map(Into::into)
638 .collect();
639
640 Ok(GetOutsResponse {
641 base: helper::response_base(false),
642 outs,
643 })
644}
645
646async fn pop_blocks(
648 mut state: CupratedRpcHandler,
649 request: PopBlocksRequest,
650) -> Result<PopBlocksResponse, Error> {
651 let height = blockchain_manager::pop_blocks(todo!(), request.nblocks).await?;
652
653 Ok(PopBlocksResponse {
654 base: helper::response_base(false),
655 height,
656 })
657}
658
659async fn get_transaction_pool_hashes(
661 mut state: CupratedRpcHandler,
662 _: GetTransactionPoolHashesRequest,
663) -> Result<GetTransactionPoolHashesResponse, Error> {
664 Ok(GetTransactionPoolHashesResponse {
665 base: helper::response_base(false),
666 tx_hashes: shared::get_transaction_pool_hashes(state)
667 .await?
668 .into_iter()
669 .map(Hex)
670 .collect(),
671 })
672}
673
674async fn get_public_nodes(
676 mut state: CupratedRpcHandler,
677 request: GetPublicNodesRequest,
678) -> Result<GetPublicNodesResponse, Error> {
679 let (white, gray) = address_book::peerlist::<ClearNet>(&mut DummyAddressBook).await?;
680
681 fn map(peers: Vec<cuprate_types::rpc::Peer>) -> Vec<PublicNode> {
682 peers
683 .into_iter()
684 .map(|peer| {
685 let cuprate_types::rpc::Peer {
686 host,
687 rpc_port,
688 rpc_credits_per_hash,
689 last_seen,
690 ..
691 } = peer;
692
693 PublicNode {
694 host,
695 rpc_port,
696 rpc_credits_per_hash,
697 last_seen,
698 }
699 })
700 .collect()
701 }
702
703 let white = map(white);
704 let gray = map(gray);
705
706 Ok(GetPublicNodesResponse {
707 base: helper::response_base(false),
708 white,
709 gray,
710 })
711}
712
713async fn set_bootstrap_daemon(
717 state: CupratedRpcHandler,
718 request: SetBootstrapDaemonRequest,
719) -> Result<SetBootstrapDaemonResponse, Error> {
720 todo!();
721}
722
723async fn update(
725 state: CupratedRpcHandler,
726 request: UpdateRequest,
727) -> Result<UpdateResponse, Error> {
728 todo!();
729}
730
731async fn set_log_level(
733 state: CupratedRpcHandler,
734 request: SetLogLevelRequest,
735) -> Result<SetLogLevelResponse, Error> {
736 todo!()
737}
738
739async fn set_log_categories(
741 state: CupratedRpcHandler,
742 request: SetLogCategoriesRequest,
743) -> Result<SetLogCategoriesResponse, Error> {
744 todo!()
745}
746
747async fn start_mining(
751 state: CupratedRpcHandler,
752 request: StartMiningRequest,
753) -> Result<StartMiningResponse, Error> {
754 unreachable!()
755}
756
757async fn stop_mining(
759 state: CupratedRpcHandler,
760 request: StopMiningRequest,
761) -> Result<StopMiningResponse, Error> {
762 unreachable!();
763}
764
765async fn mining_status(
767 state: CupratedRpcHandler,
768 request: MiningStatusRequest,
769) -> Result<MiningStatusResponse, Error> {
770 unreachable!();
771}
772
773async fn set_log_hash_rate(
775 state: CupratedRpcHandler,
776 request: SetLogHashRateRequest,
777) -> Result<SetLogHashRateResponse, Error> {
778 unreachable!();
779}