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_serai::{
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)]
46#[cfg_attr(feature = "std", derive(thiserror::Error))]
47pub enum RpcError {
48 #[cfg_attr(feature = "std", error("internal error ({0})"))]
50 InternalError(String),
51 #[cfg_attr(feature = "std", error("connection error ({0})"))]
53 ConnectionError(String),
54 #[cfg_attr(feature = "std", error("invalid node ({0})"))]
56 InvalidNode(String),
57 #[cfg_attr(feature = "std", error("transactions not found"))]
59 TransactionsNotFound(Vec<[u8; 32]>),
60 #[cfg_attr(feature = "std", error("pruned transaction"))]
64 PrunedTransaction,
65 #[cfg_attr(feature = "std", error("invalid transaction ({0:?})"))]
67 InvalidTransaction([u8; 32]),
68 #[cfg_attr(feature = "std", error("unexpected fee response"))]
70 InvalidFee,
71 #[cfg_attr(feature = "std", error("invalid priority"))]
73 InvalidPriority,
74}
75
76#[derive(Clone, PartialEq, Eq, Debug)]
78pub struct ScannableBlock {
79 pub block: Block,
81 pub transactions: Vec<Transaction<Pruned>>,
83 pub output_index_for_first_ringct_output: Option<u64>,
87}
88
89#[derive(Clone, Copy, PartialEq, Eq, Debug, Zeroize)]
93pub struct FeeRate {
94 per_weight: u64,
96 mask: u64,
98}
99
100impl FeeRate {
101 pub fn new(per_weight: u64, mask: u64) -> Result<FeeRate, RpcError> {
103 if (per_weight == 0) || (mask == 0) {
104 Err(RpcError::InvalidFee)?;
105 }
106 Ok(FeeRate { per_weight, mask })
107 }
108
109 pub fn write(&self, w: &mut impl io::Write) -> io::Result<()> {
114 w.write_all(&self.per_weight.to_le_bytes())?;
115 w.write_all(&self.mask.to_le_bytes())
116 }
117
118 pub fn serialize(&self) -> Vec<u8> {
123 let mut res = Vec::with_capacity(16);
124 self.write(&mut res).unwrap();
125 res
126 }
127
128 pub fn read(r: &mut impl io::Read) -> io::Result<FeeRate> {
133 let per_weight = read_u64(r)?;
134 let mask = read_u64(r)?;
135 FeeRate::new(per_weight, mask).map_err(io::Error::other)
136 }
137
138 pub fn calculate_fee_from_weight(&self, weight: usize) -> u64 {
142 let fee = self.per_weight * u64::try_from(weight).unwrap();
143 let fee = fee.div_ceil(self.mask) * self.mask;
144 debug_assert_eq!(weight, self.calculate_weight_from_fee(fee), "Miscalculated weight from fee");
145 fee
146 }
147
148 pub fn calculate_weight_from_fee(&self, fee: u64) -> usize {
150 usize::try_from(fee / self.per_weight).unwrap()
151 }
152}
153
154#[derive(Clone, Copy, PartialEq, Eq, Debug)]
158#[allow(non_camel_case_types)]
159pub enum FeePriority {
160 Unimportant,
162 Normal,
164 Elevated,
166 Priority,
168 Custom {
170 priority: u32,
172 },
173}
174
175impl FeePriority {
178 pub(crate) fn fee_priority(&self) -> u32 {
179 match self {
180 FeePriority::Unimportant => 1,
181 FeePriority::Normal => 2,
182 FeePriority::Elevated => 3,
183 FeePriority::Priority => 4,
184 FeePriority::Custom { priority, .. } => *priority,
185 }
186 }
187}
188
189#[derive(Debug, Deserialize)]
190struct JsonRpcResponse<T> {
191 result: T,
192}
193
194#[derive(Debug, Deserialize)]
195struct TransactionResponse {
196 tx_hash: String,
197 as_hex: String,
198 pruned_as_hex: String,
199}
200#[derive(Debug, Deserialize)]
201struct TransactionsResponse {
202 #[serde(default)]
203 missed_tx: Vec<String>,
204 txs: Vec<TransactionResponse>,
205}
206
207#[derive(Clone, Copy, PartialEq, Eq, Debug)]
209pub struct OutputInformation {
210 pub height: usize,
214 pub unlocked: bool,
216 pub key: CompressedEdwardsY,
221 pub commitment: EdwardsPoint,
223 pub transaction: [u8; 32],
225}
226
227fn rpc_hex(value: &str) -> Result<Vec<u8>, RpcError> {
228 hex::decode(value).map_err(|_| RpcError::InvalidNode("expected hex wasn't hex".to_string()))
229}
230
231fn hash_hex(hash: &str) -> Result<[u8; 32], RpcError> {
232 rpc_hex(hash)?.try_into().map_err(|_| RpcError::InvalidNode("hash wasn't 32-bytes".to_string()))
233}
234
235fn rpc_point(point: &str) -> Result<EdwardsPoint, RpcError> {
236 decompress_point(CompressedEdwardsY(
237 rpc_hex(point)?
238 .try_into()
239 .map_err(|_| RpcError::InvalidNode(format!("invalid point: {point}")))?,
240 ))
241 .ok_or_else(|| RpcError::InvalidNode(format!("invalid point: {point}")))
242}
243
244pub trait Rpc: Sync + Clone {
253 fn post(
257 &self,
258 route: &str,
259 body: Vec<u8>,
260 ) -> impl Send + Future<Output = Result<Vec<u8>, RpcError>>;
261
262 fn rpc_call<Params: Send + Serialize + Debug, Response: DeserializeOwned + Debug>(
267 &self,
268 route: &str,
269 params: Option<Params>,
270 ) -> impl Send + Future<Output = Result<Response, RpcError>> {
271 async move {
272 let res = self
273 .post(
274 route,
275 if let Some(params) = params {
276 serde_json::to_string(¶ms).unwrap().into_bytes()
277 } else {
278 vec![]
279 },
280 )
281 .await?;
282 let res_str = std_shims::str::from_utf8(&res)
283 .map_err(|_| RpcError::InvalidNode("response wasn't utf-8".to_string()))?;
284 serde_json::from_str(res_str)
285 .map_err(|_| RpcError::InvalidNode(format!("response wasn't the expected json: {res_str}")))
286 }
287 }
288
289 fn json_rpc_call<Response: DeserializeOwned + Debug>(
291 &self,
292 method: &str,
293 params: Option<Value>,
294 ) -> impl Send + Future<Output = Result<Response, RpcError>> {
295 async move {
296 let mut req = json!({ "method": method });
297 if let Some(params) = params {
298 req.as_object_mut().unwrap().insert("params".into(), params);
299 }
300 Ok(self.rpc_call::<_, JsonRpcResponse<Response>>("json_rpc", Some(req)).await?.result)
301 }
302 }
303
304 fn bin_call(
306 &self,
307 route: &str,
308 params: Vec<u8>,
309 ) -> impl Send + Future<Output = Result<Vec<u8>, RpcError>> {
310 async move { self.post(route, params).await }
311 }
312
313 fn get_hardfork_version(&self) -> impl Send + Future<Output = Result<u8, RpcError>> {
317 async move {
318 #[derive(Debug, Deserialize)]
319 struct HeaderResponse {
320 major_version: u8,
321 }
322
323 #[derive(Debug, Deserialize)]
324 struct LastHeaderResponse {
325 block_header: HeaderResponse,
326 }
327
328 Ok(
329 self
330 .json_rpc_call::<LastHeaderResponse>("get_last_block_header", None)
331 .await?
332 .block_header
333 .major_version,
334 )
335 }
336 }
337
338 fn get_height(&self) -> impl Send + Future<Output = Result<usize, RpcError>> {
343 async move {
344 #[derive(Debug, Deserialize)]
345 struct HeightResponse {
346 height: usize,
347 }
348 let res = self.rpc_call::<Option<()>, HeightResponse>("get_height", None).await?.height;
349 if res == 0 {
350 Err(RpcError::InvalidNode("node responded with 0 for the height".to_string()))?;
351 }
352 Ok(res)
353 }
354 }
355
356 fn get_transactions(
361 &self,
362 hashes: &[[u8; 32]],
363 ) -> impl Send + Future<Output = Result<Vec<Transaction>, RpcError>> {
364 async move {
365 if hashes.is_empty() {
366 return Ok(vec![]);
367 }
368
369 let mut hashes_hex = hashes.iter().map(hex::encode).collect::<Vec<_>>();
370 let mut all_txs = Vec::with_capacity(hashes.len());
371 while !hashes_hex.is_empty() {
372 let this_count = TXS_PER_REQUEST.min(hashes_hex.len());
373
374 let txs: TransactionsResponse = self
375 .rpc_call(
376 "get_transactions",
377 Some(json!({
378 "txs_hashes": hashes_hex.drain(.. this_count).collect::<Vec<_>>(),
379 })),
380 )
381 .await?;
382
383 if !txs.missed_tx.is_empty() {
384 Err(RpcError::TransactionsNotFound(
385 txs.missed_tx.iter().map(|hash| hash_hex(hash)).collect::<Result<_, _>>()?,
386 ))?;
387 }
388
389 all_txs.extend(txs.txs);
390 }
391
392 all_txs
393 .iter()
394 .enumerate()
395 .map(|(i, res)| {
396 let buf = rpc_hex(if !res.as_hex.is_empty() { &res.as_hex } else { &res.pruned_as_hex })?;
398 let mut buf = buf.as_slice();
399 let tx = Transaction::read(&mut buf).map_err(|_| match hash_hex(&res.tx_hash) {
400 Ok(hash) => RpcError::InvalidTransaction(hash),
401 Err(err) => err,
402 })?;
403 if !buf.is_empty() {
404 Err(RpcError::InvalidNode("transaction had extra bytes after it".to_string()))?;
405 }
406
407 if res.as_hex.is_empty() {
412 match tx.prefix().inputs.first() {
413 Some(Input::Gen { .. }) => (),
414 _ => Err(RpcError::PrunedTransaction)?,
415 }
416 }
417
418 if tx.hash() != hashes[i] {
421 Err(RpcError::InvalidNode(
422 "replied with transaction wasn't the requested transaction".to_string(),
423 ))?;
424 }
425
426 Ok(tx)
427 })
428 .collect()
429 }
430 }
431
432 fn get_pruned_transactions(
434 &self,
435 hashes: &[[u8; 32]],
436 ) -> impl Send + Future<Output = Result<Vec<Transaction<Pruned>>, RpcError>> {
437 async move {
438 if hashes.is_empty() {
439 return Ok(vec![]);
440 }
441
442 let mut hashes_hex = hashes.iter().map(hex::encode).collect::<Vec<_>>();
443 let mut all_txs = Vec::with_capacity(hashes.len());
444 while !hashes_hex.is_empty() {
445 let this_count = TXS_PER_REQUEST.min(hashes_hex.len());
446
447 let txs: TransactionsResponse = self
448 .rpc_call(
449 "get_transactions",
450 Some(json!({
451 "txs_hashes": hashes_hex.drain(.. this_count).collect::<Vec<_>>(),
452 "prune": true,
453 })),
454 )
455 .await?;
456
457 if !txs.missed_tx.is_empty() {
458 Err(RpcError::TransactionsNotFound(
459 txs.missed_tx.iter().map(|hash| hash_hex(hash)).collect::<Result<_, _>>()?,
460 ))?;
461 }
462
463 all_txs.extend(txs.txs);
464 }
465
466 all_txs
467 .iter()
468 .map(|res| {
469 let buf = rpc_hex(&res.pruned_as_hex)?;
470 let mut buf = buf.as_slice();
471 let tx =
472 Transaction::<Pruned>::read(&mut buf).map_err(|_| match hash_hex(&res.tx_hash) {
473 Ok(hash) => RpcError::InvalidTransaction(hash),
474 Err(err) => err,
475 })?;
476 if !buf.is_empty() {
477 Err(RpcError::InvalidNode("pruned transaction had extra bytes after it".to_string()))?;
478 }
479 Ok(tx)
480 })
481 .collect()
482 }
483 }
484
485 fn get_transaction(
490 &self,
491 tx: [u8; 32],
492 ) -> impl Send + Future<Output = Result<Transaction, RpcError>> {
493 async move { self.get_transactions(&[tx]).await.map(|mut txs| txs.swap_remove(0)) }
494 }
495
496 fn get_pruned_transaction(
498 &self,
499 tx: [u8; 32],
500 ) -> impl Send + Future<Output = Result<Transaction<Pruned>, RpcError>> {
501 async move { self.get_pruned_transactions(&[tx]).await.map(|mut txs| txs.swap_remove(0)) }
502 }
503
504 fn get_block_hash(
509 &self,
510 number: usize,
511 ) -> impl Send + Future<Output = Result<[u8; 32], RpcError>> {
512 async move {
513 #[derive(Debug, Deserialize)]
514 struct BlockHeaderResponse {
515 hash: String,
516 }
517 #[derive(Debug, Deserialize)]
518 struct BlockHeaderByHeightResponse {
519 block_header: BlockHeaderResponse,
520 }
521
522 let header: BlockHeaderByHeightResponse =
523 self.json_rpc_call("get_block_header_by_height", Some(json!({ "height": number }))).await?;
524 hash_hex(&header.block_header.hash)
525 }
526 }
527
528 fn get_block(&self, hash: [u8; 32]) -> impl Send + Future<Output = Result<Block, RpcError>> {
532 async move {
533 #[derive(Debug, Deserialize)]
534 struct BlockResponse {
535 blob: String,
536 }
537
538 let res: BlockResponse =
539 self.json_rpc_call("get_block", Some(json!({ "hash": hex::encode(hash) }))).await?;
540
541 let block = Block::read::<&[u8]>(&mut rpc_hex(&res.blob)?.as_ref())
542 .map_err(|_| RpcError::InvalidNode("invalid block".to_string()))?;
543 if block.hash() != hash {
544 Err(RpcError::InvalidNode("different block than requested (hash)".to_string()))?;
545 }
546 Ok(block)
547 }
548 }
549
550 fn get_block_by_number(
555 &self,
556 number: usize,
557 ) -> impl Send + Future<Output = Result<Block, RpcError>> {
558 async move {
559 #[derive(Debug, Deserialize)]
560 struct BlockResponse {
561 blob: String,
562 }
563
564 let res: BlockResponse =
565 self.json_rpc_call("get_block", Some(json!({ "height": number }))).await?;
566
567 let block = Block::read::<&[u8]>(&mut rpc_hex(&res.blob)?.as_ref())
568 .map_err(|_| RpcError::InvalidNode("invalid block".to_string()))?;
569
570 match block.miner_transaction.prefix().inputs.first() {
572 Some(Input::Gen(actual)) => {
573 if *actual == number {
574 Ok(block)
575 } else {
576 Err(RpcError::InvalidNode("different block than requested (number)".to_string()))
577 }
578 }
579 _ => Err(RpcError::InvalidNode(
580 "block's miner_transaction didn't have an input of kind Input::Gen".to_string(),
581 )),
582 }
583 }
584 }
585
586 fn get_scannable_block(
588 &self,
589 block: Block,
590 ) -> impl Send + Future<Output = Result<ScannableBlock, RpcError>> {
591 async move {
592 let transactions = self.get_pruned_transactions(&block.transactions).await?;
593
594 let mut output_index_for_first_ringct_output = None;
634 let miner_tx_hash = block.miner_transaction.hash();
635 let miner_tx = Transaction::<Pruned>::from(block.miner_transaction.clone());
636 for (hash, tx) in core::iter::once((&miner_tx_hash, &miner_tx))
637 .chain(block.transactions.iter().zip(&transactions))
638 {
639 if (!matches!(tx, Transaction::V2 { .. })) || tx.prefix().outputs.is_empty() {
641 continue;
642 }
643
644 let index = *self.get_o_indexes(*hash).await?.first().ok_or_else(|| {
645 RpcError::InvalidNode(
646 "requested output indexes for a TX with outputs and got none".to_string(),
647 )
648 })?;
649 output_index_for_first_ringct_output = Some(index);
650 break;
651 }
652
653 Ok(ScannableBlock { block, transactions, output_index_for_first_ringct_output })
654 }
655 }
656
657 fn get_scannable_block_by_hash(
660 &self,
661 hash: [u8; 32],
662 ) -> impl Send + Future<Output = Result<ScannableBlock, RpcError>> {
663 async move { self.get_scannable_block(self.get_block(hash).await?).await }
664 }
665
666 fn get_scannable_block_by_number(
669 &self,
670 number: usize,
671 ) -> impl Send + Future<Output = Result<ScannableBlock, RpcError>> {
672 async move { self.get_scannable_block(self.get_block_by_number(number).await?).await }
673 }
674
675 fn get_fee_rate(
681 &self,
682 priority: FeePriority,
683 ) -> impl Send + Future<Output = Result<FeeRate, RpcError>> {
684 async move {
685 #[derive(Debug, Deserialize)]
686 struct FeeResponse {
687 status: String,
688 fees: Option<Vec<u64>>,
689 fee: u64,
690 quantization_mask: u64,
691 }
692
693 let res: FeeResponse = self
694 .json_rpc_call(
695 "get_fee_estimate",
696 Some(json!({ "grace_blocks": GRACE_BLOCKS_FOR_FEE_ESTIMATE })),
697 )
698 .await?;
699
700 if res.status != "OK" {
701 Err(RpcError::InvalidFee)?;
702 }
703
704 if let Some(fees) = res.fees {
705 let priority_idx = usize::try_from(if priority.fee_priority() >= 4 {
708 3
709 } else {
710 priority.fee_priority().saturating_sub(1)
711 })
712 .map_err(|_| RpcError::InvalidPriority)?;
713
714 if priority_idx >= fees.len() {
715 Err(RpcError::InvalidPriority)
716 } else {
717 FeeRate::new(fees[priority_idx], res.quantization_mask)
718 }
719 } else {
720 let priority_idx = usize::try_from(if priority.fee_priority() == 0 {
725 1
726 } else {
727 priority.fee_priority() - 1
728 })
729 .map_err(|_| RpcError::InvalidPriority)?;
730 let multipliers = [1, 5, 25, 1000];
731 if priority_idx >= multipliers.len() {
732 Err(RpcError::InvalidPriority)?;
734 }
735 let fee_multiplier = multipliers[priority_idx];
736
737 FeeRate::new(res.fee * fee_multiplier, res.quantization_mask)
738 }
739 }
740 }
741
742 fn publish_transaction(
744 &self,
745 tx: &Transaction,
746 ) -> impl Send + Future<Output = Result<(), RpcError>> {
747 async move {
748 #[allow(dead_code)]
749 #[derive(Debug, Deserialize)]
750 struct SendRawResponse {
751 status: String,
752 double_spend: bool,
753 fee_too_low: bool,
754 invalid_input: bool,
755 invalid_output: bool,
756 low_mixin: bool,
757 not_relayed: bool,
758 overspend: bool,
759 too_big: bool,
760 too_few_outputs: bool,
761 reason: String,
762 }
763
764 let res: SendRawResponse = self
765 .rpc_call(
766 "send_raw_transaction",
767 Some(json!({ "tx_as_hex": hex::encode(tx.serialize()), "do_sanity_checks": false })),
768 )
769 .await?;
770
771 if res.status != "OK" {
772 Err(RpcError::InvalidTransaction(tx.hash()))?;
773 }
774
775 Ok(())
776 }
777 }
778
779 fn generate_blocks<const ADDR_BYTES: u128>(
783 &self,
784 address: &Address<ADDR_BYTES>,
785 block_count: usize,
786 ) -> impl Send + Future<Output = Result<(Vec<[u8; 32]>, usize), RpcError>> {
787 async move {
788 #[derive(Debug, Deserialize)]
789 struct BlocksResponse {
790 blocks: Vec<String>,
791 height: usize,
792 }
793
794 let res = self
795 .json_rpc_call::<BlocksResponse>(
796 "generateblocks",
797 Some(json!({
798 "wallet_address": address.to_string(),
799 "amount_of_blocks": block_count
800 })),
801 )
802 .await?;
803
804 let mut blocks = Vec::with_capacity(res.blocks.len());
805 for block in res.blocks {
806 blocks.push(hash_hex(&block)?);
807 }
808 Ok((blocks, res.height))
809 }
810 }
811
812 fn get_o_indexes(
814 &self,
815 hash: [u8; 32],
816 ) -> impl Send + Future<Output = Result<Vec<u64>, RpcError>> {
817 async move {
818 const EPEE_HEADER: &[u8] = b"\x01\x11\x01\x01\x01\x01\x02\x01\x01";
823
824 fn read_epee_vi<R: io::Read>(reader: &mut R) -> io::Result<u64> {
826 let vi_start = read_byte(reader)?;
827 let len = match vi_start & 0b11 {
828 0 => 1,
829 1 => 2,
830 2 => 4,
831 3 => 8,
832 _ => unreachable!(),
833 };
834 let mut vi = u64::from(vi_start >> 2);
835 for i in 1 .. len {
836 vi |= u64::from(read_byte(reader)?) << (((i - 1) * 8) + 6);
837 }
838 Ok(vi)
839 }
840
841 let mut request = EPEE_HEADER.to_vec();
842 request.push(1 << 2);
844 request.push(4);
846 request.extend(b"txid");
848 request.push(10);
850 request.push(32 << 2);
852 request.extend(hash);
854
855 let indexes_buf = self.bin_call("get_o_indexes.bin", request).await?;
856 let mut indexes: &[u8] = indexes_buf.as_ref();
857
858 (|| {
859 let mut res = None;
860 let mut has_status = false;
861
862 if read_bytes::<_, { EPEE_HEADER.len() }>(&mut indexes)? != EPEE_HEADER {
863 Err(io::Error::other("invalid header"))?;
864 }
865
866 let read_object = |reader: &mut &[u8]| -> io::Result<Vec<u64>> {
867 let fields = read_byte(reader)? >> 2;
869
870 for _ in 0 .. fields {
871 let name_len = read_byte(reader)?;
873 let name = read_raw_vec(read_byte, name_len.into(), reader)?;
875
876 let type_with_array_flag = read_byte(reader)?;
877 let kind = type_with_array_flag & (!0x80);
879 let has_array_flag = type_with_array_flag != kind;
880
881 let iters = if has_array_flag { read_epee_vi(reader)? } else { 1 };
883
884 {
886 #[allow(clippy::match_same_arms)]
887 let (expected_type, expected_array_flag) = match name.as_slice() {
888 b"o_indexes" => (5, true),
889 b"status" => (10, false),
890 b"untrusted" => (11, false),
891 b"credits" => (5, false),
892 b"top_hash" => (10, false),
893 _ => {
899 Err(io::Error::other(format!("unrecognized field in get_o_indexes: {name:?}")))?
900 }
901 };
902 if (expected_type != kind) || (expected_array_flag != has_array_flag) {
903 let fmt_array_bool = |array_bool| if array_bool { "array" } else { "not array" };
904 Err(io::Error::other(format!(
905 "field {name:?} was {kind} ({}), expected {expected_type} ({})",
906 fmt_array_bool(has_array_flag),
907 fmt_array_bool(expected_array_flag)
908 )))?;
909 }
910 }
911
912 let read_field_as_bytes = match kind {
913 5 => |reader: &mut &[u8]| read_raw_vec(read_byte, 8, reader),
925 10 => |reader: &mut &[u8]| {
937 let len = read_epee_vi(reader)?;
938 read_raw_vec(
939 read_byte,
940 len.try_into().map_err(|_| io::Error::other("u64 length exceeded usize"))?,
941 reader,
942 )
943 },
944 11 => |reader: &mut &[u8]| read_raw_vec(read_byte, 1, reader),
946 _ => |_: &mut &[u8]| Err(io::Error::other("node used an invalid type")),
955 };
956
957 let mut bytes_res = vec![];
958 for _ in 0 .. iters {
959 bytes_res.push(read_field_as_bytes(reader)?);
960 }
961
962 let mut actual_res = Vec::with_capacity(bytes_res.len());
963 match name.as_slice() {
964 b"o_indexes" => {
965 for o_index in bytes_res {
966 actual_res.push(read_u64(&mut o_index.as_slice())?);
967 }
968 res = Some(actual_res);
969 }
970 b"status" => {
971 if bytes_res
972 .first()
973 .ok_or_else(|| io::Error::other("status was a 0-length array"))?
974 .as_slice() !=
975 b"OK"
976 {
977 Err(io::Error::other("response wasn't OK"))?;
978 }
979 has_status = true;
980 }
981 b"untrusted" | b"credits" | b"top_hash" => continue,
982 _ => Err(io::Error::other("unrecognized field in get_o_indexes"))?,
983 }
984 }
985
986 if !has_status {
987 Err(io::Error::other("response didn't contain a status"))?;
988 }
989
990 Ok(res.unwrap_or(vec![]))
992 };
993
994 read_object(&mut indexes)
995 })()
996 .map_err(|e| RpcError::InvalidNode(format!("invalid binary response: {e:?}")))
997 }
998 }
999}
1000
1001pub trait DecoyRpc: Sync {
1007 fn get_output_distribution_end_height(
1012 &self,
1013 ) -> impl Send + Future<Output = Result<usize, RpcError>>;
1014
1015 fn get_output_distribution(
1020 &self,
1021 range: impl Send + RangeBounds<usize>,
1022 ) -> impl Send + Future<Output = Result<Vec<u64>, RpcError>>;
1023
1024 fn get_outs(
1026 &self,
1027 indexes: &[u64],
1028 ) -> impl Send + Future<Output = Result<Vec<OutputInformation>, RpcError>>;
1029
1030 fn get_unlocked_outputs(
1042 &self,
1043 indexes: &[u64],
1044 height: usize,
1045 fingerprintable_deterministic: bool,
1046 ) -> impl Send + Future<Output = Result<Vec<Option<[EdwardsPoint; 2]>>, RpcError>>;
1047}
1048
1049impl<R: Rpc> DecoyRpc for R {
1050 fn get_output_distribution_end_height(
1051 &self,
1052 ) -> impl Send + Future<Output = Result<usize, RpcError>> {
1053 async move { <Self as Rpc>::get_height(self).await }
1054 }
1055
1056 fn get_output_distribution(
1057 &self,
1058 range: impl Send + RangeBounds<usize>,
1059 ) -> impl Send + Future<Output = Result<Vec<u64>, RpcError>> {
1060 async move {
1061 #[derive(Default, Debug, Deserialize)]
1062 struct Distribution {
1063 distribution: Vec<u64>,
1064 start_height: usize,
1066 }
1067
1068 #[derive(Debug, Deserialize)]
1069 struct Distributions {
1070 distributions: [Distribution; 1],
1071 status: String,
1072 }
1073
1074 let from = match range.start_bound() {
1075 Bound::Included(from) => *from,
1076 Bound::Excluded(from) => from.checked_add(1).ok_or_else(|| {
1077 RpcError::InternalError("range's from wasn't representable".to_string())
1078 })?,
1079 Bound::Unbounded => 0,
1080 };
1081 let to = match range.end_bound() {
1082 Bound::Included(to) => *to,
1083 Bound::Excluded(to) => to
1084 .checked_sub(1)
1085 .ok_or_else(|| RpcError::InternalError("range's to wasn't representable".to_string()))?,
1086 Bound::Unbounded => self.get_height().await? - 1,
1087 };
1088 if from > to {
1089 Err(RpcError::InternalError(format!(
1090 "malformed range: inclusive start {from}, inclusive end {to}"
1091 )))?;
1092 }
1093
1094 let zero_zero_case = (from == 0) && (to == 0);
1095 let distributions: Distributions = self
1096 .json_rpc_call(
1097 "get_output_distribution",
1098 Some(json!({
1099 "binary": false,
1100 "amounts": [0],
1101 "cumulative": true,
1102 "from_height": from,
1104 "to_height": if zero_zero_case { 1 } else { to },
1105 })),
1106 )
1107 .await?;
1108
1109 if distributions.status != "OK" {
1110 Err(RpcError::ConnectionError(
1111 "node couldn't service this request for the output distribution".to_string(),
1112 ))?;
1113 }
1114
1115 let mut distributions = distributions.distributions;
1116 let Distribution { start_height, mut distribution } = core::mem::take(&mut distributions[0]);
1117 if start_height < from {
1122 Err(RpcError::InvalidNode(format!(
1123 "requested distribution from {from} and got from {start_height}"
1124 )))?;
1125 }
1126 if start_height > to {
1128 Err(RpcError::InvalidNode(format!(
1129 "requested distribution to {to} and got from {start_height}"
1130 )))?;
1131 }
1132
1133 let expected_len = if zero_zero_case { 2 } else { (to - start_height) + 1 };
1134 if expected_len != distribution.len() {
1136 Err(RpcError::InvalidNode(format!(
1137 "distribution length ({}) wasn't of the requested length ({})",
1138 distribution.len(),
1139 expected_len
1140 )))?;
1141 }
1142 if zero_zero_case {
1146 distribution.pop();
1147 }
1148 Ok(distribution)
1149 }
1150 }
1151
1152 fn get_outs(
1153 &self,
1154 indexes: &[u64],
1155 ) -> impl Send + Future<Output = Result<Vec<OutputInformation>, RpcError>> {
1156 async move {
1157 #[derive(Debug, Deserialize)]
1158 struct OutputResponse {
1159 height: usize,
1160 unlocked: bool,
1161 key: String,
1162 mask: String,
1163 txid: String,
1164 }
1165
1166 #[derive(Debug, Deserialize)]
1167 struct OutsResponse {
1168 status: String,
1169 outs: Vec<OutputResponse>,
1170 }
1171
1172 const MAX_OUTS: usize = 5000;
1175
1176 let mut res = Vec::with_capacity(indexes.len());
1177 for indexes in indexes.chunks(MAX_OUTS) {
1178 let rpc_res: OutsResponse = self
1179 .rpc_call(
1180 "get_outs",
1181 Some(json!({
1182 "get_txid": true,
1183 "outputs": indexes.iter().map(|o| json!({
1184 "amount": 0,
1185 "index": o
1186 })).collect::<Vec<_>>()
1187 })),
1188 )
1189 .await?;
1190
1191 if rpc_res.status != "OK" {
1192 Err(RpcError::InvalidNode("bad response to get_outs".to_string()))?;
1193 }
1194
1195 res.extend(
1196 rpc_res
1197 .outs
1198 .into_iter()
1199 .map(|output| {
1200 Ok(OutputInformation {
1201 height: output.height,
1202 unlocked: output.unlocked,
1203 key: CompressedEdwardsY(
1204 rpc_hex(&output.key)?
1205 .try_into()
1206 .map_err(|_| RpcError::InvalidNode("output key wasn't 32 bytes".to_string()))?,
1207 ),
1208 commitment: rpc_point(&output.mask)?,
1209 transaction: hash_hex(&output.txid)?,
1210 })
1211 })
1212 .collect::<Result<Vec<_>, RpcError>>()?,
1213 );
1214 }
1215
1216 Ok(res)
1217 }
1218 }
1219
1220 fn get_unlocked_outputs(
1221 &self,
1222 indexes: &[u64],
1223 height: usize,
1224 fingerprintable_deterministic: bool,
1225 ) -> impl Send + Future<Output = Result<Vec<Option<[EdwardsPoint; 2]>>, RpcError>> {
1226 async move {
1227 let outs = self.get_outs(indexes).await?;
1228
1229 let txs = if fingerprintable_deterministic {
1231 self.get_transactions(&outs.iter().map(|out| out.transaction).collect::<Vec<_>>()).await?
1232 } else {
1233 vec![]
1234 };
1235
1236 outs
1238 .iter()
1239 .enumerate()
1240 .map(|(i, out)| {
1241 let Some(key) = out.key.decompress() else {
1246 return Ok(None);
1247 };
1248 Ok(Some([key, out.commitment]).filter(|_| {
1249 if fingerprintable_deterministic {
1250 const ACCEPTED_TIMELOCK_DELTA: usize = 1;
1254
1255 ((out.height + DEFAULT_LOCK_WINDOW) <= height) &&
1259 (Timelock::Block(height - 1 + ACCEPTED_TIMELOCK_DELTA) >=
1260 txs[i].prefix().additional_timelock)
1261 } else {
1262 out.unlocked
1263 }
1264 }))
1265 })
1266 .collect()
1267 }
1268 }
1269}