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, MoveStruct, 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::{MoveStructExt, 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_struct = 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_struct,
96                    params,
97                    protocol_config,
98                );
99            }
100            2 => {
101                Self::advance_epoch_safe_mode_impl::<IotaSystemStateV2>(
102                    move_struct,
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_struct,
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_struct,
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_struct,
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_struct: &mut MoveStruct,
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_struct.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_struct
160            .update_contents_advance_epoch_safe_mode(new_contents, protocol_config)
161            .expect(
162                "Update iota system object content cannot fail since it should be small or unbounded",
163            );
164    }
165}
166
167/// This is the standard API that all inner system state object type should
168/// implement.
169#[enum_dispatch]
170pub trait IotaSystemStateTrait {
171    fn epoch(&self) -> u64;
172    fn reference_gas_price(&self) -> u64;
173    fn protocol_version(&self) -> u64;
174    fn system_state_version(&self) -> u64;
175    fn epoch_start_timestamp_ms(&self) -> u64;
176    fn epoch_duration_ms(&self) -> u64;
177    fn safe_mode(&self) -> bool;
178    fn advance_epoch_safe_mode(&mut self, params: &AdvanceEpochParams);
179    fn get_current_epoch_committee(&self) -> CommitteeWithNetworkMetadata;
180    fn get_pending_active_validators<S: ObjectStore + ?Sized>(
181        &self,
182        object_store: &S,
183    ) -> Result<Vec<IotaValidatorSummary>, IotaError>;
184    #[cfg(not(target_arch = "wasm32"))]
185    fn into_epoch_start_state(self) -> EpochStartSystemState;
186    fn into_iota_system_state_summary(self) -> IotaSystemStateSummary;
187}
188
189/// IotaSystemState provides an abstraction over multiple versions of the inner
190/// IotaSystemStateInner object. This should be the primary interface to the
191/// system state object in Rust. We use enum dispatch to dispatch all methods
192/// defined in IotaSystemStateTrait to the actual implementation in the inner
193/// types.
194#[derive(Debug, Serialize, Deserialize, Clone, Eq, PartialEq)]
195#[enum_dispatch(IotaSystemStateTrait)]
196pub enum IotaSystemState {
197    V1(IotaSystemStateV1),
198    V2(IotaSystemStateV2),
199    #[cfg(msim)]
200    SimTestV1(SimTestIotaSystemStateV1),
201    #[cfg(msim)]
202    SimTestShallowV1(SimTestIotaSystemStateShallowV1),
203    #[cfg(msim)]
204    SimTestDeepV1(SimTestIotaSystemStateDeepV1),
205}
206
207/// This is the fixed type used by genesis.
208pub type IotaSystemStateInnerGenesis = IotaSystemStateV1;
209pub type IotaValidatorGenesis = ValidatorV1;
210
211impl IotaSystemState {
212    /// Always return the version that we will be using for genesis.
213    /// Genesis always uses this version regardless of the current version.
214    /// Note that since it's possible for the actual genesis of the network to
215    /// diverge from the genesis of the latest Rust code, it's important
216    /// that we only use this for tooling purposes.
217    pub fn into_genesis_version_for_tooling(self) -> IotaSystemStateInnerGenesis {
218        match self {
219            IotaSystemState::V1(inner) => inner,
220            // Types other than V1 should be unreachable
221            _ => unreachable!(),
222        }
223    }
224
225    pub fn version(&self) -> u64 {
226        self.system_state_version()
227    }
228
229    /// Build a minimal `IotaSystemState::V1` whose only meaningful fields are
230    /// `epoch` and `protocol_version`. Everything else is zeroed/defaulted.
231    /// Intended for test fixtures that need a structurally valid system
232    /// state to exercise BCS round-trip paths.
233    pub fn for_testing(epoch: u64, protocol_version: u64) -> Self {
234        use iota_sdk_types::ObjectId;
235
236        use crate::{
237            balance::{Balance, Supply},
238            coin::TreasuryCap,
239            collection_types::{Bag, Table, TableVec, VecMap},
240            gas_coin::IotaTreasuryCap,
241            id::UID,
242            iota_system_state::iota_system_state_inner_v1::{
243                IotaSystemStateV1, StorageFundV1, SystemParametersV1, ValidatorSetV1,
244            },
245            system_admin_cap::IotaSystemAdminCap,
246        };
247        IotaSystemState::V1(IotaSystemStateV1 {
248            epoch,
249            protocol_version,
250            system_state_version: 1,
251            iota_treasury_cap: IotaTreasuryCap {
252                inner: TreasuryCap {
253                    id: UID::new(ObjectId::ZERO),
254                    total_supply: Supply { value: 0 },
255                },
256            },
257            validators: ValidatorSetV1 {
258                total_stake: 0,
259                active_validators: Vec::new(),
260                pending_active_validators: TableVec::default(),
261                pending_removals: Vec::new(),
262                staking_pool_mappings: Table::default(),
263                inactive_validators: Table::default(),
264                validator_candidates: Table::default(),
265                at_risk_validators: VecMap {
266                    contents: Vec::new(),
267                },
268                extra_fields: Bag::default(),
269            },
270            storage_fund: StorageFundV1 {
271                total_object_storage_rebates: Balance::new(0),
272                non_refundable_balance: Balance::new(0),
273            },
274            parameters: SystemParametersV1 {
275                epoch_duration_ms: 0,
276                min_validator_count: 0,
277                max_validator_count: 0,
278                min_validator_joining_stake: 0,
279                validator_low_stake_threshold: 0,
280                validator_very_low_stake_threshold: 0,
281                validator_low_stake_grace_period: 0,
282                extra_fields: Bag::default(),
283            },
284            iota_system_admin_cap: IotaSystemAdminCap::default(),
285            reference_gas_price: 0,
286            validator_report_records: VecMap {
287                contents: Vec::new(),
288            },
289            safe_mode: false,
290            safe_mode_storage_charges: Balance::new(0),
291            safe_mode_computation_rewards: Balance::new(0),
292            safe_mode_storage_rebates: 0,
293            safe_mode_non_refundable_storage_fee: 0,
294            epoch_start_timestamp_ms: 0,
295            extra_fields: Bag::default(),
296        })
297    }
298}
299
300/// The raw system state wrapper object together with the
301/// `IotaSystemStateWrapper` decoded from its contents.
302fn get_iota_system_state_wrapper_with_object(
303    object_store: &dyn ObjectStore,
304) -> Result<(Object, IotaSystemStateWrapper), IotaError> {
305    let wrapper_object = object_store
306        .try_get_object(&ObjectId::SYSTEM_STATE)?
307        // Don't panic here on None because object_store is a generic store.
308        .ok_or_else(|| {
309            IotaError::IotaSystemStateRead("IotaSystemStateWrapper object not found".to_owned())
310        })?;
311    let move_object = wrapper_object.data.as_opt_struct().ok_or_else(|| {
312        IotaError::IotaSystemStateRead(
313            "IotaSystemStateWrapper object must be a Move object".to_owned(),
314        )
315    })?;
316    let wrapper = bcs::from_bytes::<IotaSystemStateWrapper>(move_object.contents())
317        .map_err(|err| IotaError::IotaSystemStateRead(err.to_string()))?;
318    Ok((wrapper_object, wrapper))
319}
320
321pub fn get_iota_system_state_wrapper(
322    object_store: &dyn ObjectStore,
323) -> Result<IotaSystemStateWrapper, IotaError> {
324    Ok(get_iota_system_state_wrapper_with_object(object_store)?.1)
325}
326
327pub fn get_iota_system_state(object_store: &dyn ObjectStore) -> Result<IotaSystemState, IotaError> {
328    let wrapper = get_iota_system_state_wrapper(object_store)?;
329    let id = wrapper.id.id.bytes;
330    match wrapper.version {
331        1 => {
332            let result: IotaSystemStateV1 =
333                get_dynamic_field_from_store(object_store, id, &wrapper.version).map_err(
334                    |err| {
335                        IotaError::DynamicFieldRead(format!(
336                            "Failed to load iota system state inner object with ID {:?} and version {:?}: {:?}",
337                            id, wrapper.version, err
338                        ))
339                    },
340                )?;
341            Ok(IotaSystemState::V1(result))
342        }
343        2 => {
344            let result: IotaSystemStateV2 =
345                get_dynamic_field_from_store(object_store, id, &wrapper.version).map_err(
346                    |err| {
347                        IotaError::DynamicFieldRead(format!(
348                            "Failed to load iota system state inner object with ID {:?} and version {:?}: {:?}",
349                            id, wrapper.version, err
350                        ))
351                    },
352                )?;
353            Ok(IotaSystemState::V2(result))
354        }
355        #[cfg(msim)]
356        IOTA_SYSTEM_STATE_SIM_TEST_V1 => {
357            let result: SimTestIotaSystemStateV1 =
358                get_dynamic_field_from_store(object_store, id, &wrapper.version).map_err(
359                    |err| {
360                        IotaError::DynamicFieldRead(format!(
361                            "Failed to load iota system state inner object with ID {:?} and version {:?}: {:?}",
362                            id, wrapper.version, err
363                        ))
364                    },
365                )?;
366            Ok(IotaSystemState::SimTestV1(result))
367        }
368        #[cfg(msim)]
369        IOTA_SYSTEM_STATE_SIM_TEST_SHALLOW_V1 => {
370            let result: SimTestIotaSystemStateShallowV1 =
371                get_dynamic_field_from_store(object_store, id, &wrapper.version).map_err(
372                    |err| {
373                        IotaError::DynamicFieldRead(format!(
374                            "Failed to load iota system state inner object with ID {:?} and version {:?}: {:?}",
375                            id, wrapper.version, err
376                        ))
377                    },
378                )?;
379            Ok(IotaSystemState::SimTestShallowV1(result))
380        }
381        #[cfg(msim)]
382        IOTA_SYSTEM_STATE_SIM_TEST_DEEP_V1 => {
383            let result: SimTestIotaSystemStateDeepV1 =
384                get_dynamic_field_from_store(object_store, id, &wrapper.version).map_err(
385                    |err| {
386                        IotaError::DynamicFieldRead(format!(
387                            "Failed to load iota system state inner object with ID {:?} and version {:?}: {:?}",
388                            id, wrapper.version, err
389                        ))
390                    },
391                )?;
392            Ok(IotaSystemState::SimTestDeepV1(result))
393        }
394        _ => Err(IotaError::IotaSystemStateRead(format!(
395            "Unsupported IotaSystemState version: {}",
396            wrapper.version
397        ))),
398    }
399}
400
401/// The two objects `get_iota_system_state` reads to decode the system state:
402/// the raw system state wrapper object and its inner system-state object. These
403/// two fully determine the state, so none of the per-validator objects the
404/// epoch-change tx also writes are needed. Returned as raw `Object`s so a
405/// caller can persist the exact bytes their `ObjectDigest`s commit to.
406pub fn get_iota_system_state_objects(
407    object_store: &dyn ObjectStore,
408) -> Result<[Object; 2], IotaError> {
409    let (wrapper_object, wrapper) = get_iota_system_state_wrapper_with_object(object_store)?;
410    // Same derivation as `get_iota_system_state`, so this can never select a
411    // different inner object than the one that decodes the state.
412    let inner_object =
413        get_dynamic_field_object_from_store(object_store, wrapper.id.id.bytes, &wrapper.version)?;
414    Ok([wrapper_object, inner_object])
415}
416
417/// Given a system state type version, and the ID of the table, along with a
418/// key, retrieve the dynamic field as a Validator type. We need the version to
419/// determine which inner type to use for the Validator type. This is assuming
420/// that the validator is stored in the table as Validator type.
421pub fn get_validator_from_table<K>(
422    object_store: &dyn ObjectStore,
423    table_id: ObjectId,
424    key: &K,
425    protocol_version: Option<u64>,
426) -> Result<IotaValidatorSummary, IotaError>
427where
428    K: MoveTypeTagTrait + Serialize + DeserializeOwned + fmt::Debug,
429{
430    let field: Validator =
431        get_dynamic_field_from_store(object_store, table_id, key).map_err(|err| {
432            IotaError::IotaSystemStateRead(format!(
433                "Failed to load validator wrapper from table: {err:?}"
434            ))
435        })?;
436    let versioned = field.inner;
437    let version = versioned.version;
438    match version {
439        1 => {
440            let validator: ValidatorV1 =
441                get_dynamic_field_from_store(object_store, versioned.id.id.bytes, &version)
442                    .map_err(|err| {
443                        IotaError::IotaSystemStateRead(format!(
444                            "Failed to load inner validator from the wrapper: {err:?}"
445                        ))
446                    })?;
447            Ok(validator.into_iota_validator_summary(protocol_version))
448        }
449        #[cfg(msim)]
450        IOTA_SYSTEM_STATE_SIM_TEST_V1 => {
451            let validator: SimTestValidatorV1 =
452                get_dynamic_field_from_store(object_store, versioned.id.id.bytes, &version)
453                    .map_err(|err| {
454                        IotaError::IotaSystemStateRead(format!(
455                            "Failed to load inner validator from the wrapper: {err:?}"
456                        ))
457                    })?;
458            Ok(validator.into_iota_validator_summary())
459        }
460        #[cfg(msim)]
461        IOTA_SYSTEM_STATE_SIM_TEST_DEEP_V1 => {
462            let validator: SimTestValidatorDeepV1 =
463                get_dynamic_field_from_store(object_store, versioned.id.id.bytes, &version)
464                    .map_err(|err| {
465                        IotaError::IotaSystemStateRead(format!(
466                            "Failed to load inner validator from the wrapper: {err:?}"
467                        ))
468                    })?;
469            Ok(validator.into_iota_validator_summary())
470        }
471        _ => Err(IotaError::IotaSystemStateRead(format!(
472            "Unsupported Validator version: {version}"
473        ))),
474    }
475}
476
477pub fn get_validators_from_table_vec<S, ValidatorType>(
478    object_store: &S,
479    table_id: ObjectId,
480    table_size: u64,
481) -> Result<Vec<ValidatorType>, IotaError>
482where
483    S: ObjectStore + ?Sized,
484    ValidatorType: Serialize + DeserializeOwned,
485{
486    let mut validators = vec![];
487    for i in 0..table_size {
488        let validator: ValidatorType = get_dynamic_field_from_store(&object_store, table_id, &i)
489            .map_err(|err| {
490                IotaError::IotaSystemStateRead(format!(
491                    "Failed to load validator from table: {err:?}"
492                ))
493            })?;
494        validators.push(validator);
495    }
496    Ok(validators)
497}
498
499#[derive(Debug, Serialize, Deserialize, Clone, Eq, PartialEq, Default)]
500pub struct PoolTokenExchangeRate {
501    iota_amount: u64,
502    pool_token_amount: u64,
503}
504
505impl PoolTokenExchangeRate {
506    /// Rate of the staking pool, pool token amount : IOTA amount
507    pub fn rate(&self) -> f64 {
508        if self.iota_amount == 0 {
509            1_f64
510        } else {
511            self.pool_token_amount as f64 / self.iota_amount as f64
512        }
513    }
514
515    pub fn new_for_testing(iota_amount: u64, pool_token_amount: u64) -> Self {
516        Self {
517            iota_amount,
518            pool_token_amount,
519        }
520    }
521}
522
523#[derive(Debug, Serialize, Deserialize, Clone, Eq, PartialEq)]
524pub struct Validator {
525    pub inner: Versioned,
526}
527
528#[derive(Debug)]
529pub struct AdvanceEpochParams {
530    pub epoch: u64,
531    pub next_protocol_version: ProtocolVersion,
532    pub validator_subsidy: u64,
533    pub storage_charge: u64,
534    pub computation_charge: u64,
535    pub computation_charge_burned: u64,
536    pub storage_rebate: u64,
537    pub non_refundable_storage_fee: u64,
538    pub reward_slashing_rate: u64,
539    pub epoch_start_timestamp_ms: u64,
540    pub max_committee_members_count: u64,
541    pub eligible_active_validators: Vec<u64>,
542    pub scores: Vec<u64>,
543    pub adjust_rewards_by_score: bool,
544}
545
546#[cfg(msim)]
547pub mod advance_epoch_result_injection {
548    use std::cell::RefCell;
549
550    use crate::{
551        committee::EpochId,
552        error::{ExecutionError, ExecutionErrorKind},
553        execution::ResultWithTimings,
554    };
555
556    thread_local! {
557        /// Override the result of advance_epoch in the range [start, end).
558        static OVERRIDE: RefCell<Option<(EpochId, EpochId)>>  = const { RefCell::new(None) };
559    }
560
561    /// Override the result of advance_epoch transaction if new epoch is in the
562    /// provided range [start, end).
563    pub fn set_override(value: Option<(EpochId, EpochId)>) {
564        OVERRIDE.with(|o| *o.borrow_mut() = value);
565    }
566
567    /// This function is used to modify the result of advance_epoch transaction
568    /// for testing. If the override is set, the result will be an execution
569    /// error, otherwise the original result will be returned.
570    pub fn maybe_modify_result(
571        result: ResultWithTimings<(), ExecutionError>,
572        current_epoch: EpochId,
573    ) -> ResultWithTimings<(), ExecutionError> {
574        if let Some((start, end)) = OVERRIDE.with(|o| *o.borrow()) {
575            if current_epoch >= start && current_epoch < end {
576                return Err((
577                    ExecutionError::new(ExecutionErrorKind::FunctionNotFound, None),
578                    vec![],
579                ));
580            }
581        }
582        result
583    }
584}