Skip to main content

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 iota_sdk_move_types::{
11    iota_framework::dynamic_field::Field,
12    iota_system::{
13        iota_system::IotaSystemState as IotaSystemStateWrapper, validator_wrapper::Validator,
14    },
15};
16use iota_sdk_types::{Identifier, MoveStruct, ObjectId};
17use serde::{Deserialize, Serialize, de::DeserializeOwned};
18
19use self::{
20    iota_system_state_inner_v1::{IotaSystemStateV1, ValidatorV1},
21    iota_system_state_inner_v2::IotaSystemStateV2,
22    iota_system_state_summary::{IotaSystemStateSummary, IotaValidatorSummary},
23};
24#[cfg(not(target_arch = "wasm32"))]
25use crate::iota_system_state::epoch_start_iota_system_state::EpochStartSystemState;
26use crate::{
27    MoveTypeTagTrait,
28    committee::CommitteeWithNetworkMetadata,
29    dynamic_field::{get_dynamic_field_from_store, get_dynamic_field_object_from_store},
30    error::IotaError,
31    object::{MoveStructExt, Object},
32    storage::ObjectStore,
33};
34
35// `EpochStartSystemState` pulls in anemo / starfish-config (consensus + p2p),
36// which don't compile to wasm32. It's only consumed by the node, so the whole
37// module and the `into_epoch_start_state` accessor are gated out of wasm.
38#[cfg(not(target_arch = "wasm32"))]
39pub mod epoch_start_iota_system_state;
40pub mod iota_system_state_inner_v1;
41pub mod iota_system_state_inner_v2;
42pub mod iota_system_state_summary;
43
44#[cfg(msim)]
45mod simtest_iota_system_state_inner;
46#[cfg(msim)]
47use self::simtest_iota_system_state_inner::{
48    SimTestIotaSystemStateDeepV1, SimTestIotaSystemStateShallowV1, SimTestIotaSystemStateV1,
49    SimTestValidatorDeepV1, SimTestValidatorV1,
50};
51
52pub const ADVANCE_EPOCH_FUNCTION_NAME: Identifier = Identifier::from_static("advance_epoch");
53pub const ADVANCE_EPOCH_SAFE_MODE_FUNCTION_NAME: Identifier =
54    Identifier::from_static("advance_epoch_safe_mode");
55
56#[cfg(msim)]
57pub const IOTA_SYSTEM_STATE_SIM_TEST_V1: u64 = 18446744073709551605; // u64::MAX - 10
58#[cfg(msim)]
59pub const IOTA_SYSTEM_STATE_SIM_TEST_SHALLOW_V1: u64 = 18446744073709551606; // u64::MAX - 9
60#[cfg(msim)]
61pub const IOTA_SYSTEM_STATE_SIM_TEST_DEEP_V1: u64 = 18446744073709551607; // u64::MAX - 8
62
63mod iota_system_state_wrapper_ext {
64    pub trait Sealed {}
65    impl Sealed for super::IotaSystemStateWrapper {}
66}
67
68pub trait IotaSystemStateWrapperExt: Sized + iota_system_state_wrapper_ext::Sealed {
69    /// Advances epoch in safe mode natively in Rust, without involking Move.
70    /// This ensures that there cannot be any failure from Move and is
71    /// guaranteed to succeed. Returns the old and new inner system state
72    /// object.
73    fn advance_epoch_safe_mode(
74        &self,
75        params: &AdvanceEpochParams,
76        object_store: &dyn ObjectStore,
77        protocol_config: &ProtocolConfig,
78    ) -> (Object, Object);
79}
80
81impl IotaSystemStateWrapperExt for IotaSystemStateWrapper {
82    fn advance_epoch_safe_mode(
83        &self,
84        params: &AdvanceEpochParams,
85        object_store: &dyn ObjectStore,
86        protocol_config: &ProtocolConfig,
87    ) -> (Object, Object) {
88        let id = self.id.id.bytes;
89        let old_field_object = get_dynamic_field_object_from_store(object_store, id, &self.version)
90            .expect("Dynamic field object of wrapper should always be present in the object store");
91        let mut new_field_object = old_field_object.clone();
92        let move_struct = new_field_object
93            .data
94            .as_opt_mut_struct()
95            .expect("Dynamic field object must be a Move object");
96        match self.version {
97            1 => {
98                advance_epoch_safe_mode_impl::<IotaSystemStateV1>(
99                    move_struct,
100                    params,
101                    protocol_config,
102                );
103            }
104            2 => {
105                advance_epoch_safe_mode_impl::<IotaSystemStateV2>(
106                    move_struct,
107                    params,
108                    protocol_config,
109                );
110            }
111            #[cfg(msim)]
112            IOTA_SYSTEM_STATE_SIM_TEST_V1 => {
113                advance_epoch_safe_mode_impl::<SimTestIotaSystemStateV1>(
114                    move_struct,
115                    params,
116                    protocol_config,
117                );
118            }
119            #[cfg(msim)]
120            IOTA_SYSTEM_STATE_SIM_TEST_SHALLOW_V1 => {
121                advance_epoch_safe_mode_impl::<SimTestIotaSystemStateShallowV1>(
122                    move_struct,
123                    params,
124                    protocol_config,
125                );
126            }
127            #[cfg(msim)]
128            IOTA_SYSTEM_STATE_SIM_TEST_DEEP_V1 => {
129                advance_epoch_safe_mode_impl::<SimTestIotaSystemStateDeepV1>(
130                    move_struct,
131                    params,
132                    protocol_config,
133                );
134            }
135            _ => unreachable!(),
136        }
137        (old_field_object, new_field_object)
138    }
139}
140
141fn advance_epoch_safe_mode_impl<T>(
142    move_struct: &mut MoveStruct,
143    params: &AdvanceEpochParams,
144    protocol_config: &ProtocolConfig,
145) where
146    T: Serialize + DeserializeOwned + IotaSystemStateTrait,
147{
148    let mut field: Field<u64, T> =
149        bcs::from_bytes(move_struct.contents()).expect("bcs deserialization should never fail");
150    tracing::info!(
151        "Advance epoch safe mode: current epoch: {}, protocol_version: {}, system_state_version: {}",
152        field.value.epoch(),
153        field.value.protocol_version(),
154        field.value.system_state_version()
155    );
156    field.value.advance_epoch_safe_mode(params);
157    tracing::info!(
158        "Safe mode activated. New epoch: {}, protocol_version: {}, system_state_version: {}",
159        field.value.epoch(),
160        field.value.protocol_version(),
161        field.value.system_state_version()
162    );
163    let new_contents = bcs::to_bytes(&field).expect("bcs serialization should never fail");
164    move_struct
165        .update_contents_advance_epoch_safe_mode(new_contents, protocol_config)
166        .expect(
167            "Update iota system object content cannot fail since it should be small or unbounded",
168        );
169}
170
171/// This is the standard API that all inner system state object type should
172/// implement.
173#[enum_dispatch]
174pub trait IotaSystemStateTrait {
175    fn epoch(&self) -> u64;
176    fn reference_gas_price(&self) -> u64;
177    fn protocol_version(&self) -> u64;
178    fn system_state_version(&self) -> u64;
179    fn epoch_start_timestamp_ms(&self) -> u64;
180    fn epoch_duration_ms(&self) -> u64;
181    fn safe_mode(&self) -> bool;
182    fn advance_epoch_safe_mode(&mut self, params: &AdvanceEpochParams);
183    fn get_current_epoch_committee(&self) -> CommitteeWithNetworkMetadata;
184    fn get_pending_active_validators<S: ObjectStore + ?Sized>(
185        &self,
186        object_store: &S,
187    ) -> Result<Vec<IotaValidatorSummary>, IotaError>;
188    #[cfg(not(target_arch = "wasm32"))]
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    /// Build a minimal `IotaSystemState::V1` whose only meaningful fields are
234    /// `epoch` and `protocol_version`. Everything else is zeroed/defaulted.
235    /// Intended for test fixtures that need a structurally valid system
236    /// state to exercise BCS round-trip paths.
237    pub fn for_testing(epoch: u64, protocol_version: u64) -> Self {
238        use iota_sdk_move_types::{
239            iota_framework::{
240                bag::Bag, object::UID, system_admin_cap::IotaSystemAdminCap, table::Table,
241                table_vec::TableVec, vec_map::VecMap,
242            },
243            iota_system::iota_system_state_inner::SystemParametersV1,
244        };
245        use iota_sdk_types::ObjectId;
246
247        use crate::{
248            balance::{Balance, Supply},
249            coin::TreasuryCap,
250            gas_coin::IotaTreasuryCap,
251            iota_system_state::iota_system_state_inner_v1::{
252                IotaSystemStateV1, StorageFundV1, ValidatorSetV1,
253            },
254        };
255        IotaSystemState::V1(IotaSystemStateV1 {
256            epoch,
257            protocol_version,
258            system_state_version: 1,
259            iota_treasury_cap: IotaTreasuryCap {
260                inner: TreasuryCap {
261                    id: UID::new(ObjectId::ZERO),
262                    total_supply: Supply { value: 0 },
263                },
264            },
265            validators: ValidatorSetV1 {
266                total_stake: 0,
267                active_validators: Vec::new(),
268                pending_active_validators: TableVec::new(Table::new(ObjectId::ZERO.into(), 0)),
269                pending_removals: Vec::new(),
270                staking_pool_mappings: Table::new(ObjectId::ZERO.into(), 0),
271                inactive_validators: Table::new(ObjectId::ZERO.into(), 0),
272                validator_candidates: Table::new(ObjectId::ZERO.into(), 0),
273                at_risk_validators: VecMap {
274                    contents: Vec::new(),
275                },
276                extra_fields: Bag::new(ObjectId::ZERO.into(), 0),
277            },
278            storage_fund: StorageFundV1 {
279                total_object_storage_rebates: Balance::new(0),
280                non_refundable_balance: Balance::new(0),
281            },
282            parameters: SystemParametersV1 {
283                epoch_duration_ms: 0,
284                min_validator_count: 0,
285                max_validator_count: 0,
286                min_validator_joining_stake: 0,
287                validator_low_stake_threshold: 0,
288                validator_very_low_stake_threshold: 0,
289                validator_low_stake_grace_period: 0,
290                extra_fields: Bag::new(ObjectId::ZERO.into(), 0),
291            },
292            iota_system_admin_cap: IotaSystemAdminCap::default(),
293            reference_gas_price: 0,
294            validator_report_records: VecMap {
295                contents: Vec::new(),
296            },
297            safe_mode: false,
298            safe_mode_storage_charges: Balance::new(0),
299            safe_mode_computation_rewards: Balance::new(0),
300            safe_mode_storage_rebates: 0,
301            safe_mode_non_refundable_storage_fee: 0,
302            epoch_start_timestamp_ms: 0,
303            extra_fields: Bag::new(ObjectId::ZERO.into(), 0),
304        })
305    }
306}
307
308/// The raw system state wrapper object together with the
309/// `IotaSystemStateWrapper` decoded from its contents.
310fn get_iota_system_state_wrapper_with_object(
311    object_store: &dyn ObjectStore,
312) -> Result<(Object, IotaSystemStateWrapper), IotaError> {
313    let wrapper_object = object_store
314        .try_get_object(&ObjectId::SYSTEM_STATE)?
315        // Don't panic here on None because object_store is a generic store.
316        .ok_or_else(|| {
317            IotaError::IotaSystemStateRead("IotaSystemStateWrapper object not found".to_owned())
318        })?;
319    let move_object = wrapper_object.data.as_opt_struct().ok_or_else(|| {
320        IotaError::IotaSystemStateRead(
321            "IotaSystemStateWrapper object must be a Move object".to_owned(),
322        )
323    })?;
324    let wrapper = bcs::from_bytes::<IotaSystemStateWrapper>(move_object.contents())
325        .map_err(|err| IotaError::IotaSystemStateRead(err.to_string()))?;
326    Ok((wrapper_object, wrapper))
327}
328
329pub fn get_iota_system_state_wrapper(
330    object_store: &dyn ObjectStore,
331) -> Result<IotaSystemStateWrapper, IotaError> {
332    Ok(get_iota_system_state_wrapper_with_object(object_store)?.1)
333}
334
335pub fn get_iota_system_state(object_store: &dyn ObjectStore) -> Result<IotaSystemState, IotaError> {
336    let wrapper = get_iota_system_state_wrapper(object_store)?;
337    let id = wrapper.id.id.bytes;
338    match wrapper.version {
339        1 => {
340            let result: IotaSystemStateV1 =
341                get_dynamic_field_from_store(object_store, id, &wrapper.version).map_err(
342                    |err| {
343                        IotaError::DynamicFieldRead(format!(
344                            "Failed to load iota system state inner object with ID {:?} and version {:?}: {:?}",
345                            id, wrapper.version, err
346                        ))
347                    },
348                )?;
349            Ok(IotaSystemState::V1(result))
350        }
351        2 => {
352            let result: IotaSystemStateV2 =
353                get_dynamic_field_from_store(object_store, id, &wrapper.version).map_err(
354                    |err| {
355                        IotaError::DynamicFieldRead(format!(
356                            "Failed to load iota system state inner object with ID {:?} and version {:?}: {:?}",
357                            id, wrapper.version, err
358                        ))
359                    },
360                )?;
361            Ok(IotaSystemState::V2(result))
362        }
363        #[cfg(msim)]
364        IOTA_SYSTEM_STATE_SIM_TEST_V1 => {
365            let result: SimTestIotaSystemStateV1 =
366                get_dynamic_field_from_store(object_store, id, &wrapper.version).map_err(
367                    |err| {
368                        IotaError::DynamicFieldRead(format!(
369                            "Failed to load iota system state inner object with ID {:?} and version {:?}: {:?}",
370                            id, wrapper.version, err
371                        ))
372                    },
373                )?;
374            Ok(IotaSystemState::SimTestV1(result))
375        }
376        #[cfg(msim)]
377        IOTA_SYSTEM_STATE_SIM_TEST_SHALLOW_V1 => {
378            let result: SimTestIotaSystemStateShallowV1 =
379                get_dynamic_field_from_store(object_store, id, &wrapper.version).map_err(
380                    |err| {
381                        IotaError::DynamicFieldRead(format!(
382                            "Failed to load iota system state inner object with ID {:?} and version {:?}: {:?}",
383                            id, wrapper.version, err
384                        ))
385                    },
386                )?;
387            Ok(IotaSystemState::SimTestShallowV1(result))
388        }
389        #[cfg(msim)]
390        IOTA_SYSTEM_STATE_SIM_TEST_DEEP_V1 => {
391            let result: SimTestIotaSystemStateDeepV1 =
392                get_dynamic_field_from_store(object_store, id, &wrapper.version).map_err(
393                    |err| {
394                        IotaError::DynamicFieldRead(format!(
395                            "Failed to load iota system state inner object with ID {:?} and version {:?}: {:?}",
396                            id, wrapper.version, err
397                        ))
398                    },
399                )?;
400            Ok(IotaSystemState::SimTestDeepV1(result))
401        }
402        _ => Err(IotaError::IotaSystemStateRead(format!(
403            "Unsupported IotaSystemState version: {}",
404            wrapper.version
405        ))),
406    }
407}
408
409/// The two objects `get_iota_system_state` reads to decode the system state:
410/// the raw system state wrapper object and its inner system-state object. These
411/// two fully determine the state, so none of the per-validator objects the
412/// epoch-change tx also writes are needed. Returned as raw `Object`s so a
413/// caller can persist the exact bytes their `ObjectDigest`s commit to.
414pub fn get_iota_system_state_objects(
415    object_store: &dyn ObjectStore,
416) -> Result<[Object; 2], IotaError> {
417    let (wrapper_object, wrapper) = get_iota_system_state_wrapper_with_object(object_store)?;
418    // Same derivation as `get_iota_system_state`, so this can never select a
419    // different inner object than the one that decodes the state.
420    let inner_object =
421        get_dynamic_field_object_from_store(object_store, wrapper.id.id.bytes, &wrapper.version)?;
422    Ok([wrapper_object, inner_object])
423}
424
425/// Given a system state type version, and the ID of the table, along with a
426/// key, retrieve the dynamic field as a Validator type. We need the version to
427/// determine which inner type to use for the Validator type. This is assuming
428/// that the validator is stored in the table as Validator type.
429pub fn get_validator_from_table<K>(
430    object_store: &dyn ObjectStore,
431    table_id: ObjectId,
432    key: &K,
433    protocol_version: Option<u64>,
434) -> Result<IotaValidatorSummary, IotaError>
435where
436    K: MoveTypeTagTrait + Serialize + DeserializeOwned + fmt::Debug,
437{
438    let wrapper: Validator =
439        get_dynamic_field_from_store(object_store, table_id, key).map_err(|err| {
440            IotaError::IotaSystemStateRead(format!(
441                "Failed to load validator wrapper from table: {err:?}"
442            ))
443        })?;
444    validator_summary_from_wrapper(object_store, wrapper, protocol_version)
445}
446
447/// The summary of the validator a table's `Validator` wrapper holds, for a
448/// caller that has already read the wrapper; as [`get_validator_from_table`]
449/// otherwise.
450pub fn validator_summary_from_wrapper(
451    object_store: &dyn ObjectStore,
452    wrapper: Validator,
453    protocol_version: Option<u64>,
454) -> Result<IotaValidatorSummary, IotaError> {
455    let versioned = wrapper.inner;
456    let version = versioned.version;
457    match version {
458        1 => {
459            let validator: ValidatorV1 =
460                get_dynamic_field_from_store(object_store, versioned.id.id.bytes, &version)
461                    .map_err(|err| {
462                        IotaError::IotaSystemStateRead(format!(
463                            "Failed to load inner validator from the wrapper: {err:?}"
464                        ))
465                    })?;
466            Ok(validator.into_iota_validator_summary(protocol_version))
467        }
468        #[cfg(msim)]
469        IOTA_SYSTEM_STATE_SIM_TEST_V1 => {
470            let validator: SimTestValidatorV1 =
471                get_dynamic_field_from_store(object_store, versioned.id.id.bytes, &version)
472                    .map_err(|err| {
473                        IotaError::IotaSystemStateRead(format!(
474                            "Failed to load inner validator from the wrapper: {err:?}"
475                        ))
476                    })?;
477            Ok(validator.into_iota_validator_summary())
478        }
479        #[cfg(msim)]
480        IOTA_SYSTEM_STATE_SIM_TEST_DEEP_V1 => {
481            let validator: SimTestValidatorDeepV1 =
482                get_dynamic_field_from_store(object_store, versioned.id.id.bytes, &version)
483                    .map_err(|err| {
484                        IotaError::IotaSystemStateRead(format!(
485                            "Failed to load inner validator from the wrapper: {err:?}"
486                        ))
487                    })?;
488            Ok(validator.into_iota_validator_summary())
489        }
490        _ => Err(IotaError::IotaSystemStateRead(format!(
491            "Unsupported Validator version: {version}"
492        ))),
493    }
494}
495
496pub fn get_validators_from_table_vec<S, ValidatorType>(
497    object_store: &S,
498    table_id: ObjectId,
499    table_size: u64,
500) -> Result<Vec<ValidatorType>, IotaError>
501where
502    S: ObjectStore + ?Sized,
503    ValidatorType: Serialize + DeserializeOwned,
504{
505    let mut validators = vec![];
506    for i in 0..table_size {
507        let validator: ValidatorType = get_dynamic_field_from_store(&object_store, table_id, &i)
508            .map_err(|err| {
509                IotaError::IotaSystemStateRead(format!(
510                    "Failed to load validator from table: {err:?}"
511                ))
512            })?;
513        validators.push(validator);
514    }
515    Ok(validators)
516}
517
518#[derive(Debug, Serialize, Deserialize, Clone, Eq, PartialEq, Default)]
519pub struct PoolTokenExchangeRate {
520    iota_amount: u64,
521    pool_token_amount: u64,
522}
523
524impl PoolTokenExchangeRate {
525    /// Rate of the staking pool, pool token amount : IOTA amount
526    pub fn rate(&self) -> f64 {
527        if self.iota_amount == 0 {
528            1_f64
529        } else {
530            self.pool_token_amount as f64 / self.iota_amount as f64
531        }
532    }
533
534    pub fn new_for_testing(iota_amount: u64, pool_token_amount: u64) -> Self {
535        Self {
536            iota_amount,
537            pool_token_amount,
538        }
539    }
540}
541
542#[derive(Debug)]
543pub struct AdvanceEpochParams {
544    pub epoch: u64,
545    pub next_protocol_version: ProtocolVersion,
546    pub validator_subsidy: u64,
547    pub storage_charge: u64,
548    pub computation_charge: u64,
549    pub computation_charge_burned: u64,
550    pub storage_rebate: u64,
551    pub non_refundable_storage_fee: u64,
552    pub reward_slashing_rate: u64,
553    pub epoch_start_timestamp_ms: u64,
554    pub max_committee_members_count: u64,
555    pub eligible_active_validators: Vec<u64>,
556    pub scores: Vec<u64>,
557    pub adjust_rewards_by_score: bool,
558}
559
560#[cfg(msim)]
561pub mod advance_epoch_result_injection {
562    use std::cell::RefCell;
563
564    use crate::{
565        committee::EpochId,
566        error::{ExecutionError, ExecutionErrorKind},
567        execution::ResultWithTimings,
568    };
569
570    thread_local! {
571        /// Override the result of advance_epoch in the range [start, end).
572        static OVERRIDE: RefCell<Option<(EpochId, EpochId)>>  = const { RefCell::new(None) };
573    }
574
575    /// Override the result of advance_epoch transaction if new epoch is in the
576    /// provided range [start, end).
577    pub fn set_override(value: Option<(EpochId, EpochId)>) {
578        OVERRIDE.with(|o| *o.borrow_mut() = value);
579    }
580
581    /// This function is used to modify the result of advance_epoch transaction
582    /// for testing. If the override is set, the result will be an execution
583    /// error, otherwise the original result will be returned.
584    pub fn maybe_modify_result(
585        result: ResultWithTimings<(), ExecutionError>,
586        current_epoch: EpochId,
587    ) -> ResultWithTimings<(), ExecutionError> {
588        if let Some((start, end)) = OVERRIDE.with(|o| *o.borrow()) {
589            if current_epoch >= start && current_epoch < end {
590                return Err((
591                    ExecutionError::new(ExecutionErrorKind::FunctionNotFound, None),
592                    vec![],
593                ));
594            }
595        }
596        result
597    }
598}