1use std::{collections::BTreeSet, fmt::Display, time::Duration};
6
7use async_graphql::*;
8use fastcrypto_zkp::bn254::zk_login_api::ZkLoginEnv;
9use iota_graphql_config::GraphQLConfig;
10use iota_names::config::IotaNamesConfig;
11use serde::{Deserialize, Serialize};
12
13use crate::functional_group::FunctionalGroup;
14
15pub(crate) const RPC_TIMEOUT_ERR_SLEEP_RETRY_PERIOD: Duration = Duration::from_millis(10_000);
16pub(crate) const MAX_CONCURRENT_REQUESTS: usize = 1_000;
17
18#[GraphQLConfig]
20#[derive(Default)]
21pub struct ServerConfig {
22 pub service: ServiceConfig,
23 pub connection: ConnectionConfig,
24 pub internal_features: InternalFeatureConfig,
25 pub tx_exec_full_node: TxExecFullNodeConfig,
26 pub ide: Ide,
27}
28
29#[GraphQLConfig]
34#[derive(Clone, Eq, PartialEq)]
35pub struct ConnectionConfig {
36 pub(crate) port: u16,
38 pub(crate) host: String,
40 pub(crate) db_url: String,
41 pub(crate) db_pool_size: u32,
42 pub(crate) prom_url: String,
43 pub(crate) prom_port: u16,
44}
45
46#[GraphQLConfig]
50#[derive(Default)]
51pub struct ServiceConfig {
52 pub(crate) versions: Versions,
53 pub(crate) limits: Limits,
54 pub(crate) disabled_features: BTreeSet<FunctionalGroup>,
55 pub(crate) experiments: Experiments,
56 pub(crate) iota_names: IotaNamesConfig,
57 pub(crate) background_tasks: BackgroundTasksConfig,
58 pub(crate) zklogin: ZkLoginConfig,
59}
60
61#[GraphQLConfig]
62pub struct Versions {
63 versions: Vec<String>,
64}
65
66#[GraphQLConfig]
67pub struct Limits {
68 pub max_query_depth: u32,
70 pub max_query_nodes: u32,
72 pub max_output_nodes: u32,
74 pub max_query_payload_size: u32,
76 pub max_db_query_cost: u32,
80 pub default_page_size: u32,
83 pub max_page_size: u32,
85 pub mutation_timeout_ms: u32,
90 pub request_timeout_ms: u32,
94 pub max_type_argument_depth: u32,
97 pub max_type_argument_width: u32,
99 pub max_type_nodes: u32,
101 pub max_move_value_depth: u32,
103 pub max_transaction_ids: u32,
106 pub max_scan_limit: u32,
108}
109
110#[GraphQLConfig]
111#[derive(Copy)]
112pub struct BackgroundTasksConfig {
113 pub watermark_update_ms: u64,
116}
117
118#[derive(Copy, Clone, Debug)]
122pub struct Version {
123 pub year: &'static str,
125 pub month: &'static str,
127 pub patch: &'static str,
130 pub sha: &'static str,
132 pub full: &'static str,
136}
137
138impl Version {
139 pub fn for_testing() -> Self {
141 Self {
142 year: env!("CARGO_PKG_VERSION_MAJOR"),
143 month: env!("CARGO_PKG_VERSION_MINOR"),
144 patch: env!("CARGO_PKG_VERSION_PATCH"),
145 sha: "testing-no-sha",
146 full: const_str::concat!(
148 env!("CARGO_PKG_VERSION_MAJOR"),
149 ".",
150 env!("CARGO_PKG_VERSION_MINOR"),
151 ".",
152 env!("CARGO_PKG_VERSION_PATCH"),
153 "-testing-no-sha"
154 ),
155 }
156 }
157}
158
159#[GraphQLConfig]
160pub struct Ide {
161 pub(crate) ide_title: String,
162}
163
164#[GraphQLConfig]
165#[derive(Default)]
166pub struct Experiments {
167 #[cfg(test)]
170 test_flag: bool,
171}
172
173#[GraphQLConfig]
174pub struct InternalFeatureConfig {
175 pub(crate) query_limits_checker: bool,
176 pub(crate) directive_checker: bool,
177 pub(crate) feature_gate: bool,
178 pub(crate) logger: bool,
179 pub(crate) query_timeout: bool,
180 pub(crate) metrics: bool,
181 pub(crate) tracing: bool,
182 pub(crate) apollo_tracing: bool,
183 pub(crate) open_telemetry: bool,
184}
185
186#[GraphQLConfig]
187#[derive(Default)]
188pub struct TxExecFullNodeConfig {
189 pub(crate) node_rpc_url: Option<String>,
190}
191
192#[GraphQLConfig]
193#[derive(Default)]
194pub struct ZkLoginConfig {
195 pub env: ZkLoginEnv,
196}
197
198#[Object]
200impl ServiceConfig {
201 async fn is_enabled(&self, feature: FunctionalGroup) -> bool {
203 !self.disabled_features.contains(&feature)
204 }
205
206 async fn available_versions(&self) -> Vec<String> {
208 self.versions.versions.clone()
209 }
210
211 async fn enabled_features(&self) -> Vec<FunctionalGroup> {
213 FunctionalGroup::all()
214 .iter()
215 .filter(|g| !self.disabled_features.contains(g))
216 .copied()
217 .collect()
218 }
219
220 pub async fn max_query_depth(&self) -> u32 {
222 self.limits.max_query_depth
223 }
224
225 pub async fn max_query_nodes(&self) -> u32 {
228 self.limits.max_query_nodes
229 }
230
231 pub async fn max_output_nodes(&self) -> u32 {
244 self.limits.max_output_nodes
245 }
246
247 async fn max_db_query_cost(&self) -> u32 {
251 self.limits.max_db_query_cost
252 }
253
254 async fn default_page_size(&self) -> u32 {
256 self.limits.default_page_size
257 }
258
259 async fn max_page_size(&self) -> u32 {
261 self.limits.max_page_size
262 }
263
264 async fn mutation_timeout_ms(&self) -> u32 {
271 self.limits.mutation_timeout_ms
272 }
273
274 async fn request_timeout_ms(&self) -> u32 {
277 self.limits.request_timeout_ms
278 }
279
280 async fn max_query_payload_size(&self) -> u32 {
282 self.limits.max_query_payload_size
283 }
284
285 async fn max_type_argument_depth(&self) -> u32 {
288 self.limits.max_type_argument_depth
289 }
290
291 async fn max_type_argument_width(&self) -> u32 {
294 self.limits.max_type_argument_width
295 }
296
297 async fn max_type_nodes(&self) -> u32 {
300 self.limits.max_type_nodes
301 }
302
303 async fn max_move_value_depth(&self) -> u32 {
306 self.limits.max_move_value_depth
307 }
308
309 async fn max_transaction_ids(&self) -> u32 {
312 self.limits.max_transaction_ids
313 }
314
315 async fn max_scan_limit(&self) -> u32 {
317 self.limits.max_scan_limit
318 }
319}
320
321impl TxExecFullNodeConfig {
322 pub fn new(node_rpc_url: Option<String>) -> Self {
323 Self { node_rpc_url }
324 }
325}
326
327impl ConnectionConfig {
328 pub fn new(
329 port: Option<u16>,
330 host: Option<String>,
331 db_url: Option<String>,
332 db_pool_size: Option<u32>,
333 prom_url: Option<String>,
334 prom_port: Option<u16>,
335 ) -> Self {
336 let default = Self::default();
337 Self {
338 port: port.unwrap_or(default.port),
339 host: host.unwrap_or(default.host),
340 db_url: db_url.unwrap_or(default.db_url),
341 db_pool_size: db_pool_size.unwrap_or(default.db_pool_size),
342 prom_url: prom_url.unwrap_or(default.prom_url),
343 prom_port: prom_port.unwrap_or(default.prom_port),
344 }
345 }
346
347 pub fn ci_integration_test_cfg() -> Self {
348 Self {
349 db_url: "postgres://postgres:postgrespw@localhost:5432/iota_graphql_rpc_e2e_tests"
350 .to_string(),
351 ..Default::default()
352 }
353 }
354
355 pub fn ci_integration_test_cfg_with_db_name(
356 db_name: String,
357 port: u16,
358 prom_port: u16,
359 ) -> Self {
360 Self {
361 db_url: format!("postgres://postgres:postgrespw@localhost:5432/{}", db_name),
362 port,
363 prom_port,
364 ..Default::default()
365 }
366 }
367
368 pub fn db_name(&self) -> String {
369 self.db_url.split('/').next_back().unwrap().to_string()
370 }
371
372 pub fn db_url(&self) -> String {
373 self.db_url.clone()
374 }
375
376 pub fn db_pool_size(&self) -> u32 {
377 self.db_pool_size
378 }
379
380 pub fn server_address(&self) -> String {
381 format!("{}:{}", self.host, self.port)
382 }
383}
384
385impl ServiceConfig {
386 pub fn read(contents: &str) -> Result<Self, toml::de::Error> {
387 toml::de::from_str::<Self>(contents)
388 }
389
390 pub fn test_defaults() -> Self {
391 Self {
392 background_tasks: BackgroundTasksConfig::test_defaults(),
393 zklogin: ZkLoginConfig {
394 env: ZkLoginEnv::Test,
395 },
396 ..Default::default()
397 }
398 }
399}
400
401impl Limits {
402 pub fn package_resolver_limits(&self) -> iota_package_resolver::Limits {
404 iota_package_resolver::Limits {
405 max_type_argument_depth: self.max_type_argument_depth as usize,
406 max_type_argument_width: self.max_type_argument_width as usize,
407 max_type_nodes: self.max_type_nodes as usize,
408 max_move_value_depth: self.max_move_value_depth as usize,
409 }
410 }
411}
412
413impl Ide {
414 pub fn new(ide_title: Option<String>) -> Self {
415 ide_title
416 .map(|ide_title| Ide { ide_title })
417 .unwrap_or_default()
418 }
419}
420
421impl BackgroundTasksConfig {
422 pub fn test_defaults() -> Self {
423 Self {
424 watermark_update_ms: 100, }
426 }
427}
428
429impl Default for Versions {
430 fn default() -> Self {
431 Self {
432 versions: vec![format!(
433 "{}.{}",
434 env!("CARGO_PKG_VERSION_MAJOR"),
435 env!("CARGO_PKG_VERSION_MINOR")
436 )],
437 }
438 }
439}
440
441impl Default for Ide {
442 fn default() -> Self {
443 Self {
444 ide_title: "IOTA GraphQL IDE".to_string(),
445 }
446 }
447}
448
449impl Default for ConnectionConfig {
450 fn default() -> Self {
451 Self {
452 port: 8000,
453 host: "127.0.0.1".to_string(),
454 db_url: "postgres://postgres:postgrespw@localhost:5432/iota_indexer".to_string(),
455 db_pool_size: 10,
456 prom_url: "0.0.0.0".to_string(),
457 prom_port: 9184,
458 }
459 }
460}
461
462impl Default for Limits {
463 fn default() -> Self {
464 Self {
467 max_query_depth: 20,
468 max_query_nodes: 300,
469 max_output_nodes: 100_000,
470 max_query_payload_size: 5_000,
471 max_db_query_cost: 20_000,
472 default_page_size: 20,
473 max_page_size: 50,
474 mutation_timeout_ms: 74_000,
479 request_timeout_ms: 40_000,
480 max_type_argument_depth: 16,
483 max_type_argument_width: 32,
485 max_type_nodes: 256,
487 max_move_value_depth: 128,
489 max_transaction_ids: 1000,
492 max_scan_limit: 100_000_000,
493 }
494 }
495}
496
497impl Default for InternalFeatureConfig {
498 fn default() -> Self {
499 Self {
500 query_limits_checker: true,
501 directive_checker: true,
502 feature_gate: true,
503 logger: true,
504 query_timeout: true,
505 metrics: true,
506 tracing: false,
507 apollo_tracing: false,
508 open_telemetry: false,
509 }
510 }
511}
512
513impl Default for BackgroundTasksConfig {
514 fn default() -> Self {
515 Self {
516 watermark_update_ms: 500,
517 }
518 }
519}
520
521impl Display for Version {
522 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
523 write!(f, "{}", self.full)
524 }
525}
526
527#[cfg(test)]
528mod tests {
529 use super::*;
530
531 #[test]
532 fn test_read_empty_service_config() {
533 let actual = ServiceConfig::read("").unwrap();
534 let expect = ServiceConfig::default();
535 assert_eq!(actual, expect);
536 }
537
538 #[test]
539 fn test_read_limits_in_service_config() {
540 let actual = ServiceConfig::read(
541 r#" [limits]
542 max-query-depth = 100
543 max-query-nodes = 300
544 max-output-nodes = 200000
545 max-query-payload-size = 2000
546 max-db-query-cost = 50
547 default-page-size = 20
548 max-page-size = 50
549 mutation-timeout-ms = 74000
550 request-timeout-ms = 27000
551 max-type-argument-depth = 32
552 max-type-argument-width = 64
553 max-type-nodes = 128
554 max-move-value-depth = 256
555 max-transaction-ids = 11
556 max-scan-limit = 50
557 "#,
558 )
559 .unwrap();
560
561 let expect = ServiceConfig {
562 limits: Limits {
563 max_query_depth: 100,
564 max_query_nodes: 300,
565 max_output_nodes: 200000,
566 max_query_payload_size: 2000,
567 max_db_query_cost: 50,
568 default_page_size: 20,
569 max_page_size: 50,
570 mutation_timeout_ms: 74_000,
571 request_timeout_ms: 27_000,
572 max_type_argument_depth: 32,
573 max_type_argument_width: 64,
574 max_type_nodes: 128,
575 max_move_value_depth: 256,
576 max_transaction_ids: 11,
577 max_scan_limit: 50,
578 },
579 ..Default::default()
580 };
581
582 assert_eq!(actual, expect)
583 }
584
585 #[test]
586 fn test_read_enabled_features_in_service_config() {
587 let actual = ServiceConfig::read(
588 r#" disabled-features = [
589 "coins",
590 ]
591 "#,
592 )
593 .unwrap();
594
595 use FunctionalGroup as G;
596 let expect = ServiceConfig {
597 disabled_features: BTreeSet::from([G::Coins]),
598 ..Default::default()
599 };
600
601 assert_eq!(actual, expect)
602 }
603
604 #[test]
605 fn test_read_experiments_in_service_config() {
606 let actual = ServiceConfig::read(
607 r#" [experiments]
608 test-flag = true
609 "#,
610 )
611 .unwrap();
612
613 let expect = ServiceConfig {
614 experiments: Experiments { test_flag: true },
615 ..Default::default()
616 };
617
618 assert_eq!(actual, expect)
619 }
620
621 #[test]
622 fn test_read_everything_in_service_config() {
623 let actual = ServiceConfig::read(
624 r#" disabled-features = ["analytics"]
625
626 [limits]
627 max-query-depth = 42
628 max-query-nodes = 320
629 max-output-nodes = 200000
630 max-query-payload-size = 200
631 max-db-query-cost = 20
632 default-page-size = 10
633 max-page-size = 20
634 mutation-timeout-ms = 74000
635 request-timeout-ms = 30000
636 max-type-argument-depth = 32
637 max-type-argument-width = 64
638 max-type-nodes = 128
639 max-move-value-depth = 256
640 max-transaction-ids = 42
641 max-scan-limit = 420
642
643 [experiments]
644 test-flag = true
645 "#,
646 )
647 .unwrap();
648
649 let expect = ServiceConfig {
650 limits: Limits {
651 max_query_depth: 42,
652 max_query_nodes: 320,
653 max_output_nodes: 200000,
654 max_query_payload_size: 200,
655 max_db_query_cost: 20,
656 default_page_size: 10,
657 max_page_size: 20,
658 mutation_timeout_ms: 74_000,
659 request_timeout_ms: 30_000,
660 max_type_argument_depth: 32,
661 max_type_argument_width: 64,
662 max_type_nodes: 128,
663 max_move_value_depth: 256,
664 max_transaction_ids: 42,
665 max_scan_limit: 420,
666 },
667 disabled_features: BTreeSet::from([FunctionalGroup::Analytics]),
668 experiments: Experiments { test_flag: true },
669 ..Default::default()
670 };
671
672 assert_eq!(actual, expect);
673 }
674
675 #[test]
676 fn test_read_partial_in_service_config() {
677 let actual = ServiceConfig::read(
678 r#" disabled-features = ["analytics"]
679
680 [limits]
681 max-query-depth = 42
682 max-query-nodes = 320
683 "#,
684 )
685 .unwrap();
686
687 let expect = ServiceConfig {
690 limits: Limits {
691 max_query_depth: 42,
692 max_query_nodes: 320,
693 ..Default::default()
694 },
695 disabled_features: BTreeSet::from([FunctionalGroup::Analytics]),
696 ..Default::default()
697 };
698
699 assert_eq!(actual, expect);
700 }
701}