1use std::net::SocketAddr;
5use std::time::Duration;
6
7#[derive(Clone, Debug, Default, serde::Deserialize, serde::Serialize)]
8#[serde(rename_all = "kebab-case")]
9pub struct RpcConfig {
10 #[serde(skip_serializing_if = "Option::is_none")]
20 pub enable_indexing: Option<bool>,
21
22 #[serde(skip_serializing_if = "Option::is_none")]
26 pub https_address: Option<SocketAddr>,
27
28 #[serde(skip_serializing_if = "Option::is_none")]
32 pub tls: Option<RpcTlsConfig>,
33
34 #[serde(skip_serializing_if = "Option::is_none")]
41 pub max_json_move_value_size: Option<usize>,
42
43 #[serde(skip_serializing_if = "Option::is_none")]
52 pub max_json_move_value_response_size: Option<usize>,
53
54 #[serde(skip_serializing_if = "Option::is_none")]
56 pub index_initialization: Option<RpcIndexInitConfig>,
57
58 #[serde(skip_serializing_if = "Option::is_none")]
68 pub ledger_history_indexing: Option<bool>,
69
70 #[serde(skip_serializing_if = "Option::is_none")]
75 pub ledger_history: Option<LedgerHistoryConfig>,
76
77 #[serde(skip_serializing_if = "Option::is_none")]
79 pub display: Option<DisplayConfig>,
80}
81
82impl RpcConfig {
83 pub fn enable_indexing(&self) -> bool {
84 self.enable_indexing.unwrap_or(false)
85 }
86
87 pub fn https_address(&self) -> SocketAddr {
88 self.https_address
89 .unwrap_or_else(|| SocketAddr::from(([0, 0, 0, 0], 9443)))
90 }
91
92 pub fn tls_config(&self) -> Option<&RpcTlsConfig> {
93 self.tls.as_ref()
94 }
95
96 pub fn max_json_move_value_size(&self) -> usize {
97 self.max_json_move_value_size.unwrap_or(1024 * 1024)
98 }
99
100 pub fn max_json_move_value_response_size(&self) -> usize {
101 self.max_json_move_value_response_size
102 .unwrap_or(16 * 1024 * 1024)
103 }
104
105 pub fn index_initialization_config(&self) -> Option<&RpcIndexInitConfig> {
106 self.index_initialization.as_ref()
107 }
108
109 pub fn ledger_history_indexing(&self) -> bool {
110 self.ledger_history_indexing.unwrap_or(false)
111 }
112
113 pub fn ledger_history(&self) -> &LedgerHistoryConfig {
114 const DEFAULT_LEDGER_HISTORY_CONFIG: LedgerHistoryConfig = LedgerHistoryConfig {
115 list_transactions: None,
116 list_events: None,
117 list_checkpoints: None,
118 bitmap_bucket_scan_budget: None,
119 chunk_bucket_scan_budget: None,
120 max_bitmap_filter_literals: None,
121 };
122
123 self.ledger_history
124 .as_ref()
125 .unwrap_or(&DEFAULT_LEDGER_HISTORY_CONFIG)
126 }
127
128 pub fn validate(&self) -> anyhow::Result<()> {
131 self.ledger_history().validate()
132 }
133
134 pub fn display(&self) -> &DisplayConfig {
135 const DEFAULT_DISPLAY_CONFIG: DisplayConfig = DisplayConfig {
136 max_field_depth: None,
137 max_format_nodes: None,
138 max_object_loads: None,
139 max_move_value_depth: None,
140 max_output_size: None,
141 };
142
143 self.display.as_ref().unwrap_or(&DEFAULT_DISPLAY_CONFIG)
144 }
145}
146
147#[derive(Clone, Debug, Default, serde::Deserialize, serde::Serialize)]
148#[serde(rename_all = "kebab-case")]
149pub struct RpcTlsConfig {
150 cert: String,
152 key: String,
154}
155
156impl RpcTlsConfig {
157 pub fn cert(&self) -> &str {
158 &self.cert
159 }
160
161 pub fn key(&self) -> &str {
162 &self.key
163 }
164}
165
166#[derive(Clone, Debug, Default, serde::Deserialize, serde::Serialize)]
168#[serde(rename_all = "kebab-case")]
169pub struct RpcIndexInitConfig {
170 #[serde(skip_serializing_if = "Option::is_none")]
175 pub db_write_buffer_size: Option<usize>,
176
177 #[serde(skip_serializing_if = "Option::is_none")]
181 pub cf_write_buffer_size: Option<usize>,
182
183 #[serde(skip_serializing_if = "Option::is_none")]
188 pub cf_max_write_buffer_number: Option<i32>,
189
190 #[serde(skip_serializing_if = "Option::is_none")]
194 pub max_background_jobs: Option<i32>,
195
196 #[serde(skip_serializing_if = "Option::is_none")]
201 pub batch_size_limit: Option<usize>,
202}
203
204#[derive(Clone, Debug, Default, serde::Deserialize, serde::Serialize)]
205#[serde(rename_all = "kebab-case")]
206pub struct DisplayConfig {
207 #[serde(skip_serializing_if = "Option::is_none")]
212 max_field_depth: Option<usize>,
213
214 #[serde(skip_serializing_if = "Option::is_none")]
219 max_format_nodes: Option<usize>,
220
221 #[serde(skip_serializing_if = "Option::is_none")]
225 max_object_loads: Option<usize>,
226
227 #[serde(skip_serializing_if = "Option::is_none")]
231 max_move_value_depth: Option<usize>,
232
233 #[serde(skip_serializing_if = "Option::is_none")]
240 max_output_size: Option<usize>,
241}
242
243impl DisplayConfig {
244 pub fn max_field_depth(&self) -> usize {
245 self.max_field_depth.unwrap_or(32)
246 }
247
248 pub fn max_format_nodes(&self) -> usize {
249 self.max_format_nodes.unwrap_or(32768)
250 }
251
252 pub fn max_object_loads(&self) -> usize {
253 self.max_object_loads.unwrap_or(8)
254 }
255
256 pub fn max_move_value_depth(&self) -> usize {
257 self.max_move_value_depth.unwrap_or(32)
258 }
259
260 pub fn max_output_size(&self) -> usize {
261 self.max_output_size.unwrap_or(1024 * 1024)
262 }
263}
264
265const DEFAULT_LEDGER_HISTORY_METHOD_TIMEOUT_MS: u64 = 5_000;
266const DEFAULT_BITMAP_BUCKET_SCAN_BUDGET: usize = 1_024;
267const DEFAULT_CHUNK_BUCKET_SCAN_BUDGET: usize = 256;
268const DEFAULT_MAX_BITMAP_FILTER_LITERALS: usize = 10;
269const _: () = assert!(DEFAULT_CHUNK_BUCKET_SCAN_BUDGET <= DEFAULT_BITMAP_BUCKET_SCAN_BUDGET);
273
274struct LedgerHistoryMethodDefaults {
277 default_limit_items: u32,
278 max_limit_items: u32,
279 chunk_max: usize,
280}
281
282const LIST_TRANSACTIONS_DEFAULTS: LedgerHistoryMethodDefaults = LedgerHistoryMethodDefaults {
283 default_limit_items: 50,
284 max_limit_items: 500,
285 chunk_max: 32,
286};
287const LIST_EVENTS_DEFAULTS: LedgerHistoryMethodDefaults = LedgerHistoryMethodDefaults {
288 default_limit_items: 50,
289 max_limit_items: 1_000,
290 chunk_max: 32,
291};
292const LIST_CHECKPOINTS_DEFAULTS: LedgerHistoryMethodDefaults = LedgerHistoryMethodDefaults {
293 default_limit_items: 10,
294 max_limit_items: 100,
295 chunk_max: 16,
296};
297
298#[derive(Clone, Debug, Default, serde::Deserialize, serde::Serialize)]
301#[serde(rename_all = "kebab-case")]
302pub struct LedgerHistoryMethodConfig {
303 #[serde(skip_serializing_if = "Option::is_none")]
305 pub timeout_ms: Option<u64>,
306
307 #[serde(skip_serializing_if = "Option::is_none")]
309 pub default_limit_items: Option<u32>,
310
311 #[serde(skip_serializing_if = "Option::is_none")]
313 pub max_limit_items: Option<u32>,
314
315 #[serde(skip_serializing_if = "Option::is_none")]
317 pub chunk_max: Option<usize>,
318}
319
320#[derive(Clone, Copy, Debug)]
322pub struct ResolvedLedgerHistoryMethodConfig {
323 pub timeout: Duration,
324 pub default_limit_items: u32,
325 pub max_limit_items: u32,
326 pub chunk_max: usize,
327}
328
329impl LedgerHistoryMethodConfig {
330 fn resolve(
331 this: Option<&LedgerHistoryMethodConfig>,
332 defaults: LedgerHistoryMethodDefaults,
333 ) -> ResolvedLedgerHistoryMethodConfig {
334 ResolvedLedgerHistoryMethodConfig {
335 timeout: Duration::from_millis(
336 this.and_then(|c| c.timeout_ms)
337 .unwrap_or(DEFAULT_LEDGER_HISTORY_METHOD_TIMEOUT_MS),
338 ),
339 default_limit_items: this
340 .and_then(|c| c.default_limit_items)
341 .unwrap_or(defaults.default_limit_items),
342 max_limit_items: this
343 .and_then(|c| c.max_limit_items)
344 .unwrap_or(defaults.max_limit_items),
345 chunk_max: this.and_then(|c| c.chunk_max).unwrap_or(defaults.chunk_max),
346 }
347 }
348}
349
350#[derive(Clone, Debug, Default, serde::Deserialize, serde::Serialize)]
354#[serde(rename_all = "kebab-case")]
355pub struct LedgerHistoryConfig {
356 #[serde(skip_serializing_if = "Option::is_none")]
358 pub list_transactions: Option<LedgerHistoryMethodConfig>,
359
360 #[serde(skip_serializing_if = "Option::is_none")]
362 pub list_events: Option<LedgerHistoryMethodConfig>,
363
364 #[serde(skip_serializing_if = "Option::is_none")]
366 pub list_checkpoints: Option<LedgerHistoryMethodConfig>,
367
368 #[serde(skip_serializing_if = "Option::is_none")]
374 pub bitmap_bucket_scan_budget: Option<usize>,
375
376 #[serde(skip_serializing_if = "Option::is_none")]
383 pub chunk_bucket_scan_budget: Option<usize>,
384
385 #[serde(skip_serializing_if = "Option::is_none")]
392 pub max_bitmap_filter_literals: Option<usize>,
393}
394
395impl LedgerHistoryConfig {
396 pub fn list_transactions(&self) -> ResolvedLedgerHistoryMethodConfig {
397 LedgerHistoryMethodConfig::resolve(
398 self.list_transactions.as_ref(),
399 LIST_TRANSACTIONS_DEFAULTS,
400 )
401 }
402
403 pub fn list_events(&self) -> ResolvedLedgerHistoryMethodConfig {
404 LedgerHistoryMethodConfig::resolve(self.list_events.as_ref(), LIST_EVENTS_DEFAULTS)
405 }
406
407 pub fn list_checkpoints(&self) -> ResolvedLedgerHistoryMethodConfig {
408 LedgerHistoryMethodConfig::resolve(
409 self.list_checkpoints.as_ref(),
410 LIST_CHECKPOINTS_DEFAULTS,
411 )
412 }
413
414 pub fn bitmap_bucket_scan_budget(&self) -> usize {
415 self.bitmap_bucket_scan_budget
416 .unwrap_or(DEFAULT_BITMAP_BUCKET_SCAN_BUDGET)
417 }
418
419 pub fn chunk_bucket_scan_budget(&self) -> usize {
420 self.chunk_bucket_scan_budget
421 .unwrap_or(DEFAULT_CHUNK_BUCKET_SCAN_BUDGET)
422 .min(self.bitmap_bucket_scan_budget())
423 }
424
425 pub fn max_bitmap_filter_literals(&self) -> usize {
426 self.max_bitmap_filter_literals
427 .unwrap_or(DEFAULT_MAX_BITMAP_FILTER_LITERALS)
428 }
429
430 pub fn validate(&self) -> anyhow::Result<()> {
437 anyhow::ensure!(
438 self.bitmap_bucket_scan_budget() >= self.max_bitmap_filter_literals(),
439 "ledger_history.bitmap_bucket_scan_budget ({}) must be >= \
440 max_bitmap_filter_literals ({}) so every filter leaf gets at least one \
441 bucket before SCAN_LIMIT",
442 self.bitmap_bucket_scan_budget(),
443 self.max_bitmap_filter_literals(),
444 );
445 Ok(())
446 }
447}