sui_rpc_api/grpc/v2/ledger_service/
get_epoch.rs

1// Copyright (c) Mysten Labs, Inc.
2// SPDX-License-Identifier: Apache-2.0
3
4use crate::ErrorReason;
5use crate::Result;
6use crate::RpcService;
7use prost_types::FieldMask;
8use sui_protocol_config::ProtocolConfigValue;
9use sui_rpc::field::FieldMaskTree;
10use sui_rpc::field::FieldMaskUtil;
11use sui_rpc::merge::Merge;
12use sui_rpc::proto::google::rpc::bad_request::FieldViolation;
13use sui_rpc::proto::sui::rpc::v2::Epoch;
14use sui_rpc::proto::sui::rpc::v2::GetEpochRequest;
15use sui_rpc::proto::sui::rpc::v2::GetEpochResponse;
16use sui_rpc::proto::sui::rpc::v2::ProtocolConfig;
17use sui_rpc::proto::timestamp_ms_to_proto;
18use sui_sdk_types::EpochId;
19use sui_types::sui_system_state::SuiSystemStateTrait;
20
21pub const READ_MASK_DEFAULT: &str = "epoch,first_checkpoint,last_checkpoint,start,end,reference_gas_price,protocol_config.protocol_version";
22
23#[tracing::instrument(skip(service))]
24pub fn get_epoch(service: &RpcService, request: GetEpochRequest) -> Result<GetEpochResponse> {
25    let read_mask = {
26        let read_mask = request
27            .read_mask
28            .unwrap_or_else(|| FieldMask::from_str(READ_MASK_DEFAULT));
29        read_mask.validate::<Epoch>().map_err(|path| {
30            FieldViolation::new("read_mask")
31                .with_description(format!("invalid read_mask path: {path}"))
32                .with_reason(ErrorReason::FieldInvalid)
33        })?;
34        FieldMaskTree::from(read_mask)
35    };
36
37    let mut message = Epoch::default();
38
39    let current_system_state = service.reader.get_system_state()?;
40    let current_epoch = current_system_state.epoch();
41
42    let epoch = request.epoch.unwrap_or(current_epoch);
43
44    if read_mask.contains(Epoch::EPOCH_FIELD.name) {
45        message.set_epoch(epoch);
46    }
47
48    // Fetch epoch info, if indexing is available.
49    let mut epoch_info = service
50        .reader
51        .inner()
52        .indexes()
53        .and_then(|indexes| indexes.get_epoch_info(epoch).ok().flatten());
54
55    let system_state = if epoch == current_epoch {
56        Some(current_system_state)
57    } else {
58        epoch_info
59            .as_mut()
60            .and_then(|info| info.system_state.take())
61    };
62
63    if let Some(system_state) = system_state {
64        if let Some(submask) = read_mask.subtree(Epoch::PROTOCOL_CONFIG_FIELD) {
65            let chain = service.reader.inner().get_chain_identifier()?.chain();
66            let config = get_protocol_config(system_state.protocol_version(), chain)?;
67
68            message.set_protocol_config(ProtocolConfig::merge_from(config, &submask));
69        }
70
71        if read_mask.contains(Epoch::START_FIELD) {
72            message.set_start(timestamp_ms_to_proto(
73                system_state.epoch_start_timestamp_ms(),
74            ));
75        }
76
77        if read_mask.contains(Epoch::REFERENCE_GAS_PRICE_FIELD) {
78            message.set_reference_gas_price(system_state.reference_gas_price());
79        }
80
81        if read_mask.contains(Epoch::SYSTEM_STATE_FIELD) {
82            message.system_state = Some(Box::new(system_state.into()));
83        }
84    }
85
86    if let Some(epoch_info) = epoch_info {
87        if read_mask.contains(Epoch::FIRST_CHECKPOINT_FIELD) {
88            message.first_checkpoint = epoch_info.start_checkpoint;
89        }
90
91        if read_mask.contains(Epoch::LAST_CHECKPOINT_FIELD) {
92            message.last_checkpoint = epoch_info.end_checkpoint;
93        }
94
95        if read_mask.contains(Epoch::START_FIELD) && message.start.is_none() {
96            message.start = epoch_info.start_timestamp_ms.map(timestamp_ms_to_proto);
97        }
98
99        if read_mask.contains(Epoch::END_FIELD) {
100            message.end = epoch_info.end_timestamp_ms.map(timestamp_ms_to_proto);
101        }
102
103        if read_mask.contains(Epoch::REFERENCE_GAS_PRICE_FIELD.name)
104            && message.reference_gas_price.is_none()
105        {
106            message.reference_gas_price = epoch_info.reference_gas_price;
107        }
108
109        if let Some(submask) = read_mask.subtree(Epoch::PROTOCOL_CONFIG_FIELD.name)
110            && message.protocol_config.is_none()
111        {
112            let chain = service.reader.inner().get_chain_identifier()?.chain();
113            let protocol_config = epoch_info
114                .protocol_version
115                .map(|version| get_protocol_config(version, chain))
116                .transpose()?;
117
118            message.protocol_config =
119                protocol_config.map(|config| ProtocolConfig::merge_from(config, &submask));
120        }
121    }
122
123    if read_mask.contains(Epoch::COMMITTEE_FIELD.name) {
124        message.committee = Some(
125            service
126                .reader
127                .get_committee(epoch)
128                .ok_or_else(|| CommitteeNotFoundError::new(epoch))?
129                .into(),
130        );
131    }
132
133    Ok(GetEpochResponse::new(message))
134}
135
136#[derive(Debug)]
137pub struct CommitteeNotFoundError {
138    epoch: EpochId,
139}
140
141impl CommitteeNotFoundError {
142    pub fn new(epoch: EpochId) -> Self {
143        Self { epoch }
144    }
145}
146
147impl std::fmt::Display for CommitteeNotFoundError {
148    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
149        write!(f, "Committee for epoch {} not found", self.epoch)
150    }
151}
152
153impl std::error::Error for CommitteeNotFoundError {}
154
155impl From<CommitteeNotFoundError> for crate::RpcError {
156    fn from(value: CommitteeNotFoundError) -> Self {
157        Self::new(tonic::Code::NotFound, value.to_string())
158    }
159}
160
161#[derive(Debug)]
162struct ProtocolVersionNotFoundError {
163    version: u64,
164}
165
166impl ProtocolVersionNotFoundError {
167    pub fn new(version: u64) -> Self {
168        Self { version }
169    }
170}
171
172impl std::fmt::Display for ProtocolVersionNotFoundError {
173    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
174        write!(f, "Protocol version {} not found", self.version)
175    }
176}
177
178impl std::error::Error for ProtocolVersionNotFoundError {}
179
180impl From<ProtocolVersionNotFoundError> for crate::RpcError {
181    fn from(value: ProtocolVersionNotFoundError) -> Self {
182        Self::new(tonic::Code::NotFound, value.to_string())
183    }
184}
185
186fn get_protocol_config(
187    version: u64,
188    chain: sui_protocol_config::Chain,
189) -> Result<ProtocolConfig, ProtocolVersionNotFoundError> {
190    let config =
191        sui_protocol_config::ProtocolConfig::get_for_version_if_supported(version.into(), chain)
192            .ok_or_else(|| ProtocolVersionNotFoundError::new(version))?;
193    Ok(protocol_config_to_proto(config))
194}
195
196pub fn protocol_config_to_proto(config: sui_protocol_config::ProtocolConfig) -> ProtocolConfig {
197    let protocol_version = config.version.as_u64();
198    let attributes = config
199        .attr_map()
200        .into_iter()
201        .filter_map(|(k, maybe_v)| {
202            maybe_v.map(move |v| {
203                let v = match v {
204                    ProtocolConfigValue::u16(x) => x.to_string(),
205                    ProtocolConfigValue::u32(y) => y.to_string(),
206                    ProtocolConfigValue::u64(z) => z.to_string(),
207                    ProtocolConfigValue::bool(b) => b.to_string(),
208                };
209                (k, v)
210            })
211        })
212        .collect();
213    let feature_flags = config.feature_map().into_iter().collect();
214    let mut message = ProtocolConfig::default();
215    message.protocol_version = Some(protocol_version);
216    message.feature_flags = feature_flags;
217    message.attributes = attributes;
218    message
219}