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