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