Skip to main content

miden_client/rpc/domain/
limits.rs

1// RPC LIMITS
2// ================================================================================================
3
4use core::convert::TryFrom;
5
6use miden_tx::utils::{ByteReader, ByteWriter, Deserializable, DeserializationError, Serializable};
7
8use crate::rpc::RpcEndpoint;
9use crate::rpc::errors::RpcConversionError;
10use crate::rpc::generated::rpc as proto;
11
12/// Key used to store RPC limits in the settings table.
13pub(crate) const RPC_LIMITS_STORE_SETTING: &str = "rpc_limits";
14
15const DEFAULT_NOTE_IDS_LIMIT: u32 = 100;
16const DEFAULT_NULLIFIERS_LIMIT: u32 = 1000;
17const DEFAULT_ACCOUNT_IDS_LIMIT: u32 = 1000;
18const DEFAULT_NOTE_TAGS_LIMIT: u32 = 1000;
19
20/// Domain type representing RPC endpoint limits.
21///
22/// These limits define the maximum number of items that can be sent in a single RPC request.
23/// Exceeding these limits will result in the request being rejected by the node.
24#[derive(Debug, Clone, Copy, PartialEq, Eq)]
25pub struct RpcLimits {
26    /// Maximum number of note IDs that can be sent in a single `GetNotesById` request.
27    pub note_ids_limit: u32,
28    /// Maximum number of nullifier prefixes that can be sent in `CheckNullifiers` or
29    /// `SyncNullifiers` requests.
30    pub nullifiers_limit: u32,
31    /// Maximum number of account IDs that can be sent in a single `SyncTransactions` request.
32    pub account_ids_limit: u32,
33    /// Maximum number of note tags that can be sent in a single `SyncNotes` request.
34    pub note_tags_limit: u32,
35}
36
37impl Default for RpcLimits {
38    fn default() -> Self {
39        Self {
40            note_ids_limit: DEFAULT_NOTE_IDS_LIMIT,
41            nullifiers_limit: DEFAULT_NULLIFIERS_LIMIT,
42            account_ids_limit: DEFAULT_ACCOUNT_IDS_LIMIT,
43            note_tags_limit: DEFAULT_NOTE_TAGS_LIMIT,
44        }
45    }
46}
47
48impl Serializable for RpcLimits {
49    fn write_into<W: ByteWriter>(&self, target: &mut W) {
50        self.note_ids_limit.write_into(target);
51        self.nullifiers_limit.write_into(target);
52        self.account_ids_limit.write_into(target);
53        self.note_tags_limit.write_into(target);
54    }
55}
56
57impl Deserializable for RpcLimits {
58    fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
59        Ok(Self {
60            note_ids_limit: u32::read_from(source)?,
61            nullifiers_limit: u32::read_from(source)?,
62            account_ids_limit: u32::read_from(source)?,
63            note_tags_limit: u32::read_from(source)?,
64        })
65    }
66}
67
68/// Extracts a parameter limit from the proto response for a given endpoint and parameter name.
69fn get_param(
70    proto: &proto::RpcLimits,
71    endpoint: RpcEndpoint,
72    param: &'static str,
73) -> Result<u32, RpcConversionError> {
74    let ep = proto.endpoints.get(endpoint.proto_name()).ok_or(
75        RpcConversionError::MissingFieldInProtobufRepresentation {
76            entity: "RpcLimits",
77            field_name: param,
78        },
79    )?;
80    let limit = ep.parameters.get(param).ok_or(
81        RpcConversionError::MissingFieldInProtobufRepresentation {
82            entity: "RpcLimits",
83            field_name: param,
84        },
85    )?;
86    Ok(*limit)
87}
88
89impl TryFrom<proto::RpcLimits> for RpcLimits {
90    type Error = RpcConversionError;
91
92    fn try_from(proto: proto::RpcLimits) -> Result<Self, Self::Error> {
93        Ok(Self {
94            note_ids_limit: get_param(&proto, RpcEndpoint::GetNotesById, "note_id")?,
95            nullifiers_limit: get_param(&proto, RpcEndpoint::CheckNullifiers, "nullifier")
96                .or_else(|_| get_param(&proto, RpcEndpoint::SyncNullifiers, "nullifier"))?,
97            account_ids_limit: get_param(&proto, RpcEndpoint::SyncTransactions, "account_id")?,
98            note_tags_limit: get_param(&proto, RpcEndpoint::SyncNotes, "note_tag")?,
99        })
100    }
101}
102
103#[cfg(test)]
104mod tests {
105    use super::*;
106
107    #[test]
108    fn rpc_limits_serialization_roundtrip() {
109        let original = RpcLimits {
110            note_ids_limit: 100,
111            nullifiers_limit: 1000,
112            account_ids_limit: 1000,
113            note_tags_limit: 1000,
114        };
115
116        let bytes = original.to_bytes();
117        let deserialized = RpcLimits::read_from_bytes(&bytes).expect("deserialization failed");
118
119        assert_eq!(original, deserialized);
120    }
121}