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#[derive(Debug, Clone, Args, PartialEq, Eq)]
13#[command(next_help_heading = "RPC State Cache")]
14pub struct RpcStateCacheArgs {
15 #[arg(
17 long = "rpc-cache.max-blocks",
18 default_value_t = DEFAULT_BLOCK_CACHE_MAX_LEN,
19 )]
20 pub max_blocks: u32,
21
22 #[arg(
24 long = "rpc-cache.max-receipts",
25 default_value_t = DEFAULT_RECEIPT_CACHE_MAX_LEN,
26 )]
27 pub max_receipts: u32,
28
29 #[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 #[arg(
40 long = "rpc-cache.max-bals",
41 default_value_t = DEFAULT_BAL_CACHE_MAX_LEN,
42 )]
43 pub max_bals: u32,
44
45 #[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 #[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 #[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 #[arg(long = "rpc-cache.idle-timeout", value_name = "DURATION", value_parser = humantime::parse_duration, default_value = "1h")]
64 pub idle_timeout: Duration,
65
66 #[arg(long = "rpc-cache.cache-computed-bals")]
68 pub cache_computed_bals: bool,
69
70 #[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 #[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 #[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 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}