Skip to content

Commit

Permalink
[indexer] index protocol configs and feature flags (#18450)
Browse files Browse the repository at this point in the history
## Description 

This PR adds two tables `protocol_configs` and `feature_flags` that get
populated at indexer startup time and every epoch change time if
protocol version has changed.

## Test plan 

Tested locally against devnet.

---

## Release notes

Check each box that your changes affect. If none of the boxes relate to
your changes, release notes aren't required.

For each box you select, include information after the relevant heading
that describes the impact of your changes that a user might notice and
any actions they must take to implement updates.

- [ ] Protocol: 
- [ ] Nodes (Validators and Full nodes): 
- [x] Indexer: adds two new indexer tables that stores protocol configs
and features flags of different versions.
- [ ] JSON-RPC: 
- [x] GraphQL: uses the stored data to query for protocol configs
instead of native configs stored in the binary.
- [ ] CLI: 
- [ ] Rust SDK:
  • Loading branch information
emmazzz authored Aug 21, 2024
1 parent b9d696e commit b1976e6
Show file tree
Hide file tree
Showing 13 changed files with 366 additions and 62 deletions.
2 changes: 1 addition & 1 deletion crates/sui-benchmark/tests/simtest.rs
Original file line number Diff line number Diff line change
Expand Up @@ -550,7 +550,7 @@ mod test {
.build()
.await,
);
test_simulated_load(test_cluster, 10).await;
test_simulated_load(test_cluster, 30).await;

let checkpoint_files = std::fs::read_dir(path)
.map(|entries| {
Expand Down
40 changes: 40 additions & 0 deletions crates/sui-graphql-e2e-tests/tests/epoch/protocol_configs.exp
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
processed 4 tasks

init:
C: object(0,0)

task 1, line 6:
//# create-checkpoint
Checkpoint created: 1

task 2, lines 8-19:
//# run-graphql
Response: {
"data": {
"protocolConfig": {
"protocolVersion": 51,
"config": {
"value": "128"
},
"featureFlag": {
"value": true
}
}
}
}

task 3, lines 21-32:
//# run-graphql
Response: {
"data": {
"protocolConfig": {
"protocolVersion": 8,
"config": {
"value": null
},
"featureFlag": {
"value": false
}
}
}
}
32 changes: 32 additions & 0 deletions crates/sui-graphql-e2e-tests/tests/epoch/protocol_configs.move
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
// Copyright (c) Mysten Labs, Inc.
// SPDX-License-Identifier: Apache-2.0

//# init --protocol-version 51 --simulator --accounts C

//# create-checkpoint

//# run-graphql
{
protocolConfig {
protocolVersion
config(key: "max_move_identifier_len") {
value
}
featureFlag(key: "enable_coin_deny_list") {
value
}
}
}

//# run-graphql
{
protocolConfig(protocolVersion: 8) {
protocolVersion
config(key: "max_move_identifier_len") {
value
}
featureFlag(key: "enable_coin_deny_list") {
value
}
}
}
114 changes: 67 additions & 47 deletions crates/sui-graphql-rpc/src/types/protocol_config.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
// Copyright (c) Mysten Labs, Inc.
// SPDX-License-Identifier: Apache-2.0

use std::collections::BTreeMap;

use async_graphql::*;
use diesel::{ExpressionMethods, QueryDsl};
use sui_indexer::schema::{chain_identifier, epochs};
use sui_protocol_config::{ProtocolConfig as NativeProtocolConfig, ProtocolVersion};
use sui_indexer::schema::{epochs, feature_flags, protocol_configs};

use crate::{
data::{Db, DbConnection, QueryExecutor},
error::Error,
types::chain_identifier::ChainIdentifier,
};

use super::uint53::UInt53;
Expand All @@ -30,7 +30,9 @@ pub(crate) struct ProtocolConfigFeatureFlag {

#[derive(Clone, Debug)]
pub(crate) struct ProtocolConfigs {
native: NativeProtocolConfig,
version: u64,
configs: BTreeMap<String, Option<String>>,
feature_flags: BTreeMap<String, bool>,
}

/// Constants that control how the chain operates.
Expand All @@ -41,15 +43,15 @@ impl ProtocolConfigs {
/// The protocol is not required to change on every epoch boundary, so the protocol version
/// tracks which change to the protocol these configs are from.
async fn protocol_version(&self) -> UInt53 {
self.native.version.as_u64().into()
self.version.into()
}

/// List all available feature flags and their values. Feature flags are a form of boolean
/// configuration that are usually used to gate features while they are in development. Once a
/// flag has been enabled, it is rare for it to be disabled.
async fn feature_flags(&self) -> Vec<ProtocolConfigFeatureFlag> {
self.native
.feature_map()
self.feature_flags
.clone()
.into_iter()
.map(|(key, value)| ProtocolConfigFeatureFlag { key, value })
.collect()
Expand All @@ -58,68 +60,86 @@ impl ProtocolConfigs {
/// List all available configurations and their values. These configurations can take any value
/// (but they will all be represented in string form), and do not include feature flags.
async fn configs(&self) -> Vec<ProtocolConfigAttr> {
self.native
.attr_map()
self.configs
.clone()
.into_iter()
.map(|(key, value)| ProtocolConfigAttr {
key,
value: value.map(|v| v.to_string()),
})
.map(|(key, value)| ProtocolConfigAttr { key, value })
.collect()
}

/// Query for the value of the configuration with name `key`.
async fn config(&self, key: String) -> Option<ProtocolConfigAttr> {
self.native
.attr_map()
.get(&key)
.map(|value| ProtocolConfigAttr {
key,
value: value.as_ref().map(|v| v.to_string()),
})
self.configs.get(&key).map(|value| ProtocolConfigAttr {
key,
value: value.as_ref().map(|v| v.to_string()),
})
}

/// Query for the state of the feature flag with name `key`.
async fn feature_flag(&self, key: String) -> Option<ProtocolConfigFeatureFlag> {
self.native
.feature_map()
self.feature_flags
.get(&key)
.map(|value| ProtocolConfigFeatureFlag { key, value: *value })
}
}

impl ProtocolConfigs {
pub(crate) async fn query(db: &Db, protocol_version: Option<u64>) -> Result<Self, Error> {
use chain_identifier::dsl as c;
use epochs::dsl as e;
use feature_flags::dsl as f;
use protocol_configs::dsl as p;

let version = if let Some(version) = protocol_version {
version
} else {
let latest_version: i64 = db
.execute(move |conn| {
conn.first(move || {
e::epochs
.select(e::protocol_version)
.order_by(e::epoch.desc())
})
})
.await
.map_err(|e| {
Error::Internal(format!(
"Failed to fetch latest protocol version in db: {e}"
))
})?;
latest_version as u64
};

// TODO: This could be optimized by fetching all configs and flags in a single query.
let configs: BTreeMap<String, Option<String>> = db
.execute(move |conn| {
conn.results(move || {
p::protocol_configs
.select((p::config_name, p::config_value))
.filter(p::protocol_version.eq(version as i64))
})
})
.await
.map_err(|e| Error::Internal(format!("Failed to fetch protocol configs in db: {e}")))?
.into_iter()
.collect();

let (latest_version, digest_bytes): (i64, Option<Vec<u8>>) = db
let feature_flags: BTreeMap<String, bool> = db
.execute(move |conn| {
conn.first(move || {
e::epochs
.select((
e::protocol_version,
c::chain_identifier
.select(c::checkpoint_digest)
.single_value(),
))
.order_by(e::epoch.desc())
conn.results(move || {
f::feature_flags
.select((f::flag_name, f::flag_value))
.filter(f::protocol_version.eq(version as i64))
})
})
.await
.map_err(|e| Error::Internal(format!("Failed to fetch system details: {e}")))?;

let native = NativeProtocolConfig::get_for_version_if_supported(
protocol_version.unwrap_or(latest_version as u64).into(),
ChainIdentifier::from_bytes(digest_bytes.unwrap_or_default())?.chain(),
)
.ok_or_else(|| {
Error::ProtocolVersionUnsupported(
ProtocolVersion::MIN.as_u64(),
ProtocolVersion::MAX.as_u64(),
)
})?;

Ok(ProtocolConfigs { native })
.map_err(|e| Error::Internal(format!("Failed to fetch feature flags in db: {e}")))?
.into_iter()
.collect();

Ok(ProtocolConfigs {
version,
configs,
feature_flags,
})
}
}
19 changes: 19 additions & 0 deletions crates/sui-indexer/migrations/pg/2023-08-19-044052_epochs/up.sql
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,22 @@ CREATE TABLE epochs
-- of the epoch
epoch_commitments bytea
);

-- Table storing the protocol configs for each protocol version.
-- Examples include gas schedule, transaction limits, etc.
CREATE TABLE protocol_configs
(
protocol_version BIGINT NOT NULL,
config_name TEXT NOT NULL,
config_value TEXT,
PRIMARY KEY(protocol_version, config_name)
);

-- Table storing the feature flags for each protocol version.
CREATE TABLE feature_flags
(
protocol_version BIGINT NOT NULL,
flag_name TEXT NOT NULL,
flag_value BOOLEAN NOT NULL,
PRIMARY KEY(protocol_version, flag_name)
);
13 changes: 13 additions & 0 deletions crates/sui-indexer/src/handlers/committer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,8 @@ async fn commit_checkpoints<S>(
.expect("Persisting data into DB should not fail.");
}

let is_epoch_end = epoch.is_some();

// handle partitioning on epoch boundary
if let Some(epoch_data) = epoch {
state
Expand All @@ -184,6 +186,17 @@ async fn commit_checkpoints<S>(
);
})
.expect("Persisting data into DB should not fail.");

if is_epoch_end {
// The epoch has advanced so we update the configs for the new protocol version, if it has changed.
let chain_id = state
.get_chain_identifier()
.await
.expect("Failed to get chain identifier")
.expect("Chain identifier should have been indexed at this point");
let _ = state.persist_protocol_configs_and_feature_flags(chain_id);
}

let elapsed = guard.stop_and_record();

commit_notifier
Expand Down
8 changes: 8 additions & 0 deletions crates/sui-indexer/src/indexer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,14 @@ impl Indexer {
spawn_monitored_task!(pruner.start(CancellationToken::new()));
}

// If we already have chain identifier indexed (i.e. the first checkpoint has been indexed),
// then we persist protocol configs for protocol versions not yet in the db.
// Otherwise, we would do the persisting in `commit_checkpoint` while the first cp is
// being indexed.
if let Some(chain_id) = store.get_chain_identifier().await? {
store.persist_protocol_configs_and_feature_flags(chain_id)?;
}

let cancel_clone = cancel.clone();
let (exit_sender, exit_receiver) = oneshot::channel();
// Spawn a task that links the cancellation token to the exit sender
Expand Down
18 changes: 17 additions & 1 deletion crates/sui-indexer/src/models/epoch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@

use diesel::{Insertable, Queryable, Selectable};

use crate::errors::IndexerError;
use crate::schema::epochs;
use crate::types::IndexedEpochInfo;
use crate::{errors::IndexerError, schema::feature_flags, schema::protocol_configs};
use sui_json_rpc_types::{EndOfEpochInfo, EpochInfo};
use sui_types::sui_system_state::sui_system_state_summary::SuiSystemStateSummary;

Expand Down Expand Up @@ -33,6 +33,22 @@ pub struct StoredEpochInfo {
pub epoch_commitments: Option<Vec<u8>>,
}

#[derive(Queryable, Insertable, Debug, Clone, Default)]
#[diesel(table_name = protocol_configs)]
pub struct StoredProtocolConfig {
pub protocol_version: i64,
pub config_name: String,
pub config_value: Option<String>,
}

#[derive(Queryable, Insertable, Debug, Clone, Default)]
#[diesel(table_name = feature_flags)]
pub struct StoredFeatureFlag {
pub protocol_version: i64,
pub flag_name: String,
pub flag_value: bool,
}

#[derive(Queryable, Selectable, Clone)]
#[diesel(table_name = epochs)]
pub struct QueryableEpochInfo {
Expand Down
6 changes: 6 additions & 0 deletions crates/sui-indexer/src/schema/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,13 @@ mod inner {
pub use crate::schema::pg::event_struct_name;
pub use crate::schema::pg::event_struct_package;
pub use crate::schema::pg::events;
pub use crate::schema::pg::feature_flags;
pub use crate::schema::pg::objects;
pub use crate::schema::pg::objects_history;
pub use crate::schema::pg::objects_snapshot;
pub use crate::schema::pg::objects_version;
pub use crate::schema::pg::packages;
pub use crate::schema::pg::protocol_configs;
pub use crate::schema::pg::pruner_cp_watermark;
pub use crate::schema::pg::transactions;
pub use crate::schema::pg::tx_calls_fun;
Expand Down Expand Up @@ -57,11 +59,13 @@ mod inner {
pub use crate::schema::mysql::event_struct_name;
pub use crate::schema::mysql::event_struct_package;
pub use crate::schema::mysql::events;
pub use crate::schema::mysql::feature_flags;
pub use crate::schema::mysql::objects;
pub use crate::schema::mysql::objects_history;
pub use crate::schema::mysql::objects_snapshot;
pub use crate::schema::mysql::objects_version;
pub use crate::schema::mysql::packages;
pub use crate::schema::mysql::protocol_configs;
pub use crate::schema::mysql::pruner_cp_watermark;
pub use crate::schema::mysql::transactions;
pub use crate::schema::mysql::tx_calls_fun;
Expand All @@ -87,11 +91,13 @@ pub use inner::event_struct_module;
pub use inner::event_struct_name;
pub use inner::event_struct_package;
pub use inner::events;
pub use inner::feature_flags;
pub use inner::objects;
pub use inner::objects_history;
pub use inner::objects_snapshot;
pub use inner::objects_version;
pub use inner::packages;
pub use inner::protocol_configs;
pub use inner::pruner_cp_watermark;
pub use inner::transactions;
pub use inner::tx_calls_fun;
Expand Down
Loading

0 comments on commit b1976e6

Please sign in to comment.