sui_rpc_api/grpc/v2/ledger_service/
get_epoch.rs1use crate::ErrorReason;
5use crate::Result;
6use crate::RpcService;
7use prost_types::FieldMask;
8use sui_rpc::field::FieldMaskTree;
9use sui_rpc::field::FieldMaskUtil;
10use sui_rpc::merge::Merge;
11use sui_rpc::proto::google::rpc::bad_request::FieldViolation;
12use sui_rpc::proto::sui::rpc::v2::Epoch;
13use sui_rpc::proto::sui::rpc::v2::GetEpochRequest;
14use sui_rpc::proto::sui::rpc::v2::GetEpochResponse;
15use sui_rpc::proto::sui::rpc::v2::ProtocolConfig;
16use sui_rpc::proto::timestamp_ms_to_proto;
17use sui_sdk_types::EpochId;
18use sui_types::sui_system_state::SuiSystemStateTrait;
19
20pub const READ_MASK_DEFAULT: &str = crate::read_mask_defaults::EPOCH;
21
22#[tracing::instrument(skip(service))]
23pub fn get_epoch(service: &RpcService, request: GetEpochRequest) -> Result<GetEpochResponse> {
24 let read_mask = {
25 let read_mask = request
26 .read_mask
27 .unwrap_or_else(|| FieldMask::from_str(READ_MASK_DEFAULT));
28 read_mask.validate::<Epoch>().map_err(|path| {
29 FieldViolation::new("read_mask")
30 .with_description(format!("invalid read_mask path: {path}"))
31 .with_reason(ErrorReason::FieldInvalid)
32 })?;
33 FieldMaskTree::from(read_mask)
34 };
35
36 let mut message = Epoch::default();
37
38 let current_system_state = service.reader.get_system_state()?;
39 let current_epoch = current_system_state.epoch();
40
41 let epoch = request.epoch.unwrap_or(current_epoch);
42
43 if read_mask.contains(Epoch::EPOCH_FIELD.name) {
44 message.set_epoch(epoch);
45 }
46
47 let mut epoch_info = service
49 .reader
50 .inner()
51 .indexes()
52 .and_then(|indexes| indexes.get_epoch_info(epoch).ok().flatten());
53
54 let system_state = if epoch == current_epoch {
55 Some(current_system_state)
56 } else {
57 epoch_info
58 .as_mut()
59 .and_then(|info| info.system_state.take())
60 };
61
62 if let Some(system_state) = system_state {
63 if let Some(submask) = read_mask.subtree(Epoch::PROTOCOL_CONFIG_FIELD) {
64 let chain = service.reader.inner().get_chain_identifier()?.chain();
65 let config = get_protocol_config(system_state.protocol_version(), chain)?;
66
67 message.set_protocol_config(ProtocolConfig::merge_from(config, &submask));
68 }
69
70 if read_mask.contains(Epoch::START_FIELD) {
71 message.set_start(timestamp_ms_to_proto(
72 system_state.epoch_start_timestamp_ms(),
73 ));
74 }
75
76 if read_mask.contains(Epoch::REFERENCE_GAS_PRICE_FIELD) {
77 message.set_reference_gas_price(system_state.reference_gas_price());
78 }
79
80 if read_mask.contains(Epoch::SYSTEM_STATE_FIELD) {
81 message.system_state = Some(Box::new(system_state.into()));
82 }
83 }
84
85 if let Some(epoch_info) = epoch_info {
86 if read_mask.contains(Epoch::FIRST_CHECKPOINT_FIELD) {
87 message.first_checkpoint = epoch_info.start_checkpoint;
88 }
89
90 if read_mask.contains(Epoch::LAST_CHECKPOINT_FIELD) {
91 message.last_checkpoint = epoch_info.end_checkpoint;
92 }
93
94 if read_mask.contains(Epoch::START_FIELD) && message.start.is_none() {
95 message.start = epoch_info.start_timestamp_ms.map(timestamp_ms_to_proto);
96 }
97
98 if read_mask.contains(Epoch::END_FIELD) {
99 message.end = epoch_info.end_timestamp_ms.map(timestamp_ms_to_proto);
100 }
101
102 if read_mask.contains(Epoch::REFERENCE_GAS_PRICE_FIELD.name)
103 && message.reference_gas_price.is_none()
104 {
105 message.reference_gas_price = epoch_info.reference_gas_price;
106 }
107
108 if let Some(submask) = read_mask.subtree(Epoch::PROTOCOL_CONFIG_FIELD.name)
109 && message.protocol_config.is_none()
110 {
111 let chain = service.reader.inner().get_chain_identifier()?.chain();
112 let protocol_config = epoch_info
113 .protocol_version
114 .map(|version| get_protocol_config(version, chain))
115 .transpose()?;
116
117 message.protocol_config =
118 protocol_config.map(|config| ProtocolConfig::merge_from(config, &submask));
119 }
120 }
121
122 if read_mask.contains(Epoch::COMMITTEE_FIELD.name) {
123 message.committee = Some(
124 service
125 .reader
126 .get_committee(epoch)
127 .ok_or_else(|| CommitteeNotFoundError::new(epoch))?
128 .into(),
129 );
130 }
131
132 Ok(GetEpochResponse::new(message))
133}
134
135#[derive(Debug)]
136pub struct CommitteeNotFoundError {
137 epoch: EpochId,
138}
139
140impl CommitteeNotFoundError {
141 pub fn new(epoch: EpochId) -> Self {
142 Self { epoch }
143 }
144}
145
146impl std::fmt::Display for CommitteeNotFoundError {
147 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
148 write!(f, "Committee for epoch {} not found", self.epoch)
149 }
150}
151
152impl std::error::Error for CommitteeNotFoundError {}
153
154impl From<CommitteeNotFoundError> for crate::RpcError {
155 fn from(value: CommitteeNotFoundError) -> Self {
156 Self::new(tonic::Code::NotFound, value.to_string())
157 }
158}
159
160#[derive(Debug)]
161struct ProtocolVersionNotFoundError {
162 version: u64,
163}
164
165impl ProtocolVersionNotFoundError {
166 pub fn new(version: u64) -> Self {
167 Self { version }
168 }
169}
170
171impl std::fmt::Display for ProtocolVersionNotFoundError {
172 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
173 write!(f, "Protocol version {} not found", self.version)
174 }
175}
176
177impl std::error::Error for ProtocolVersionNotFoundError {}
178
179impl From<ProtocolVersionNotFoundError> for crate::RpcError {
180 fn from(value: ProtocolVersionNotFoundError) -> Self {
181 Self::new(tonic::Code::NotFound, value.to_string())
182 }
183}
184
185fn get_protocol_config(
186 version: u64,
187 chain: sui_protocol_config::Chain,
188) -> Result<ProtocolConfig, ProtocolVersionNotFoundError> {
189 let config =
190 sui_protocol_config::ProtocolConfig::get_for_version_if_supported(version.into(), chain)
191 .ok_or_else(|| ProtocolVersionNotFoundError::new(version))?;
192 Ok(protocol_config_to_proto(config))
193}
194
195pub fn protocol_config_to_proto(config: sui_protocol_config::ProtocolConfig) -> ProtocolConfig {
196 use prost_types::value::Kind;
197
198 let mut message = ProtocolConfig::default();
199 message.set_protocol_version(config.version.as_u64());
200
201 message.set_feature_flags(config.feature_map());
203
204 let mut configs = config
208 .render::<prost_types::Value>(&mut mysten_common::rpc_format::Unmetered)
209 .expect("render to prost Value should succeed")
210 .into_iter()
211 .filter(|(_, v)| !matches!(v.kind, None | Some(Kind::NullValue(_))))
213 .collect::<std::collections::BTreeMap<_, _>>();
214
215 message.set_attributes(
218 configs
219 .iter()
220 .filter_map(|(k, v)| match &v.kind {
221 Some(Kind::NullValue(_)) => None,
222 Some(Kind::NumberValue(n)) => Some((k.to_owned(), n.to_string())),
223 Some(Kind::StringValue(s)) => Some((k.to_owned(), s.to_owned())),
224 Some(Kind::BoolValue(b)) => Some((k.to_owned(), b.to_string())),
225 Some(Kind::StructValue(s)) => Some((
226 k.to_owned(),
227 serde_json::to_string(&sui_rpc::_serde::StructSerializer(s)).unwrap(),
228 )),
229 Some(Kind::ListValue(list)) => Some((
230 k.to_owned(),
231 serde_json::to_string(&sui_rpc::_serde::ListValueSerializer(list)).unwrap(),
232 )),
233 None => None,
234 })
235 .collect(),
236 );
237
238 for (k, v) in config
240 .feature_map()
241 .into_iter()
242 .map(|(key, value)| (key, prost_types::Value::from(value)))
243 {
244 let old = configs.insert(k, v);
245
246 debug_assert!(
247 old.is_none(),
248 "feature flags and attributes can't have keys which are the same"
249 );
250 }
251
252 message.set_configs(configs);
254
255 message
256}