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(clap::Args, Clone, Eq, PartialEq)]
35pub struct ConnectionConfig {
36 #[arg(short, long, default_value_t = ConnectionConfig::default().port)]
38 pub port: u16,
39 #[arg(long, default_value_t = ConnectionConfig::default().host)]
41 pub host: String,
42 #[arg(short, long, default_value_t = ConnectionConfig::default().db_url)]
44 pub db_url: String,
45 #[arg(long, default_value_t = ConnectionConfig::default().db_pool_size)]
47 pub db_pool_size: u32,
48 #[arg(long, default_value_t = ConnectionConfig::default().prom_host)]
50 pub prom_host: String,
51 #[arg(long, default_value_t = ConnectionConfig::default().prom_port)]
53 pub prom_port: u16,
54 #[arg(long, default_value_t = ConnectionConfig::default().skip_migration_consistency_check)]
57 pub skip_migration_consistency_check: bool,
58}
59
60#[GraphQLConfig]
64#[derive(Default)]
65pub struct ServiceConfig {
66 pub versions: Versions,
67 pub limits: Limits,
68 pub disabled_features: BTreeSet<FunctionalGroup>,
69 pub experiments: Experiments,
70 pub iota_names: IotaNamesConfig,
71 pub background_tasks: BackgroundTasksConfig,
72 pub zklogin: ZkLoginConfig,
73}
74
75#[GraphQLConfig]
76pub struct Versions {
77 versions: Vec<String>,
78}
79
80#[GraphQLConfig]
81pub struct Limits {
82 pub max_query_depth: u32,
84 pub max_query_nodes: u32,
86 pub max_output_nodes: u32,
88 pub max_tx_payload_size: u32,
92 pub max_query_payload_size: u32,
95 pub max_db_query_cost: u32,
99 pub default_page_size: u32,
102 pub max_page_size: u32,
104 pub mutation_timeout_ms: u32,
109 pub request_timeout_ms: u32,
113 pub max_type_argument_depth: u32,
116 pub max_type_argument_width: u32,
118 pub max_type_nodes: u32,
120 pub max_move_value_depth: u32,
122 pub max_transaction_ids: u32,
125 pub max_scan_limit: u32,
127}
128
129#[GraphQLConfig]
130#[derive(Copy)]
131pub struct BackgroundTasksConfig {
132 pub watermark_update_ms: u64,
135}
136
137#[derive(Copy, Clone, Debug)]
141pub struct Version {
142 pub year: &'static str,
144 pub month: &'static str,
146 pub patch: &'static str,
149 pub sha: &'static str,
151 pub full: &'static str,
155}
156
157impl Version {
158 pub fn for_testing() -> Self {
160 Self {
161 year: env!("CARGO_PKG_VERSION_MAJOR"),
162 month: env!("CARGO_PKG_VERSION_MINOR"),
163 patch: env!("CARGO_PKG_VERSION_PATCH"),
164 sha: "testing-no-sha",
165 full: const_str::concat!(
167 env!("CARGO_PKG_VERSION_MAJOR"),
168 ".",
169 env!("CARGO_PKG_VERSION_MINOR"),
170 ".",
171 env!("CARGO_PKG_VERSION_PATCH"),
172 "-testing-no-sha"
173 ),
174 }
175 }
176}
177
178#[GraphQLConfig]
179#[derive(clap::Args)]
180pub struct Ide {
181 #[arg(short, long, default_value_t = Ide::default().ide_title)]
183 pub(crate) ide_title: String,
184}
185
186#[GraphQLConfig]
187#[derive(Default)]
188pub struct Experiments {
189 #[cfg(test)]
192 test_flag: bool,
193}
194
195#[GraphQLConfig]
196pub struct InternalFeatureConfig {
197 pub(crate) query_limits_checker: bool,
198 pub(crate) directive_checker: bool,
199 pub(crate) feature_gate: bool,
200 pub(crate) logger: bool,
201 pub(crate) query_timeout: bool,
202 pub(crate) metrics: bool,
203 pub(crate) tracing: bool,
204 pub(crate) apollo_tracing: bool,
205 pub(crate) open_telemetry: bool,
206}
207
208#[GraphQLConfig]
209#[derive(clap::Args, Default)]
210pub struct TxExecFullNodeConfig {
211 #[arg(long)]
213 pub(crate) node_rpc_url: Option<String>,
214}
215
216#[GraphQLConfig]
217#[derive(Default)]
218pub struct ZkLoginConfig {
219 pub env: ZkLoginEnv,
220}
221
222#[Object]
224impl ServiceConfig {
225 async fn is_enabled(&self, feature: FunctionalGroup) -> bool {
227 !self.disabled_features.contains(&feature)
228 }
229
230 async fn available_versions(&self) -> Vec<String> {
232 self.versions.versions.clone()
233 }
234
235 async fn enabled_features(&self) -> Vec<FunctionalGroup> {
237 FunctionalGroup::all()
238 .iter()
239 .filter(|g| !self.disabled_features.contains(g))
240 .copied()
241 .collect()
242 }
243
244 pub async fn max_query_depth(&self) -> u32 {
246 self.limits.max_query_depth
247 }
248
249 pub async fn max_query_nodes(&self) -> u32 {
252 self.limits.max_query_nodes
253 }
254
255 pub async fn max_output_nodes(&self) -> u32 {
268 self.limits.max_output_nodes
269 }
270
271 async fn max_db_query_cost(&self) -> u32 {
275 self.limits.max_db_query_cost
276 }
277
278 async fn default_page_size(&self) -> u32 {
280 self.limits.default_page_size
281 }
282
283 async fn max_page_size(&self) -> u32 {
285 self.limits.max_page_size
286 }
287
288 async fn mutation_timeout_ms(&self) -> u32 {
295 self.limits.mutation_timeout_ms
296 }
297
298 async fn request_timeout_ms(&self) -> u32 {
301 self.limits.request_timeout_ms
302 }
303
304 async fn max_transaction_payload_size(&self) -> u32 {
314 self.limits.max_tx_payload_size
315 }
316
317 async fn max_query_payload_size(&self) -> u32 {
322 self.limits.max_query_payload_size
323 }
324
325 async fn max_type_argument_depth(&self) -> u32 {
328 self.limits.max_type_argument_depth
329 }
330
331 async fn max_type_argument_width(&self) -> u32 {
334 self.limits.max_type_argument_width
335 }
336
337 async fn max_type_nodes(&self) -> u32 {
340 self.limits.max_type_nodes
341 }
342
343 async fn max_move_value_depth(&self) -> u32 {
346 self.limits.max_move_value_depth
347 }
348
349 async fn max_transaction_ids(&self) -> u32 {
352 self.limits.max_transaction_ids
353 }
354
355 async fn max_scan_limit(&self) -> u32 {
357 self.limits.max_scan_limit
358 }
359}
360
361impl ConnectionConfig {
362 pub fn new(
363 port: Option<u16>,
364 host: Option<String>,
365 db_url: Option<String>,
366 db_pool_size: Option<u32>,
367 prom_host: Option<String>,
368 prom_port: Option<u16>,
369 skip_migration_consistency_check: Option<bool>,
370 ) -> Self {
371 let default = Self::default();
372 Self {
373 port: port.unwrap_or(default.port),
374 host: host.unwrap_or(default.host),
375 db_url: db_url.unwrap_or(default.db_url),
376 db_pool_size: db_pool_size.unwrap_or(default.db_pool_size),
377 prom_host: prom_host.unwrap_or(default.prom_host),
378 prom_port: prom_port.unwrap_or(default.prom_port),
379 skip_migration_consistency_check: skip_migration_consistency_check
380 .unwrap_or(default.skip_migration_consistency_check),
381 }
382 }
383
384 pub fn ci_integration_test_cfg() -> Self {
385 Self {
386 db_url: "postgres://postgres:postgrespw@localhost:5432/iota_graphql_rpc_e2e_tests"
387 .to_string(),
388 ..Default::default()
389 }
390 }
391
392 pub fn ci_integration_test_cfg_with_db_name(
393 db_name: String,
394 port: u16,
395 prom_port: u16,
396 ) -> Self {
397 Self {
398 db_url: format!("postgres://postgres:postgrespw@localhost:5432/{db_name}"),
399 port,
400 prom_port,
401 ..Default::default()
402 }
403 }
404
405 pub fn db_name(&self) -> String {
406 self.db_url.split('/').next_back().unwrap().to_string()
407 }
408
409 pub fn db_url(&self) -> String {
410 self.db_url.clone()
411 }
412
413 pub fn db_pool_size(&self) -> u32 {
414 self.db_pool_size
415 }
416
417 pub fn server_address(&self) -> String {
418 format!("{}:{}", self.host, self.port)
419 }
420}
421
422impl ServiceConfig {
423 pub fn read(contents: &str) -> Result<Self, toml::de::Error> {
424 toml::de::from_str::<Self>(contents)
425 }
426
427 pub fn test_defaults() -> Self {
428 Self {
429 background_tasks: BackgroundTasksConfig::test_defaults(),
430 zklogin: ZkLoginConfig {
431 env: ZkLoginEnv::Test,
432 },
433 ..Default::default()
434 }
435 }
436}
437
438impl Limits {
439 pub fn package_resolver_limits(&self) -> iota_package_resolver::Limits {
441 iota_package_resolver::Limits {
442 max_type_argument_depth: self.max_type_argument_depth as usize,
443 max_type_argument_width: self.max_type_argument_width as usize,
444 max_type_nodes: self.max_type_nodes as usize,
445 max_move_value_depth: self.max_move_value_depth as usize,
446 }
447 }
448}
449
450impl BackgroundTasksConfig {
451 pub fn test_defaults() -> Self {
452 Self {
453 watermark_update_ms: 100, }
455 }
456}
457
458impl Default for Versions {
459 fn default() -> Self {
460 Self {
461 versions: vec![format!(
462 "{}.{}",
463 env!("CARGO_PKG_VERSION_MAJOR"),
464 env!("CARGO_PKG_VERSION_MINOR")
465 )],
466 }
467 }
468}
469
470impl Default for Ide {
471 fn default() -> Self {
472 Self {
473 ide_title: "IOTA GraphQL IDE".to_string(),
474 }
475 }
476}
477
478impl Default for ConnectionConfig {
479 fn default() -> Self {
480 Self {
481 port: 8000,
482 host: "127.0.0.1".to_string(),
483 db_url: "postgres://postgres:postgrespw@localhost:5432/iota_indexer".to_string(),
484 db_pool_size: 10,
485 prom_host: "0.0.0.0".to_string(),
486 prom_port: 9184,
487 skip_migration_consistency_check: false,
488 }
489 }
490}
491
492impl Default for Limits {
493 fn default() -> Self {
494 Self {
497 max_query_depth: 20,
498 max_query_nodes: 300,
499 max_output_nodes: 100_000,
500 max_query_payload_size: 5_000,
501 max_db_query_cost: 20_000,
502 default_page_size: 20,
503 max_page_size: 50,
504 mutation_timeout_ms: 74_000,
509 request_timeout_ms: 40_000,
510 max_type_argument_depth: 16,
513 max_type_argument_width: 32,
515 max_type_nodes: 256,
517 max_move_value_depth: 128,
519 max_transaction_ids: 1000,
522 max_scan_limit: 100_000_000,
523 max_tx_payload_size: (128u32 * 1024u32 * 4u32).div_ceil(3),
528 }
529 }
530}
531
532impl Default for InternalFeatureConfig {
533 fn default() -> Self {
534 Self {
535 query_limits_checker: true,
536 directive_checker: true,
537 feature_gate: true,
538 logger: true,
539 query_timeout: true,
540 metrics: true,
541 tracing: false,
542 apollo_tracing: false,
543 open_telemetry: false,
544 }
545 }
546}
547
548impl Default for BackgroundTasksConfig {
549 fn default() -> Self {
550 Self {
551 watermark_update_ms: 500,
552 }
553 }
554}
555
556impl Display for Version {
557 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
558 write!(f, "{}", self.full)
559 }
560}
561
562#[cfg(test)]
563mod tests {
564 use super::*;
565
566 #[test]
567 fn test_read_empty_service_config() {
568 let actual = ServiceConfig::read("").unwrap();
569 let expect = ServiceConfig::default();
570 assert_eq!(actual, expect);
571 }
572
573 #[test]
574 fn test_read_limits_in_service_config() {
575 let actual = ServiceConfig::read(
576 r#" [limits]
577 max-query-depth = 100
578 max-query-nodes = 300
579 max-output-nodes = 200000
580 max-tx-payload-size = 174763
581 max-query-payload-size = 2000
582 max-db-query-cost = 50
583 default-page-size = 20
584 max-page-size = 50
585 mutation-timeout-ms = 74000
586 request-timeout-ms = 27000
587 max-type-argument-depth = 32
588 max-type-argument-width = 64
589 max-type-nodes = 128
590 max-move-value-depth = 256
591 max-transaction-ids = 11
592 max-scan-limit = 50
593 "#,
594 )
595 .unwrap();
596
597 let expect = ServiceConfig {
598 limits: Limits {
599 max_query_depth: 100,
600 max_query_nodes: 300,
601 max_output_nodes: 200000,
602 max_tx_payload_size: 174763,
603 max_query_payload_size: 2000,
604 max_db_query_cost: 50,
605 default_page_size: 20,
606 max_page_size: 50,
607 mutation_timeout_ms: 74_000,
608 request_timeout_ms: 27_000,
609 max_type_argument_depth: 32,
610 max_type_argument_width: 64,
611 max_type_nodes: 128,
612 max_move_value_depth: 256,
613 max_transaction_ids: 11,
614 max_scan_limit: 50,
615 },
616 ..Default::default()
617 };
618
619 assert_eq!(actual, expect)
620 }
621
622 #[test]
623 fn test_read_enabled_features_in_service_config() {
624 let actual = ServiceConfig::read(
625 r#" disabled-features = [
626 "coins",
627 ]
628 "#,
629 )
630 .unwrap();
631
632 use FunctionalGroup as G;
633 let expect = ServiceConfig {
634 disabled_features: BTreeSet::from([G::Coins]),
635 ..Default::default()
636 };
637
638 assert_eq!(actual, expect)
639 }
640
641 #[test]
642 fn test_read_experiments_in_service_config() {
643 let actual = ServiceConfig::read(
644 r#" [experiments]
645 test-flag = true
646 "#,
647 )
648 .unwrap();
649
650 let expect = ServiceConfig {
651 experiments: Experiments { test_flag: true },
652 ..Default::default()
653 };
654
655 assert_eq!(actual, expect)
656 }
657
658 #[test]
659 fn test_read_everything_in_service_config() {
660 let actual = ServiceConfig::read(
661 r#" disabled-features = ["analytics"]
662
663 [limits]
664 max-query-depth = 42
665 max-query-nodes = 320
666 max-output-nodes = 200000
667 max-tx-payload-size = 181017
668 max-query-payload-size = 200
669 max-db-query-cost = 20
670 default-page-size = 10
671 max-page-size = 20
672 mutation-timeout-ms = 74000
673 request-timeout-ms = 30000
674 max-type-argument-depth = 32
675 max-type-argument-width = 64
676 max-type-nodes = 128
677 max-move-value-depth = 256
678 max-transaction-ids = 42
679 max-scan-limit = 420
680
681 [experiments]
682 test-flag = true
683 "#,
684 )
685 .unwrap();
686
687 let expect = ServiceConfig {
688 limits: Limits {
689 max_query_depth: 42,
690 max_query_nodes: 320,
691 max_output_nodes: 200000,
692 max_tx_payload_size: 181017,
693 max_query_payload_size: 200,
694 max_db_query_cost: 20,
695 default_page_size: 10,
696 max_page_size: 20,
697 mutation_timeout_ms: 74_000,
698 request_timeout_ms: 30_000,
699 max_type_argument_depth: 32,
700 max_type_argument_width: 64,
701 max_type_nodes: 128,
702 max_move_value_depth: 256,
703 max_transaction_ids: 42,
704 max_scan_limit: 420,
705 },
706 disabled_features: BTreeSet::from([FunctionalGroup::Analytics]),
707 experiments: Experiments { test_flag: true },
708 ..Default::default()
709 };
710
711 assert_eq!(actual, expect);
712 }
713
714 #[test]
715 fn test_read_partial_in_service_config() {
716 let actual = ServiceConfig::read(
717 r#" disabled-features = ["analytics"]
718
719 [limits]
720 max-query-depth = 42
721 max-query-nodes = 320
722 "#,
723 )
724 .unwrap();
725
726 let expect = ServiceConfig {
729 limits: Limits {
730 max_query_depth: 42,
731 max_query_nodes: 320,
732 ..Default::default()
733 },
734 disabled_features: BTreeSet::from([FunctionalGroup::Analytics]),
735 ..Default::default()
736 };
737
738 assert_eq!(actual, expect);
739 }
740}