Skip to main content

reth_node_core/args/
rpc_state_cache.rs

1use super::{database::parse_byte_size, DefaultRpcServerArgs};
2use clap::{builder::Resettable, 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 = DefaultRpcServerArgs::get_global().rpc_state_cache.max_blocks,
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 = DefaultRpcServerArgs::get_global().rpc_state_cache.max_receipts,
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 = DefaultRpcServerArgs::get_global().rpc_state_cache.max_headers,
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 = DefaultRpcServerArgs::get_global().rpc_state_cache.max_bals,
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 = DefaultRpcServerArgs::get_global().rpc_state_cache.max_blocks_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 = DefaultRpcServerArgs::get_global().rpc_state_cache.max_receipts_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 = DefaultRpcServerArgs::get_global().rpc_state_cache.max_bals_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 = humantime::format_duration(DefaultRpcServerArgs::get_global().rpc_state_cache.idle_timeout).to_string())]
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", default_value_t = DefaultRpcServerArgs::get_global().rpc_state_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        default_value = Resettable::from(DefaultRpcServerArgs::get_global().rpc_state_cache.prewarm_bals.map(|v| v.to_string().into())),
81        require_equals = true,
82    )]
83    pub prewarm_bals: Option<usize>,
84
85    /// Max number of concurrent database requests.
86    #[arg(
87        long = "rpc-cache.max-concurrent-db-requests",
88        default_value_t = DefaultRpcServerArgs::get_global().rpc_state_cache.max_concurrent_db_requests,
89    )]
90    pub max_concurrent_db_requests: usize,
91
92    /// Maximum number of transaction hashes to cache for transaction lookups.
93    #[arg(
94        long = "rpc-cache.max-cached-tx-hashes",
95        default_value_t = DefaultRpcServerArgs::get_global().rpc_state_cache.max_cached_tx_hashes,
96    )]
97    pub max_cached_tx_hashes: u32,
98}
99
100impl RpcStateCacheArgs {
101    /// Sets the Cache sizes to zero, effectively disabling caching.
102    pub const fn set_zero_lengths(&mut self) {
103        self.max_blocks = 0;
104        self.max_receipts = 0;
105        self.max_headers = 0;
106        self.max_bals = 0;
107        self.max_blocks_bytes = 0;
108        self.max_receipts_bytes = 0;
109        self.max_bals_bytes = 0;
110        self.idle_timeout = Duration::ZERO;
111        self.cache_computed_bals = false;
112        self.prewarm_bals = None;
113    }
114}
115
116impl Default for RpcStateCacheArgs {
117    fn default() -> Self {
118        Self {
119            max_blocks: DEFAULT_BLOCK_CACHE_MAX_LEN,
120            max_receipts: DEFAULT_RECEIPT_CACHE_MAX_LEN,
121            max_headers: DEFAULT_HEADER_CACHE_MAX_LEN,
122            max_bals: DEFAULT_BAL_CACHE_MAX_LEN,
123            max_blocks_bytes: DEFAULT_BLOCK_CACHE_MAX_BYTES,
124            max_receipts_bytes: DEFAULT_RECEIPT_CACHE_MAX_BYTES,
125            max_bals_bytes: DEFAULT_BAL_CACHE_MAX_BYTES,
126            idle_timeout: DEFAULT_CACHE_IDLE_TIMEOUT,
127            cache_computed_bals: false,
128            prewarm_bals: None,
129            max_concurrent_db_requests: DEFAULT_CONCURRENT_DB_REQUESTS,
130            max_cached_tx_hashes: DEFAULT_MAX_CACHED_TX_HASHES,
131        }
132    }
133}
134
135#[cfg(test)]
136mod tests {
137    use super::*;
138    use crate::node_config::NodeConfig;
139    use clap::Parser;
140
141    #[derive(Parser)]
142    struct CommandParser {
143        #[command(flatten)]
144        args: RpcStateCacheArgs,
145    }
146
147    #[test]
148    fn rpc_cache_defaults() {
149        let args = CommandParser::parse_from(["reth"]).args;
150        assert_eq!(args, RpcStateCacheArgs::default());
151
152        for flag in ["--rpc-cache.max-headers", "--rpc-cache.max-envs"] {
153            let args = CommandParser::parse_from(["reth", flag, "123"]).args;
154            assert_eq!(args.max_headers, 123);
155        }
156    }
157
158    #[test]
159    fn rpc_cache_byte_limits_reject_invalid_values() {
160        let overflow = format!("{}0", usize::MAX);
161        let unit_overflow = format!("{}GB", usize::MAX);
162        for flag in [
163            "--rpc-cache.max-blocks-bytes",
164            "--rpc-cache.max-receipts-bytes",
165            "--rpc-cache.max-bals-bytes",
166        ] {
167            for value in ["-1", "-1GB", "1XB", "1.5GB", &overflow, &unit_overflow] {
168                assert!(
169                    CommandParser::try_parse_from(["reth", &format!("{flag}={value}")]).is_err(),
170                    "accepted {flag}={value}"
171                );
172            }
173        }
174    }
175
176    #[test]
177    fn rpc_cache_byte_limits_accept_units() {
178        for (value, expected) in [
179            ("0GB", 0),
180            ("1024B", 1024),
181            ("1KB", 1024),
182            ("500MB", 500 * 1024 * 1024),
183            ("1GB", 1024 * 1024 * 1024),
184            ("1gb", 1024 * 1024 * 1024),
185            (" 500 MB ", 500 * 1024 * 1024),
186            ("1TB", 1024usize.pow(4)),
187        ] {
188            let args = CommandParser::parse_from([
189                "reth",
190                "--rpc-cache.max-blocks-bytes",
191                value,
192                "--rpc-cache.max-receipts-bytes",
193                value,
194                "--rpc-cache.max-bals-bytes",
195                value,
196            ])
197            .args;
198            assert_eq!(args.max_blocks_bytes, expected);
199            assert_eq!(args.max_receipts_bytes, expected);
200            assert_eq!(args.max_bals_bytes, expected);
201        }
202    }
203
204    #[test]
205    fn disabling_rpc_cache_overrides_positive_byte_limits() {
206        let mut config = NodeConfig::default();
207        config.rpc.rpc_state_cache = RpcStateCacheArgs {
208            max_blocks_bytes: 1024,
209            max_receipts_bytes: 2048,
210            max_bals_bytes: 4096,
211            idle_timeout: Duration::from_secs(60),
212            cache_computed_bals: true,
213            prewarm_bals: Some(10),
214            ..Default::default()
215        };
216
217        let args = config.with_disabled_rpc_cache().rpc.rpc_state_cache;
218        assert_eq!(args.max_blocks, 0);
219        assert_eq!(args.max_receipts, 0);
220        assert_eq!(args.max_bals, 0);
221        assert_eq!(args.max_blocks_bytes, 0);
222        assert_eq!(args.max_receipts_bytes, 0);
223        assert_eq!(args.max_bals_bytes, 0);
224        assert_eq!(args.idle_timeout, Duration::ZERO);
225        assert!(!args.cache_computed_bals);
226        assert_eq!(args.prewarm_bals, None);
227    }
228}