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 #[arg(
62 long,
63 default_value_t = ConnectionConfig::default().max_available_range,
64 env = "MAX_AVAILABLE_RANGE",
65 )]
66 pub max_available_range: u64,
67}
68
69#[GraphQLConfig]
73#[derive(Default)]
74pub struct ServiceConfig {
75 pub versions: Versions,
76 pub limits: Limits,
77 pub disabled_features: BTreeSet<FunctionalGroup>,
78 pub experiments: Experiments,
79 pub iota_names: IotaNamesConfig,
80 pub background_tasks: BackgroundTasksConfig,
81}
82
83#[GraphQLConfig]
84pub struct Versions {
85 versions: Vec<String>,
86}
87
88#[GraphQLConfig]
89pub struct Limits {
90 pub max_query_depth: u32,
92 pub max_query_nodes: u32,
94 pub max_output_nodes: u32,
96 pub max_tx_payload_size: u32,
100 pub max_query_payload_size: u32,
103 pub max_db_query_cost: u32,
107 pub default_page_size: u32,
110 pub max_page_size: u32,
112 pub mutation_timeout_ms: u32,
117 pub request_timeout_ms: u32,
121 pub max_type_argument_depth: u32,
124 pub max_type_argument_width: u32,
126 pub max_type_nodes: u32,
128 pub max_move_value_depth: u32,
130 pub max_transaction_ids: u32,
133 pub max_scan_limit: u32,
135}
136
137#[GraphQLConfig]
138#[derive(Copy)]
139pub struct BackgroundTasksConfig {
140 pub watermark_update_ms: u64,
143}
144
145#[derive(Copy, Clone, Debug)]
149pub struct Version {
150 pub year: &'static str,
152 pub month: &'static str,
154 pub patch: &'static str,
157 pub sha: &'static str,
159 pub full: &'static str,
163}
164
165impl Version {
166 pub fn for_testing() -> Self {
168 Self {
169 year: env!("CARGO_PKG_VERSION_MAJOR"),
170 month: env!("CARGO_PKG_VERSION_MINOR"),
171 patch: env!("CARGO_PKG_VERSION_PATCH"),
172 sha: "testing-no-sha",
173 full: const_str::concat!(
175 env!("CARGO_PKG_VERSION_MAJOR"),
176 ".",
177 env!("CARGO_PKG_VERSION_MINOR"),
178 ".",
179 env!("CARGO_PKG_VERSION_PATCH"),
180 "-testing-no-sha"
181 ),
182 }
183 }
184}
185
186#[GraphQLConfig]
187#[derive(clap::Args)]
188pub struct Ide {
189 #[arg(short, long, default_value_t = Ide::default().ide_title)]
191 pub(crate) ide_title: String,
192}
193
194#[GraphQLConfig]
195#[derive(Default)]
196pub struct Experiments {
197 #[cfg(test)]
200 test_flag: bool,
201}
202
203#[GraphQLConfig]
204pub struct InternalFeatureConfig {
205 pub(crate) query_limits_checker: bool,
206 pub(crate) directive_checker: bool,
207 pub(crate) feature_gate: bool,
208 pub(crate) logger: bool,
209 pub(crate) query_timeout: bool,
210 pub(crate) metrics: bool,
211 pub(crate) tracing: bool,
212 pub(crate) apollo_tracing: bool,
213 pub(crate) open_telemetry: bool,
214}
215
216#[GraphQLConfig]
217#[derive(clap::Args, Default)]
218pub struct TxExecFullNodeConfig {
219 #[arg(long)]
221 pub(crate) node_rpc_url: Option<String>,
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 max_available_range: Option<u64>,
373 ) -> Self {
374 let default = Self::default();
375 Self {
376 port: port.unwrap_or(default.port),
377 host: host.unwrap_or(default.host),
378 db_url: db_url.unwrap_or(default.db_url),
379 db_pool_size: db_pool_size.unwrap_or(default.db_pool_size),
380 prom_host: prom_host.unwrap_or(default.prom_host),
381 prom_port: prom_port.unwrap_or(default.prom_port),
382 skip_migration_consistency_check: skip_migration_consistency_check
383 .unwrap_or(default.skip_migration_consistency_check),
384 max_available_range: max_available_range.unwrap_or(default.max_available_range),
385 }
386 }
387
388 pub fn ci_integration_test_cfg() -> Self {
389 Self {
390 db_url: "postgres://postgres:postgrespw@localhost:5432/iota_graphql_rpc_e2e_tests"
391 .to_string(),
392 ..Default::default()
393 }
394 }
395
396 pub fn ci_integration_test_cfg_with_db_name(
397 db_name: String,
398 port: u16,
399 prom_port: u16,
400 ) -> Self {
401 Self {
402 db_url: format!("postgres://postgres:postgrespw@localhost:5432/{db_name}"),
403 port,
404 prom_port,
405 ..Default::default()
406 }
407 }
408
409 pub fn db_name(&self) -> String {
410 self.db_url.split('/').next_back().unwrap().to_string()
411 }
412
413 pub fn db_url(&self) -> String {
414 self.db_url.clone()
415 }
416
417 pub fn db_pool_size(&self) -> u32 {
418 self.db_pool_size
419 }
420
421 pub fn server_address(&self) -> String {
422 format!("{}:{}", self.host, self.port)
423 }
424}
425
426impl ServiceConfig {
427 pub fn read(contents: &str) -> Result<Self, toml::de::Error> {
428 toml::de::from_str::<Self>(contents)
429 }
430
431 pub fn test_defaults() -> Self {
432 Self {
433 background_tasks: BackgroundTasksConfig::test_defaults(),
434 ..Default::default()
435 }
436 }
437}
438
439impl Limits {
440 pub fn package_resolver_limits(&self) -> iota_package_resolver::Limits {
442 iota_package_resolver::Limits {
443 max_type_argument_depth: self.max_type_argument_depth as usize,
444 max_type_argument_width: self.max_type_argument_width as usize,
445 max_type_nodes: self.max_type_nodes as usize,
446 max_move_value_depth: self.max_move_value_depth as usize,
447 }
448 }
449}
450
451impl BackgroundTasksConfig {
452 pub fn test_defaults() -> Self {
453 Self {
454 watermark_update_ms: 100, }
456 }
457}
458
459impl Default for Versions {
460 fn default() -> Self {
461 Self {
462 versions: vec![format!(
463 "{}.{}",
464 env!("CARGO_PKG_VERSION_MAJOR"),
465 env!("CARGO_PKG_VERSION_MINOR")
466 )],
467 }
468 }
469}
470
471impl Default for Ide {
472 fn default() -> Self {
473 Self {
474 ide_title: "IOTA GraphQL IDE".to_string(),
475 }
476 }
477}
478
479impl Default for ConnectionConfig {
480 fn default() -> Self {
481 Self {
482 port: 8000,
483 host: "127.0.0.1".to_string(),
484 db_url: "postgres://postgres:postgrespw@localhost:5432/iota_indexer".to_string(),
485 db_pool_size: 10,
486 prom_host: "0.0.0.0".to_string(),
487 prom_port: 9184,
488 skip_migration_consistency_check: false,
489 max_available_range: 9_000,
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}