1use crate::Chain;
4use number_prefix::NumberPrefix;
5use serde::{Deserialize, Deserializer, Serialize, Serializer};
6use std::{fmt, fmt::Formatter, str::FromStr};
7
8#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
10pub struct StorageCachingConfig {
11 pub chains: CachedChains,
13 pub endpoints: CachedEndpoints,
15}
16
17impl StorageCachingConfig {
18 pub fn enable_for_endpoint(&self, endpoint: impl AsRef<str>) -> bool {
20 self.endpoints.is_match(endpoint)
21 }
22
23 pub fn enable_for_chain_id(&self, chain_id: u64) -> bool {
25 if [99, 1337, 31337].contains(&chain_id) {
27 return false
28 }
29 self.chains.is_match(chain_id)
30 }
31}
32
33#[derive(Clone, Debug, Default, PartialEq, Eq)]
35pub enum CachedChains {
36 #[default]
38 All,
39 None,
41 Chains(Vec<Chain>),
43}
44impl CachedChains {
45 pub fn is_match(&self, chain: u64) -> bool {
47 match self {
48 Self::All => true,
49 Self::None => false,
50 Self::Chains(chains) => chains.iter().any(|c| c.id() == chain),
51 }
52 }
53}
54
55impl Serialize for CachedChains {
56 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
57 where
58 S: Serializer,
59 {
60 match self {
61 Self::All => serializer.serialize_str("all"),
62 Self::None => serializer.serialize_str("none"),
63 Self::Chains(chains) => chains.serialize(serializer),
64 }
65 }
66}
67
68impl<'de> Deserialize<'de> for CachedChains {
69 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
70 where
71 D: Deserializer<'de>,
72 {
73 #[derive(Deserialize)]
74 #[serde(untagged)]
75 enum Chains {
76 All(String),
77 Chains(Vec<Chain>),
78 }
79
80 match Chains::deserialize(deserializer)? {
81 Chains::All(s) => match s.as_str() {
82 "all" => Ok(Self::All),
83 "none" => Ok(Self::None),
84 s => Err(serde::de::Error::unknown_variant(s, &["all", "none"])),
85 },
86 Chains::Chains(chains) => Ok(Self::Chains(chains)),
87 }
88 }
89}
90
91#[derive(Clone, Debug, Default)]
93pub enum CachedEndpoints {
94 #[default]
96 All,
97 Remote,
99 Pattern(regex::Regex),
101}
102
103impl CachedEndpoints {
104 pub fn is_match(&self, endpoint: impl AsRef<str>) -> bool {
106 let endpoint = endpoint.as_ref();
107 match self {
108 Self::All => true,
109 Self::Remote => !endpoint.contains("localhost:") && !endpoint.contains("127.0.0.1:"),
110 Self::Pattern(re) => re.is_match(endpoint),
111 }
112 }
113}
114
115impl PartialEq for CachedEndpoints {
116 fn eq(&self, other: &Self) -> bool {
117 match (self, other) {
118 (Self::Pattern(a), Self::Pattern(b)) => a.as_str() == b.as_str(),
119 (&Self::All, &Self::All) => true,
120 (&Self::Remote, &Self::Remote) => true,
121 _ => false,
122 }
123 }
124}
125
126impl Eq for CachedEndpoints {}
127
128impl fmt::Display for CachedEndpoints {
129 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
130 match self {
131 Self::All => f.write_str("all"),
132 Self::Remote => f.write_str("remote"),
133 Self::Pattern(s) => s.fmt(f),
134 }
135 }
136}
137
138impl FromStr for CachedEndpoints {
139 type Err = regex::Error;
140
141 fn from_str(s: &str) -> Result<Self, Self::Err> {
142 match s {
143 "all" => Ok(Self::All),
144 "remote" => Ok(Self::Remote),
145 _ => Ok(Self::Pattern(s.parse()?)),
146 }
147 }
148}
149
150impl<'de> Deserialize<'de> for CachedEndpoints {
151 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
152 where
153 D: Deserializer<'de>,
154 {
155 String::deserialize(deserializer)?.parse().map_err(serde::de::Error::custom)
156 }
157}
158
159impl Serialize for CachedEndpoints {
160 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
161 where
162 S: Serializer,
163 {
164 match self {
165 Self::All => serializer.serialize_str("all"),
166 Self::Remote => serializer.serialize_str("remote"),
167 Self::Pattern(pattern) => serializer.serialize_str(pattern.as_str()),
168 }
169 }
170}
171
172#[derive(Debug, Default)]
174pub struct Cache {
175 pub chains: Vec<ChainCache>,
177}
178
179impl fmt::Display for Cache {
180 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
181 for chain in &self.chains {
182 match NumberPrefix::decimal(
183 chain.block_explorer as f32 + chain.blocks.iter().map(|x| x.1).sum::<u64>() as f32,
184 ) {
185 NumberPrefix::Standalone(size) => {
186 writeln!(f, "- {} ({size:.1} B)", chain.name)?;
187 }
188 NumberPrefix::Prefixed(prefix, size) => {
189 writeln!(f, "- {} ({size:.1} {prefix}B)", chain.name)?;
190 }
191 }
192 match NumberPrefix::decimal(chain.block_explorer as f32) {
193 NumberPrefix::Standalone(size) => {
194 writeln!(f, "\t- Block Explorer ({size:.1} B)\n")?;
195 }
196 NumberPrefix::Prefixed(prefix, size) => {
197 writeln!(f, "\t- Block Explorer ({size:.1} {prefix}B)\n")?;
198 }
199 }
200 for block in &chain.blocks {
201 match NumberPrefix::decimal(block.1 as f32) {
202 NumberPrefix::Standalone(size) => {
203 writeln!(f, "\t- Block {} ({size:.1} B)", block.0)?;
204 }
205 NumberPrefix::Prefixed(prefix, size) => {
206 writeln!(f, "\t- Block {} ({size:.1} {prefix}B)", block.0)?;
207 }
208 }
209 }
210 }
211 Ok(())
212 }
213}
214
215#[derive(Debug)]
217pub struct ChainCache {
218 pub name: String,
220
221 pub blocks: Vec<(String, u64)>,
223
224 pub block_explorer: u64,
226}
227
228#[cfg(test)]
229mod tests {
230 use super::*;
231 use similar_asserts::assert_eq;
232
233 #[test]
234 fn can_parse_storage_config() {
235 #[derive(Serialize, Deserialize)]
236 pub struct Wrapper {
237 pub rpc_storage_caching: StorageCachingConfig,
238 }
239
240 let s = r#"rpc_storage_caching = { chains = "all", endpoints = "remote"}"#;
241 let w: Wrapper = toml::from_str(s).unwrap();
242
243 assert_eq!(
244 w.rpc_storage_caching,
245 StorageCachingConfig { chains: CachedChains::All, endpoints: CachedEndpoints::Remote }
246 );
247
248 let s = r#"rpc_storage_caching = { chains = [1, "optimism", 999999], endpoints = "all"}"#;
249 let w: Wrapper = toml::from_str(s).unwrap();
250
251 assert_eq!(
252 w.rpc_storage_caching,
253 StorageCachingConfig {
254 chains: CachedChains::Chains(vec![
255 Chain::mainnet(),
256 Chain::optimism_mainnet(),
257 Chain::from_id(999999)
258 ]),
259 endpoints: CachedEndpoints::All,
260 }
261 )
262 }
263
264 #[test]
265 fn cache_to_string() {
266 let cache = Cache {
267 chains: vec![
268 ChainCache {
269 name: "mainnet".to_string(),
270 blocks: vec![("1".to_string(), 1), ("2".to_string(), 2)],
271 block_explorer: 500,
272 },
273 ChainCache {
274 name: "ropsten".to_string(),
275 blocks: vec![("1".to_string(), 1), ("2".to_string(), 2)],
276 block_explorer: 4567,
277 },
278 ChainCache {
279 name: "rinkeby".to_string(),
280 blocks: vec![("1".to_string(), 1032), ("2".to_string(), 2000000)],
281 block_explorer: 4230000,
282 },
283 ChainCache {
284 name: "mumbai".to_string(),
285 blocks: vec![("1".to_string(), 1), ("2".to_string(), 2)],
286 block_explorer: 0,
287 },
288 ],
289 };
290
291 let expected = "\
292 - mainnet (503.0 B)\n\t\
293 - Block Explorer (500.0 B)\n\n\t\
294 - Block 1 (1.0 B)\n\t\
295 - Block 2 (2.0 B)\n\
296 - ropsten (4.6 kB)\n\t\
297 - Block Explorer (4.6 kB)\n\n\t\
298 - Block 1 (1.0 B)\n\t\
299 - Block 2 (2.0 B)\n\
300 - rinkeby (6.2 MB)\n\t\
301 - Block Explorer (4.2 MB)\n\n\t\
302 - Block 1 (1.0 kB)\n\t\
303 - Block 2 (2.0 MB)\n\
304 - mumbai (3.0 B)\n\t\
305 - Block Explorer (0.0 B)\n\n\t\
306 - Block 1 (1.0 B)\n\t\
307 - Block 2 (2.0 B)\n";
308 assert_eq!(format!("{cache}"), expected);
309 }
310}