iota_types/iota_system_state/
mod.rs

1// Copyright (c) Mysten Labs, Inc.
2// Modifications Copyright (c) 2024 IOTA Stiftung
3// SPDX-License-Identifier: Apache-2.0
4
5use std::fmt;
6
7use anyhow::Result;
8use enum_dispatch::enum_dispatch;
9use iota_protocol_config::{ProtocolConfig, ProtocolVersion};
10use move_core_types::{ident_str, identifier::IdentStr, language_storage::StructTag};
11use serde::{Deserialize, Serialize, de::DeserializeOwned};
12
13use self::{
14    iota_system_state_inner_v1::{IotaSystemStateV1, ValidatorV1},
15    iota_system_state_inner_v2::IotaSystemStateV2,
16    iota_system_state_summary::{IotaSystemStateSummary, IotaValidatorSummary},
17};
18use crate::{
19    IOTA_SYSTEM_ADDRESS, IOTA_SYSTEM_STATE_OBJECT_ID, MoveTypeTagTrait,
20    base_types::ObjectID,
21    committee::CommitteeWithNetworkMetadata,
22    dynamic_field::{Field, get_dynamic_field_from_store, get_dynamic_field_object_from_store},
23    error::IotaError,
24    id::UID,
25    iota_system_state::epoch_start_iota_system_state::EpochStartSystemState,
26    object::{MoveObject, Object},
27    storage::ObjectStore,
28    versioned::Versioned,
29};
30
31pub mod epoch_start_iota_system_state;
32pub mod iota_system_state_inner_v1;
33pub mod iota_system_state_inner_v2;
34pub mod iota_system_state_summary;
35
36#[cfg(msim)]
37mod simtest_iota_system_state_inner;
38#[cfg(msim)]
39use self::simtest_iota_system_state_inner::{
40    SimTestIotaSystemStateDeepV1, SimTestIotaSystemStateShallowV1, SimTestIotaSystemStateV1,
41    SimTestValidatorDeepV1, SimTestValidatorV1,
42};
43
44const IOTA_SYSTEM_STATE_WRAPPER_STRUCT_NAME: &IdentStr = ident_str!("IotaSystemState");
45
46pub const IOTA_SYSTEM_MODULE_NAME: &IdentStr = ident_str!("iota_system");
47pub const ADVANCE_EPOCH_FUNCTION_NAME: &IdentStr = ident_str!("advance_epoch");
48pub const ADVANCE_EPOCH_SAFE_MODE_FUNCTION_NAME: &IdentStr = ident_str!("advance_epoch_safe_mode");
49
50#[cfg(msim)]
51pub const IOTA_SYSTEM_STATE_SIM_TEST_V1: u64 = 18446744073709551605; // u64::MAX - 10
52#[cfg(msim)]
53pub const IOTA_SYSTEM_STATE_SIM_TEST_SHALLOW_V1: u64 = 18446744073709551606; // u64::MAX - 9
54#[cfg(msim)]
55pub const IOTA_SYSTEM_STATE_SIM_TEST_DEEP_V1: u64 = 18446744073709551607; // u64::MAX - 8
56
57/// Rust version of the Move iota::iota_system::IotaSystemState type
58/// This repreents the object with 0x5 ID.
59/// In Rust, this type should be rarely used since it's just a thin
60/// wrapper used to access the inner object.
61/// Within this module, we use it to determine the current version of the system
62/// state inner object type, so that we could deserialize the inner object
63/// correctly. Outside of this module, we only use it in genesis snapshot and
64/// testing.
65#[derive(Debug, Serialize, Deserialize, Clone)]
66pub struct IotaSystemStateWrapper {
67    pub id: UID,
68    pub version: u64,
69}
70
71impl IotaSystemStateWrapper {
72    pub fn type_() -> StructTag {
73        StructTag {
74            address: IOTA_SYSTEM_ADDRESS,
75            name: IOTA_SYSTEM_STATE_WRAPPER_STRUCT_NAME.to_owned(),
76            module: IOTA_SYSTEM_MODULE_NAME.to_owned(),
77            type_params: vec![],
78        }
79    }
80
81    /// Advances epoch in safe mode natively in Rust, without involking Move.
82    /// This ensures that there cannot be any failure from Move and is
83    /// guaranteed to succeed. Returns the old and new inner system state
84    /// object.
85    pub fn advance_epoch_safe_mode(
86        &self,
87        params: &AdvanceEpochParams,
88        object_store: &dyn ObjectStore,
89        protocol_config: &ProtocolConfig,
90    ) -> (Object, Object) {
91        let id = self.id.id.bytes;
92        let old_field_object = get_dynamic_field_object_from_store(object_store, id, &self.version)
93            .expect("Dynamic field object of wrapper should always be present in the object store");
94        let mut new_field_object = old_field_object.clone();
95        let move_object = new_field_object
96            .data
97            .try_as_move_mut()
98            .expect("Dynamic field object must be a Move object");
99        match self.version {
100            1 => {
101                Self::advance_epoch_safe_mode_impl::<IotaSystemStateV1>(
102                    move_object,
103                    params,
104                    protocol_config,
105                );
106            }
107            2 => {
108                Self::advance_epoch_safe_mode_impl::<IotaSystemStateV2>(
109                    move_object,
110                    params,
111                    protocol_config,
112                );
113            }
114            #[cfg(msim)]
115            IOTA_SYSTEM_STATE_SIM_TEST_V1 => {
116                Self::advance_epoch_safe_mode_impl::<SimTestIotaSystemStateV1>(
117                    move_object,
118                    params,
119                    protocol_config,
120                );
121            }
122            #[cfg(msim)]
123            IOTA_SYSTEM_STATE_SIM_TEST_SHALLOW_V1 => {
124                Self::advance_epoch_safe_mode_impl::<SimTestIotaSystemStateShallowV1>(
125                    move_object,
126                    params,
127                    protocol_config,
128                );
129            }
130            #[cfg(msim)]
131            IOTA_SYSTEM_STATE_SIM_TEST_DEEP_V1 => {
132                Self::advance_epoch_safe_mode_impl::<SimTestIotaSystemStateDeepV1>(
133                    move_object,
134                    params,
135                    protocol_config,
136                );
137            }
138            _ => unreachable!(),
139        }
140        (old_field_object, new_field_object)
141    }
142
143    fn advance_epoch_safe_mode_impl<T>(
144        move_object: &mut MoveObject,
145        params: &AdvanceEpochParams,
146        protocol_config: &ProtocolConfig,
147    ) where
148        T: Serialize + DeserializeOwned + IotaSystemStateTrait,
149    {
150        let mut field: Field<u64, T> =
151            bcs::from_bytes(move_object.contents()).expect("bcs deserialization should never fail");
152        tracing::info!(
153            "Advance epoch safe mode: current epoch: {}, protocol_version: {}, system_state_version: {}",
154            field.value.epoch(),
155            field.value.protocol_version(),
156            field.value.system_state_version()
157        );
158        field.value.advance_epoch_safe_mode(params);
159        tracing::info!(
160            "Safe mode activated. New epoch: {}, protocol_version: {}, system_state_version: {}",
161            field.value.epoch(),
162            field.value.protocol_version(),
163            field.value.system_state_version()
164        );
165        let new_contents = bcs::to_bytes(&field).expect("bcs serialization should never fail");
166        move_object
167            .update_contents(new_contents, protocol_config)
168            .expect("Update iota system object content cannot fail since it should be small");
169    }
170}
171
172/// This is the standard API that all inner system state object type should
173/// implement.
174#[enum_dispatch]
175pub trait IotaSystemStateTrait {
176    fn epoch(&self) -> u64;
177    fn reference_gas_price(&self) -> u64;
178    fn protocol_version(&self) -> u64;
179    fn system_state_version(&self) -> u64;
180    fn epoch_start_timestamp_ms(&self) -> u64;
181    fn epoch_duration_ms(&self) -> u64;
182    fn safe_mode(&self) -> bool;
183    fn advance_epoch_safe_mode(&mut self, params: &AdvanceEpochParams);
184    fn get_current_epoch_committee(&self) -> CommitteeWithNetworkMetadata;
185    fn get_pending_active_validators<S: ObjectStore + ?Sized>(
186        &self,
187        object_store: &S,
188    ) -> Result<Vec<IotaValidatorSummary>, IotaError>;
189    fn into_epoch_start_state(self) -> EpochStartSystemState;
190    fn into_iota_system_state_summary(self) -> IotaSystemStateSummary;
191}
192
193/// IotaSystemState provides an abstraction over multiple versions of the inner
194/// IotaSystemStateInner object. This should be the primary interface to the
195/// system state object in Rust. We use enum dispatch to dispatch all methods
196/// defined in IotaSystemStateTrait to the actual implementation in the inner
197/// types.
198#[derive(Debug, Serialize, Deserialize, Clone, Eq, PartialEq)]
199#[enum_dispatch(IotaSystemStateTrait)]
200pub enum IotaSystemState {
201    V1(IotaSystemStateV1),
202    V2(IotaSystemStateV2),
203    #[cfg(msim)]
204    SimTestV1(SimTestIotaSystemStateV1),
205    #[cfg(msim)]
206    SimTestShallowV1(SimTestIotaSystemStateShallowV1),
207    #[cfg(msim)]
208    SimTestDeepV1(SimTestIotaSystemStateDeepV1),
209}
210
211/// This is the fixed type used by genesis.
212pub type IotaSystemStateInnerGenesis = IotaSystemStateV1;
213pub type IotaValidatorGenesis = ValidatorV1;
214
215impl IotaSystemState {
216    /// Always return the version that we will be using for genesis.
217    /// Genesis always uses this version regardless of the current version.
218    /// Note that since it's possible for the actual genesis of the network to
219    /// diverge from the genesis of the latest Rust code, it's important
220    /// that we only use this for tooling purposes.
221    pub fn into_genesis_version_for_tooling(self) -> IotaSystemStateInnerGenesis {
222        match self {
223            IotaSystemState::V1(inner) => inner,
224            // Types other than V1 should be unreachable
225            _ => unreachable!(),
226        }
227    }
228
229    pub fn version(&self) -> u64 {
230        self.system_state_version()
231    }
232}
233
234pub fn get_iota_system_state_wrapper(
235    object_store: &dyn ObjectStore,
236) -> Result<IotaSystemStateWrapper, IotaError> {
237    let wrapper = object_store
238        .get_object(&IOTA_SYSTEM_STATE_OBJECT_ID)?
239        // Don't panic here on None because object_store is a generic store.
240        .ok_or_else(|| {
241            IotaError::IotaSystemStateRead("IotaSystemStateWrapper object not found".to_owned())
242        })?;
243    let move_object = wrapper.data.try_as_move().ok_or_else(|| {
244        IotaError::IotaSystemStateRead(
245            "IotaSystemStateWrapper object must be a Move object".to_owned(),
246        )
247    })?;
248    let result = bcs::from_bytes::<IotaSystemStateWrapper>(move_object.contents())
249        .map_err(|err| IotaError::IotaSystemStateRead(err.to_string()))?;
250    Ok(result)
251}
252
253pub fn get_iota_system_state(object_store: &dyn ObjectStore) -> Result<IotaSystemState, IotaError> {
254    let wrapper = get_iota_system_state_wrapper(object_store)?;
255    let id = wrapper.id.id.bytes;
256    match wrapper.version {
257        1 => {
258            let result: IotaSystemStateV1 =
259                get_dynamic_field_from_store(object_store, id, &wrapper.version).map_err(
260                    |err| {
261                        IotaError::DynamicFieldRead(format!(
262                            "Failed to load iota system state inner object with ID {:?} and version {:?}: {:?}",
263                            id, wrapper.version, err
264                        ))
265                    },
266                )?;
267            Ok(IotaSystemState::V1(result))
268        }
269        2 => {
270            let result: IotaSystemStateV2 =
271                get_dynamic_field_from_store(object_store, id, &wrapper.version).map_err(
272                    |err| {
273                        IotaError::DynamicFieldRead(format!(
274                            "Failed to load iota system state inner object with ID {:?} and version {:?}: {:?}",
275                            id, wrapper.version, err
276                        ))
277                    },
278                )?;
279            Ok(IotaSystemState::V2(result))
280        }
281        #[cfg(msim)]
282        IOTA_SYSTEM_STATE_SIM_TEST_V1 => {
283            let result: SimTestIotaSystemStateV1 =
284                get_dynamic_field_from_store(object_store, id, &wrapper.version).map_err(
285                    |err| {
286                        IotaError::DynamicFieldRead(format!(
287                            "Failed to load iota system state inner object with ID {:?} and version {:?}: {:?}",
288                            id, wrapper.version, err
289                        ))
290                    },
291                )?;
292            Ok(IotaSystemState::SimTestV1(result))
293        }
294        #[cfg(msim)]
295        IOTA_SYSTEM_STATE_SIM_TEST_SHALLOW_V1 => {
296            let result: SimTestIotaSystemStateShallowV1 =
297                get_dynamic_field_from_store(object_store, id, &wrapper.version).map_err(
298                    |err| {
299                        IotaError::DynamicFieldRead(format!(
300                            "Failed to load iota system state inner object with ID {:?} and version {:?}: {:?}",
301                            id, wrapper.version, err
302                        ))
303                    },
304                )?;
305            Ok(IotaSystemState::SimTestShallowV1(result))
306        }
307        #[cfg(msim)]
308        IOTA_SYSTEM_STATE_SIM_TEST_DEEP_V1 => {
309            let result: SimTestIotaSystemStateDeepV1 =
310                get_dynamic_field_from_store(object_store, id, &wrapper.version).map_err(
311                    |err| {
312                        IotaError::DynamicFieldRead(format!(
313                            "Failed to load iota system state inner object with ID {:?} and version {:?}: {:?}",
314                            id, wrapper.version, err
315                        ))
316                    },
317                )?;
318            Ok(IotaSystemState::SimTestDeepV1(result))
319        }
320        _ => Err(IotaError::IotaSystemStateRead(format!(
321            "Unsupported IotaSystemState version: {}",
322            wrapper.version
323        ))),
324    }
325}
326
327/// Given a system state type version, and the ID of the table, along with a
328/// key, retrieve the dynamic field as a Validator type. We need the version to
329/// determine which inner type to use for the Validator type. This is assuming
330/// that the validator is stored in the table as Validator type.
331pub fn get_validator_from_table<K>(
332    object_store: &dyn ObjectStore,
333    table_id: ObjectID,
334    key: &K,
335) -> Result<IotaValidatorSummary, IotaError>
336where
337    K: MoveTypeTagTrait + Serialize + DeserializeOwned + fmt::Debug,
338{
339    let field: Validator =
340        get_dynamic_field_from_store(object_store, table_id, key).map_err(|err| {
341            IotaError::IotaSystemStateRead(format!(
342                "Failed to load validator wrapper from table: {:?}",
343                err
344            ))
345        })?;
346    let versioned = field.inner;
347    let version = versioned.version;
348    match version {
349        1 => {
350            let validator: ValidatorV1 =
351                get_dynamic_field_from_store(object_store, versioned.id.id.bytes, &version)
352                    .map_err(|err| {
353                        IotaError::IotaSystemStateRead(format!(
354                            "Failed to load inner validator from the wrapper: {:?}",
355                            err
356                        ))
357                    })?;
358            Ok(validator.into_iota_validator_summary())
359        }
360        #[cfg(msim)]
361        IOTA_SYSTEM_STATE_SIM_TEST_V1 => {
362            let validator: SimTestValidatorV1 =
363                get_dynamic_field_from_store(object_store, versioned.id.id.bytes, &version)
364                    .map_err(|err| {
365                        IotaError::IotaSystemStateRead(format!(
366                            "Failed to load inner validator from the wrapper: {:?}",
367                            err
368                        ))
369                    })?;
370            Ok(validator.into_iota_validator_summary())
371        }
372        #[cfg(msim)]
373        IOTA_SYSTEM_STATE_SIM_TEST_DEEP_V1 => {
374            let validator: SimTestValidatorDeepV1 =
375                get_dynamic_field_from_store(object_store, versioned.id.id.bytes, &version)
376                    .map_err(|err| {
377                        IotaError::IotaSystemStateRead(format!(
378                            "Failed to load inner validator from the wrapper: {:?}",
379                            err
380                        ))
381                    })?;
382            Ok(validator.into_iota_validator_summary())
383        }
384        _ => Err(IotaError::IotaSystemStateRead(format!(
385            "Unsupported Validator version: {}",
386            version
387        ))),
388    }
389}
390
391pub fn get_validators_from_table_vec<S, ValidatorType>(
392    object_store: &S,
393    table_id: ObjectID,
394    table_size: u64,
395) -> Result<Vec<ValidatorType>, IotaError>
396where
397    S: ObjectStore + ?Sized,
398    ValidatorType: Serialize + DeserializeOwned,
399{
400    let mut validators = vec![];
401    for i in 0..table_size {
402        let validator: ValidatorType = get_dynamic_field_from_store(&object_store, table_id, &i)
403            .map_err(|err| {
404                IotaError::IotaSystemStateRead(format!(
405                    "Failed to load validator from table: {:?}",
406                    err
407                ))
408            })?;
409        validators.push(validator);
410    }
411    Ok(validators)
412}
413
414#[derive(Debug, Serialize, Deserialize, Clone, Eq, PartialEq, Default)]
415pub struct PoolTokenExchangeRate {
416    iota_amount: u64,
417    pool_token_amount: u64,
418}
419
420impl PoolTokenExchangeRate {
421    /// Rate of the staking pool, pool token amount : IOTA amount
422    pub fn rate(&self) -> f64 {
423        if self.iota_amount == 0 {
424            1_f64
425        } else {
426            self.pool_token_amount as f64 / self.iota_amount as f64
427        }
428    }
429
430    pub fn new_for_testing(iota_amount: u64, pool_token_amount: u64) -> Self {
431        Self {
432            iota_amount,
433            pool_token_amount,
434        }
435    }
436}
437
438#[derive(Debug, Serialize, Deserialize, Clone, Eq, PartialEq)]
439pub struct Validator {
440    pub inner: Versioned,
441}
442
443#[derive(Debug)]
444pub struct AdvanceEpochParams {
445    pub epoch: u64,
446    pub next_protocol_version: ProtocolVersion,
447    pub validator_subsidy: u64,
448    pub storage_charge: u64,
449    pub computation_charge: u64,
450    pub computation_charge_burned: u64,
451    pub storage_rebate: u64,
452    pub non_refundable_storage_fee: u64,
453    pub reward_slashing_rate: u64,
454    pub epoch_start_timestamp_ms: u64,
455    pub max_committee_members_count: u64,
456}
457
458#[cfg(msim)]
459pub mod advance_epoch_result_injection {
460    use std::cell::RefCell;
461
462    use crate::{
463        committee::EpochId,
464        error::{ExecutionError, ExecutionErrorKind},
465    };
466
467    thread_local! {
468        /// Override the result of advance_epoch in the range [start, end).
469        static OVERRIDE: RefCell<Option<(EpochId, EpochId)>>  = const { RefCell::new(None) };
470    }
471
472    /// Override the result of advance_epoch transaction if new epoch is in the
473    /// provided range [start, end).
474    pub fn set_override(value: Option<(EpochId, EpochId)>) {
475        OVERRIDE.with(|o| *o.borrow_mut() = value);
476    }
477
478    /// This function is used to modify the result of advance_epoch transaction
479    /// for testing. If the override is set, the result will be an execution
480    /// error, otherwise the original result will be returned.
481    pub fn maybe_modify_result(
482        result: Result<(), ExecutionError>,
483        current_epoch: EpochId,
484    ) -> Result<(), ExecutionError> {
485        if let Some((start, end)) = OVERRIDE.with(|o| *o.borrow()) {
486            if current_epoch >= start && current_epoch < end {
487                return Err::<(), ExecutionError>(ExecutionError::new(
488                    ExecutionErrorKind::FunctionNotFound,
489                    None,
490                ));
491            }
492        }
493        result
494    }
495}