Skip to main content

iota_replay/
types.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::Debug;
6
7use iota_json_rpc_types::{IotaEvent, IotaObjectResponseError, IotaTransactionBlockEffects};
8use iota_protocol_config::{Chain, ProtocolVersion};
9use iota_sdk::error::Error as IotaRpcError;
10use iota_sdk_types::{
11    Address, ObjectDigest, ObjectId, ObjectReference, SenderSignedTransaction, TransactionDigest,
12    TransactionKind, Version,
13};
14use iota_types::{
15    base_types::VersionNumber,
16    error::{IotaError, IotaResult, UserInputError},
17    object::Object,
18    transaction::InputObjectKind,
19};
20use jsonrpsee::core::ClientError as JsonRpseeError;
21use move_binary_format::CompiledModule;
22use move_core_types::{
23    account_address::AccountAddress,
24    language_storage::{ModuleId, StructTag},
25};
26use serde::{Deserialize, Serialize};
27use thiserror::Error;
28use tokio::time::Duration;
29use tracing::warn;
30
31use crate::config::ReplayableNetworkConfigSet;
32
33// TODO: make these configurable
34pub(crate) const RPC_TIMEOUT_ERR_SLEEP_RETRY_PERIOD: Duration = Duration::from_millis(100_000);
35pub(crate) const RPC_TIMEOUT_ERR_NUM_RETRIES: u32 = 3;
36pub(crate) const MAX_CONCURRENT_REQUESTS: usize = 1_000;
37
38// Struct tag used in system epoch change events
39pub(crate) const EPOCH_CHANGE_STRUCT_TAGS: [&str; 2] = [
40    "0x3::iota_system_state_inner::SystemEpochInfoEventV2",
41    "0x3::iota_system_state_inner::SystemEpochInfoEventV1",
42];
43
44// TODO: A lot of the information in OnChainTransactionInfo is redundant from
45// what's already in SenderSignedTransaction. We should consider removing them.
46#[derive(Clone, Debug, Serialize, Deserialize)]
47pub struct OnChainTransactionInfo {
48    pub tx_digest: TransactionDigest,
49    pub sender_signed_data: SenderSignedTransaction,
50    pub sender: Address,
51    pub input_objects: Vec<InputObjectKind>,
52    pub kind: TransactionKind,
53    pub modified_at_versions: Vec<(ObjectId, Version)>,
54    pub shared_object_refs: Vec<ObjectReference>,
55    pub gas: Vec<ObjectReference>,
56    #[serde(default)]
57    pub gas_owner: Option<Address>,
58    pub gas_budget: u64,
59    pub gas_price: u64,
60    pub executed_epoch: u64,
61    pub dependencies: Vec<TransactionDigest>,
62    #[serde(skip)]
63    pub receiving_objs: Vec<(ObjectId, Version)>,
64    #[serde(skip)]
65    pub config_objects: Vec<(ObjectId, Version)>,
66    // TODO: There are two problems with this being a json-rpc type:
67    // 1. The json-rpc type is not a perfect mirror with TransactionEffects since v2. We lost the
68    // ability to replay effects v2 specific forks. We need to fix this asap. Unfortunately at the
69    // moment it is really difficult to get the raw effects given a transaction digest.
70    // 2. This data structure is not bcs/bincode friendly. It makes it much more expensive to
71    // store the sandbox state for batch replay.
72    pub effects: IotaTransactionBlockEffects,
73    pub protocol_version: ProtocolVersion,
74    pub epoch_start_timestamp: u64,
75    pub reference_gas_price: u64,
76    #[serde(default = "unspecified_chain")]
77    pub chain: Chain,
78}
79
80fn unspecified_chain() -> Chain {
81    warn!("Unable to determine chain id. Defaulting to unknown");
82    Chain::Unknown
83}
84
85#[derive(Debug, Error, Clone)]
86pub enum ReplayEngineError {
87    #[error("IotaError: {:#?}", err)]
88    IotaError { err: IotaError },
89
90    #[error("IotaRpcError: {:#?}", err)]
91    IotaRpcError { err: String },
92
93    #[error("IotaObjectResponseError: {:#?}", err)]
94    IotaObjectResponseError { err: IotaObjectResponseError },
95
96    #[error("UserInputError: {:#?}", err)]
97    UserInputError { err: UserInputError },
98
99    #[error("GeneralError: {:#?}", err)]
100    GeneralError { err: String },
101
102    #[error("IotaRpcRequestTimeout")]
103    IotaRpcRequestTimeout,
104
105    #[error("ObjectNotExist: {:#?}", id)]
106    ObjectNotExist { id: ObjectId },
107
108    #[error("ObjectVersionNotFound: {:#?} version {}", id, version)]
109    ObjectVersionNotFound { id: ObjectId, version: Version },
110
111    #[error(
112        "ObjectVersionTooHigh: {:#?}, requested version {}, latest version found {}",
113        id,
114        asked_version,
115        latest_version
116    )]
117    ObjectVersionTooHigh {
118        id: ObjectId,
119        asked_version: Version,
120        latest_version: Version,
121    },
122
123    #[error(
124        "ObjectDeleted: {:#?} at version {:#?} digest {:#?}",
125        id,
126        version,
127        digest
128    )]
129    ObjectDeleted {
130        id: ObjectId,
131        version: Version,
132        digest: ObjectDigest,
133    },
134
135    #[error(
136        "EffectsForked: Effects for digest {} forked with diff {}",
137        digest,
138        diff
139    )]
140    EffectsForked {
141        digest: TransactionDigest,
142        diff: String,
143        on_chain: Box<IotaTransactionBlockEffects>,
144        local: Box<IotaTransactionBlockEffects>,
145    },
146
147    #[error(
148        "Transaction {:#?} not supported by replay. Reason: {:?}",
149        digest,
150        reason
151    )]
152    TransactionNotSupported {
153        digest: TransactionDigest,
154        reason: String,
155    },
156
157    #[error(
158        "Fatal! No framework versions for protocol version {protocol_version}. Make sure version tables are populated"
159    )]
160    FrameworkObjectVersionTableNotPopulated { protocol_version: u64 },
161
162    #[error("Protocol version not found for epoch {epoch}")]
163    ProtocolVersionNotFound { epoch: u64 },
164
165    #[error("Error querying system events for epoch {epoch}")]
166    ErrorQueryingSystemEvents { epoch: u64 },
167
168    #[error("Invalid epoch change transaction in events for epoch {epoch}")]
169    InvalidEpochChangeTx { epoch: u64 },
170
171    #[error("Unexpected event format {:#?}", event)]
172    UnexpectedEventFormat { event: Box<IotaEvent> },
173
174    #[error("Unable to find event for epoch {epoch}")]
175    EventNotFound { epoch: u64 },
176
177    #[error("Unable to find checkpoints for epoch {epoch}")]
178    UnableToDetermineCheckpoint { epoch: u64 },
179
180    #[error("Unable to query system events; {}", rpc_err)]
181    UnableToQuerySystemEvents { rpc_err: String },
182
183    #[error("Internal error or cache corrupted! Object {id}{} should be in cache.", version.map(|q| format!(" version {q:#?}")).unwrap_or_default()
184    )]
185    InternalCacheInvariantViolation {
186        id: ObjectId,
187        version: Option<Version>,
188    },
189
190    #[error("Error getting dynamic fields loaded objects: {}", rpc_err)]
191    UnableToGetDynamicFieldLoadedObjects { rpc_err: String },
192
193    #[error("Unable to open yaml cfg file at {}: {}", path, err)]
194    UnableToOpenYamlFile { path: String, err: String },
195
196    #[error("Unable to write yaml file at {}: {}", path, err)]
197    UnableToWriteYamlFile { path: String, err: String },
198
199    #[error("Unable to convert string {} to URL {}", url, err)]
200    InvalidUrl { url: String, err: String },
201
202    #[error(
203        "Unable to execute transaction with existing network configs {:#?}",
204        cfgs
205    )]
206    UnableToExecuteWithNetworkConfigs { cfgs: ReplayableNetworkConfigSet },
207
208    #[error("Unable to get chain id: {}", err)]
209    UnableToGetChainId { err: String },
210}
211
212impl From<IotaObjectResponseError> for ReplayEngineError {
213    fn from(err: IotaObjectResponseError) -> Self {
214        match err {
215            IotaObjectResponseError::NotExists { object_id } => {
216                ReplayEngineError::ObjectNotExist { id: object_id }
217            }
218            IotaObjectResponseError::Deleted {
219                object_id,
220                digest,
221                version,
222            } => ReplayEngineError::ObjectDeleted {
223                id: object_id,
224                version: version.into(),
225                digest,
226            },
227            _ => ReplayEngineError::IotaObjectResponseError { err },
228        }
229    }
230}
231
232impl From<ReplayEngineError> for IotaError {
233    fn from(err: ReplayEngineError) -> Self {
234        IotaError::Unknown(format!("{err:#?}"))
235    }
236}
237
238impl From<IotaError> for ReplayEngineError {
239    fn from(err: IotaError) -> Self {
240        ReplayEngineError::IotaError { err }
241    }
242}
243impl From<IotaRpcError> for ReplayEngineError {
244    fn from(err: IotaRpcError) -> Self {
245        match err {
246            IotaRpcError::Rpc(JsonRpseeError::RequestTimeout) => {
247                ReplayEngineError::IotaRpcRequestTimeout
248            }
249            _ => ReplayEngineError::IotaRpcError {
250                err: format!("{err:?}"),
251            },
252        }
253    }
254}
255
256impl From<UserInputError> for ReplayEngineError {
257    fn from(err: UserInputError) -> Self {
258        ReplayEngineError::UserInputError { err }
259    }
260}
261
262impl From<anyhow::Error> for ReplayEngineError {
263    fn from(err: anyhow::Error) -> Self {
264        ReplayEngineError::GeneralError {
265            err: format!("{err:#?}"),
266        }
267    }
268}
269
270/// TODO: Limited set but will add more
271#[derive(Debug)]
272#[expect(clippy::large_enum_variant)]
273pub enum ExecutionStoreEvent {
274    BackingPackageGetPackageObject {
275        package_id: ObjectId,
276        result: IotaResult<Option<Object>>,
277    },
278    ChildObjectResolverStoreReadChildObject {
279        parent: ObjectId,
280        child: ObjectId,
281        result: IotaResult<Option<Object>>,
282    },
283    ResourceResolverGetResource {
284        address: AccountAddress,
285        typ: StructTag,
286        result: IotaResult<Option<Vec<u8>>>,
287    },
288    ModuleResolverGetModule {
289        module_id: ModuleId,
290        result: IotaResult<Option<Vec<u8>>>,
291    },
292    ObjectStoreGetObject {
293        object_id: ObjectId,
294        result: IotaResult<Option<Object>>,
295    },
296    ObjectStoreGetObjectByKey {
297        object_id: ObjectId,
298        version: VersionNumber,
299        result: IotaResult<Option<Object>>,
300    },
301    GetModuleGetModuleByModuleId {
302        id: ModuleId,
303        result: IotaResult<Option<CompiledModule>>,
304    },
305    ReceiveObject {
306        owner: ObjectId,
307        receive: ObjectId,
308        receive_at_version: Version,
309        result: IotaResult<Option<Object>>,
310    },
311}