1#![cfg_attr(docsrs, feature(doc_auto_cfg))]
2#![doc = include_str!("../README.md")]
3#![deny(missing_docs)]
4#![cfg_attr(not(feature = "std"), no_std)]
5
6use core::{
7 future::Future,
8 fmt::Debug,
9 ops::{Bound, RangeBounds},
10};
11use std_shims::{
12 alloc::format,
13 vec,
14 vec::Vec,
15 io,
16 string::{String, ToString},
17};
18
19use zeroize::Zeroize;
20
21use curve25519_dalek::edwards::{CompressedEdwardsY, EdwardsPoint};
22
23use serde::{Serialize, Deserialize, de::DeserializeOwned};
24use serde_json::{Value, json};
25
26use monero_oxide::{
27 io::*,
28 transaction::{Input, Timelock, Pruned, Transaction},
29 block::Block,
30 DEFAULT_LOCK_WINDOW,
31};
32use monero_address::Address;
33
34const GRACE_BLOCKS_FOR_FEE_ESTIMATE: u64 = 10;
38
39const TXS_PER_REQUEST: usize = 100;
43
44#[derive(Clone, PartialEq, Eq, Debug, thiserror::Error)]
46pub enum RpcError {
47 #[error("internal error ({0})")]
49 InternalError(String),
50 #[error("connection error ({0})")]
52 ConnectionError(String),
53 #[error("invalid node ({0})")]
55 InvalidNode(String),
56 #[error("transactions not found")]
58 TransactionsNotFound(Vec<[u8; 32]>),
59 #[error("pruned transaction")]
63 PrunedTransaction,
64 #[error("invalid transaction ({0:?})")]
66 InvalidTransaction([u8; 32]),
67 #[error("unexpected fee response")]
69 InvalidFee,
70 #[error("invalid priority")]
72 InvalidPriority,
73}
74
75#[derive(Clone, PartialEq, Eq, Debug)]
77pub struct ScannableBlock {
78 pub block: Block,
80 pub transactions: Vec<Transaction<Pruned>>,
82 pub output_index_for_first_ringct_output: Option<u64>,
86}
87
88#[derive(Clone, Copy, PartialEq, Eq, Debug, Zeroize)]
92pub struct FeeRate {
93 per_weight: u64,
95 mask: u64,
97}
98
99impl FeeRate {
100 pub fn new(per_weight: u64, mask: u64) -> Result<FeeRate, RpcError> {
102 if (per_weight == 0) || (mask == 0) {
103 Err(RpcError::InvalidFee)?;
104 }
105 Ok(FeeRate { per_weight, mask })
106 }
107
108 pub fn write(&self, w: &mut impl io::Write) -> io::Result<()> {
113 w.write_all(&self.per_weight.to_le_bytes())?;
114 w.write_all(&self.mask.to_le_bytes())
115 }
116
117 pub fn serialize(&self) -> Vec<u8> {
122 let mut res = Vec::with_capacity(16);
123 self.write(&mut res).expect("write failed but <Vec as io::Write> doesn't fail");
124 res
125 }
126
127 pub fn read(r: &mut impl io::Read) -> io::Result<FeeRate> {
132 let per_weight = read_u64(r)?;
133 let mask = read_u64(r)?;
134 FeeRate::new(per_weight, mask).map_err(io::Error::other)
135 }
136
137 pub fn calculate_fee_from_weight(&self, weight: usize) -> u64 {
141 let fee =
142 self.per_weight * u64::try_from(weight).expect("couldn't convert weight (usize) to u64");
143 let fee = fee.div_ceil(self.mask) * self.mask;
144 debug_assert_eq!(
145 Some(weight),
146 self.calculate_weight_from_fee(fee),
147 "Miscalculated weight from fee"
148 );
149 fee
150 }
151
152 pub fn calculate_weight_from_fee(&self, fee: u64) -> Option<usize> {
156 usize::try_from(fee / self.per_weight).ok()
157 }
158}
159
160#[derive(Clone, Copy, PartialEq, Eq, Debug)]
164#[allow(non_camel_case_types)]
165pub enum FeePriority {
166 Unimportant,
168 Normal,
170 Elevated,
172 Priority,
174 Custom {
176 priority: u32,
178 },
179}
180
181impl FeePriority {
184 pub(crate) fn fee_priority(&self) -> u32 {
185 match self {
186 FeePriority::Unimportant => 1,
187 FeePriority::Normal => 2,
188 FeePriority::Elevated => 3,
189 FeePriority::Priority => 4,
190 FeePriority::Custom { priority, .. } => *priority,
191 }
192 }
193}
194
195#[derive(Debug, Deserialize)]
196struct JsonRpcResponse<T> {
197 result: T,
198}
199
200#[derive(Debug, Deserialize)]
201struct TransactionResponse {
202 tx_hash: String,
203 as_hex: String,
204 pruned_as_hex: String,
205}
206#[derive(Debug, Deserialize)]
207struct TransactionsResponse {
208 #[serde(default)]
209 missed_tx: Vec<String>,
210 txs: Vec<TransactionResponse>,
211}
212
213#[derive(Clone, Copy, PartialEq, Eq, Debug)]
215pub struct OutputInformation {
216 pub height: usize,
220 pub unlocked: bool,
222 pub key: CompressedEdwardsY,
227 pub commitment: EdwardsPoint,
229 pub transaction: [u8; 32],
231}
232
233fn rpc_hex(value: &str) -> Result<Vec<u8>, RpcError> {
234 hex::decode(value).map_err(|_| RpcError::InvalidNode("expected hex wasn't hex".to_string()))
235}
236
237fn hash_hex(hash: &str) -> Result<[u8; 32], RpcError> {
238 rpc_hex(hash)?.try_into().map_err(|_| RpcError::InvalidNode("hash wasn't 32-bytes".to_string()))
239}
240
241fn rpc_point(point: &str) -> Result<EdwardsPoint, RpcError> {
242 decompress_point(
243 rpc_hex(point)?
244 .try_into()
245 .map_err(|_| RpcError::InvalidNode(format!("invalid point: {point}")))?,
246 )
247 .ok_or_else(|| RpcError::InvalidNode(format!("invalid point: {point}")))
248}
249
250pub trait Rpc: Sync + Clone {
259 fn post(
263 &self,
264 route: &str,
265 body: Vec<u8>,
266 ) -> impl Send + Future<Output = Result<Vec<u8>, RpcError>>;
267
268 fn rpc_call<Params: Send + Serialize + Debug, Response: DeserializeOwned + Debug>(
273 &self,
274 route: &str,
275 params: Option<Params>,
276 ) -> impl Send + Future<Output = Result<Response, RpcError>> {
277 async move {
278 let res = self
279 .post(
280 route,
281 if let Some(params) = params.as_ref() {
282 serde_json::to_string(params)
283 .map_err(|e| {
284 RpcError::InternalError(format!(
285 "couldn't convert parameters ({params:?}) to JSON: {e:?}"
286 ))
287 })?
288 .into_bytes()
289 } else {
290 vec![]
291 },
292 )
293 .await?;
294 let res_str = std_shims::str::from_utf8(&res)
295 .map_err(|_| RpcError::InvalidNode("response wasn't utf-8".to_string()))?;
296 serde_json::from_str(res_str)
297 .map_err(|_| RpcError::InvalidNode(format!("response wasn't the expected json: {res_str}")))
298 }
299 }
300
301 fn json_rpc_call<Response: DeserializeOwned + Debug>(
303 &self,
304 method: &str,
305 params: Option<Value>,
306 ) -> impl Send + Future<Output = Result<Response, RpcError>> {
307 async move {
308 let mut req = json!({ "method": method });
309 if let Some(params) = params {
310 req
311 .as_object_mut()
312 .expect("accessing object as object failed?")
313 .insert("params".into(), params);
314 }
315 Ok(self.rpc_call::<_, JsonRpcResponse<Response>>("json_rpc", Some(req)).await?.result)
316 }
317 }
318
319 fn bin_call(
321 &self,
322 route: &str,
323 params: Vec<u8>,
324 ) -> impl Send + Future<Output = Result<Vec<u8>, RpcError>> {
325 async move { self.post(route, params).await }
326 }
327
328 fn get_hardfork_version(&self) -> impl Send + Future<Output = Result<u8, RpcError>> {
332 async move {
333 #[derive(Debug, Deserialize)]
334 struct HeaderResponse {
335 major_version: u8,
336 }
337
338 #[derive(Debug, Deserialize)]
339 struct LastHeaderResponse {
340 block_header: HeaderResponse,
341 }
342
343 Ok(
344 self
345 .json_rpc_call::<LastHeaderResponse>("get_last_block_header", None)
346 .await?
347 .block_header
348 .major_version,
349 )
350 }
351 }
352
353 fn get_height(&self) -> impl Send + Future<Output = Result<usize, RpcError>> {
358 async move {
359 #[derive(Debug, Deserialize)]
360 struct HeightResponse {
361 height: usize,
362 }
363 let res = self.rpc_call::<Option<()>, HeightResponse>("get_height", None).await?.height;
364 if res == 0 {
365 Err(RpcError::InvalidNode("node responded with 0 for the height".to_string()))?;
366 }
367 Ok(res)
368 }
369 }
370
371 fn get_transactions(
376 &self,
377 hashes: &[[u8; 32]],
378 ) -> impl Send + Future<Output = Result<Vec<Transaction>, RpcError>> {
379 async move {
380 if hashes.is_empty() {
381 return Ok(vec![]);
382 }
383
384 let mut hashes_hex = hashes.iter().map(hex::encode).collect::<Vec<_>>();
385 let mut all_txs = Vec::with_capacity(hashes.len());
386 while !hashes_hex.is_empty() {
387 let this_count = TXS_PER_REQUEST.min(hashes_hex.len());
388
389 let txs: TransactionsResponse = self
390 .rpc_call(
391 "get_transactions",
392 Some(json!({
393 "txs_hashes": hashes_hex.drain(.. this_count).collect::<Vec<_>>(),
394 })),
395 )
396 .await?;
397
398 if !txs.missed_tx.is_empty() {
399 Err(RpcError::TransactionsNotFound(
400 txs.missed_tx.iter().map(|hash| hash_hex(hash)).collect::<Result<_, _>>()?,
401 ))?;
402 }
403 if txs.txs.len() != this_count {
404 Err(RpcError::InvalidNode(
405 "not missing any transactions yet didn't return all transactions".to_string(),
406 ))?;
407 }
408
409 all_txs.extend(txs.txs);
410 }
411
412 all_txs
413 .iter()
414 .enumerate()
415 .map(|(i, res)| {
416 let buf = rpc_hex(if !res.as_hex.is_empty() { &res.as_hex } else { &res.pruned_as_hex })?;
418 let mut buf = buf.as_slice();
419 let tx = Transaction::read(&mut buf).map_err(|_| match hash_hex(&res.tx_hash) {
420 Ok(hash) => RpcError::InvalidTransaction(hash),
421 Err(err) => err,
422 })?;
423 if !buf.is_empty() {
424 Err(RpcError::InvalidNode("transaction had extra bytes after it".to_string()))?;
425 }
426
427 if res.as_hex.is_empty() {
432 match tx.prefix().inputs.first() {
433 Some(Input::Gen { .. }) => (),
434 _ => Err(RpcError::PrunedTransaction)?,
435 }
436 }
437
438 if tx.hash() != hashes[i] {
441 Err(RpcError::InvalidNode(
442 "replied with transaction wasn't the requested transaction".to_string(),
443 ))?;
444 }
445
446 Ok(tx)
447 })
448 .collect()
449 }
450 }
451
452 fn get_pruned_transactions(
454 &self,
455 hashes: &[[u8; 32]],
456 ) -> impl Send + Future<Output = Result<Vec<Transaction<Pruned>>, RpcError>> {
457 async move {
458 if hashes.is_empty() {
459 return Ok(vec![]);
460 }
461
462 let mut hashes_hex = hashes.iter().map(hex::encode).collect::<Vec<_>>();
463 let mut all_txs = Vec::with_capacity(hashes.len());
464 while !hashes_hex.is_empty() {
465 let this_count = TXS_PER_REQUEST.min(hashes_hex.len());
466
467 let txs: TransactionsResponse = self
468 .rpc_call(
469 "get_transactions",
470 Some(json!({
471 "txs_hashes": hashes_hex.drain(.. this_count).collect::<Vec<_>>(),
472 "prune": true,
473 })),
474 )
475 .await?;
476
477 if !txs.missed_tx.is_empty() {
478 Err(RpcError::TransactionsNotFound(
479 txs.missed_tx.iter().map(|hash| hash_hex(hash)).collect::<Result<_, _>>()?,
480 ))?;
481 }
482
483 all_txs.extend(txs.txs);
484 }
485
486 all_txs
487 .iter()
488 .map(|res| {
489 let buf = rpc_hex(&res.pruned_as_hex)?;
490 let mut buf = buf.as_slice();
491 let tx =
492 Transaction::<Pruned>::read(&mut buf).map_err(|_| match hash_hex(&res.tx_hash) {
493 Ok(hash) => RpcError::InvalidTransaction(hash),
494 Err(err) => err,
495 })?;
496 if !buf.is_empty() {
497 Err(RpcError::InvalidNode("pruned transaction had extra bytes after it".to_string()))?;
498 }
499 Ok(tx)
500 })
501 .collect()
502 }
503 }
504
505 fn get_transaction(
510 &self,
511 tx: [u8; 32],
512 ) -> impl Send + Future<Output = Result<Transaction, RpcError>> {
513 async move { self.get_transactions(&[tx]).await.map(|mut txs| txs.swap_remove(0)) }
514 }
515
516 fn get_pruned_transaction(
518 &self,
519 tx: [u8; 32],
520 ) -> impl Send + Future<Output = Result<Transaction<Pruned>, RpcError>> {
521 async move { self.get_pruned_transactions(&[tx]).await.map(|mut txs| txs.swap_remove(0)) }
522 }
523
524 fn get_block_hash(
529 &self,
530 number: usize,
531 ) -> impl Send + Future<Output = Result<[u8; 32], RpcError>> {
532 async move {
533 #[derive(Debug, Deserialize)]
534 struct BlockHeaderResponse {
535 hash: String,
536 }
537 #[derive(Debug, Deserialize)]
538 struct BlockHeaderByHeightResponse {
539 block_header: BlockHeaderResponse,
540 }
541
542 let header: BlockHeaderByHeightResponse =
543 self.json_rpc_call("get_block_header_by_height", Some(json!({ "height": number }))).await?;
544 hash_hex(&header.block_header.hash)
545 }
546 }
547
548 fn get_block(&self, hash: [u8; 32]) -> impl Send + Future<Output = Result<Block, RpcError>> {
552 async move {
553 #[derive(Debug, Deserialize)]
554 struct BlockResponse {
555 blob: String,
556 }
557
558 let res: BlockResponse =
559 self.json_rpc_call("get_block", Some(json!({ "hash": hex::encode(hash) }))).await?;
560
561 let block = Block::read::<&[u8]>(&mut rpc_hex(&res.blob)?.as_ref())
562 .map_err(|_| RpcError::InvalidNode("invalid block".to_string()))?;
563 if block.hash() != hash {
564 Err(RpcError::InvalidNode("different block than requested (hash)".to_string()))?;
565 }
566 Ok(block)
567 }
568 }
569
570 fn get_block_by_number(
575 &self,
576 number: usize,
577 ) -> impl Send + Future<Output = Result<Block, RpcError>> {
578 async move {
579 #[derive(Debug, Deserialize)]
580 struct BlockResponse {
581 blob: String,
582 }
583
584 let res: BlockResponse =
585 self.json_rpc_call("get_block", Some(json!({ "height": number }))).await?;
586
587 let block = Block::read::<&[u8]>(&mut rpc_hex(&res.blob)?.as_ref())
588 .map_err(|_| RpcError::InvalidNode("invalid block".to_string()))?;
589
590 match block.miner_transaction.prefix().inputs.first() {
592 Some(Input::Gen(actual)) => {
593 if *actual == number {
594 Ok(block)
595 } else {
596 Err(RpcError::InvalidNode("different block than requested (number)".to_string()))
597 }
598 }
599 _ => Err(RpcError::InvalidNode(
600 "block's miner_transaction didn't have an input of kind Input::Gen".to_string(),
601 )),
602 }
603 }
604 }
605
606 fn get_scannable_block(
608 &self,
609 block: Block,
610 ) -> impl Send + Future<Output = Result<ScannableBlock, RpcError>> {
611 async move {
612 let transactions = self.get_pruned_transactions(&block.transactions).await?;
613
614 let mut output_index_for_first_ringct_output = None;
654 let miner_tx_hash = block.miner_transaction.hash();
655 let miner_tx = Transaction::<Pruned>::from(block.miner_transaction.clone());
656 for (hash, tx) in core::iter::once((&miner_tx_hash, &miner_tx))
657 .chain(block.transactions.iter().zip(&transactions))
658 {
659 if (!matches!(tx, Transaction::V2 { .. })) || tx.prefix().outputs.is_empty() {
661 continue;
662 }
663
664 let index = *self.get_o_indexes(*hash).await?.first().ok_or_else(|| {
665 RpcError::InvalidNode(
666 "requested output indexes for a TX with outputs and got none".to_string(),
667 )
668 })?;
669 output_index_for_first_ringct_output = Some(index);
670 break;
671 }
672
673 Ok(ScannableBlock { block, transactions, output_index_for_first_ringct_output })
674 }
675 }
676
677 fn get_scannable_block_by_hash(
680 &self,
681 hash: [u8; 32],
682 ) -> impl Send + Future<Output = Result<ScannableBlock, RpcError>> {
683 async move { self.get_scannable_block(self.get_block(hash).await?).await }
684 }
685
686 fn get_scannable_block_by_number(
689 &self,
690 number: usize,
691 ) -> impl Send + Future<Output = Result<ScannableBlock, RpcError>> {
692 async move { self.get_scannable_block(self.get_block_by_number(number).await?).await }
693 }
694
695 fn get_fee_rate(
701 &self,
702 priority: FeePriority,
703 ) -> impl Send + Future<Output = Result<FeeRate, RpcError>> {
704 async move {
705 #[derive(Debug, Deserialize)]
706 struct FeeResponse {
707 status: String,
708 fees: Option<Vec<u64>>,
709 fee: u64,
710 quantization_mask: u64,
711 }
712
713 let res: FeeResponse = self
714 .json_rpc_call(
715 "get_fee_estimate",
716 Some(json!({ "grace_blocks": GRACE_BLOCKS_FOR_FEE_ESTIMATE })),
717 )
718 .await?;
719
720 if res.status != "OK" {
721 Err(RpcError::InvalidFee)?;
722 }
723
724 if let Some(fees) = res.fees {
725 let priority_idx = usize::try_from(if priority.fee_priority() >= 4 {
728 3
729 } else {
730 priority.fee_priority().saturating_sub(1)
731 })
732 .map_err(|_| RpcError::InvalidPriority)?;
733
734 if priority_idx >= fees.len() {
735 Err(RpcError::InvalidPriority)
736 } else {
737 FeeRate::new(fees[priority_idx], res.quantization_mask)
738 }
739 } else {
740 let priority_idx = usize::try_from(if priority.fee_priority() == 0 {
745 1
746 } else {
747 priority.fee_priority() - 1
748 })
749 .map_err(|_| RpcError::InvalidPriority)?;
750 let multipliers = [1, 5, 25, 1000];
751 if priority_idx >= multipliers.len() {
752 Err(RpcError::InvalidPriority)?;
754 }
755 let fee_multiplier = multipliers[priority_idx];
756
757 FeeRate::new(res.fee * fee_multiplier, res.quantization_mask)
758 }
759 }
760 }
761
762 fn publish_transaction(
764 &self,
765 tx: &Transaction,
766 ) -> impl Send + Future<Output = Result<(), RpcError>> {
767 async move {
768 #[allow(dead_code)]
769 #[derive(Debug, Deserialize)]
770 struct SendRawResponse {
771 status: String,
772 double_spend: bool,
773 fee_too_low: bool,
774 invalid_input: bool,
775 invalid_output: bool,
776 low_mixin: bool,
777 not_relayed: bool,
778 overspend: bool,
779 too_big: bool,
780 too_few_outputs: bool,
781 reason: String,
782 }
783
784 let res: SendRawResponse = self
785 .rpc_call(
786 "send_raw_transaction",
787 Some(json!({ "tx_as_hex": hex::encode(tx.serialize()), "do_sanity_checks": false })),
788 )
789 .await?;
790
791 if res.status != "OK" {
792 Err(RpcError::InvalidTransaction(tx.hash()))?;
793 }
794
795 Ok(())
796 }
797 }
798
799 fn generate_blocks<const ADDR_BYTES: u128>(
803 &self,
804 address: &Address<ADDR_BYTES>,
805 block_count: usize,
806 ) -> impl Send + Future<Output = Result<(Vec<[u8; 32]>, usize), RpcError>> {
807 async move {
808 #[derive(Debug, Deserialize)]
809 struct BlocksResponse {
810 blocks: Vec<String>,
811 height: usize,
812 }
813
814 let res = self
815 .json_rpc_call::<BlocksResponse>(
816 "generateblocks",
817 Some(json!({
818 "wallet_address": address.to_string(),
819 "amount_of_blocks": block_count
820 })),
821 )
822 .await?;
823
824 let mut blocks = Vec::with_capacity(res.blocks.len());
825 for block in res.blocks {
826 blocks.push(hash_hex(&block)?);
827 }
828 Ok((blocks, res.height))
829 }
830 }
831
832 fn get_o_indexes(
834 &self,
835 hash: [u8; 32],
836 ) -> impl Send + Future<Output = Result<Vec<u64>, RpcError>> {
837 async move {
838 const EPEE_HEADER: &[u8] = b"\x01\x11\x01\x01\x01\x01\x02\x01\x01";
843
844 fn read_epee_vi<R: io::Read>(reader: &mut R) -> io::Result<u64> {
846 let vi_start = read_byte(reader)?;
847 let len = match vi_start & 0b11 {
848 0 => 1,
849 1 => 2,
850 2 => 4,
851 3 => 8,
852 _ => unreachable!(),
853 };
854 let mut vi = u64::from(vi_start >> 2);
855 for i in 1 .. len {
856 vi |= u64::from(read_byte(reader)?) << (((i - 1) * 8) + 6);
857 }
858 Ok(vi)
859 }
860
861 let mut request = EPEE_HEADER.to_vec();
862 request.push(1 << 2);
864 request.push(4);
866 request.extend(b"txid");
868 request.push(10);
870 request.push(32 << 2);
872 request.extend(hash);
874
875 let indexes_buf = self.bin_call("get_o_indexes.bin", request).await?;
876 let mut indexes: &[u8] = indexes_buf.as_ref();
877
878 (|| {
879 let mut res = None;
880 let mut has_status = false;
881
882 if read_bytes::<_, { EPEE_HEADER.len() }>(&mut indexes)? != EPEE_HEADER {
883 Err(io::Error::other("invalid header"))?;
884 }
885
886 let read_object = |reader: &mut &[u8]| -> io::Result<Vec<u64>> {
887 let fields = read_byte(reader)? >> 2;
889
890 for _ in 0 .. fields {
891 let name_len = read_byte(reader)?;
893 let name = read_raw_vec(read_byte, name_len.into(), reader)?;
895
896 let type_with_array_flag = read_byte(reader)?;
897 let kind = type_with_array_flag & (!0x80);
899 let has_array_flag = type_with_array_flag != kind;
900
901 let iters = if has_array_flag { read_epee_vi(reader)? } else { 1 };
903
904 {
906 #[allow(clippy::match_same_arms)]
907 let (expected_type, expected_array_flag) = match name.as_slice() {
908 b"o_indexes" => (5, true),
909 b"status" => (10, false),
910 b"untrusted" => (11, false),
911 b"credits" => (5, false),
912 b"top_hash" => (10, false),
913 _ => {
919 Err(io::Error::other(format!("unrecognized field in get_o_indexes: {name:?}")))?
920 }
921 };
922 if (expected_type != kind) || (expected_array_flag != has_array_flag) {
923 let fmt_array_bool = |array_bool| if array_bool { "array" } else { "not array" };
924 Err(io::Error::other(format!(
925 "field {name:?} was {kind} ({}), expected {expected_type} ({})",
926 fmt_array_bool(has_array_flag),
927 fmt_array_bool(expected_array_flag)
928 )))?;
929 }
930 }
931
932 let read_field_as_bytes = match kind {
933 5 => |reader: &mut &[u8]| read_raw_vec(read_byte, 8, reader),
945 10 => |reader: &mut &[u8]| {
957 let len = read_epee_vi(reader)?;
958 read_raw_vec(
959 read_byte,
960 len.try_into().map_err(|_| io::Error::other("u64 length exceeded usize"))?,
961 reader,
962 )
963 },
964 11 => |reader: &mut &[u8]| read_raw_vec(read_byte, 1, reader),
966 _ => |_: &mut &[u8]| Err(io::Error::other("node used an invalid type")),
975 };
976
977 let mut bytes_res = vec![];
978 for _ in 0 .. iters {
979 bytes_res.push(read_field_as_bytes(reader)?);
980 }
981
982 let mut actual_res = Vec::with_capacity(bytes_res.len());
983 match name.as_slice() {
984 b"o_indexes" => {
985 for o_index in bytes_res {
986 actual_res.push(read_u64(&mut o_index.as_slice())?);
987 }
988 res = Some(actual_res);
989 }
990 b"status" => {
991 if bytes_res
992 .first()
993 .ok_or_else(|| io::Error::other("status was a 0-length array"))?
994 .as_slice() !=
995 b"OK"
996 {
997 Err(io::Error::other("response wasn't OK"))?;
998 }
999 has_status = true;
1000 }
1001 b"untrusted" | b"credits" | b"top_hash" => continue,
1002 _ => Err(io::Error::other("unrecognized field in get_o_indexes"))?,
1003 }
1004 }
1005
1006 if !has_status {
1007 Err(io::Error::other("response didn't contain a status"))?;
1008 }
1009
1010 Ok(res.unwrap_or(vec![]))
1012 };
1013
1014 read_object(&mut indexes)
1015 })()
1016 .map_err(|e| RpcError::InvalidNode(format!("invalid binary response: {e:?}")))
1017 }
1018 }
1019}
1020
1021pub trait DecoyRpc: Sync {
1027 fn get_output_distribution_end_height(
1032 &self,
1033 ) -> impl Send + Future<Output = Result<usize, RpcError>>;
1034
1035 fn get_output_distribution(
1040 &self,
1041 range: impl Send + RangeBounds<usize>,
1042 ) -> impl Send + Future<Output = Result<Vec<u64>, RpcError>>;
1043
1044 fn get_outs(
1046 &self,
1047 indexes: &[u64],
1048 ) -> impl Send + Future<Output = Result<Vec<OutputInformation>, RpcError>>;
1049
1050 fn get_unlocked_outputs(
1062 &self,
1063 indexes: &[u64],
1064 height: usize,
1065 fingerprintable_deterministic: bool,
1066 ) -> impl Send + Future<Output = Result<Vec<Option<[EdwardsPoint; 2]>>, RpcError>>;
1067}
1068
1069impl<R: Rpc> DecoyRpc for R {
1070 fn get_output_distribution_end_height(
1071 &self,
1072 ) -> impl Send + Future<Output = Result<usize, RpcError>> {
1073 async move { <Self as Rpc>::get_height(self).await }
1074 }
1075
1076 fn get_output_distribution(
1077 &self,
1078 range: impl Send + RangeBounds<usize>,
1079 ) -> impl Send + Future<Output = Result<Vec<u64>, RpcError>> {
1080 async move {
1081 #[derive(Default, Debug, Deserialize)]
1082 struct Distribution {
1083 distribution: Vec<u64>,
1084 start_height: usize,
1086 }
1087
1088 #[derive(Debug, Deserialize)]
1089 struct Distributions {
1090 distributions: [Distribution; 1],
1091 status: String,
1092 }
1093
1094 let from = match range.start_bound() {
1095 Bound::Included(from) => *from,
1096 Bound::Excluded(from) => from.checked_add(1).ok_or_else(|| {
1097 RpcError::InternalError("range's from wasn't representable".to_string())
1098 })?,
1099 Bound::Unbounded => 0,
1100 };
1101 let to = match range.end_bound() {
1102 Bound::Included(to) => *to,
1103 Bound::Excluded(to) => to
1104 .checked_sub(1)
1105 .ok_or_else(|| RpcError::InternalError("range's to wasn't representable".to_string()))?,
1106 Bound::Unbounded => self.get_height().await? - 1,
1107 };
1108 if from > to {
1109 Err(RpcError::InternalError(format!(
1110 "malformed range: inclusive start {from}, inclusive end {to}"
1111 )))?;
1112 }
1113
1114 let zero_zero_case = (from == 0) && (to == 0);
1115 let distributions: Distributions = self
1116 .json_rpc_call(
1117 "get_output_distribution",
1118 Some(json!({
1119 "binary": false,
1120 "amounts": [0],
1121 "cumulative": true,
1122 "from_height": from,
1124 "to_height": if zero_zero_case { 1 } else { to },
1125 })),
1126 )
1127 .await?;
1128
1129 if distributions.status != "OK" {
1130 Err(RpcError::ConnectionError(
1131 "node couldn't service this request for the output distribution".to_string(),
1132 ))?;
1133 }
1134
1135 let mut distributions = distributions.distributions;
1136 let Distribution { start_height, mut distribution } = core::mem::take(&mut distributions[0]);
1137 if start_height < from {
1142 Err(RpcError::InvalidNode(format!(
1143 "requested distribution from {from} and got from {start_height}"
1144 )))?;
1145 }
1146 if start_height > to {
1148 Err(RpcError::InvalidNode(format!(
1149 "requested distribution to {to} and got from {start_height}"
1150 )))?;
1151 }
1152
1153 let expected_len = if zero_zero_case {
1154 2
1155 } else {
1156 (to - start_height).checked_add(1).ok_or_else(|| {
1157 RpcError::InternalError("expected length of distribution exceeded usize".to_string())
1158 })?
1159 };
1160 if expected_len != distribution.len() {
1162 Err(RpcError::InvalidNode(format!(
1163 "distribution length ({}) wasn't of the requested length ({})",
1164 distribution.len(),
1165 expected_len
1166 )))?;
1167 }
1168 if zero_zero_case {
1172 distribution.pop();
1173 }
1174
1175 {
1177 let mut monotonic = 0;
1178 for d in &distribution {
1179 if *d < monotonic {
1180 Err(RpcError::InvalidNode(
1181 "received output distribution didn't increase monotonically".to_string(),
1182 ))?;
1183 }
1184 monotonic = *d;
1185 }
1186 }
1187
1188 Ok(distribution)
1189 }
1190 }
1191
1192 fn get_outs(
1193 &self,
1194 indexes: &[u64],
1195 ) -> impl Send + Future<Output = Result<Vec<OutputInformation>, RpcError>> {
1196 async move {
1197 #[derive(Debug, Deserialize)]
1198 struct OutputResponse {
1199 height: usize,
1200 unlocked: bool,
1201 key: String,
1202 mask: String,
1203 txid: String,
1204 }
1205
1206 #[derive(Debug, Deserialize)]
1207 struct OutsResponse {
1208 status: String,
1209 outs: Vec<OutputResponse>,
1210 }
1211
1212 const MAX_OUTS: usize = 5000;
1215
1216 let mut res = Vec::with_capacity(indexes.len());
1217 for indexes in indexes.chunks(MAX_OUTS) {
1218 let rpc_res: OutsResponse = self
1219 .rpc_call(
1220 "get_outs",
1221 Some(json!({
1222 "get_txid": true,
1223 "outputs": indexes.iter().map(|o| json!({
1224 "amount": 0,
1225 "index": o
1226 })).collect::<Vec<_>>()
1227 })),
1228 )
1229 .await?;
1230
1231 if rpc_res.status != "OK" {
1232 Err(RpcError::InvalidNode("bad response to get_outs".to_string()))?;
1233 }
1234
1235 res.extend(
1236 rpc_res
1237 .outs
1238 .into_iter()
1239 .map(|output| {
1240 Ok(OutputInformation {
1241 height: output.height,
1242 unlocked: output.unlocked,
1243 key: CompressedEdwardsY(
1244 rpc_hex(&output.key)?
1245 .try_into()
1246 .map_err(|_| RpcError::InvalidNode("output key wasn't 32 bytes".to_string()))?,
1247 ),
1248 commitment: rpc_point(&output.mask)?,
1249 transaction: hash_hex(&output.txid)?,
1250 })
1251 })
1252 .collect::<Result<Vec<_>, RpcError>>()?,
1253 );
1254 }
1255
1256 Ok(res)
1257 }
1258 }
1259
1260 fn get_unlocked_outputs(
1261 &self,
1262 indexes: &[u64],
1263 height: usize,
1264 fingerprintable_deterministic: bool,
1265 ) -> impl Send + Future<Output = Result<Vec<Option<[EdwardsPoint; 2]>>, RpcError>> {
1266 async move {
1267 let outs = self.get_outs(indexes).await?;
1268
1269 let txs = if fingerprintable_deterministic {
1271 self.get_transactions(&outs.iter().map(|out| out.transaction).collect::<Vec<_>>()).await?
1272 } else {
1273 vec![]
1274 };
1275
1276 outs
1278 .iter()
1279 .enumerate()
1280 .map(|(i, out)| {
1281 let Some(key) = out.key.decompress() else {
1286 return Ok(None);
1287 };
1288 Ok(Some([key, out.commitment]).filter(|_| {
1289 if fingerprintable_deterministic {
1290 const ACCEPTED_TIMELOCK_DELTA: usize = 1;
1294
1295 out.height.checked_add(DEFAULT_LOCK_WINDOW).is_some_and(|locked| locked <= height) &&
1299 (Timelock::Block(height.wrapping_add(ACCEPTED_TIMELOCK_DELTA - 1)) >=
1300 txs[i].prefix().additional_timelock)
1301 } else {
1302 out.unlocked
1303 }
1304 }))
1305 })
1306 .collect()
1307 }
1308 }
1309}