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;
19
20pub const READ_MASK_DEFAULT: &str = "epoch,first_checkpoint,last_checkpoint,start,end,reference_gas_price,protocol_config.protocol_version";
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_epoch = service.reader.inner().get_latest_checkpoint()?.epoch();
39 let epoch = request.epoch.unwrap_or(current_epoch);
40
41 let mut system_state =
42 if epoch == current_epoch && read_mask.contains(Epoch::SYSTEM_STATE_FIELD.name) {
43 Some(service.reader.get_system_state()?)
44 } else {
45 None
46 };
47
48 if read_mask.contains(Epoch::EPOCH_FIELD.name) {
49 message.epoch = Some(epoch);
50 }
51
52 if let Some(epoch_info) = service
53 .reader
54 .inner()
55 .indexes()
56 .and_then(|indexes| indexes.get_epoch_info(epoch).ok().flatten())
57 {
58 if read_mask.contains(Epoch::FIRST_CHECKPOINT_FIELD.name) {
59 message.first_checkpoint = epoch_info.start_checkpoint;
60 }
61
62 if read_mask.contains(Epoch::LAST_CHECKPOINT_FIELD.name) {
63 message.last_checkpoint = epoch_info.end_checkpoint;
64 }
65
66 if read_mask.contains(Epoch::START_FIELD.name) {
67 message.start = epoch_info.start_timestamp_ms.map(timestamp_ms_to_proto);
68 }
69
70 if read_mask.contains(Epoch::END_FIELD.name) {
71 message.end = epoch_info.end_timestamp_ms.map(timestamp_ms_to_proto);
72 }
73
74 if read_mask.contains(Epoch::REFERENCE_GAS_PRICE_FIELD.name) {
75 message.reference_gas_price = epoch_info.reference_gas_price;
76 }
77
78 if let Some(submask) = read_mask.subtree(Epoch::PROTOCOL_CONFIG_FIELD.name) {
79 let chain = service.reader.inner().get_chain_identifier()?.chain();
80 let protocol_config = epoch_info
81 .protocol_version
82 .map(|version| get_protocol_config(version, chain))
83 .transpose()?;
84
85 message.protocol_config =
86 protocol_config.map(|config| ProtocolConfig::merge_from(config, &submask));
87 }
88
89 if system_state.is_none() {
92 system_state = epoch_info.system_state;
93 }
94 }
95
96 if let Some(system_state) = system_state
97 && read_mask.contains(Epoch::SYSTEM_STATE_FIELD.name)
98 {
99 message.system_state = Some(Box::new(system_state.into()));
100 }
101
102 if read_mask.contains(Epoch::COMMITTEE_FIELD.name) {
103 message.committee = Some(
104 service
105 .reader
106 .get_committee(epoch)
107 .ok_or_else(|| CommitteeNotFoundError::new(epoch))?
108 .into(),
109 );
110 }
111
112 Ok(GetEpochResponse::new(message))
113}
114
115#[derive(Debug)]
116pub struct CommitteeNotFoundError {
117 epoch: EpochId,
118}
119
120impl CommitteeNotFoundError {
121 pub fn new(epoch: EpochId) -> Self {
122 Self { epoch }
123 }
124}
125
126impl std::fmt::Display for CommitteeNotFoundError {
127 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
128 write!(f, "Committee for epoch {} not found", self.epoch)
129 }
130}
131
132impl std::error::Error for CommitteeNotFoundError {}
133
134impl From<CommitteeNotFoundError> for crate::RpcError {
135 fn from(value: CommitteeNotFoundError) -> Self {
136 Self::new(tonic::Code::NotFound, value.to_string())
137 }
138}
139
140#[derive(Debug)]
141struct ProtocolVersionNotFoundError {
142 version: u64,
143}
144
145impl ProtocolVersionNotFoundError {
146 pub fn new(version: u64) -> Self {
147 Self { version }
148 }
149}
150
151impl std::fmt::Display for ProtocolVersionNotFoundError {
152 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
153 write!(f, "Protocol version {} not found", self.version)
154 }
155}
156
157impl std::error::Error for ProtocolVersionNotFoundError {}
158
159impl From<ProtocolVersionNotFoundError> for crate::RpcError {
160 fn from(value: ProtocolVersionNotFoundError) -> Self {
161 Self::new(tonic::Code::NotFound, value.to_string())
162 }
163}
164
165fn get_protocol_config(
166 version: u64,
167 chain: sui_protocol_config::Chain,
168) -> Result<ProtocolConfig, ProtocolVersionNotFoundError> {
169 let config =
170 sui_protocol_config::ProtocolConfig::get_for_version_if_supported(version.into(), chain)
171 .ok_or_else(|| ProtocolVersionNotFoundError::new(version))?;
172 Ok(protocol_config_to_proto(config))
173}
174
175pub fn protocol_config_to_proto(config: sui_protocol_config::ProtocolConfig) -> ProtocolConfig {
176 let protocol_version = config.version.as_u64();
177 let attributes = config
178 .attr_map()
179 .into_iter()
180 .filter_map(|(k, maybe_v)| {
181 maybe_v.map(move |v| {
182 let v = match v {
183 ProtocolConfigValue::u16(x) => x.to_string(),
184 ProtocolConfigValue::u32(y) => y.to_string(),
185 ProtocolConfigValue::u64(z) => z.to_string(),
186 ProtocolConfigValue::bool(b) => b.to_string(),
187 };
188 (k, v)
189 })
190 })
191 .collect();
192 let feature_flags = config.feature_map().into_iter().collect();
193 let mut message = ProtocolConfig::default();
194 message.protocol_version = Some(protocol_version);
195 message.feature_flags = feature_flags;
196 message.attributes = attributes;
197 message
198}