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;
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 we're not loading the current epoch then grab the indexed snapshot of the system
90        // state at the start of the epoch.
91        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}