1use std::str::FromStr;
4
5use crate::functional_group::FunctionalGroup;
6use async_graphql::*;
7use fastcrypto_zkp::bn254::zk_login_api::ZkLoginEnv;
8use move_core_types::ident_str;
9use move_core_types::identifier::IdentStr;
10use serde::{Deserialize, Serialize};
11use std::{collections::BTreeSet, fmt::Display, time::Duration};
12use sui_default_config::DefaultConfig;
13use sui_name_service::NameServiceConfig;
14use sui_types::base_types::{ObjectID, SuiAddress};
15
16pub(crate) const RPC_TIMEOUT_ERR_SLEEP_RETRY_PERIOD: Duration = Duration::from_millis(30_000);
17pub(crate) const MAX_CONCURRENT_REQUESTS: usize = 1_000;
18
19pub(crate) const MOVE_REGISTRY_MODULE: &IdentStr = ident_str!("name");
21pub(crate) const MOVE_REGISTRY_TYPE: &IdentStr = ident_str!("Name");
22const MOVE_REGISTRY_PACKAGE: &str =
23 "0x62c1f5b1cb9e3bfc3dd1f73c95066487b662048a6358eabdbf67f6cdeca6db4b";
24const MOVE_REGISTRY_TABLE_ID: &str =
25 "0xe8417c530cde59eddf6dfb760e8a0e3e2c6f17c69ddaab5a73dd6a6e65fc463b";
26const DEFAULT_PAGE_LIMIT: u16 = 50;
27
28#[DefaultConfig]
30#[derive(Clone, Default, Debug)]
31pub struct ServerConfig {
32 pub service: ServiceConfig,
33 pub connection: ConnectionConfig,
34 pub internal_features: InternalFeatureConfig,
35 pub tx_exec_full_node: TxExecFullNodeConfig,
36 pub ide: Ide,
37}
38
39#[DefaultConfig]
43#[derive(clap::Args, Clone, Eq, PartialEq, Debug)]
44pub struct ConnectionConfig {
45 #[clap(short, long, default_value_t = ConnectionConfig::default().port)]
47 pub port: u16,
48 #[clap(long, default_value_t = ConnectionConfig::default().host)]
50 pub host: String,
51 #[clap(short, long, default_value_t = ConnectionConfig::default().db_url)]
53 pub db_url: String,
54 #[clap(long, default_value_t = ConnectionConfig::default().db_pool_size)]
56 pub db_pool_size: u32,
57 #[clap(long, default_value_t = ConnectionConfig::default().prom_host)]
59 pub prom_host: String,
60 #[clap(long, default_value_t = ConnectionConfig::default().prom_port)]
62 pub prom_port: u16,
63 #[clap(long, default_value_t = ConnectionConfig::default().skip_migration_consistency_check)]
66 pub skip_migration_consistency_check: bool,
67}
68
69#[DefaultConfig]
73#[derive(Clone, Default, Eq, PartialEq, Debug)]
74pub struct ServiceConfig {
75 pub limits: Limits,
76 pub disabled_features: BTreeSet<FunctionalGroup>,
77 pub experiments: Experiments,
78 pub name_service: NameServiceConfig,
79 pub background_tasks: BackgroundTasksConfig,
80 pub zklogin: ZkLoginConfig,
81 pub move_registry: MoveRegistryConfig,
82}
83
84#[DefaultConfig]
85#[derive(Clone, Eq, PartialEq, Debug)]
86pub struct Limits {
87 pub max_query_depth: u32,
89 pub max_query_nodes: u32,
91 pub max_output_nodes: u32,
93 pub max_tx_payload_size: u32,
97 pub max_query_payload_size: u32,
100 pub max_db_query_cost: u32,
103 pub default_page_size: u32,
105 pub max_page_size: u32,
107 pub mutation_timeout_ms: u32,
111 pub request_timeout_ms: u32,
114 pub max_type_argument_depth: u32,
117 pub max_type_argument_width: u32,
119 pub max_type_nodes: u32,
121 pub max_move_value_depth: u32,
123 pub max_transaction_ids: u32,
125 pub max_multi_get_objects_keys: u32,
127 pub max_scan_limit: u32,
129}
130
131#[DefaultConfig]
132#[derive(Copy, Clone, Eq, PartialEq, Debug)]
133pub struct BackgroundTasksConfig {
134 pub watermark_update_ms: u64,
137}
138
139#[DefaultConfig]
140#[derive(Clone, Eq, PartialEq, Debug)]
141pub struct MoveRegistryConfig {
142 pub(crate) external_api_url: Option<String>,
143 pub(crate) resolution_type: ResolutionType,
144 pub(crate) page_limit: u16,
145 pub(crate) package_address: SuiAddress,
146 pub(crate) registry_id: ObjectID,
147}
148
149#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)]
150pub(crate) enum ResolutionType {
151 Internal,
152 External,
153}
154
155#[derive(Copy, Clone, Debug)]
159pub struct Version {
160 pub major: &'static str,
162 pub minor: &'static str,
164 pub patch: &'static str,
166 pub sha: &'static str,
168 pub full: &'static str,
173}
174
175impl Version {
176 pub fn for_testing() -> Self {
178 Self {
179 major: "42",
180 minor: "43",
181 patch: "44",
182 sha: "testing-no-sha",
183 full: "42.43.44-testing-no-sha",
185 }
186 }
187}
188
189#[DefaultConfig]
190#[derive(clap::Args, Clone, Debug)]
191pub struct Ide {
192 #[clap(short, long, default_value_t = Ide::default().ide_title)]
194 pub ide_title: String,
195}
196
197#[DefaultConfig]
198#[derive(Clone, Default, Eq, PartialEq, Debug)]
199pub struct Experiments {
200 #[cfg(test)]
203 test_flag: bool,
204}
205
206#[DefaultConfig]
207#[derive(Clone, Debug)]
208pub struct InternalFeatureConfig {
209 pub(crate) query_limits_checker: bool,
210 pub(crate) directive_checker: bool,
211 pub(crate) feature_gate: bool,
212 pub(crate) logger: bool,
213 pub(crate) query_timeout: bool,
214 pub(crate) metrics: bool,
215 pub(crate) tracing: bool,
216 pub(crate) apollo_tracing: bool,
217 pub(crate) open_telemetry: bool,
218}
219
220#[DefaultConfig]
221#[derive(clap::Args, Clone, Default, Debug)]
222pub struct TxExecFullNodeConfig {
223 #[clap(long)]
225 pub(crate) node_rpc_url: Option<String>,
226}
227
228#[DefaultConfig]
229#[derive(Clone, Default, Eq, PartialEq, Debug)]
230pub struct ZkLoginConfig {
231 pub env: ZkLoginEnv,
232}
233
234#[Object]
236impl ServiceConfig {
237 async fn is_enabled(&self, feature: FunctionalGroup) -> bool {
239 !self.disabled_features.contains(&feature)
240 }
241
242 async fn enabled_features(&self) -> Vec<FunctionalGroup> {
244 FunctionalGroup::all()
245 .iter()
246 .filter(|g| !self.disabled_features.contains(g))
247 .copied()
248 .collect()
249 }
250
251 pub async fn max_query_depth(&self) -> u32 {
253 self.limits.max_query_depth
254 }
255
256 pub async fn max_query_nodes(&self) -> u32 {
258 self.limits.max_query_nodes
259 }
260
261 pub async fn max_output_nodes(&self) -> u32 {
272 self.limits.max_output_nodes
273 }
274
275 async fn max_db_query_cost(&self) -> u32 {
278 self.limits.max_db_query_cost
279 }
280
281 async fn default_page_size(&self) -> u32 {
283 self.limits.default_page_size
284 }
285
286 async fn max_page_size(&self) -> u32 {
288 self.limits.max_page_size
289 }
290
291 async fn mutation_timeout_ms(&self) -> u32 {
296 self.limits.mutation_timeout_ms
297 }
298
299 async fn request_timeout_ms(&self) -> u32 {
301 self.limits.request_timeout_ms
302 }
303
304 async fn max_transaction_payload_size(&self) -> u32 {
310 self.limits.max_tx_payload_size
311 }
312
313 async fn max_query_payload_size(&self) -> u32 {
318 self.limits.max_query_payload_size
319 }
320
321 async fn max_type_argument_depth(&self) -> u32 {
323 self.limits.max_type_argument_depth
324 }
325
326 async fn max_type_argument_width(&self) -> u32 {
329 self.limits.max_type_argument_width
330 }
331
332 async fn max_type_nodes(&self) -> u32 {
335 self.limits.max_type_nodes
336 }
337
338 async fn max_move_value_depth(&self) -> u32 {
340 self.limits.max_move_value_depth
341 }
342
343 async fn max_transaction_ids(&self) -> u32 {
345 self.limits.max_transaction_ids
346 }
347
348 async fn max_multi_get_objects_keys(&self) -> u32 {
350 self.limits.max_multi_get_objects_keys
351 }
352
353 async fn max_scan_limit(&self) -> u32 {
355 self.limits.max_scan_limit
356 }
357}
358
359impl TxExecFullNodeConfig {
360 pub fn new(node_rpc_url: Option<String>) -> Self {
361 Self { node_rpc_url }
362 }
363}
364
365impl ConnectionConfig {
366 pub fn db_name(&self) -> String {
367 self.db_url.split('/').next_back().unwrap().to_string()
368 }
369
370 pub fn db_url(&self) -> String {
371 self.db_url.clone()
372 }
373
374 pub fn db_pool_size(&self) -> u32 {
375 self.db_pool_size
376 }
377
378 pub fn server_address(&self) -> String {
379 format!("{}:{}", self.host, self.port)
380 }
381
382 pub fn host(&self) -> String {
383 self.host.clone()
384 }
385
386 pub fn port(&self) -> u16 {
387 self.port
388 }
389}
390
391impl ServiceConfig {
392 pub fn read(contents: &str) -> Result<Self, toml::de::Error> {
393 toml::de::from_str::<Self>(contents)
394 }
395
396 pub fn test_defaults() -> Self {
397 Self {
398 background_tasks: BackgroundTasksConfig::test_defaults(),
399 zklogin: ZkLoginConfig {
400 env: ZkLoginEnv::Test,
401 },
402 ..Default::default()
403 }
404 }
405
406 pub fn move_registry_test_defaults(
407 external: bool,
408 endpoint: Option<String>,
409 pkg_address: Option<SuiAddress>,
410 object_id: Option<ObjectID>,
411 page_limit: Option<u16>,
412 ) -> Self {
413 Self {
414 move_registry: MoveRegistryConfig {
415 resolution_type: if external {
416 ResolutionType::External
417 } else {
418 ResolutionType::Internal
419 },
420 external_api_url: endpoint,
421 package_address: pkg_address.unwrap_or_default(),
422 registry_id: object_id.unwrap_or(ObjectID::random()),
423 page_limit: page_limit.unwrap_or(50),
424 },
425 ..Self::test_defaults()
426 }
427 }
428}
429
430impl Limits {
431 pub fn package_resolver_limits(&self) -> sui_package_resolver::Limits {
433 sui_package_resolver::Limits {
434 max_type_argument_depth: self.max_type_argument_depth as usize,
435 max_type_argument_width: self.max_type_argument_width as usize,
436 max_type_nodes: self.max_type_nodes as usize,
437 max_move_value_depth: self.max_move_value_depth as usize,
438 }
439 }
440}
441
442impl BackgroundTasksConfig {
443 pub fn test_defaults() -> Self {
444 Self {
445 watermark_update_ms: 100, }
447 }
448}
449
450impl MoveRegistryConfig {
451 pub(crate) fn new(
452 resolution_type: ResolutionType,
453 external_api_url: Option<String>,
454 page_limit: u16,
455 package_address: SuiAddress,
456 registry_id: ObjectID,
457 ) -> Self {
458 Self {
459 resolution_type,
460 external_api_url,
461 page_limit,
462 package_address,
463 registry_id,
464 }
465 }
466}
467
468impl Default for Ide {
469 fn default() -> Self {
470 Self {
471 ide_title: "Sui GraphQL IDE".to_string(),
472 }
473 }
474}
475
476impl Default for ConnectionConfig {
477 fn default() -> Self {
478 Self {
479 port: 8000,
480 host: "127.0.0.1".to_string(),
481 db_url: "postgres://postgres:postgrespw@localhost:5432/sui_indexer".to_string(),
482 db_pool_size: 10,
483 prom_host: "0.0.0.0".to_string(),
484 prom_port: 9184,
485 skip_migration_consistency_check: false,
486 }
487 }
488}
489
490impl Default for Limits {
491 fn default() -> Self {
492 Self {
495 max_query_depth: 20,
496 max_query_nodes: 300,
497 max_output_nodes: 100_000,
498 max_query_payload_size: 5_000,
499 max_db_query_cost: 20_000,
500 default_page_size: 20,
501 max_page_size: 50,
502 mutation_timeout_ms: 74_000,
507 request_timeout_ms: 40_000,
508 max_type_argument_depth: 16,
511 max_type_argument_width: 32,
513 max_type_nodes: 256,
515 max_move_value_depth: 128,
517 max_transaction_ids: 1000,
520 max_multi_get_objects_keys: 500,
521 max_scan_limit: 100_000_000,
522 max_tx_payload_size: (128u32 * 1024u32 * 4u32).div_ceil(3),
527 }
528 }
529}
530
531impl Default for InternalFeatureConfig {
532 fn default() -> Self {
533 Self {
534 query_limits_checker: true,
535 directive_checker: true,
536 feature_gate: true,
537 logger: true,
538 query_timeout: true,
539 metrics: true,
540 tracing: false,
541 apollo_tracing: false,
542 open_telemetry: false,
543 }
544 }
545}
546
547impl Default for BackgroundTasksConfig {
548 fn default() -> Self {
549 Self {
550 watermark_update_ms: 500,
551 }
552 }
553}
554
555impl Display for Version {
556 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
557 write!(f, "{}", self.full)
558 }
559}
560
561impl Default for MoveRegistryConfig {
564 fn default() -> Self {
565 Self::new(
566 ResolutionType::Internal,
567 None,
568 DEFAULT_PAGE_LIMIT,
569 SuiAddress::from_str(MOVE_REGISTRY_PACKAGE).unwrap(),
570 ObjectID::from_str(MOVE_REGISTRY_TABLE_ID).unwrap(),
571 )
572 }
573}
574
575#[cfg(test)]
576mod tests {
577 use super::*;
578
579 #[test]
580 fn test_read_empty_service_config() {
581 let actual = ServiceConfig::read("").unwrap();
582 let expect = ServiceConfig::default();
583 assert_eq!(actual, expect);
584 }
585
586 #[test]
587 fn test_read_limits_in_service_config() {
588 let actual = ServiceConfig::read(
589 r#" [limits]
590 max-query-depth = 100
591 max-query-nodes = 300
592 max-output-nodes = 200000
593 max-mutation-payload-size = 174763
594 max-query-payload-size = 2000
595 max-db-query-cost = 50
596 default-page-size = 20
597 max-page-size = 50
598 mutation-timeout-ms = 74000
599 request-timeout-ms = 27000
600 max-type-argument-depth = 32
601 max-type-argument-width = 64
602 max-type-nodes = 128
603 max-move-value-depth = 256
604 max-transaction-ids = 11
605 max-multi-get-objects-keys = 11
606 max-scan-limit = 50
607 "#,
608 )
609 .unwrap();
610
611 let expect = ServiceConfig {
612 limits: Limits {
613 max_query_depth: 100,
614 max_query_nodes: 300,
615 max_output_nodes: 200000,
616 max_tx_payload_size: 174763,
617 max_query_payload_size: 2000,
618 max_db_query_cost: 50,
619 default_page_size: 20,
620 max_page_size: 50,
621 mutation_timeout_ms: 74_000,
622 request_timeout_ms: 27_000,
623 max_type_argument_depth: 32,
624 max_type_argument_width: 64,
625 max_type_nodes: 128,
626 max_move_value_depth: 256,
627 max_transaction_ids: 11,
628 max_multi_get_objects_keys: 11,
629 max_scan_limit: 50,
630 },
631 ..Default::default()
632 };
633
634 assert_eq!(actual, expect)
635 }
636
637 #[test]
638 fn test_read_enabled_features_in_service_config() {
639 let actual = ServiceConfig::read(
640 r#" disabled-features = [
641 "coins",
642 "name-service",
643 ]
644 "#,
645 )
646 .unwrap();
647
648 use FunctionalGroup as G;
649 let expect = ServiceConfig {
650 disabled_features: BTreeSet::from([G::Coins, G::NameService]),
651 ..Default::default()
652 };
653
654 assert_eq!(actual, expect)
655 }
656
657 #[test]
658 fn test_read_experiments_in_service_config() {
659 let actual = ServiceConfig::read(
660 r#" [experiments]
661 test-flag = true
662 "#,
663 )
664 .unwrap();
665
666 let expect = ServiceConfig {
667 experiments: Experiments { test_flag: true },
668 ..Default::default()
669 };
670
671 assert_eq!(actual, expect)
672 }
673
674 #[test]
675 fn test_read_everything_in_service_config() {
676 let actual = ServiceConfig::read(
677 r#" disabled-features = ["analytics"]
678
679 [limits]
680 max-query-depth = 42
681 max-query-nodes = 320
682 max-output-nodes = 200000
683 max-tx-payload-size = 181017
684 max-query-payload-size = 200
685 max-db-query-cost = 20
686 default-page-size = 10
687 max-page-size = 20
688 mutation-timeout-ms = 74000
689 request-timeout-ms = 30000
690 max-type-argument-depth = 32
691 max-type-argument-width = 64
692 max-type-nodes = 128
693 max-move-value-depth = 256
694 max-transaction-ids = 42
695 max-multi-get-objects-keys = 42
696 max-scan-limit = 420
697
698 [experiments]
699 test-flag = true
700 "#,
701 )
702 .unwrap();
703
704 let expect = ServiceConfig {
705 limits: Limits {
706 max_query_depth: 42,
707 max_query_nodes: 320,
708 max_output_nodes: 200000,
709 max_tx_payload_size: 181017,
710 max_query_payload_size: 200,
711 max_db_query_cost: 20,
712 default_page_size: 10,
713 max_page_size: 20,
714 mutation_timeout_ms: 74_000,
715 request_timeout_ms: 30_000,
716 max_type_argument_depth: 32,
717 max_type_argument_width: 64,
718 max_type_nodes: 128,
719 max_move_value_depth: 256,
720 max_transaction_ids: 42,
721 max_multi_get_objects_keys: 42,
722 max_scan_limit: 420,
723 },
724 disabled_features: BTreeSet::from([FunctionalGroup::Analytics]),
725 experiments: Experiments { test_flag: true },
726 ..Default::default()
727 };
728
729 assert_eq!(actual, expect);
730 }
731
732 #[test]
733 fn test_read_partial_in_service_config() {
734 let actual = ServiceConfig::read(
735 r#" disabled-features = ["analytics"]
736
737 [limits]
738 max-query-depth = 42
739 max-query-nodes = 320
740 "#,
741 )
742 .unwrap();
743
744 let expect = ServiceConfig {
746 limits: Limits {
747 max_query_depth: 42,
748 max_query_nodes: 320,
749 ..Default::default()
750 },
751 disabled_features: BTreeSet::from([FunctionalGroup::Analytics]),
752 ..Default::default()
753 };
754
755 assert_eq!(actual, expect);
756 }
757}