sui_rpc_api/grpc/v2/ledger_service/
get_epoch.rs1use 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 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}