Skip to main content

miden_client/rpc/domain/
limits.rs

1// RPC LIMITS
2// ================================================================================================
3
4use core::convert::TryFrom;
5
6use miden_tx::utils::serde::{
7    ByteReader,
8    ByteWriter,
9    Deserializable,
10    DeserializationError,
11    Serializable,
12};
13
14use crate::rpc::RpcEndpoint;
15use crate::rpc::errors::RpcConversionError;
16use crate::rpc::generated::rpc as proto;
17
18/// Key used to store RPC limits in the settings table.
19pub(crate) const RPC_LIMITS_STORE_SETTING: &str = "rpc_limits";
20
21const DEFAULT_NOTE_IDS_LIMIT: u32 = 100;
22const DEFAULT_NULLIFIERS_LIMIT: u32 = 1000;
23const DEFAULT_ACCOUNT_IDS_LIMIT: u32 = 1000;
24const DEFAULT_NOTE_TAGS_LIMIT: u32 = 1000;
25
26/// Domain type representing RPC endpoint limits.
27///
28/// These limits define the maximum number of items that can be sent in a single RPC request.
29/// Exceeding these limits will result in the request being rejected by the node.
30#[derive(Debug, Clone, Copy, PartialEq, Eq)]
31pub struct RpcLimits {
32    /// Maximum number of note IDs that can be sent in a single `GetNotesById` request.
33    pub note_ids_limit: u32,
34    /// Maximum number of nullifier prefixes that can be sent in `CheckNullifiers` or
35    /// `SyncNullifiers` requests.
36    pub nullifiers_limit: u32,
37    /// Maximum number of account IDs that can be sent in a single `SyncTransactions` request.
38    pub account_ids_limit: u32,
39    /// Maximum number of note tags that can be sent in a single `SyncNotes` request.
40    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
74/// Extracts a parameter limit from the proto response for a given endpoint and parameter name.
75fn 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    Ok(*limit)
93}
94
95impl TryFrom<proto::RpcLimits> for RpcLimits {
96    type Error = RpcConversionError;
97
98    fn try_from(proto: proto::RpcLimits) -> Result<Self, Self::Error> {
99        Ok(Self {
100            note_ids_limit: get_param(&proto, RpcEndpoint::GetNotesById, "note_id")?,
101            nullifiers_limit: get_param(&proto, RpcEndpoint::CheckNullifiers, "nullifier")
102                .or_else(|_| get_param(&proto, RpcEndpoint::SyncNullifiers, "nullifier"))?,
103            account_ids_limit: get_param(&proto, RpcEndpoint::SyncTransactions, "account_id")?,
104            note_tags_limit: get_param(&proto, RpcEndpoint::SyncNotes, "note_tag")?,
105        })
106    }
107}
108
109#[cfg(test)]
110mod tests {
111    use super::*;
112
113    #[test]
114    fn rpc_limits_serialization_roundtrip() {
115        let original = RpcLimits {
116            note_ids_limit: 100,
117            nullifiers_limit: 1000,
118            account_ids_limit: 1000,
119            note_tags_limit: 1000,
120        };
121
122        let bytes = original.to_bytes();
123        let deserialized = RpcLimits::read_from_bytes(&bytes).expect("deserialization failed");
124
125        assert_eq!(original, deserialized);
126    }
127}