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