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 CompressedPoint(
243 rpc_hex(point)?
244 .try_into()
245 .map_err(|_| RpcError::InvalidNode(format!("invalid point: {point}")))?,
246 )
247 .decompress()
248 .ok_or_else(|| RpcError::InvalidNode(format!("invalid point: {point}")))
249}
250
251pub trait Rpc: Sync + Clone {
260 fn post(
264 &self,
265 route: &str,
266 body: Vec<u8>,
267 ) -> impl Send + Future<Output = Result<Vec<u8>, RpcError>>;
268
269 fn rpc_call<Params: Send + Serialize + Debug, Response: DeserializeOwned + Debug>(
274 &self,
275 route: &str,
276 params: Option<Params>,
277 ) -> impl Send + Future<Output = Result<Response, RpcError>> {
278 async move {
279 let res = self
280 .post(
281 route,
282 if let Some(params) = params.as_ref() {
283 serde_json::to_string(params)
284 .map_err(|e| {
285 RpcError::InternalError(format!(
286 "couldn't convert parameters ({params:?}) to JSON: {e:?}"
287 ))
288 })?
289 .into_bytes()
290 } else {
291 vec![]
292 },
293 )
294 .await?;
295 let res_str = std_shims::str::from_utf8(&res)
296 .map_err(|_| RpcError::InvalidNode("response wasn't utf-8".to_string()))?;
297 serde_json::from_str(res_str)
298 .map_err(|_| RpcError::InvalidNode(format!("response wasn't the expected json: {res_str}")))
299 }
300 }
301
302 fn json_rpc_call<Response: DeserializeOwned + Debug>(
304 &self,
305 method: &str,
306 params: Option<Value>,
307 ) -> impl Send + Future<Output = Result<Response, RpcError>> {
308 async move {
309 let mut req = json!({ "method": method });
310 if let Some(params) = params {
311 req
312 .as_object_mut()
313 .expect("accessing object as object failed?")
314 .insert("params".into(), params);
315 }
316 Ok(self.rpc_call::<_, JsonRpcResponse<Response>>("json_rpc", Some(req)).await?.result)
317 }
318 }
319
320 fn bin_call(
322 &self,
323 route: &str,
324 params: Vec<u8>,
325 ) -> impl Send + Future<Output = Result<Vec<u8>, RpcError>> {
326 async move { self.post(route, params).await }
327 }
328
329 fn get_hardfork_version(&self) -> impl Send + Future<Output = Result<u8, RpcError>> {
333 async move {
334 #[derive(Debug, Deserialize)]
335 struct HeaderResponse {
336 major_version: u8,
337 }
338
339 #[derive(Debug, Deserialize)]
340 struct LastHeaderResponse {
341 block_header: HeaderResponse,
342 }
343
344 Ok(
345 self
346 .json_rpc_call::<LastHeaderResponse>("get_last_block_header", None)
347 .await?
348 .block_header
349 .major_version,
350 )
351 }
352 }
353
354 fn get_height(&self) -> impl Send + Future<Output = Result<usize, RpcError>> {
359 async move {
360 #[derive(Debug, Deserialize)]
361 struct HeightResponse {
362 height: usize,
363 }
364 let res = self.rpc_call::<Option<()>, HeightResponse>("get_height", None).await?.height;
365 if res == 0 {
366 Err(RpcError::InvalidNode("node responded with 0 for the height".to_string()))?;
367 }
368 Ok(res)
369 }
370 }
371
372 fn get_transactions(
377 &self,
378 hashes: &[[u8; 32]],
379 ) -> impl Send + Future<Output = Result<Vec<Transaction>, RpcError>> {
380 async move {
381 if hashes.is_empty() {
382 return Ok(vec![]);
383 }
384
385 let mut hashes_hex = hashes.iter().map(hex::encode).collect::<Vec<_>>();
386 let mut all_txs = Vec::with_capacity(hashes.len());
387 while !hashes_hex.is_empty() {
388 let this_count = TXS_PER_REQUEST.min(hashes_hex.len());
389
390 let txs: TransactionsResponse = self
391 .rpc_call(
392 "get_transactions",
393 Some(json!({
394 "txs_hashes": hashes_hex.drain(.. this_count).collect::<Vec<_>>(),
395 })),
396 )
397 .await?;
398
399 if !txs.missed_tx.is_empty() {
400 Err(RpcError::TransactionsNotFound(
401 txs.missed_tx.iter().map(|hash| hash_hex(hash)).collect::<Result<_, _>>()?,
402 ))?;
403 }
404 if txs.txs.len() != this_count {
405 Err(RpcError::InvalidNode(
406 "not missing any transactions yet didn't return all transactions".to_string(),
407 ))?;
408 }
409
410 all_txs.extend(txs.txs);
411 }
412
413 all_txs
414 .iter()
415 .enumerate()
416 .map(|(i, res)| {
417 let buf = rpc_hex(if !res.as_hex.is_empty() { &res.as_hex } else { &res.pruned_as_hex })?;
419 let mut buf = buf.as_slice();
420 let tx = Transaction::read(&mut buf).map_err(|_| match hash_hex(&res.tx_hash) {
421 Ok(hash) => RpcError::InvalidTransaction(hash),
422 Err(err) => err,
423 })?;
424 if !buf.is_empty() {
425 Err(RpcError::InvalidNode("transaction had extra bytes after it".to_string()))?;
426 }
427
428 if res.as_hex.is_empty() {
433 match tx.prefix().inputs.first() {
434 Some(Input::Gen { .. }) => (),
435 _ => Err(RpcError::PrunedTransaction)?,
436 }
437 }
438
439 if tx.hash() != hashes[i] {
442 Err(RpcError::InvalidNode(
443 "replied with transaction wasn't the requested transaction".to_string(),
444 ))?;
445 }
446
447 Ok(tx)
448 })
449 .collect()
450 }
451 }
452
453 fn get_pruned_transactions(
455 &self,
456 hashes: &[[u8; 32]],
457 ) -> impl Send + Future<Output = Result<Vec<Transaction<Pruned>>, RpcError>> {
458 async move {
459 if hashes.is_empty() {
460 return Ok(vec![]);
461 }
462
463 let mut hashes_hex = hashes.iter().map(hex::encode).collect::<Vec<_>>();
464 let mut all_txs = Vec::with_capacity(hashes.len());
465 while !hashes_hex.is_empty() {
466 let this_count = TXS_PER_REQUEST.min(hashes_hex.len());
467
468 let txs: TransactionsResponse = self
469 .rpc_call(
470 "get_transactions",
471 Some(json!({
472 "txs_hashes": hashes_hex.drain(.. this_count).collect::<Vec<_>>(),
473 "prune": true,
474 })),
475 )
476 .await?;
477
478 if !txs.missed_tx.is_empty() {
479 Err(RpcError::TransactionsNotFound(
480 txs.missed_tx.iter().map(|hash| hash_hex(hash)).collect::<Result<_, _>>()?,
481 ))?;
482 }
483
484 all_txs.extend(txs.txs);
485 }
486
487 all_txs
488 .iter()
489 .map(|res| {
490 let buf = rpc_hex(&res.pruned_as_hex)?;
491 let mut buf = buf.as_slice();
492 let tx =
493 Transaction::<Pruned>::read(&mut buf).map_err(|_| match hash_hex(&res.tx_hash) {
494 Ok(hash) => RpcError::InvalidTransaction(hash),
495 Err(err) => err,
496 })?;
497 if !buf.is_empty() {
498 Err(RpcError::InvalidNode("pruned transaction had extra bytes after it".to_string()))?;
499 }
500 Ok(tx)
501 })
502 .collect()
503 }
504 }
505
506 fn get_transaction(
511 &self,
512 tx: [u8; 32],
513 ) -> impl Send + Future<Output = Result<Transaction, RpcError>> {
514 async move { self.get_transactions(&[tx]).await.map(|mut txs| txs.swap_remove(0)) }
515 }
516
517 fn get_pruned_transaction(
519 &self,
520 tx: [u8; 32],
521 ) -> impl Send + Future<Output = Result<Transaction<Pruned>, RpcError>> {
522 async move { self.get_pruned_transactions(&[tx]).await.map(|mut txs| txs.swap_remove(0)) }
523 }
524
525 fn get_block_hash(
530 &self,
531 number: usize,
532 ) -> impl Send + Future<Output = Result<[u8; 32], RpcError>> {
533 async move {
534 #[derive(Debug, Deserialize)]
535 struct BlockHeaderResponse {
536 hash: String,
537 }
538 #[derive(Debug, Deserialize)]
539 struct BlockHeaderByHeightResponse {
540 block_header: BlockHeaderResponse,
541 }
542
543 let header: BlockHeaderByHeightResponse =
544 self.json_rpc_call("get_block_header_by_height", Some(json!({ "height": number }))).await?;
545 hash_hex(&header.block_header.hash)
546 }
547 }
548
549 fn get_block(&self, hash: [u8; 32]) -> impl Send + Future<Output = Result<Block, RpcError>> {
553 async move {
554 #[derive(Debug, Deserialize)]
555 struct BlockResponse {
556 blob: String,
557 }
558
559 let res: BlockResponse =
560 self.json_rpc_call("get_block", Some(json!({ "hash": hex::encode(hash) }))).await?;
561
562 let block = Block::read(&mut rpc_hex(&res.blob)?.as_slice())
563 .map_err(|_| RpcError::InvalidNode("invalid block".to_string()))?;
564 if block.hash() != hash {
565 Err(RpcError::InvalidNode("different block than requested (hash)".to_string()))?;
566 }
567 Ok(block)
568 }
569 }
570
571 fn get_block_by_number(
576 &self,
577 number: usize,
578 ) -> impl Send + Future<Output = Result<Block, RpcError>> {
579 async move {
580 #[derive(Debug, Deserialize)]
581 struct BlockResponse {
582 blob: String,
583 }
584
585 let res: BlockResponse =
586 self.json_rpc_call("get_block", Some(json!({ "height": number }))).await?;
587
588 let block = Block::read(&mut rpc_hex(&res.blob)?.as_slice())
589 .map_err(|_| RpcError::InvalidNode("invalid block".to_string()))?;
590
591 match block.miner_transaction.prefix().inputs.first() {
593 Some(Input::Gen(actual)) => {
594 if *actual == number {
595 Ok(block)
596 } else {
597 Err(RpcError::InvalidNode("different block than requested (number)".to_string()))
598 }
599 }
600 _ => Err(RpcError::InvalidNode(
601 "block's miner_transaction didn't have an input of kind Input::Gen".to_string(),
602 )),
603 }
604 }
605 }
606
607 fn get_scannable_block(
609 &self,
610 block: Block,
611 ) -> impl Send + Future<Output = Result<ScannableBlock, RpcError>> {
612 async move {
613 let transactions = self.get_pruned_transactions(&block.transactions).await?;
614
615 let mut output_index_for_first_ringct_output = None;
655 let miner_tx_hash = block.miner_transaction.hash();
656 let miner_tx = Transaction::<Pruned>::from(block.miner_transaction.clone());
657 for (hash, tx) in core::iter::once((&miner_tx_hash, &miner_tx))
658 .chain(block.transactions.iter().zip(&transactions))
659 {
660 if (!matches!(tx, Transaction::V2 { .. })) || tx.prefix().outputs.is_empty() {
662 continue;
663 }
664
665 let index = *self.get_o_indexes(*hash).await?.first().ok_or_else(|| {
666 RpcError::InvalidNode(
667 "requested output indexes for a TX with outputs and got none".to_string(),
668 )
669 })?;
670 output_index_for_first_ringct_output = Some(index);
671 break;
672 }
673
674 Ok(ScannableBlock { block, transactions, output_index_for_first_ringct_output })
675 }
676 }
677
678 fn get_scannable_block_by_hash(
681 &self,
682 hash: [u8; 32],
683 ) -> impl Send + Future<Output = Result<ScannableBlock, RpcError>> {
684 async move { self.get_scannable_block(self.get_block(hash).await?).await }
685 }
686
687 fn get_scannable_block_by_number(
690 &self,
691 number: usize,
692 ) -> impl Send + Future<Output = Result<ScannableBlock, RpcError>> {
693 async move { self.get_scannable_block(self.get_block_by_number(number).await?).await }
694 }
695
696 fn get_fee_rate(
702 &self,
703 priority: FeePriority,
704 ) -> impl Send + Future<Output = Result<FeeRate, RpcError>> {
705 async move {
706 #[derive(Debug, Deserialize)]
707 struct FeeResponse {
708 status: String,
709 fees: Option<Vec<u64>>,
710 fee: u64,
711 quantization_mask: u64,
712 }
713
714 let res: FeeResponse = self
715 .json_rpc_call(
716 "get_fee_estimate",
717 Some(json!({ "grace_blocks": GRACE_BLOCKS_FOR_FEE_ESTIMATE })),
718 )
719 .await?;
720
721 if res.status != "OK" {
722 Err(RpcError::InvalidFee)?;
723 }
724
725 if let Some(fees) = res.fees {
726 let priority_idx = usize::try_from(if priority.fee_priority() >= 4 {
729 3
730 } else {
731 priority.fee_priority().saturating_sub(1)
732 })
733 .map_err(|_| RpcError::InvalidPriority)?;
734
735 if priority_idx >= fees.len() {
736 Err(RpcError::InvalidPriority)
737 } else {
738 FeeRate::new(fees[priority_idx], res.quantization_mask)
739 }
740 } else {
741 let priority_idx = usize::try_from(if priority.fee_priority() == 0 {
746 1
747 } else {
748 priority.fee_priority() - 1
749 })
750 .map_err(|_| RpcError::InvalidPriority)?;
751 let multipliers = [1, 5, 25, 1000];
752 if priority_idx >= multipliers.len() {
753 Err(RpcError::InvalidPriority)?;
755 }
756 let fee_multiplier = multipliers[priority_idx];
757
758 FeeRate::new(res.fee * fee_multiplier, res.quantization_mask)
759 }
760 }
761 }
762
763 fn publish_transaction(
765 &self,
766 tx: &Transaction,
767 ) -> impl Send + Future<Output = Result<(), RpcError>> {
768 async move {
769 #[allow(dead_code)]
770 #[derive(Debug, Deserialize)]
771 struct SendRawResponse {
772 status: String,
773 double_spend: bool,
774 fee_too_low: bool,
775 invalid_input: bool,
776 invalid_output: bool,
777 low_mixin: bool,
778 not_relayed: bool,
779 overspend: bool,
780 too_big: bool,
781 too_few_outputs: bool,
782 reason: String,
783 }
784
785 let res: SendRawResponse = self
786 .rpc_call(
787 "send_raw_transaction",
788 Some(json!({ "tx_as_hex": hex::encode(tx.serialize()), "do_sanity_checks": false })),
789 )
790 .await?;
791
792 if res.status != "OK" {
793 Err(RpcError::InvalidTransaction(tx.hash()))?;
794 }
795
796 Ok(())
797 }
798 }
799
800 fn generate_blocks<const ADDR_BYTES: u128>(
804 &self,
805 address: &Address<ADDR_BYTES>,
806 block_count: usize,
807 ) -> impl Send + Future<Output = Result<(Vec<[u8; 32]>, usize), RpcError>> {
808 async move {
809 #[derive(Debug, Deserialize)]
810 struct BlocksResponse {
811 blocks: Vec<String>,
812 height: usize,
813 }
814
815 let res = self
816 .json_rpc_call::<BlocksResponse>(
817 "generateblocks",
818 Some(json!({
819 "wallet_address": address.to_string(),
820 "amount_of_blocks": block_count
821 })),
822 )
823 .await?;
824
825 let mut blocks = Vec::with_capacity(res.blocks.len());
826 for block in res.blocks {
827 blocks.push(hash_hex(&block)?);
828 }
829 Ok((blocks, res.height))
830 }
831 }
832
833 fn get_o_indexes(
835 &self,
836 hash: [u8; 32],
837 ) -> impl Send + Future<Output = Result<Vec<u64>, RpcError>> {
838 async move {
839 const EPEE_HEADER: &[u8] = b"\x01\x11\x01\x01\x01\x01\x02\x01\x01";
844
845 fn read_epee_vi<R: io::Read>(reader: &mut R) -> io::Result<u64> {
847 let vi_start = read_byte(reader)?;
848 let len = match vi_start & 0b11 {
849 0 => 1,
850 1 => 2,
851 2 => 4,
852 3 => 8,
853 _ => unreachable!(),
854 };
855 let mut vi = u64::from(vi_start >> 2);
856 for i in 1 .. len {
857 vi |= u64::from(read_byte(reader)?) << (((i - 1) * 8) + 6);
858 }
859 Ok(vi)
860 }
861
862 let mut request = EPEE_HEADER.to_vec();
863 request.push(1 << 2);
865 request.push(4);
867 request.extend(b"txid");
869 request.push(10);
871 request.push(32 << 2);
873 request.extend(hash);
875
876 let indexes_buf = self.bin_call("get_o_indexes.bin", request).await?;
877 let mut indexes = indexes_buf.as_slice();
878
879 (|| {
880 let mut res = None;
881 let mut has_status = false;
882
883 if read_bytes::<_, { EPEE_HEADER.len() }>(&mut indexes)? != EPEE_HEADER {
884 Err(io::Error::other("invalid header"))?;
885 }
886
887 let read_object = |reader: &mut &[u8]| -> io::Result<Vec<u64>> {
888 let fields = read_byte(reader)? >> 2;
890
891 for _ in 0 .. fields {
892 let name_len = read_byte(reader)?;
894 let name = read_raw_vec(read_byte, name_len.into(), reader)?;
896
897 let type_with_array_flag = read_byte(reader)?;
898 let kind = type_with_array_flag & (!0x80);
900 let has_array_flag = type_with_array_flag != kind;
901
902 let iters = if has_array_flag { read_epee_vi(reader)? } else { 1 };
904
905 {
907 #[allow(clippy::match_same_arms)]
908 let (expected_type, expected_array_flag) = match name.as_slice() {
909 b"o_indexes" => (5, true),
910 b"status" => (10, false),
911 b"untrusted" => (11, false),
912 b"credits" => (5, false),
913 b"top_hash" => (10, false),
914 _ => {
920 Err(io::Error::other(format!("unrecognized field in get_o_indexes: {name:?}")))?
921 }
922 };
923 if (expected_type != kind) || (expected_array_flag != has_array_flag) {
924 let fmt_array_bool = |array_bool| if array_bool { "array" } else { "not array" };
925 Err(io::Error::other(format!(
926 "field {name:?} was {kind} ({}), expected {expected_type} ({})",
927 fmt_array_bool(has_array_flag),
928 fmt_array_bool(expected_array_flag)
929 )))?;
930 }
931 }
932
933 let read_field_as_bytes = match kind {
934 5 => |reader: &mut &[u8]| read_raw_vec(read_byte, 8, reader),
946 10 => |reader: &mut &[u8]| {
958 let len = read_epee_vi(reader)?;
959 read_raw_vec(
960 read_byte,
961 len.try_into().map_err(|_| io::Error::other("u64 length exceeded usize"))?,
962 reader,
963 )
964 },
965 11 => |reader: &mut &[u8]| read_raw_vec(read_byte, 1, reader),
967 _ => |_: &mut &[u8]| Err(io::Error::other("node used an invalid type")),
976 };
977
978 let mut bytes_res = vec![];
979 for _ in 0 .. iters {
980 bytes_res.push(read_field_as_bytes(reader)?);
981 }
982
983 let mut actual_res = Vec::with_capacity(bytes_res.len());
984 match name.as_slice() {
985 b"o_indexes" => {
986 for o_index in bytes_res {
987 actual_res.push(read_u64(&mut o_index.as_slice())?);
988 }
989 res = Some(actual_res);
990 }
991 b"status" => {
992 if bytes_res
993 .first()
994 .ok_or_else(|| io::Error::other("status was a 0-length array"))?
995 .as_slice() !=
996 b"OK"
997 {
998 Err(io::Error::other("response wasn't OK"))?;
999 }
1000 has_status = true;
1001 }
1002 b"untrusted" | b"credits" | b"top_hash" => continue,
1003 _ => Err(io::Error::other("unrecognized field in get_o_indexes"))?,
1004 }
1005 }
1006
1007 if !has_status {
1008 Err(io::Error::other("response didn't contain a status"))?;
1009 }
1010
1011 Ok(res.unwrap_or(vec![]))
1013 };
1014
1015 read_object(&mut indexes)
1016 })()
1017 .map_err(|e| RpcError::InvalidNode(format!("invalid binary response: {e:?}")))
1018 }
1019 }
1020}
1021
1022pub trait DecoyRpc: Sync {
1028 fn get_output_distribution_end_height(
1033 &self,
1034 ) -> impl Send + Future<Output = Result<usize, RpcError>>;
1035
1036 fn get_output_distribution(
1041 &self,
1042 range: impl Send + RangeBounds<usize>,
1043 ) -> impl Send + Future<Output = Result<Vec<u64>, RpcError>>;
1044
1045 fn get_outs(
1047 &self,
1048 indexes: &[u64],
1049 ) -> impl Send + Future<Output = Result<Vec<OutputInformation>, RpcError>>;
1050
1051 fn get_unlocked_outputs(
1063 &self,
1064 indexes: &[u64],
1065 height: usize,
1066 fingerprintable_deterministic: bool,
1067 ) -> impl Send + Future<Output = Result<Vec<Option<[EdwardsPoint; 2]>>, RpcError>>;
1068}
1069
1070impl<R: Rpc> DecoyRpc for R {
1071 fn get_output_distribution_end_height(
1072 &self,
1073 ) -> impl Send + Future<Output = Result<usize, RpcError>> {
1074 async move { <Self as Rpc>::get_height(self).await }
1075 }
1076
1077 fn get_output_distribution(
1078 &self,
1079 range: impl Send + RangeBounds<usize>,
1080 ) -> impl Send + Future<Output = Result<Vec<u64>, RpcError>> {
1081 async move {
1082 #[derive(Default, Debug, Deserialize)]
1083 struct Distribution {
1084 distribution: Vec<u64>,
1085 start_height: usize,
1087 }
1088
1089 #[derive(Debug, Deserialize)]
1090 struct Distributions {
1091 distributions: [Distribution; 1],
1092 status: String,
1093 }
1094
1095 let from = match range.start_bound() {
1096 Bound::Included(from) => *from,
1097 Bound::Excluded(from) => from.checked_add(1).ok_or_else(|| {
1098 RpcError::InternalError("range's from wasn't representable".to_string())
1099 })?,
1100 Bound::Unbounded => 0,
1101 };
1102 let to = match range.end_bound() {
1103 Bound::Included(to) => *to,
1104 Bound::Excluded(to) => to
1105 .checked_sub(1)
1106 .ok_or_else(|| RpcError::InternalError("range's to wasn't representable".to_string()))?,
1107 Bound::Unbounded => self.get_height().await? - 1,
1108 };
1109 if from > to {
1110 Err(RpcError::InternalError(format!(
1111 "malformed range: inclusive start {from}, inclusive end {to}"
1112 )))?;
1113 }
1114
1115 let zero_zero_case = (from == 0) && (to == 0);
1116 let distributions: Distributions = self
1117 .json_rpc_call(
1118 "get_output_distribution",
1119 Some(json!({
1120 "binary": false,
1121 "amounts": [0],
1122 "cumulative": true,
1123 "from_height": from,
1125 "to_height": if zero_zero_case { 1 } else { to },
1126 })),
1127 )
1128 .await?;
1129
1130 if distributions.status != "OK" {
1131 Err(RpcError::ConnectionError(
1132 "node couldn't service this request for the output distribution".to_string(),
1133 ))?;
1134 }
1135
1136 let mut distributions = distributions.distributions;
1137 let Distribution { start_height, mut distribution } = core::mem::take(&mut distributions[0]);
1138 if start_height < from {
1143 Err(RpcError::InvalidNode(format!(
1144 "requested distribution from {from} and got from {start_height}"
1145 )))?;
1146 }
1147 if start_height > to {
1149 Err(RpcError::InvalidNode(format!(
1150 "requested distribution to {to} and got from {start_height}"
1151 )))?;
1152 }
1153
1154 let expected_len = if zero_zero_case {
1155 2
1156 } else {
1157 (to - start_height).checked_add(1).ok_or_else(|| {
1158 RpcError::InternalError("expected length of distribution exceeded usize".to_string())
1159 })?
1160 };
1161 if expected_len != distribution.len() {
1163 Err(RpcError::InvalidNode(format!(
1164 "distribution length ({}) wasn't of the requested length ({})",
1165 distribution.len(),
1166 expected_len
1167 )))?;
1168 }
1169 if zero_zero_case {
1173 distribution.pop();
1174 }
1175
1176 {
1178 let mut monotonic = 0;
1179 for d in &distribution {
1180 if *d < monotonic {
1181 Err(RpcError::InvalidNode(
1182 "received output distribution didn't increase monotonically".to_string(),
1183 ))?;
1184 }
1185 monotonic = *d;
1186 }
1187 }
1188
1189 Ok(distribution)
1190 }
1191 }
1192
1193 fn get_outs(
1194 &self,
1195 indexes: &[u64],
1196 ) -> impl Send + Future<Output = Result<Vec<OutputInformation>, RpcError>> {
1197 async move {
1198 #[derive(Debug, Deserialize)]
1199 struct OutputResponse {
1200 height: usize,
1201 unlocked: bool,
1202 key: String,
1203 mask: String,
1204 txid: String,
1205 }
1206
1207 #[derive(Debug, Deserialize)]
1208 struct OutsResponse {
1209 status: String,
1210 outs: Vec<OutputResponse>,
1211 }
1212
1213 const MAX_OUTS: usize = 5000;
1216
1217 let mut res = Vec::with_capacity(indexes.len());
1218 for indexes in indexes.chunks(MAX_OUTS) {
1219 let rpc_res: OutsResponse = self
1220 .rpc_call(
1221 "get_outs",
1222 Some(json!({
1223 "get_txid": true,
1224 "outputs": indexes.iter().map(|o| json!({
1225 "amount": 0,
1226 "index": o
1227 })).collect::<Vec<_>>()
1228 })),
1229 )
1230 .await?;
1231
1232 if rpc_res.status != "OK" {
1233 Err(RpcError::InvalidNode("bad response to get_outs".to_string()))?;
1234 }
1235
1236 res.extend(
1237 rpc_res
1238 .outs
1239 .into_iter()
1240 .map(|output| {
1241 Ok(OutputInformation {
1242 height: output.height,
1243 unlocked: output.unlocked,
1244 key: CompressedEdwardsY(
1245 rpc_hex(&output.key)?
1246 .try_into()
1247 .map_err(|_| RpcError::InvalidNode("output key wasn't 32 bytes".to_string()))?,
1248 ),
1249 commitment: rpc_point(&output.mask)?,
1250 transaction: hash_hex(&output.txid)?,
1251 })
1252 })
1253 .collect::<Result<Vec<_>, RpcError>>()?,
1254 );
1255 }
1256
1257 Ok(res)
1258 }
1259 }
1260
1261 fn get_unlocked_outputs(
1262 &self,
1263 indexes: &[u64],
1264 height: usize,
1265 fingerprintable_deterministic: bool,
1266 ) -> impl Send + Future<Output = Result<Vec<Option<[EdwardsPoint; 2]>>, RpcError>> {
1267 async move {
1268 let outs = self.get_outs(indexes).await?;
1269
1270 let txs = if fingerprintable_deterministic {
1272 self.get_transactions(&outs.iter().map(|out| out.transaction).collect::<Vec<_>>()).await?
1273 } else {
1274 vec![]
1275 };
1276
1277 outs
1279 .iter()
1280 .enumerate()
1281 .map(|(i, out)| {
1282 let Some(key) = out.key.decompress() else {
1287 return Ok(None);
1288 };
1289 Ok(Some([key, out.commitment]).filter(|_| {
1290 if fingerprintable_deterministic {
1291 const ACCEPTED_TIMELOCK_DELTA: usize = 1;
1295
1296 out.height.checked_add(DEFAULT_LOCK_WINDOW).is_some_and(|locked| locked <= height) &&
1300 (Timelock::Block(height.wrapping_add(ACCEPTED_TIMELOCK_DELTA - 1)) >=
1301 txs[i].prefix().additional_timelock)
1302 } else {
1303 out.unlocked
1304 }
1305 }))
1306 })
1307 .collect()
1308 }
1309 }
1310}