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