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