Skip to main content

reth_node_core/args/
rpc_state_cache.rs

1use super::database::parse_byte_size;
2use clap::Args;
3use reth_rpc_server_types::constants::cache::{
4    DEFAULT_BAL_CACHE_MAX_BYTES, DEFAULT_BAL_CACHE_MAX_LEN, DEFAULT_BLOCK_CACHE_MAX_BYTES,
5    DEFAULT_BLOCK_CACHE_MAX_LEN, DEFAULT_CACHE_IDLE_TIMEOUT, DEFAULT_CONCURRENT_DB_REQUESTS,
6    DEFAULT_HEADER_CACHE_MAX_LEN, DEFAULT_MAX_CACHED_TX_HASHES, DEFAULT_RECEIPT_CACHE_MAX_BYTES,
7    DEFAULT_RECEIPT_CACHE_MAX_LEN,
8};
9use std::time::Duration;
10
11/// Parameters to configure RPC state cache.
12#[derive(Debug, Clone, Args, PartialEq, Eq)]
13#[command(next_help_heading = "RPC State Cache")]
14pub struct RpcStateCacheArgs {
15    /// Max number of blocks in cache.
16    #[arg(
17        long = "rpc-cache.max-blocks",
18        default_value_t = DEFAULT_BLOCK_CACHE_MAX_LEN,
19    )]
20    pub max_blocks: u32,
21
22    /// Max number receipts in cache.
23    #[arg(
24        long = "rpc-cache.max-receipts",
25        default_value_t = DEFAULT_RECEIPT_CACHE_MAX_LEN,
26    )]
27    pub max_receipts: u32,
28
29    /// Legacy no-op retained for CLI compatibility.
30    #[arg(
31        long = "rpc-cache.max-headers",
32        alias = "rpc-cache.max-envs",
33        default_value_t = DEFAULT_HEADER_CACHE_MAX_LEN,
34        hide = true,
35    )]
36    pub max_headers: u32,
37
38    /// Max number of block access lists in cache.
39    #[arg(
40        long = "rpc-cache.max-bals",
41        default_value_t = DEFAULT_BAL_CACHE_MAX_LEN,
42    )]
43    pub max_bals: u32,
44
45    /// Maximum estimated block cache memory in bytes or with a unit (e.g. 1GB, 500MB).
46    /// Units use powers of 1024. Zero disables caching. The entry count limit also applies.
47    #[arg(long = "rpc-cache.max-blocks-bytes", value_name = "BYTES", value_parser = parse_byte_size, default_value_t = DEFAULT_BLOCK_CACHE_MAX_BYTES)]
48    pub max_blocks_bytes: usize,
49
50    /// Maximum estimated receipts cache memory in bytes or with a unit (e.g. 1GB, 500MB).
51    /// Units use powers of 1024. Zero disables caching. The entry count limit also applies.
52    #[arg(long = "rpc-cache.max-receipts-bytes", value_name = "BYTES", value_parser = parse_byte_size, default_value_t = DEFAULT_RECEIPT_CACHE_MAX_BYTES)]
53    pub max_receipts_bytes: usize,
54
55    /// Maximum estimated block access list cache memory in bytes or with a unit (e.g. 1GB, 500MB).
56    /// Units use powers of 1024. Zero disables caching. The entry count limit also applies.
57    #[arg(long = "rpc-cache.max-bals-bytes", value_name = "BYTES", value_parser = parse_byte_size, default_value_t = DEFAULT_BAL_CACHE_MAX_BYTES)]
58    pub max_bals_bytes: usize,
59
60    /// Evict blocks, receipts, and block access lists after this duration without a cache hit
61    /// (e.g. 5m, 30s). Zero disables expiration. Cleanup starts every 1 to 60 seconds and removes
62    /// at most five entries per cache per poll.
63    #[arg(long = "rpc-cache.idle-timeout", value_name = "DURATION", value_parser = humantime::parse_duration, default_value = "1h")]
64    pub idle_timeout: Duration,
65
66    /// Cache block access lists computed by RPC requests for transaction tracing.
67    #[arg(long = "rpc-cache.cache-computed-bals")]
68    pub cache_computed_bals: bool,
69
70    /// Replay new canonical blocks to cache block access lists until native BAL support.
71    ///
72    /// Optionally replay the latest BLOCKS blocks sequentially on startup. Without a count, only
73    /// new blocks are prewarmed. Implies --rpc-cache.cache-computed-bals.
74    /// Prewarming stops when a canonical block with a block access list hash is received.
75    #[arg(
76        long = "rpc-cache.prewarm-bals",
77        value_name = "BLOCKS",
78        num_args = 0..=1,
79        default_missing_value = "0",
80        require_equals = true,
81    )]
82    pub prewarm_bals: Option<usize>,
83
84    /// Max number of concurrent database requests.
85    #[arg(
86        long = "rpc-cache.max-concurrent-db-requests",
87        default_value_t = DEFAULT_CONCURRENT_DB_REQUESTS,
88    )]
89    pub max_concurrent_db_requests: usize,
90
91    /// Maximum number of transaction hashes to cache for transaction lookups.
92    #[arg(
93        long = "rpc-cache.max-cached-tx-hashes",
94        default_value_t = DEFAULT_MAX_CACHED_TX_HASHES,
95    )]
96    pub max_cached_tx_hashes: u32,
97}
98
99impl RpcStateCacheArgs {
100    /// Sets the Cache sizes to zero, effectively disabling caching.
101    pub const fn set_zero_lengths(&mut self) {
102        self.max_blocks = 0;
103        self.max_receipts = 0;
104        self.max_headers = 0;
105        self.max_bals = 0;
106        self.max_blocks_bytes = 0;
107        self.max_receipts_bytes = 0;
108        self.max_bals_bytes = 0;
109        self.idle_timeout = Duration::ZERO;
110        self.cache_computed_bals = false;
111        self.prewarm_bals = None;
112    }
113}
114
115impl Default for RpcStateCacheArgs {
116    fn default() -> Self {
117        Self {
118            max_blocks: DEFAULT_BLOCK_CACHE_MAX_LEN,
119            max_receipts: DEFAULT_RECEIPT_CACHE_MAX_LEN,
120            max_headers: DEFAULT_HEADER_CACHE_MAX_LEN,
121            max_bals: DEFAULT_BAL_CACHE_MAX_LEN,
122            max_blocks_bytes: DEFAULT_BLOCK_CACHE_MAX_BYTES,
123            max_receipts_bytes: DEFAULT_RECEIPT_CACHE_MAX_BYTES,
124            max_bals_bytes: DEFAULT_BAL_CACHE_MAX_BYTES,
125            idle_timeout: DEFAULT_CACHE_IDLE_TIMEOUT,
126            cache_computed_bals: false,
127            prewarm_bals: None,
128            max_concurrent_db_requests: DEFAULT_CONCURRENT_DB_REQUESTS,
129            max_cached_tx_hashes: DEFAULT_MAX_CACHED_TX_HASHES,
130        }
131    }
132}
133
134#[cfg(test)]
135mod tests {
136    use super::*;
137    use crate::node_config::NodeConfig;
138    use clap::Parser;
139
140    #[derive(Parser)]
141    struct CommandParser {
142        #[command(flatten)]
143        args: RpcStateCacheArgs,
144    }
145
146    #[test]
147    fn rpc_cache_defaults() {
148        let args = CommandParser::parse_from(["reth"]).args;
149        assert_eq!(args, RpcStateCacheArgs::default());
150
151        for flag in ["--rpc-cache.max-headers", "--rpc-cache.max-envs"] {
152            let args = CommandParser::parse_from(["reth", flag, "123"]).args;
153            assert_eq!(args.max_headers, 123);
154        }
155    }
156
157    #[test]
158    fn rpc_cache_byte_limits_accept_zero_and_maximum() {
159        for limit in [0, 1024, usize::MAX] {
160            let value = limit.to_string();
161            let args = CommandParser::parse_from([
162                "reth",
163                "--rpc-cache.max-blocks-bytes",
164                &value,
165                "--rpc-cache.max-receipts-bytes",
166                &value,
167                "--rpc-cache.max-bals-bytes",
168                &value,
169            ])
170            .args;
171            assert_eq!(args.max_blocks_bytes, limit);
172            assert_eq!(args.max_receipts_bytes, limit);
173            assert_eq!(args.max_bals_bytes, limit);
174        }
175    }
176
177    #[test]
178    fn rpc_cache_byte_limits_reject_invalid_values() {
179        let overflow = format!("{}0", usize::MAX);
180        let unit_overflow = format!("{}GB", usize::MAX);
181        for flag in [
182            "--rpc-cache.max-blocks-bytes",
183            "--rpc-cache.max-receipts-bytes",
184            "--rpc-cache.max-bals-bytes",
185        ] {
186            for value in ["-1", "-1GB", "1XB", "1.5GB", &overflow, &unit_overflow] {
187                assert!(
188                    CommandParser::try_parse_from(["reth", &format!("{flag}={value}")]).is_err(),
189                    "accepted {flag}={value}"
190                );
191            }
192        }
193    }
194
195    #[test]
196    fn rpc_cache_byte_limits_accept_units() {
197        for (value, expected) in [
198            ("0GB", 0),
199            ("1024B", 1024),
200            ("1KB", 1024),
201            ("500MB", 500 * 1024 * 1024),
202            ("1GB", 1024 * 1024 * 1024),
203            ("1gb", 1024 * 1024 * 1024),
204            (" 500 MB ", 500 * 1024 * 1024),
205            ("1TB", 1024usize.pow(4)),
206        ] {
207            let args = CommandParser::parse_from([
208                "reth",
209                "--rpc-cache.max-blocks-bytes",
210                value,
211                "--rpc-cache.max-receipts-bytes",
212                value,
213                "--rpc-cache.max-bals-bytes",
214                value,
215            ])
216            .args;
217            assert_eq!(args.max_blocks_bytes, expected);
218            assert_eq!(args.max_receipts_bytes, expected);
219            assert_eq!(args.max_bals_bytes, expected);
220        }
221    }
222
223    #[test]
224    fn rpc_cache_idle_timeout_parses_durations() {
225        for (value, expected) in [
226            ("0s", Duration::ZERO),
227            ("500ms", Duration::from_millis(500)),
228            ("30s", Duration::from_secs(30)),
229            ("5m", Duration::from_secs(300)),
230        ] {
231            let args = CommandParser::parse_from(["reth", "--rpc-cache.idle-timeout", value]).args;
232            assert_eq!(args.idle_timeout, expected);
233        }
234        for value in ["-1s", "invalid", "18446744073709551616s"] {
235            assert!(CommandParser::try_parse_from([
236                "reth",
237                &format!("--rpc-cache.idle-timeout={value}")
238            ])
239            .is_err());
240        }
241    }
242
243    #[test]
244    fn disabling_rpc_cache_overrides_positive_byte_limits() {
245        let mut config = NodeConfig::default();
246        config.rpc.rpc_state_cache = RpcStateCacheArgs {
247            max_blocks_bytes: 1024,
248            max_receipts_bytes: 2048,
249            max_bals_bytes: 4096,
250            idle_timeout: Duration::from_secs(60),
251            cache_computed_bals: true,
252            prewarm_bals: Some(10),
253            ..Default::default()
254        };
255
256        let args = config.with_disabled_rpc_cache().rpc.rpc_state_cache;
257        assert_eq!(args.max_blocks, 0);
258        assert_eq!(args.max_receipts, 0);
259        assert_eq!(args.max_bals, 0);
260        assert_eq!(args.max_blocks_bytes, 0);
261        assert_eq!(args.max_receipts_bytes, 0);
262        assert_eq!(args.max_bals_bytes, 0);
263        assert_eq!(args.idle_timeout, Duration::ZERO);
264        assert!(!args.cache_computed_bals);
265        assert_eq!(args.prewarm_bals, None);
266    }
267}