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_types::{
11    base_types::{IotaAddress, ObjectID, ObjectRef, SequenceNumber, VersionNumber},
12    digests::{ObjectDigest, TransactionDigest},
13    error::{IotaError, IotaResult, UserInputError},
14    object::Object,
15    transaction::{InputObjectKind, SenderSignedData, TransactionKind},
16};
17use jsonrpsee::core::ClientError as JsonRpseeError;
18use move_binary_format::CompiledModule;
19use move_core_types::{
20    account_address::AccountAddress,
21    language_storage::{ModuleId, StructTag},
22};
23use serde::{Deserialize, Serialize};
24use thiserror::Error;
25use tokio::time::Duration;
26use tracing::warn;
27
28use crate::config::ReplayableNetworkConfigSet;
29
30// TODO: make these configurable
31pub(crate) const RPC_TIMEOUT_ERR_SLEEP_RETRY_PERIOD: Duration = Duration::from_millis(100_000);
32pub(crate) const RPC_TIMEOUT_ERR_NUM_RETRIES: u32 = 3;
33pub(crate) const MAX_CONCURRENT_REQUESTS: usize = 1_000;
34
35// Struct tag used in system epoch change events
36pub(crate) const EPOCH_CHANGE_STRUCT_TAGS: [&str; 2] = [
37    "0x3::iota_system_state_inner::SystemEpochInfoEventV2",
38    "0x3::iota_system_state_inner::SystemEpochInfoEventV1",
39];
40
41// TODO: A lot of the information in OnChainTransactionInfo is redundant from
42// what's already in SenderSignedData. We should consider removing them.
43#[derive(Clone, Debug, Serialize, Deserialize)]
44pub struct OnChainTransactionInfo {
45    pub tx_digest: TransactionDigest,
46    pub sender_signed_data: SenderSignedData,
47    pub sender: IotaAddress,
48    pub input_objects: Vec<InputObjectKind>,
49    pub kind: TransactionKind,
50    pub modified_at_versions: Vec<(ObjectID, SequenceNumber)>,
51    pub shared_object_refs: Vec<ObjectRef>,
52    pub gas: Vec<ObjectRef>,
53    #[serde(default)]
54    pub gas_owner: Option<IotaAddress>,
55    pub gas_budget: u64,
56    pub gas_price: u64,
57    pub executed_epoch: u64,
58    pub dependencies: Vec<TransactionDigest>,
59    #[serde(skip)]
60    pub receiving_objs: Vec<(ObjectID, SequenceNumber)>,
61    #[serde(skip)]
62    pub config_objects: Vec<(ObjectID, SequenceNumber)>,
63    // TODO: There are two problems with this being a json-rpc type:
64    // 1. The json-rpc type is not a perfect mirror with TransactionEffects since v2. We lost the
65    // ability to replay effects v2 specific forks. We need to fix this asap. Unfortunately at the
66    // moment it is really difficult to get the raw effects given a transaction digest.
67    // 2. This data structure is not bcs/bincode friendly. It makes it much more expensive to
68    // store the sandbox state for batch replay.
69    pub effects: IotaTransactionBlockEffects,
70    pub protocol_version: ProtocolVersion,
71    pub epoch_start_timestamp: u64,
72    pub reference_gas_price: u64,
73    #[serde(default = "unspecified_chain")]
74    pub chain: Chain,
75}
76
77fn unspecified_chain() -> Chain {
78    warn!("Unable to determine chain id. Defaulting to unknown");
79    Chain::Unknown
80}
81
82#[derive(Debug, Error, Clone)]
83pub enum ReplayEngineError {
84    #[error("IotaError: {:#?}", err)]
85    IotaError { err: IotaError },
86
87    #[error("IotaRpcError: {:#?}", err)]
88    IotaRpcError { err: String },
89
90    #[error("IotaObjectResponseError: {:#?}", err)]
91    IotaObjectResponseError { err: IotaObjectResponseError },
92
93    #[error("UserInputError: {:#?}", err)]
94    UserInputError { err: UserInputError },
95
96    #[error("GeneralError: {:#?}", err)]
97    GeneralError { err: String },
98
99    #[error("IotaRpcRequestTimeout")]
100    IotaRpcRequestTimeout,
101
102    #[error("ObjectNotExist: {:#?}", id)]
103    ObjectNotExist { id: ObjectID },
104
105    #[error("ObjectVersionNotFound: {:#?} version {}", id, version)]
106    ObjectVersionNotFound {
107        id: ObjectID,
108        version: SequenceNumber,
109    },
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: SequenceNumber,
120        latest_version: SequenceNumber,
121    },
122
123    #[error(
124        "ObjectDeleted: {:#?} at version {:#?} digest {:#?}",
125        id,
126        version,
127        digest
128    )]
129    ObjectDeleted {
130        id: ObjectID,
131        version: SequenceNumber,
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<SequenceNumber>,
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,
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: SequenceNumber,
309        result: IotaResult<Option<Object>>,
310    },
311}