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_struct_mut_opt()
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
228pub fn get_iota_system_state_wrapper(
229    object_store: &dyn ObjectStore,
230) -> Result<IotaSystemStateWrapper, IotaError> {
231    let wrapper = object_store
232        .try_get_object(&ObjectId::SYSTEM_STATE)?
233        // Don't panic here on None because object_store is a generic store.
234        .ok_or_else(|| {
235            IotaError::IotaSystemStateRead("IotaSystemStateWrapper object not found".to_owned())
236        })?;
237    let move_object = wrapper.data.as_struct_opt().ok_or_else(|| {
238        IotaError::IotaSystemStateRead(
239            "IotaSystemStateWrapper object must be a Move object".to_owned(),
240        )
241    })?;
242    let result = bcs::from_bytes::<IotaSystemStateWrapper>(move_object.contents())
243        .map_err(|err| IotaError::IotaSystemStateRead(err.to_string()))?;
244    Ok(result)
245}
246
247pub fn get_iota_system_state(object_store: &dyn ObjectStore) -> Result<IotaSystemState, IotaError> {
248    let wrapper = get_iota_system_state_wrapper(object_store)?;
249    let id = wrapper.id.id.bytes;
250    match wrapper.version {
251        1 => {
252            let result: IotaSystemStateV1 =
253                get_dynamic_field_from_store(object_store, id, &wrapper.version).map_err(
254                    |err| {
255                        IotaError::DynamicFieldRead(format!(
256                            "Failed to load iota system state inner object with ID {:?} and version {:?}: {:?}",
257                            id, wrapper.version, err
258                        ))
259                    },
260                )?;
261            Ok(IotaSystemState::V1(result))
262        }
263        2 => {
264            let result: IotaSystemStateV2 =
265                get_dynamic_field_from_store(object_store, id, &wrapper.version).map_err(
266                    |err| {
267                        IotaError::DynamicFieldRead(format!(
268                            "Failed to load iota system state inner object with ID {:?} and version {:?}: {:?}",
269                            id, wrapper.version, err
270                        ))
271                    },
272                )?;
273            Ok(IotaSystemState::V2(result))
274        }
275        #[cfg(msim)]
276        IOTA_SYSTEM_STATE_SIM_TEST_V1 => {
277            let result: SimTestIotaSystemStateV1 =
278                get_dynamic_field_from_store(object_store, id, &wrapper.version).map_err(
279                    |err| {
280                        IotaError::DynamicFieldRead(format!(
281                            "Failed to load iota system state inner object with ID {:?} and version {:?}: {:?}",
282                            id, wrapper.version, err
283                        ))
284                    },
285                )?;
286            Ok(IotaSystemState::SimTestV1(result))
287        }
288        #[cfg(msim)]
289        IOTA_SYSTEM_STATE_SIM_TEST_SHALLOW_V1 => {
290            let result: SimTestIotaSystemStateShallowV1 =
291                get_dynamic_field_from_store(object_store, id, &wrapper.version).map_err(
292                    |err| {
293                        IotaError::DynamicFieldRead(format!(
294                            "Failed to load iota system state inner object with ID {:?} and version {:?}: {:?}",
295                            id, wrapper.version, err
296                        ))
297                    },
298                )?;
299            Ok(IotaSystemState::SimTestShallowV1(result))
300        }
301        #[cfg(msim)]
302        IOTA_SYSTEM_STATE_SIM_TEST_DEEP_V1 => {
303            let result: SimTestIotaSystemStateDeepV1 =
304                get_dynamic_field_from_store(object_store, id, &wrapper.version).map_err(
305                    |err| {
306                        IotaError::DynamicFieldRead(format!(
307                            "Failed to load iota system state inner object with ID {:?} and version {:?}: {:?}",
308                            id, wrapper.version, err
309                        ))
310                    },
311                )?;
312            Ok(IotaSystemState::SimTestDeepV1(result))
313        }
314        _ => Err(IotaError::IotaSystemStateRead(format!(
315            "Unsupported IotaSystemState version: {}",
316            wrapper.version
317        ))),
318    }
319}
320
321/// Given a system state type version, and the ID of the table, along with a
322/// key, retrieve the dynamic field as a Validator type. We need the version to
323/// determine which inner type to use for the Validator type. This is assuming
324/// that the validator is stored in the table as Validator type.
325pub fn get_validator_from_table<K>(
326    object_store: &dyn ObjectStore,
327    table_id: ObjectId,
328    key: &K,
329    protocol_version: Option<u64>,
330) -> Result<IotaValidatorSummary, IotaError>
331where
332    K: MoveTypeTagTrait + Serialize + DeserializeOwned + fmt::Debug,
333{
334    let field: Validator =
335        get_dynamic_field_from_store(object_store, table_id, key).map_err(|err| {
336            IotaError::IotaSystemStateRead(format!(
337                "Failed to load validator wrapper from table: {err:?}"
338            ))
339        })?;
340    let versioned = field.inner;
341    let version = versioned.version;
342    match version {
343        1 => {
344            let validator: ValidatorV1 =
345                get_dynamic_field_from_store(object_store, versioned.id.id.bytes, &version)
346                    .map_err(|err| {
347                        IotaError::IotaSystemStateRead(format!(
348                            "Failed to load inner validator from the wrapper: {err:?}"
349                        ))
350                    })?;
351            Ok(validator.into_iota_validator_summary(protocol_version))
352        }
353        #[cfg(msim)]
354        IOTA_SYSTEM_STATE_SIM_TEST_V1 => {
355            let validator: SimTestValidatorV1 =
356                get_dynamic_field_from_store(object_store, versioned.id.id.bytes, &version)
357                    .map_err(|err| {
358                        IotaError::IotaSystemStateRead(format!(
359                            "Failed to load inner validator from the wrapper: {:?}",
360                            err
361                        ))
362                    })?;
363            Ok(validator.into_iota_validator_summary())
364        }
365        #[cfg(msim)]
366        IOTA_SYSTEM_STATE_SIM_TEST_DEEP_V1 => {
367            let validator: SimTestValidatorDeepV1 =
368                get_dynamic_field_from_store(object_store, versioned.id.id.bytes, &version)
369                    .map_err(|err| {
370                        IotaError::IotaSystemStateRead(format!(
371                            "Failed to load inner validator from the wrapper: {:?}",
372                            err
373                        ))
374                    })?;
375            Ok(validator.into_iota_validator_summary())
376        }
377        _ => Err(IotaError::IotaSystemStateRead(format!(
378            "Unsupported Validator version: {version}"
379        ))),
380    }
381}
382
383pub fn get_validators_from_table_vec<S, ValidatorType>(
384    object_store: &S,
385    table_id: ObjectId,
386    table_size: u64,
387) -> Result<Vec<ValidatorType>, IotaError>
388where
389    S: ObjectStore + ?Sized,
390    ValidatorType: Serialize + DeserializeOwned,
391{
392    let mut validators = vec![];
393    for i in 0..table_size {
394        let validator: ValidatorType = get_dynamic_field_from_store(&object_store, table_id, &i)
395            .map_err(|err| {
396                IotaError::IotaSystemStateRead(format!(
397                    "Failed to load validator from table: {err:?}"
398                ))
399            })?;
400        validators.push(validator);
401    }
402    Ok(validators)
403}
404
405#[derive(Debug, Serialize, Deserialize, Clone, Eq, PartialEq, Default)]
406pub struct PoolTokenExchangeRate {
407    iota_amount: u64,
408    pool_token_amount: u64,
409}
410
411impl PoolTokenExchangeRate {
412    /// Rate of the staking pool, pool token amount : IOTA amount
413    pub fn rate(&self) -> f64 {
414        if self.iota_amount == 0 {
415            1_f64
416        } else {
417            self.pool_token_amount as f64 / self.iota_amount as f64
418        }
419    }
420
421    pub fn new_for_testing(iota_amount: u64, pool_token_amount: u64) -> Self {
422        Self {
423            iota_amount,
424            pool_token_amount,
425        }
426    }
427}
428
429#[derive(Debug, Serialize, Deserialize, Clone, Eq, PartialEq)]
430pub struct Validator {
431    pub inner: Versioned,
432}
433
434#[derive(Debug)]
435pub struct AdvanceEpochParams {
436    pub epoch: u64,
437    pub next_protocol_version: ProtocolVersion,
438    pub validator_subsidy: u64,
439    pub storage_charge: u64,
440    pub computation_charge: u64,
441    pub computation_charge_burned: u64,
442    pub storage_rebate: u64,
443    pub non_refundable_storage_fee: u64,
444    pub reward_slashing_rate: u64,
445    pub epoch_start_timestamp_ms: u64,
446    pub max_committee_members_count: u64,
447    pub eligible_active_validators: Vec<u64>,
448    pub scores: Vec<u64>,
449    pub adjust_rewards_by_score: bool,
450}
451
452#[cfg(msim)]
453pub mod advance_epoch_result_injection {
454    use std::cell::RefCell;
455
456    use crate::{
457        committee::EpochId,
458        error::{ExecutionError, ExecutionErrorKind},
459    };
460
461    thread_local! {
462        /// Override the result of advance_epoch in the range [start, end).
463        static OVERRIDE: RefCell<Option<(EpochId, EpochId)>>  = const { RefCell::new(None) };
464    }
465
466    /// Override the result of advance_epoch transaction if new epoch is in the
467    /// provided range [start, end).
468    pub fn set_override(value: Option<(EpochId, EpochId)>) {
469        OVERRIDE.with(|o| *o.borrow_mut() = value);
470    }
471
472    /// This function is used to modify the result of advance_epoch transaction
473    /// for testing. If the override is set, the result will be an execution
474    /// error, otherwise the original result will be returned.
475    pub fn maybe_modify_result(
476        result: Result<(), ExecutionError>,
477        current_epoch: EpochId,
478    ) -> Result<(), ExecutionError> {
479        if let Some((start, end)) = OVERRIDE.with(|o| *o.borrow()) {
480            if current_epoch >= start && current_epoch < end {
481                return Err::<(), ExecutionError>(ExecutionError::new(
482                    ExecutionErrorKind::FunctionNotFound,
483                    None,
484                ));
485            }
486        }
487        result
488    }
489}