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