miden_client/rpc/domain/
limits.rs1use alloc::format;
5use core::convert::TryFrom;
6
7use miden_tx::utils::serde::{
8 ByteReader,
9 ByteWriter,
10 Deserializable,
11 DeserializationError,
12 Serializable,
13};
14
15use crate::rpc::RpcEndpoint;
16use crate::rpc::errors::RpcConversionError;
17use crate::rpc::generated::rpc as proto;
18
19pub(crate) const RPC_LIMITS_STORE_SETTING: &str = "rpc_limits";
21
22const DEFAULT_NOTE_IDS_LIMIT: u32 = 100;
23const DEFAULT_NULLIFIERS_LIMIT: u32 = 1000;
24const DEFAULT_ACCOUNT_IDS_LIMIT: u32 = 1000;
25const DEFAULT_NOTE_TAGS_LIMIT: u32 = 1000;
26
27#[derive(Debug, Clone, Copy, PartialEq, Eq)]
32pub struct RpcLimits {
33 pub note_ids_limit: u32,
35 pub nullifiers_limit: u32,
37 pub account_ids_limit: u32,
39 pub note_tags_limit: u32,
41}
42
43impl Default for RpcLimits {
44 fn default() -> Self {
45 Self {
46 note_ids_limit: DEFAULT_NOTE_IDS_LIMIT,
47 nullifiers_limit: DEFAULT_NULLIFIERS_LIMIT,
48 account_ids_limit: DEFAULT_ACCOUNT_IDS_LIMIT,
49 note_tags_limit: DEFAULT_NOTE_TAGS_LIMIT,
50 }
51 }
52}
53
54impl Serializable for RpcLimits {
55 fn write_into<W: ByteWriter>(&self, target: &mut W) {
56 self.note_ids_limit.write_into(target);
57 self.nullifiers_limit.write_into(target);
58 self.account_ids_limit.write_into(target);
59 self.note_tags_limit.write_into(target);
60 }
61}
62
63impl Deserializable for RpcLimits {
64 fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
65 Ok(Self {
66 note_ids_limit: u32::read_from(source)?,
67 nullifiers_limit: u32::read_from(source)?,
68 account_ids_limit: u32::read_from(source)?,
69 note_tags_limit: u32::read_from(source)?,
70 })
71 }
72}
73
74fn get_param(
76 proto: &proto::RpcLimits,
77 endpoint: RpcEndpoint,
78 param: &'static str,
79) -> Result<u32, RpcConversionError> {
80 let ep = proto.endpoints.get(endpoint.proto_name()).ok_or(
81 RpcConversionError::MissingFieldInProtobufRepresentation {
82 entity: "RpcLimits",
83 field_name: param,
84 },
85 )?;
86 let limit = ep.parameters.get(param).ok_or(
87 RpcConversionError::MissingFieldInProtobufRepresentation {
88 entity: "RpcLimits",
89 field_name: param,
90 },
91 )?;
92 if *limit == 0 {
93 return Err(RpcConversionError::InvalidField(format!(
94 "{}.{} must be greater than zero",
95 endpoint.proto_name(),
96 param
97 )));
98 }
99
100 Ok(*limit)
101}
102
103impl TryFrom<proto::RpcLimits> for RpcLimits {
104 type Error = RpcConversionError;
105
106 fn try_from(proto: proto::RpcLimits) -> Result<Self, Self::Error> {
107 Ok(Self {
108 note_ids_limit: get_param(&proto, RpcEndpoint::GetNotesById, "note_id")?,
109 nullifiers_limit: get_param(&proto, RpcEndpoint::SyncNullifiers, "nullifier_prefix")?,
110 account_ids_limit: get_param(&proto, RpcEndpoint::SyncTransactions, "account_id")?,
111 note_tags_limit: get_param(&proto, RpcEndpoint::SyncNotes, "note_tag")?,
112 })
113 }
114}
115
116#[cfg(test)]
117mod tests {
118 use alloc::string::String;
119
120 use super::*;
121
122 #[test]
123 fn rpc_limits_serialization_roundtrip() {
124 let original = RpcLimits {
125 note_ids_limit: 100,
126 nullifiers_limit: 1000,
127 account_ids_limit: 1000,
128 note_tags_limit: 1000,
129 };
130
131 let bytes = original.to_bytes();
132 let deserialized = RpcLimits::read_from_bytes(&bytes).expect("deserialization failed");
133
134 assert_eq!(original, deserialized);
135 }
136
137 #[test]
138 fn rejects_zero_limits_from_rpc_response() {
139 let mut proto = proto::RpcLimits::default();
140
141 proto.endpoints.insert(
142 RpcEndpoint::GetNotesById.proto_name().into(),
143 proto::EndpointLimits {
144 parameters: [(String::from("note_id"), 0)].into(),
145 },
146 );
147 proto.endpoints.insert(
148 RpcEndpoint::SyncNullifiers.proto_name().into(),
149 proto::EndpointLimits {
150 parameters: [(String::from("nullifier_prefix"), 1000)].into(),
151 },
152 );
153 proto.endpoints.insert(
154 RpcEndpoint::SyncTransactions.proto_name().into(),
155 proto::EndpointLimits {
156 parameters: [(String::from("account_id"), 1000)].into(),
157 },
158 );
159 proto.endpoints.insert(
160 RpcEndpoint::SyncNotes.proto_name().into(),
161 proto::EndpointLimits {
162 parameters: [(String::from("note_tag"), 1000)].into(),
163 },
164 );
165
166 let err = RpcLimits::try_from(proto).expect_err("zero limit should be rejected");
167
168 assert!(matches!(err, RpcConversionError::InvalidField(_)), "got {err:?}");
169 }
170}