1use std::{collections::BTreeSet, fmt::Display};
6
7use async_graphql::*;
8use iota_graphql_config::GraphQLConfig;
9use iota_names::config::IotaNamesConfig;
10use serde::{Deserialize, Serialize};
11
12use crate::functional_group::FunctionalGroup;
13
14pub(crate) const DEFAULT_PAGE_SIZE: u32 = 20;
15pub(crate) const MAX_PAGE_SIZE: u32 = 50;
16
17#[GraphQLConfig]
19#[derive(Default)]
20pub struct ServerConfig {
21 pub service: ServiceConfig,
22 pub connection: ConnectionConfig,
23 pub internal_features: InternalFeatureConfig,
24 pub tx_exec_full_node: TxExecFullNodeConfig,
25 pub ide: Ide,
26}
27
28#[GraphQLConfig]
33#[derive(clap::Args, Clone, Eq, PartialEq)]
34pub struct ConnectionConfig {
35 #[arg(short, long, default_value_t = ConnectionConfig::default().port)]
37 pub port: u16,
38 #[arg(long, default_value_t = ConnectionConfig::default().host)]
40 pub host: String,
41 #[arg(short, long, default_value_t = ConnectionConfig::default().db_url)]
43 pub db_url: String,
44 #[arg(long, default_value_t = ConnectionConfig::default().db_pool_size)]
46 pub db_pool_size: u32,
47 #[arg(long, default_value_t = ConnectionConfig::default().prom_host)]
49 pub prom_host: String,
50 #[arg(long, default_value_t = ConnectionConfig::default().prom_port)]
52 pub prom_port: u16,
53 #[arg(long, default_value_t = ConnectionConfig::default().skip_migration_consistency_check)]
56 pub skip_migration_consistency_check: bool,
57}
58
59#[GraphQLConfig]
63#[derive(Default)]
64pub struct ServiceConfig {
65 pub versions: Versions,
66 pub limits: Limits,
67 pub disabled_features: BTreeSet<FunctionalGroup>,
68 pub experiments: Experiments,
69 pub iota_names: IotaNamesConfig,
70 pub background_tasks: BackgroundTasksConfig,
71}
72
73#[GraphQLConfig]
74pub struct Versions {
75 versions: Vec<String>,
76}
77
78#[GraphQLConfig]
79pub struct Limits {
80 pub max_query_depth: u32,
82 pub max_query_nodes: u32,
84 pub max_output_nodes: u32,
86 pub max_tx_payload_size: u32,
90 pub max_query_payload_size: u32,
93 pub max_db_query_cost: u32,
97 pub default_page_size: u32,
100 pub max_page_size: u32,
102 pub mutation_timeout_ms: u32,
107 pub request_timeout_ms: u32,
111 pub max_type_argument_depth: u32,
114 pub max_type_argument_width: u32,
116 pub max_type_nodes: u32,
118 pub max_move_value_depth: u32,
120 pub max_transaction_ids: u32,
123 pub max_scan_limit: u32,
125}
126
127#[GraphQLConfig]
128#[derive(Copy)]
129pub struct BackgroundTasksConfig {
130 pub watermark_update_ms: u64,
133}
134
135#[derive(Copy, Clone, Debug)]
139pub struct Version {
140 pub year: &'static str,
142 pub month: &'static str,
144 pub patch: &'static str,
147 pub sha: &'static str,
149 pub full: &'static str,
153}
154
155impl Version {
156 pub fn for_testing() -> Self {
158 Self {
159 year: env!("CARGO_PKG_VERSION_MAJOR"),
160 month: env!("CARGO_PKG_VERSION_MINOR"),
161 patch: env!("CARGO_PKG_VERSION_PATCH"),
162 sha: "testing-no-sha",
163 full: const_str::concat!(
165 env!("CARGO_PKG_VERSION_MAJOR"),
166 ".",
167 env!("CARGO_PKG_VERSION_MINOR"),
168 ".",
169 env!("CARGO_PKG_VERSION_PATCH"),
170 "-testing-no-sha"
171 ),
172 }
173 }
174}
175
176#[GraphQLConfig]
177#[derive(clap::Args)]
178pub struct Ide {
179 #[arg(short, long, default_value_t = Ide::default().ide_title)]
181 pub(crate) ide_title: String,
182}
183
184#[GraphQLConfig]
185#[derive(Default)]
186pub struct Experiments {
187 #[cfg(test)]
190 test_flag: bool,
191}
192
193#[GraphQLConfig]
194pub struct InternalFeatureConfig {
195 pub(crate) query_limits_checker: bool,
196 pub(crate) directive_checker: bool,
197 pub(crate) feature_gate: bool,
198 pub(crate) logger: bool,
199 pub(crate) query_timeout: bool,
200 pub(crate) metrics: bool,
201 pub(crate) tracing: bool,
202 pub(crate) apollo_tracing: bool,
203 pub(crate) open_telemetry: bool,
204}
205
206#[GraphQLConfig]
207#[derive(clap::Args, Default)]
208pub struct TxExecFullNodeConfig {
209 #[arg(long)]
211 pub(crate) node_rpc_url: Option<String>,
212}
213
214#[Object]
216impl ServiceConfig {
217 async fn is_enabled(&self, feature: FunctionalGroup) -> bool {
219 !self.disabled_features.contains(&feature)
220 }
221
222 async fn available_versions(&self) -> Vec<String> {
224 self.versions.versions.clone()
225 }
226
227 async fn enabled_features(&self) -> Vec<FunctionalGroup> {
229 FunctionalGroup::all()
230 .iter()
231 .filter(|g| !self.disabled_features.contains(g))
232 .copied()
233 .collect()
234 }
235
236 pub async fn max_query_depth(&self) -> u32 {
238 self.limits.max_query_depth
239 }
240
241 pub async fn max_query_nodes(&self) -> u32 {
244 self.limits.max_query_nodes
245 }
246
247 pub async fn max_output_nodes(&self) -> u32 {
260 self.limits.max_output_nodes
261 }
262
263 async fn max_db_query_cost(&self) -> u32 {
267 self.limits.max_db_query_cost
268 }
269
270 async fn default_page_size(&self) -> u32 {
272 self.limits.default_page_size
273 }
274
275 async fn max_page_size(&self) -> u32 {
277 self.limits.max_page_size
278 }
279
280 async fn mutation_timeout_ms(&self) -> u32 {
287 self.limits.mutation_timeout_ms
288 }
289
290 async fn request_timeout_ms(&self) -> u32 {
293 self.limits.request_timeout_ms
294 }
295
296 async fn max_transaction_payload_size(&self) -> u32 {
306 self.limits.max_tx_payload_size
307 }
308
309 async fn max_query_payload_size(&self) -> u32 {
314 self.limits.max_query_payload_size
315 }
316
317 async fn max_type_argument_depth(&self) -> u32 {
320 self.limits.max_type_argument_depth
321 }
322
323 async fn max_type_argument_width(&self) -> u32 {
326 self.limits.max_type_argument_width
327 }
328
329 async fn max_type_nodes(&self) -> u32 {
332 self.limits.max_type_nodes
333 }
334
335 async fn max_move_value_depth(&self) -> u32 {
338 self.limits.max_move_value_depth
339 }
340
341 async fn max_transaction_ids(&self) -> u32 {
344 self.limits.max_transaction_ids
345 }
346
347 async fn max_scan_limit(&self) -> u32 {
349 self.limits.max_scan_limit
350 }
351}
352
353impl ConnectionConfig {
354 pub fn new(
355 port: Option<u16>,
356 host: Option<String>,
357 db_url: Option<String>,
358 db_pool_size: Option<u32>,
359 prom_host: Option<String>,
360 prom_port: Option<u16>,
361 skip_migration_consistency_check: Option<bool>,
362 ) -> Self {
363 let default = Self::default();
364 Self {
365 port: port.unwrap_or(default.port),
366 host: host.unwrap_or(default.host),
367 db_url: db_url.unwrap_or(default.db_url),
368 db_pool_size: db_pool_size.unwrap_or(default.db_pool_size),
369 prom_host: prom_host.unwrap_or(default.prom_host),
370 prom_port: prom_port.unwrap_or(default.prom_port),
371 skip_migration_consistency_check: skip_migration_consistency_check
372 .unwrap_or(default.skip_migration_consistency_check),
373 }
374 }
375
376 pub fn ci_integration_test_cfg() -> Self {
377 Self {
378 db_url: "postgres://postgres:postgrespw@localhost:5432/iota_graphql_rpc_e2e_tests"
379 .to_string(),
380 ..Default::default()
381 }
382 }
383
384 pub fn ci_integration_test_cfg_with_db_name(
385 db_name: String,
386 port: u16,
387 prom_port: u16,
388 ) -> Self {
389 Self {
390 db_url: format!("postgres://postgres:postgrespw@localhost:5432/{db_name}"),
391 port,
392 prom_port,
393 ..Default::default()
394 }
395 }
396
397 pub fn db_name(&self) -> String {
398 self.db_url.split('/').next_back().unwrap().to_string()
399 }
400
401 pub fn db_url(&self) -> String {
402 self.db_url.clone()
403 }
404
405 pub fn db_pool_size(&self) -> u32 {
406 self.db_pool_size
407 }
408
409 pub fn server_address(&self) -> String {
410 format!("{}:{}", self.host, self.port)
411 }
412}
413
414impl ServiceConfig {
415 pub fn read(contents: &str) -> Result<Self, toml::de::Error> {
416 toml::de::from_str::<Self>(contents)
417 }
418
419 pub fn test_defaults() -> Self {
420 Self {
421 background_tasks: BackgroundTasksConfig::test_defaults(),
422 ..Default::default()
423 }
424 }
425}
426
427impl Limits {
428 pub fn package_resolver_limits(&self) -> iota_package_resolver::Limits {
430 iota_package_resolver::Limits {
431 max_type_argument_depth: self.max_type_argument_depth as usize,
432 max_type_argument_width: self.max_type_argument_width as usize,
433 max_type_nodes: self.max_type_nodes as usize,
434 max_move_value_depth: self.max_move_value_depth as usize,
435 }
436 }
437}
438
439impl BackgroundTasksConfig {
440 pub fn test_defaults() -> Self {
441 Self {
442 watermark_update_ms: 100, }
444 }
445}
446
447impl Default for Versions {
448 fn default() -> Self {
449 Self {
450 versions: vec![format!(
451 "{}.{}",
452 env!("CARGO_PKG_VERSION_MAJOR"),
453 env!("CARGO_PKG_VERSION_MINOR")
454 )],
455 }
456 }
457}
458
459impl Default for Ide {
460 fn default() -> Self {
461 Self {
462 ide_title: "IOTA GraphQL IDE".to_string(),
463 }
464 }
465}
466
467impl Default for ConnectionConfig {
468 fn default() -> Self {
469 Self {
470 port: 8000,
471 host: "127.0.0.1".to_string(),
472 db_url: "postgres://postgres:postgrespw@localhost:5432/iota_indexer".to_string(),
473 db_pool_size: 10,
474 prom_host: "0.0.0.0".to_string(),
475 prom_port: 9184,
476 skip_migration_consistency_check: false,
477 }
478 }
479}
480
481impl Default for Limits {
482 fn default() -> Self {
483 Self {
486 max_query_depth: 20,
487 max_query_nodes: 300,
488 max_output_nodes: 100_000,
489 max_query_payload_size: 5_000,
490 max_db_query_cost: 20_000,
491 default_page_size: DEFAULT_PAGE_SIZE,
492 max_page_size: MAX_PAGE_SIZE,
493 mutation_timeout_ms: 74_000,
498 request_timeout_ms: 40_000,
499 max_type_argument_depth: 16,
502 max_type_argument_width: 32,
504 max_type_nodes: 256,
506 max_move_value_depth: 128,
508 max_transaction_ids: 1000,
511 max_scan_limit: 100_000_000,
512 max_tx_payload_size: (128u32 * 1024u32 * 4u32).div_ceil(3),
517 }
518 }
519}
520
521impl Default for InternalFeatureConfig {
522 fn default() -> Self {
523 Self {
524 query_limits_checker: true,
525 directive_checker: true,
526 feature_gate: true,
527 logger: true,
528 query_timeout: true,
529 metrics: true,
530 tracing: false,
531 apollo_tracing: false,
532 open_telemetry: false,
533 }
534 }
535}
536
537impl Default for BackgroundTasksConfig {
538 fn default() -> Self {
539 Self {
540 watermark_update_ms: 500,
541 }
542 }
543}
544
545impl Display for Version {
546 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
547 write!(f, "{}", self.full)
548 }
549}
550
551#[cfg(test)]
552mod tests {
553 use super::*;
554
555 #[test]
556 fn test_read_empty_service_config() {
557 let actual = ServiceConfig::read("").unwrap();
558 let expect = ServiceConfig::default();
559 assert_eq!(actual, expect);
560 }
561
562 #[test]
563 fn test_read_limits_in_service_config() {
564 let actual = ServiceConfig::read(
565 r#" [limits]
566 max-query-depth = 100
567 max-query-nodes = 300
568 max-output-nodes = 200000
569 max-tx-payload-size = 174763
570 max-query-payload-size = 2000
571 max-db-query-cost = 50
572 default-page-size = 20
573 max-page-size = 50
574 mutation-timeout-ms = 74000
575 request-timeout-ms = 27000
576 max-type-argument-depth = 32
577 max-type-argument-width = 64
578 max-type-nodes = 128
579 max-move-value-depth = 256
580 max-transaction-ids = 11
581 max-scan-limit = 50
582 "#,
583 )
584 .unwrap();
585
586 let expect = ServiceConfig {
587 limits: Limits {
588 max_query_depth: 100,
589 max_query_nodes: 300,
590 max_output_nodes: 200000,
591 max_tx_payload_size: 174763,
592 max_query_payload_size: 2000,
593 max_db_query_cost: 50,
594 default_page_size: 20,
595 max_page_size: 50,
596 mutation_timeout_ms: 74_000,
597 request_timeout_ms: 27_000,
598 max_type_argument_depth: 32,
599 max_type_argument_width: 64,
600 max_type_nodes: 128,
601 max_move_value_depth: 256,
602 max_transaction_ids: 11,
603 max_scan_limit: 50,
604 },
605 ..Default::default()
606 };
607
608 assert_eq!(actual, expect)
609 }
610
611 #[test]
612 fn test_read_enabled_features_in_service_config() {
613 let actual = ServiceConfig::read(
614 r#" disabled-features = [
615 "coins",
616 ]
617 "#,
618 )
619 .unwrap();
620
621 use FunctionalGroup as G;
622 let expect = ServiceConfig {
623 disabled_features: BTreeSet::from([G::Coins]),
624 ..Default::default()
625 };
626
627 assert_eq!(actual, expect)
628 }
629
630 #[test]
631 fn test_read_experiments_in_service_config() {
632 let actual = ServiceConfig::read(
633 r#" [experiments]
634 test-flag = true
635 "#,
636 )
637 .unwrap();
638
639 let expect = ServiceConfig {
640 experiments: Experiments { test_flag: true },
641 ..Default::default()
642 };
643
644 assert_eq!(actual, expect)
645 }
646
647 #[test]
648 fn test_read_everything_in_service_config() {
649 let actual = ServiceConfig::read(
650 r#" disabled-features = ["analytics"]
651
652 [limits]
653 max-query-depth = 42
654 max-query-nodes = 320
655 max-output-nodes = 200000
656 max-tx-payload-size = 181017
657 max-query-payload-size = 200
658 max-db-query-cost = 20
659 default-page-size = 10
660 max-page-size = 20
661 mutation-timeout-ms = 74000
662 request-timeout-ms = 30000
663 max-type-argument-depth = 32
664 max-type-argument-width = 64
665 max-type-nodes = 128
666 max-move-value-depth = 256
667 max-transaction-ids = 42
668 max-scan-limit = 420
669
670 [experiments]
671 test-flag = true
672 "#,
673 )
674 .unwrap();
675
676 let expect = ServiceConfig {
677 limits: Limits {
678 max_query_depth: 42,
679 max_query_nodes: 320,
680 max_output_nodes: 200000,
681 max_tx_payload_size: 181017,
682 max_query_payload_size: 200,
683 max_db_query_cost: 20,
684 default_page_size: 10,
685 max_page_size: 20,
686 mutation_timeout_ms: 74_000,
687 request_timeout_ms: 30_000,
688 max_type_argument_depth: 32,
689 max_type_argument_width: 64,
690 max_type_nodes: 128,
691 max_move_value_depth: 256,
692 max_transaction_ids: 42,
693 max_scan_limit: 420,
694 },
695 disabled_features: BTreeSet::from([FunctionalGroup::Analytics]),
696 experiments: Experiments { test_flag: true },
697 ..Default::default()
698 };
699
700 assert_eq!(actual, expect);
701 }
702
703 #[test]
704 fn test_read_partial_in_service_config() {
705 let actual = ServiceConfig::read(
706 r#" disabled-features = ["analytics"]
707
708 [limits]
709 max-query-depth = 42
710 max-query-nodes = 320
711 "#,
712 )
713 .unwrap();
714
715 let expect = ServiceConfig {
718 limits: Limits {
719 max_query_depth: 42,
720 max_query_nodes: 320,
721 ..Default::default()
722 },
723 disabled_features: BTreeSet::from([FunctionalGroup::Analytics]),
724 ..Default::default()
725 };
726
727 assert_eq!(actual, expect);
728 }
729}