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