1use std::{collections::BTreeSet, fmt::Display};
6
7use async_graphql::*;
8use iota_graphql_config::GraphQLConfig;
9use iota_names::config::IotaNamesConfig;
10use serde::{Deserialize, Serialize};
11use url::Url;
12
13use crate::functional_group::FunctionalGroup;
14
15pub(crate) const DEFAULT_PAGE_SIZE: u32 = 20;
16pub(crate) const MAX_PAGE_SIZE: u32 = 50;
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 pub historic_fallback: HistoricFallbackOptions,
28}
29
30#[GraphQLConfig]
35#[derive(clap::Args, Clone, Eq, PartialEq)]
36pub struct ConnectionConfig {
37 #[arg(short, long, default_value_t = ConnectionConfig::default().port)]
39 pub port: u16,
40 #[arg(long, default_value_t = ConnectionConfig::default().host)]
42 pub host: String,
43 #[arg(short, long, default_value_t = ConnectionConfig::default().db_url)]
45 pub db_url: String,
46 #[arg(long, default_value_t = ConnectionConfig::default().db_pool_size)]
48 pub db_pool_size: u32,
49 #[arg(long, default_value_t = ConnectionConfig::default().prom_host)]
51 pub prom_host: String,
52 #[arg(long, default_value_t = ConnectionConfig::default().prom_port)]
54 pub prom_port: u16,
55 #[arg(long, default_value_t = ConnectionConfig::default().skip_migration_consistency_check)]
58 pub skip_migration_consistency_check: bool,
59 #[arg(
64 long,
65 default_value_t = ConnectionConfig::default().max_available_range,
66 env = "MAX_AVAILABLE_RANGE",
67 )]
68 pub max_available_range: u64,
69}
70
71#[GraphQLConfig]
74#[derive(clap::Args, Debug, Clone)]
75pub struct HistoricFallbackOptions {
76 #[arg(
77 long,
78 help = "Experimental: REST KV store URL for historic fallback. Depends on the iota-rest-kv API which is still being finalized."
79 )]
80 pub fallback_kv_url: Option<Url>,
81
82 #[arg(
83 long,
84 default_value_t = HistoricFallbackOptions::DEFAULT_MULTI_FETCH_BATCH_SIZE,
85 env = "FALLBACK_KV_MULTI_FETCH_BATCH_SIZE",
86 help = "Experimental: Maximum number of keys per batch request to fallback KV store."
87 )]
88 pub fallback_kv_multi_fetch_batch_size: usize,
89
90 #[arg(
91 long,
92 default_value_t = HistoricFallbackOptions::DEFAULT_CONCURRENT_FETCHES,
93 env = "FALLBACK_KV_CONCURRENT_FETCHES",
94 help = "Experimental: Maximum number of concurrent batch requests to fallback KV store."
95 )]
96 pub fallback_kv_concurrent_fetches: usize,
97
98 #[arg(
99 long,
100 default_value_t = HistoricFallbackOptions::DEFAULT_CACHE_SIZE,
101 env = "FALLBACK_KV_CACHE_SIZE",
102 help = "Experimental: Cache size for historic fallback."
103 )]
104 pub fallback_kv_cache_size: u64,
105}
106
107impl HistoricFallbackOptions {
108 pub const DEFAULT_MULTI_FETCH_BATCH_SIZE: usize = 100;
109 pub const DEFAULT_CONCURRENT_FETCHES: usize = 10;
110 pub const DEFAULT_CACHE_SIZE: u64 = 100_000;
111}
112
113impl Default for HistoricFallbackOptions {
114 fn default() -> Self {
115 Self {
116 fallback_kv_url: None,
117 fallback_kv_multi_fetch_batch_size: Self::DEFAULT_MULTI_FETCH_BATCH_SIZE,
118 fallback_kv_concurrent_fetches: Self::DEFAULT_CONCURRENT_FETCHES,
119 fallback_kv_cache_size: Self::DEFAULT_CACHE_SIZE,
120 }
121 }
122}
123
124#[GraphQLConfig]
128#[derive(Default)]
129pub struct ServiceConfig {
130 pub versions: Versions,
131 pub limits: Limits,
132 pub disabled_features: BTreeSet<FunctionalGroup>,
133 pub experiments: Experiments,
134 pub iota_names: IotaNamesConfig,
135 pub background_tasks: BackgroundTasksConfig,
136}
137
138#[GraphQLConfig]
139pub struct Versions {
140 versions: Vec<String>,
141}
142
143#[GraphQLConfig]
144pub struct Limits {
145 pub max_query_depth: u32,
147 pub max_query_nodes: u32,
149 pub max_output_nodes: u32,
151 pub max_tx_payload_size: u32,
155 pub max_query_payload_size: u32,
158 pub max_db_query_cost: u32,
162 pub default_page_size: u32,
165 pub max_page_size: u32,
167 pub mutation_timeout_ms: u32,
172 pub request_timeout_ms: u32,
176 pub max_type_argument_depth: u32,
179 pub max_type_argument_width: u32,
181 pub max_type_nodes: u32,
183 pub max_move_value_depth: u32,
185 pub max_transaction_ids: u32,
188 pub max_scan_limit: u32,
190}
191
192#[GraphQLConfig]
193#[derive(Copy)]
194pub struct BackgroundTasksConfig {
195 pub watermark_update_ms: u64,
198}
199
200#[derive(Copy, Clone, Debug)]
204pub struct Version {
205 pub year: &'static str,
207 pub month: &'static str,
209 pub patch: &'static str,
212 pub sha: &'static str,
214 pub full: &'static str,
218}
219
220impl Version {
221 pub fn for_testing() -> Self {
223 Self {
224 year: env!("CARGO_PKG_VERSION_MAJOR"),
225 month: env!("CARGO_PKG_VERSION_MINOR"),
226 patch: env!("CARGO_PKG_VERSION_PATCH"),
227 sha: "testing-no-sha",
228 full: const_str::concat!(
230 env!("CARGO_PKG_VERSION_MAJOR"),
231 ".",
232 env!("CARGO_PKG_VERSION_MINOR"),
233 ".",
234 env!("CARGO_PKG_VERSION_PATCH"),
235 "-testing-no-sha"
236 ),
237 }
238 }
239}
240
241#[GraphQLConfig]
242#[derive(clap::Args)]
243pub struct Ide {
244 #[arg(short, long, default_value_t = Ide::default().ide_title)]
246 pub(crate) ide_title: String,
247}
248
249#[GraphQLConfig]
250#[derive(Default)]
251pub struct Experiments {
252 #[cfg(test)]
255 test_flag: bool,
256}
257
258#[GraphQLConfig]
259pub struct InternalFeatureConfig {
260 pub(crate) query_limits_checker: bool,
261 pub(crate) directive_checker: bool,
262 pub(crate) feature_gate: bool,
263 pub(crate) logger: bool,
264 pub(crate) query_timeout: bool,
265 pub(crate) metrics: bool,
266 pub(crate) tracing: bool,
267 pub(crate) apollo_tracing: bool,
268 pub(crate) open_telemetry: bool,
269}
270
271#[GraphQLConfig]
272#[derive(clap::Args, Default)]
273pub struct TxExecFullNodeConfig {
274 #[arg(long)]
276 pub(crate) node_rpc_url: Option<String>,
277}
278
279#[Object]
281impl ServiceConfig {
282 async fn is_enabled(&self, feature: FunctionalGroup) -> bool {
284 !self.disabled_features.contains(&feature)
285 }
286
287 async fn available_versions(&self) -> Vec<String> {
289 self.versions.versions.clone()
290 }
291
292 async fn enabled_features(&self) -> Vec<FunctionalGroup> {
294 FunctionalGroup::all()
295 .iter()
296 .filter(|g| !self.disabled_features.contains(g))
297 .copied()
298 .collect()
299 }
300
301 pub async fn max_query_depth(&self) -> u32 {
303 self.limits.max_query_depth
304 }
305
306 pub async fn max_query_nodes(&self) -> u32 {
309 self.limits.max_query_nodes
310 }
311
312 pub async fn max_output_nodes(&self) -> u32 {
325 self.limits.max_output_nodes
326 }
327
328 async fn max_db_query_cost(&self) -> u32 {
332 self.limits.max_db_query_cost
333 }
334
335 async fn default_page_size(&self) -> u32 {
337 self.limits.default_page_size
338 }
339
340 async fn max_page_size(&self) -> u32 {
342 self.limits.max_page_size
343 }
344
345 async fn mutation_timeout_ms(&self) -> u32 {
352 self.limits.mutation_timeout_ms
353 }
354
355 async fn request_timeout_ms(&self) -> u32 {
358 self.limits.request_timeout_ms
359 }
360
361 async fn max_transaction_payload_size(&self) -> u32 {
371 self.limits.max_tx_payload_size
372 }
373
374 async fn max_query_payload_size(&self) -> u32 {
379 self.limits.max_query_payload_size
380 }
381
382 async fn max_type_argument_depth(&self) -> u32 {
385 self.limits.max_type_argument_depth
386 }
387
388 async fn max_type_argument_width(&self) -> u32 {
391 self.limits.max_type_argument_width
392 }
393
394 async fn max_type_nodes(&self) -> u32 {
397 self.limits.max_type_nodes
398 }
399
400 async fn max_move_value_depth(&self) -> u32 {
403 self.limits.max_move_value_depth
404 }
405
406 async fn max_transaction_ids(&self) -> u32 {
409 self.limits.max_transaction_ids
410 }
411
412 async fn max_scan_limit(&self) -> u32 {
414 self.limits.max_scan_limit
415 }
416}
417
418impl ConnectionConfig {
419 pub fn new(
420 port: Option<u16>,
421 host: Option<String>,
422 db_url: Option<String>,
423 db_pool_size: Option<u32>,
424 prom_host: Option<String>,
425 prom_port: Option<u16>,
426 skip_migration_consistency_check: Option<bool>,
427 max_available_range: Option<u64>,
428 ) -> Self {
429 let default = Self::default();
430 Self {
431 port: port.unwrap_or(default.port),
432 host: host.unwrap_or(default.host),
433 db_url: db_url.unwrap_or(default.db_url),
434 db_pool_size: db_pool_size.unwrap_or(default.db_pool_size),
435 prom_host: prom_host.unwrap_or(default.prom_host),
436 prom_port: prom_port.unwrap_or(default.prom_port),
437 skip_migration_consistency_check: skip_migration_consistency_check
438 .unwrap_or(default.skip_migration_consistency_check),
439 max_available_range: max_available_range.unwrap_or(default.max_available_range),
440 }
441 }
442
443 pub fn ci_integration_test_cfg() -> Self {
444 Self {
445 db_url: "postgres://postgres:postgrespw@localhost:5432/iota_graphql_rpc_e2e_tests"
446 .to_string(),
447 ..Default::default()
448 }
449 }
450
451 pub fn ci_integration_test_cfg_with_db_name(
452 db_name: String,
453 port: u16,
454 prom_port: u16,
455 ) -> Self {
456 Self {
457 db_url: format!("postgres://postgres:postgrespw@localhost:5432/{db_name}"),
458 port,
459 prom_port,
460 ..Default::default()
461 }
462 }
463
464 pub fn db_name(&self) -> String {
465 self.db_url.split('/').next_back().unwrap().to_string()
466 }
467
468 pub fn db_url(&self) -> String {
469 self.db_url.clone()
470 }
471
472 pub fn db_pool_size(&self) -> u32 {
473 self.db_pool_size
474 }
475
476 pub fn server_address(&self) -> String {
477 format!("{}:{}", self.host, self.port)
478 }
479}
480
481impl ServiceConfig {
482 pub fn read(contents: &str) -> Result<Self, toml::de::Error> {
483 toml::de::from_str::<Self>(contents)
484 }
485
486 pub fn test_defaults() -> Self {
487 Self {
488 background_tasks: BackgroundTasksConfig::test_defaults(),
489 ..Default::default()
490 }
491 }
492}
493
494impl Limits {
495 pub fn package_resolver_limits(&self) -> iota_package_resolver::Limits {
497 iota_package_resolver::Limits {
498 max_type_argument_depth: self.max_type_argument_depth as usize,
499 max_type_argument_width: self.max_type_argument_width as usize,
500 max_type_nodes: self.max_type_nodes as usize,
501 max_move_value_depth: self.max_move_value_depth as usize,
502 }
503 }
504}
505
506impl BackgroundTasksConfig {
507 pub fn test_defaults() -> Self {
508 Self {
509 watermark_update_ms: 100, }
511 }
512}
513
514impl Default for Versions {
515 fn default() -> Self {
516 Self {
517 versions: vec![format!(
518 "{}.{}",
519 env!("CARGO_PKG_VERSION_MAJOR"),
520 env!("CARGO_PKG_VERSION_MINOR")
521 )],
522 }
523 }
524}
525
526impl Default for Ide {
527 fn default() -> Self {
528 Self {
529 ide_title: "IOTA GraphQL IDE".to_string(),
530 }
531 }
532}
533
534impl Default for ConnectionConfig {
535 fn default() -> Self {
536 Self {
537 port: 8000,
538 host: "127.0.0.1".to_string(),
539 db_url: "postgres://postgres:postgrespw@localhost:5432/iota_indexer".to_string(),
540 db_pool_size: 10,
541 prom_host: "0.0.0.0".to_string(),
542 prom_port: 9184,
543 skip_migration_consistency_check: false,
544 max_available_range: 9_000,
545 }
546 }
547}
548
549impl Default for Limits {
550 fn default() -> Self {
551 Self {
554 max_query_depth: 20,
555 max_query_nodes: 300,
556 max_output_nodes: 100_000,
557 max_query_payload_size: 5_000,
558 max_db_query_cost: 20_000,
559 default_page_size: DEFAULT_PAGE_SIZE,
560 max_page_size: MAX_PAGE_SIZE,
561 mutation_timeout_ms: 74_000,
566 request_timeout_ms: 40_000,
567 max_type_argument_depth: 16,
570 max_type_argument_width: 32,
572 max_type_nodes: 256,
574 max_move_value_depth: 128,
576 max_transaction_ids: 1000,
579 max_scan_limit: 1_000_000,
580 max_tx_payload_size: (128u32 * 1024u32 * 4u32).div_ceil(3),
585 }
586 }
587}
588
589impl Default for InternalFeatureConfig {
590 fn default() -> Self {
591 Self {
592 query_limits_checker: true,
593 directive_checker: true,
594 feature_gate: true,
595 logger: true,
596 query_timeout: true,
597 metrics: true,
598 tracing: false,
599 apollo_tracing: false,
600 open_telemetry: false,
601 }
602 }
603}
604
605impl Default for BackgroundTasksConfig {
606 fn default() -> Self {
607 Self {
608 watermark_update_ms: 500,
609 }
610 }
611}
612
613impl Display for Version {
614 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
615 write!(f, "{}", self.full)
616 }
617}
618
619#[cfg(test)]
620mod tests {
621 use super::*;
622
623 #[test]
624 fn test_read_empty_service_config() {
625 let actual = ServiceConfig::read("").unwrap();
626 let expect = ServiceConfig::default();
627 assert_eq!(actual, expect);
628 }
629
630 #[test]
631 fn test_read_limits_in_service_config() {
632 let actual = ServiceConfig::read(
633 r#" [limits]
634 max-query-depth = 100
635 max-query-nodes = 300
636 max-output-nodes = 200000
637 max-tx-payload-size = 174763
638 max-query-payload-size = 2000
639 max-db-query-cost = 50
640 default-page-size = 20
641 max-page-size = 50
642 mutation-timeout-ms = 74000
643 request-timeout-ms = 27000
644 max-type-argument-depth = 32
645 max-type-argument-width = 64
646 max-type-nodes = 128
647 max-move-value-depth = 256
648 max-transaction-ids = 11
649 max-scan-limit = 50
650 "#,
651 )
652 .unwrap();
653
654 let expect = ServiceConfig {
655 limits: Limits {
656 max_query_depth: 100,
657 max_query_nodes: 300,
658 max_output_nodes: 200000,
659 max_tx_payload_size: 174763,
660 max_query_payload_size: 2000,
661 max_db_query_cost: 50,
662 default_page_size: 20,
663 max_page_size: 50,
664 mutation_timeout_ms: 74_000,
665 request_timeout_ms: 27_000,
666 max_type_argument_depth: 32,
667 max_type_argument_width: 64,
668 max_type_nodes: 128,
669 max_move_value_depth: 256,
670 max_transaction_ids: 11,
671 max_scan_limit: 50,
672 },
673 ..Default::default()
674 };
675
676 assert_eq!(actual, expect)
677 }
678
679 #[test]
680 fn test_read_enabled_features_in_service_config() {
681 let actual = ServiceConfig::read(
682 r#" disabled-features = [
683 "coins",
684 ]
685 "#,
686 )
687 .unwrap();
688
689 use FunctionalGroup as G;
690 let expect = ServiceConfig {
691 disabled_features: BTreeSet::from([G::Coins]),
692 ..Default::default()
693 };
694
695 assert_eq!(actual, expect)
696 }
697
698 #[test]
699 fn test_read_experiments_in_service_config() {
700 let actual = ServiceConfig::read(
701 r#" [experiments]
702 test-flag = true
703 "#,
704 )
705 .unwrap();
706
707 let expect = ServiceConfig {
708 experiments: Experiments { test_flag: true },
709 ..Default::default()
710 };
711
712 assert_eq!(actual, expect)
713 }
714
715 #[test]
716 fn test_read_everything_in_service_config() {
717 let actual = ServiceConfig::read(
718 r#" disabled-features = ["analytics"]
719
720 [limits]
721 max-query-depth = 42
722 max-query-nodes = 320
723 max-output-nodes = 200000
724 max-tx-payload-size = 181017
725 max-query-payload-size = 200
726 max-db-query-cost = 20
727 default-page-size = 10
728 max-page-size = 20
729 mutation-timeout-ms = 74000
730 request-timeout-ms = 30000
731 max-type-argument-depth = 32
732 max-type-argument-width = 64
733 max-type-nodes = 128
734 max-move-value-depth = 256
735 max-transaction-ids = 42
736 max-scan-limit = 420
737
738 [experiments]
739 test-flag = true
740 "#,
741 )
742 .unwrap();
743
744 let expect = ServiceConfig {
745 limits: Limits {
746 max_query_depth: 42,
747 max_query_nodes: 320,
748 max_output_nodes: 200000,
749 max_tx_payload_size: 181017,
750 max_query_payload_size: 200,
751 max_db_query_cost: 20,
752 default_page_size: 10,
753 max_page_size: 20,
754 mutation_timeout_ms: 74_000,
755 request_timeout_ms: 30_000,
756 max_type_argument_depth: 32,
757 max_type_argument_width: 64,
758 max_type_nodes: 128,
759 max_move_value_depth: 256,
760 max_transaction_ids: 42,
761 max_scan_limit: 420,
762 },
763 disabled_features: BTreeSet::from([FunctionalGroup::Analytics]),
764 experiments: Experiments { test_flag: true },
765 ..Default::default()
766 };
767
768 assert_eq!(actual, expect);
769 }
770
771 #[test]
772 fn test_read_partial_in_service_config() {
773 let actual = ServiceConfig::read(
774 r#" disabled-features = ["analytics"]
775
776 [limits]
777 max-query-depth = 42
778 max-query-nodes = 320
779 "#,
780 )
781 .unwrap();
782
783 let expect = ServiceConfig {
786 limits: Limits {
787 max_query_depth: 42,
788 max_query_nodes: 320,
789 ..Default::default()
790 },
791 disabled_features: BTreeSet::from([FunctionalGroup::Analytics]),
792 ..Default::default()
793 };
794
795 assert_eq!(actual, expect);
796 }
797}