iota_graphql_rpc/types/
protocol_config.rs

1// Copyright (c) Mysten Labs, Inc.
2// Modifications Copyright (c) 2024 IOTA Stiftung
3// SPDX-License-Identifier: Apache-2.0
4
5use std::collections::BTreeMap;
6
7use async_graphql::*;
8use diesel::{ExpressionMethods, QueryDsl};
9use iota_indexer::schema::{epochs, feature_flags, protocol_configs};
10
11use crate::{
12    data::{Db, DbConnection, QueryExecutor},
13    error::Error,
14    types::uint53::UInt53,
15};
16
17/// A single protocol configuration value.
18#[derive(Clone, Debug, SimpleObject)]
19pub(crate) struct ProtocolConfigAttr {
20    pub key: String,
21    pub value: Option<String>,
22}
23
24/// Whether or not a single feature is enabled in the protocol config.
25#[derive(Clone, Debug, SimpleObject)]
26pub(crate) struct ProtocolConfigFeatureFlag {
27    pub key: String,
28    pub value: bool,
29}
30
31#[derive(Clone, Debug)]
32pub(crate) struct ProtocolConfigs {
33    version: u64,
34    configs: BTreeMap<String, Option<String>>,
35    feature_flags: BTreeMap<String, bool>,
36}
37
38/// Constants that control how the chain operates.
39///
40/// These can only change during protocol upgrades which happen on epoch
41/// boundaries.
42#[Object]
43impl ProtocolConfigs {
44    /// The protocol is not required to change on every epoch boundary, so the
45    /// protocol version tracks which change to the protocol these configs
46    /// are from.
47    async fn protocol_version(&self) -> UInt53 {
48        self.version.into()
49    }
50
51    /// List all available feature flags and their values.  Feature flags are a
52    /// form of boolean configuration that are usually used to gate features
53    /// while they are in development.  Once a flag has been enabled, it is
54    /// rare for it to be disabled.
55    async fn feature_flags(&self) -> Vec<ProtocolConfigFeatureFlag> {
56        self.feature_flags
57            .clone()
58            .into_iter()
59            .map(|(key, value)| ProtocolConfigFeatureFlag { key, value })
60            .collect()
61    }
62
63    /// List all available configurations and their values.  These
64    /// configurations can take any value (but they will all be represented
65    /// in string form), and do not include feature flags.
66    async fn configs(&self) -> Vec<ProtocolConfigAttr> {
67        self.configs
68            .clone()
69            .into_iter()
70            .map(|(key, value)| ProtocolConfigAttr { key, value })
71            .collect()
72    }
73
74    /// Query for the value of the configuration with name `key`.
75    async fn config(&self, key: String) -> Option<ProtocolConfigAttr> {
76        self.configs.get(&key).map(|value| ProtocolConfigAttr {
77            key,
78            value: value.as_ref().map(|v| v.to_string()),
79        })
80    }
81
82    /// Query for the state of the feature flag with name `key`.
83    async fn feature_flag(&self, key: String) -> Option<ProtocolConfigFeatureFlag> {
84        self.feature_flags
85            .get(&key)
86            .map(|value| ProtocolConfigFeatureFlag { key, value: *value })
87    }
88}
89
90impl ProtocolConfigs {
91    pub(crate) async fn query(db: &Db, protocol_version: Option<u64>) -> Result<Self, Error> {
92        use epochs::dsl as e;
93        use feature_flags::dsl as f;
94        use protocol_configs::dsl as p;
95
96        let version = if let Some(version) = protocol_version {
97            version
98        } else {
99            let latest_version: i64 = db
100                .execute(move |conn| {
101                    conn.first(move || {
102                        e::epochs
103                            .select(e::protocol_version)
104                            .order_by(e::epoch.desc())
105                    })
106                })
107                .await
108                .map_err(|e| {
109                    Error::Internal(format!(
110                        "Failed to fetch latest protocol version in db: {e}"
111                    ))
112                })?;
113            latest_version as u64
114        };
115
116        // TODO: This could be optimized by fetching all configs and flags in a single
117        // query.
118        let configs: BTreeMap<String, Option<String>> = db
119            .execute(move |conn| {
120                conn.results(move || {
121                    p::protocol_configs
122                        .select((p::config_name, p::config_value))
123                        .filter(p::protocol_version.eq(version as i64))
124                })
125            })
126            .await
127            .map_err(|e| Error::Internal(format!("Failed to fetch protocol configs in db: {e}")))?
128            .into_iter()
129            .collect();
130
131        let feature_flags: BTreeMap<String, bool> = db
132            .execute(move |conn| {
133                conn.results(move || {
134                    f::feature_flags
135                        .select((f::flag_name, f::flag_value))
136                        .filter(f::protocol_version.eq(version as i64))
137                })
138            })
139            .await
140            .map_err(|e| Error::Internal(format!("Failed to fetch feature flags in db: {e}")))?
141            .into_iter()
142            .collect();
143
144        Ok(ProtocolConfigs {
145            version,
146            configs,
147            feature_flags,
148        })
149    }
150}