sui_indexer_alt_jsonrpc/api/
governance.rs

1// Copyright (c) Mysten Labs, Inc.
2// SPDX-License-Identifier: Apache-2.0
3
4use anyhow::Context as _;
5use diesel::ExpressionMethods;
6use diesel::QueryDsl;
7
8use jsonrpsee::core::RpcResult;
9use jsonrpsee::http_client::HttpClient;
10use jsonrpsee::proc_macros::rpc;
11use sui_indexer_alt_schema::schema::kv_epoch_starts;
12use sui_json_rpc_api::GovernanceReadApiClient;
13use sui_json_rpc_types::DelegatedStake;
14use sui_json_rpc_types::ValidatorApys;
15use sui_open_rpc::Module;
16use sui_open_rpc_macros::open_rpc;
17use sui_types::SUI_SYSTEM_STATE_OBJECT_ID;
18use sui_types::TypeTag;
19use sui_types::base_types::ObjectID;
20use sui_types::base_types::SuiAddress;
21use sui_types::dynamic_field::Field;
22use sui_types::dynamic_field::derive_dynamic_field_id;
23use sui_types::sui_serde::BigInt;
24use sui_types::sui_system_state::SuiSystemStateTrait;
25use sui_types::sui_system_state::SuiSystemStateWrapper;
26use sui_types::sui_system_state::sui_system_state_inner_v1::SuiSystemStateInnerV1;
27use sui_types::sui_system_state::sui_system_state_inner_v2::SuiSystemStateInnerV2;
28use sui_types::sui_system_state::sui_system_state_summary::SuiSystemStateSummary;
29
30use crate::api::rpc_module::RpcModule;
31use crate::context::Context;
32use crate::data::load_live_deserialized;
33use crate::error::RpcError;
34use crate::error::client_error_to_error_object;
35use crate::error::rpc_bail;
36
37#[open_rpc(namespace = "suix", tag = "Governance API")]
38#[rpc(server, namespace = "suix")]
39trait GovernanceApi {
40    /// Return the reference gas price for the network as of the latest epoch.
41    #[method(name = "getReferenceGasPrice")]
42    async fn get_reference_gas_price(&self) -> RpcResult<BigInt<u64>>;
43
44    /// Return a summary of the latest version of the Sui System State object (0x5), on-chain.
45    #[method(name = "getLatestSuiSystemState")]
46    async fn get_latest_sui_system_state(&self) -> RpcResult<SuiSystemStateSummary>;
47}
48
49#[open_rpc(namespace = "suix", tag = "Delegation Governance API")]
50#[rpc(server, namespace = "suix")]
51trait DelegationGovernanceApi {
52    /// Return one or more [DelegatedStake]. If a Stake was withdrawn its status will be Unstaked.
53    #[method(name = "getStakesByIds")]
54    async fn get_stakes_by_ids(
55        &self,
56        staked_sui_ids: Vec<ObjectID>,
57    ) -> RpcResult<Vec<DelegatedStake>>;
58
59    /// Return all [DelegatedStake].
60    #[method(name = "getStakes")]
61    async fn get_stakes(&self, owner: SuiAddress) -> RpcResult<Vec<DelegatedStake>>;
62
63    /// Return the validator APY
64    #[method(name = "getValidatorsApy")]
65    async fn get_validators_apy(&self) -> RpcResult<ValidatorApys>;
66}
67
68pub(crate) struct Governance(pub Context);
69pub(crate) struct DelegationGovernance(HttpClient);
70
71impl DelegationGovernance {
72    pub(crate) fn new(client: HttpClient) -> Self {
73        Self(client)
74    }
75}
76
77#[async_trait::async_trait]
78impl GovernanceApiServer for Governance {
79    async fn get_reference_gas_price(&self) -> RpcResult<BigInt<u64>> {
80        Ok(rgp_response(&self.0).await?)
81    }
82
83    async fn get_latest_sui_system_state(&self) -> RpcResult<SuiSystemStateSummary> {
84        Ok(latest_sui_system_state_response(&self.0).await?)
85    }
86}
87
88#[async_trait::async_trait]
89impl DelegationGovernanceApiServer for DelegationGovernance {
90    async fn get_stakes_by_ids(
91        &self,
92        staked_sui_ids: Vec<ObjectID>,
93    ) -> RpcResult<Vec<DelegatedStake>> {
94        let Self(client) = self;
95
96        client
97            .get_stakes_by_ids(staked_sui_ids)
98            .await
99            .map_err(client_error_to_error_object)
100    }
101
102    async fn get_stakes(&self, owner: SuiAddress) -> RpcResult<Vec<DelegatedStake>> {
103        let Self(client) = self;
104
105        client
106            .get_stakes(owner)
107            .await
108            .map_err(client_error_to_error_object)
109    }
110
111    async fn get_validators_apy(&self) -> RpcResult<ValidatorApys> {
112        let Self(client) = self;
113
114        client
115            .get_validators_apy()
116            .await
117            .map_err(client_error_to_error_object)
118    }
119}
120
121impl RpcModule for Governance {
122    fn schema(&self) -> Module {
123        GovernanceApiOpenRpc::module_doc()
124    }
125
126    fn into_impl(self) -> jsonrpsee::RpcModule<Self> {
127        self.into_rpc()
128    }
129}
130
131impl RpcModule for DelegationGovernance {
132    fn schema(&self) -> Module {
133        DelegationGovernanceApiOpenRpc::module_doc()
134    }
135
136    fn into_impl(self) -> jsonrpsee::RpcModule<Self> {
137        self.into_rpc()
138    }
139}
140
141/// Load data and generate response for `getReferenceGasPrice`.
142async fn rgp_response(ctx: &Context) -> Result<BigInt<u64>, RpcError> {
143    use kv_epoch_starts::dsl as e;
144
145    let mut conn = ctx
146        .pg_reader()
147        .connect()
148        .await
149        .context("Failed to connect to the database")?;
150
151    let rgp: i64 = conn
152        .first(
153            e::kv_epoch_starts
154                .select(e::reference_gas_price)
155                .order(e::epoch.desc()),
156        )
157        .await
158        .context("Failed to fetch the reference gas price")?;
159
160    Ok((rgp as u64).into())
161}
162
163/// Load data and generate response for `getLatestSuiSystemState`.
164async fn latest_sui_system_state_response(
165    ctx: &Context,
166) -> Result<SuiSystemStateSummary, RpcError> {
167    let wrapper: SuiSystemStateWrapper = load_live_deserialized(ctx, SUI_SYSTEM_STATE_OBJECT_ID)
168        .await
169        .context("Failed to fetch system state wrapper object")?;
170
171    let inner_id = derive_dynamic_field_id(
172        SUI_SYSTEM_STATE_OBJECT_ID,
173        &TypeTag::U64,
174        &bcs::to_bytes(&wrapper.version).context("Failed to serialize system state version")?,
175    )
176    .context("Failed to derive inner system state field ID")?;
177
178    Ok(match wrapper.version {
179        1 => load_live_deserialized::<Field<u64, SuiSystemStateInnerV1>>(ctx, inner_id)
180            .await
181            .context("Failed to fetch inner system state object")?
182            .value
183            .into_sui_system_state_summary(),
184        2 => load_live_deserialized::<Field<u64, SuiSystemStateInnerV2>>(ctx, inner_id)
185            .await
186            .context("Failed to fetch inner system state object")?
187            .value
188            .into_sui_system_state_summary(),
189        v => rpc_bail!("Unexpected inner system state version: {v}"),
190    })
191}