1use std::{
5 collections::{BTreeSet, HashSet},
6 sync::Arc,
7};
8
9use nonempty::NonEmpty;
10use sui_config::{
11 transaction_deny_config::TransactionDenyConfig, verifier_signing_config::VerifierSigningConfig,
12};
13use sui_execution::Executor;
14use sui_transaction_checks::{check_dev_inspect_input, check_transaction_input};
15use sui_types::{
16 base_types::{EpochId, ObjectID, ObjectRef, SystemObjectVersions},
17 coin_reservation::{CoinReservationResolverTrait, ParsedDigest},
18 digests::{ChainIdentifier, TransactionDigest},
19 effects::TransactionEffectsAPI,
20 error::{SuiErrorKind, SuiResult},
21 execution_params::{ExecutionOrEarlyError, FundsWithdrawStatus, get_early_execution_error},
22 execution_status::ExecutionErrorKind,
23 full_checkpoint_content::ObjectSet,
24 messages_checkpoint::CheckpointTimestamp,
25 metrics::{BytecodeVerifierMetrics, ExecutionMetrics},
26 object::{MoveObject, OBJECT_START_VERSION, Object, Owner},
27 storage::{
28 BackingPackageStore, BackingStore, TrackingBackingStore, get_transaction_object_set,
29 },
30 transaction::{
31 InputObjectKind, InputObjects, ObjectReadResult, ReceivingObjects, TransactionData,
32 TransactionDataAPI, TxValidityCheckContext,
33 },
34 transaction_executor::{SimulateTransactionResult, TransactionChecks},
35};
36
37use crate::{
38 accumulators::{
39 funds_read::AccountFundsRead,
40 transaction_rewriting::rewrite_transaction_for_coin_reservations,
41 },
42 authority::{DEV_INSPECT_GAS_COIN_VALUE, pre_object_load_checks},
43 transaction_outputs::unchanged_loaded_runtime_objects,
44};
45
46pub trait SimulationInputLoader {
48 fn read_objects_for_simulation(
50 &self,
51 transaction_digest: &TransactionDigest,
52 input_object_kinds: &[InputObjectKind],
53 receiving_object_refs: &[ObjectRef],
54 epoch_id: EpochId,
55 ) -> SuiResult<(InputObjects, ReceivingObjects)>;
56}
57
58pub fn simulate_transaction(
60 mut transaction: TransactionData,
61 checks: TransactionChecks,
62 allow_mock_gas_coin: bool,
63 suggested_gas_price: Option<u64>,
64 validity_check_context: TxValidityCheckContext<'_>,
65 execution_epoch_id: EpochId,
66 epoch_timestamp_ms: CheckpointTimestamp,
67 chain_identifier: ChainIdentifier,
68 transaction_deny_config: &TransactionDenyConfig,
69 certificate_deny_set: &HashSet<TransactionDigest>,
70 input_loader: &dyn SimulationInputLoader,
71 backing_store: &(dyn BackingStore + Send + Sync),
72 backing_package_store: &(dyn BackingPackageStore + Send + Sync),
73 executor: &(dyn Executor + Send + Sync),
74 coin_reservation_resolver: &dyn CoinReservationResolverTrait,
75 account_funds_read: &dyn AccountFundsRead,
76 verifier_signing_config: &VerifierSigningConfig,
77 bytecode_verifier_metrics: &Arc<BytecodeVerifierMetrics>,
78 execution_metrics: &Arc<ExecutionMetrics>,
79) -> SuiResult<SimulateTransactionResult> {
80 let dev_inspect = checks.disabled();
81
82 let protocol_config = validity_check_context.config;
85 if !protocol_config.enable_coin_reservation_obj_refs()
86 && transaction
87 .gas()
88 .iter()
89 .any(|obj_ref| ParsedDigest::is_coin_reservation_digest(&obj_ref.2))
90 {
91 return Err(SuiErrorKind::UnsupportedFeatureError {
92 error: "coin reservations in gas payment are not supported at this protocol version"
93 .to_string(),
94 }
95 .into());
96 }
97
98 let input_object_kinds = transaction.input_objects()?;
102 let receiving_object_refs = transaction.receiving_objects();
103
104 let is_gasless = protocol_config.enable_gasless() && transaction.is_gasless_transaction();
113 let mock_gas_object = if allow_mock_gas_coin && transaction.gas().is_empty() && !is_gasless {
114 let obj = Object::new_move(
115 MoveObject::new_gas_coin(
116 OBJECT_START_VERSION,
117 ObjectID::MAX,
118 DEV_INSPECT_GAS_COIN_VALUE,
119 ),
120 Owner::AddressOwner(transaction.gas_data().owner),
121 TransactionDigest::genesis_marker(),
122 );
123 transaction.gas_data_mut().payment = vec![obj.compute_object_reference()];
124 Some(obj)
125 } else {
126 None
127 };
128
129 transaction.validity_check(&validity_check_context)?;
131
132 let declared_withdrawals = pre_object_load_checks(
133 &transaction,
134 &[],
135 &input_object_kinds,
136 &receiving_object_refs,
137 protocol_config,
138 transaction_deny_config,
139 backing_package_store,
140 chain_identifier,
141 coin_reservation_resolver,
142 account_funds_read,
143 )?;
144 let address_funds: BTreeSet<_> = declared_withdrawals.keys().cloned().collect();
145
146 let transaction_digest = transaction.digest();
147 let (mut input_objects, receiving_objects) = input_loader.read_objects_for_simulation(
148 &transaction_digest,
149 &input_object_kinds,
150 &receiving_object_refs,
151 validity_check_context.epoch,
152 )?;
153
154 let mock_gas_id = mock_gas_object.map(|obj| {
156 let id = obj.id();
157 input_objects.push(ObjectReadResult::new_from_gas_object(&obj));
158 id
159 });
160
161 let checked_input_objects = if dev_inspect {
162 check_dev_inspect_input(
163 protocol_config,
164 &transaction,
165 input_objects,
166 receiving_objects,
167 validity_check_context.reference_gas_price,
168 )?
169 } else {
170 check_transaction_input(
171 protocol_config,
172 validity_check_context.reference_gas_price,
173 &transaction,
174 input_objects,
175 &receiving_objects,
176 bytecode_verifier_metrics,
177 verifier_signing_config,
178 )?
179 };
180
181 let (mut kind, signer, gas_data) = transaction.execution_parts();
182 let rewritten_inputs = rewrite_transaction_for_coin_reservations(
183 chain_identifier,
184 coin_reservation_resolver,
185 signer,
186 &mut kind,
187 None,
188 )?;
189 let early_execution_error = get_early_execution_error(
190 &transaction.digest(),
191 &checked_input_objects,
192 certificate_deny_set,
193 &FundsWithdrawStatus::MaybeSufficient,
194 );
195 let execution_params = match early_execution_error {
198 None => ExecutionOrEarlyError::ok(None),
199 Some(errors) => ExecutionOrEarlyError::failed(errors, None),
200 };
201
202 let tracking_store = TrackingBackingStore::new(backing_store);
203
204 let cloned_input_objects = checked_input_objects.clone();
206 let cloned_gas = gas_data.clone();
207 let cloned_kind = kind.clone();
208 let tx_digest = transaction_digest;
209 let system_object_versions = SystemObjectVersions::from_inputs_or_latest_in_store(
210 checked_input_objects.inner(),
211 backing_store,
212 );
213 let (inner_temp_store, _, effects, execution_result) = executor.dev_inspect_transaction(
214 &tracking_store,
215 protocol_config,
216 execution_metrics.clone(),
217 false, execution_params,
219 &execution_epoch_id,
220 epoch_timestamp_ms,
221 checked_input_objects,
222 system_object_versions.clone(),
223 gas_data,
224 validity_check_context.reference_gas_price,
225 kind,
226 rewritten_inputs.clone(),
227 signer,
228 tx_digest,
229 dev_inspect,
230 );
231
232 let (inner_temp_store, effects, execution_result) = if !protocol_config
236 .check_object_funds_withdraw_in_execution()
237 && execution_result.is_ok()
238 {
239 let has_insufficient_object_funds = inner_temp_store
240 .accumulator_running_max_withdraws
241 .iter()
242 .filter(|(id, _)| !address_funds.contains(id))
243 .any(|(id, max_withdraw)| {
244 let balance = account_funds_read.get_latest_account_amount(id);
245 balance < *max_withdraw
246 });
247
248 if has_insufficient_object_funds {
249 let (store, _, effects, result) = executor.dev_inspect_transaction(
250 &tracking_store,
251 protocol_config,
252 execution_metrics.clone(),
253 false,
254 ExecutionOrEarlyError::failed(
255 NonEmpty::new(ExecutionErrorKind::InsufficientFundsForWithdraw),
256 None,
257 ),
258 &execution_epoch_id,
259 epoch_timestamp_ms,
260 cloned_input_objects,
261 system_object_versions,
262 cloned_gas,
263 validity_check_context.reference_gas_price,
264 cloned_kind,
265 rewritten_inputs,
266 signer,
267 tx_digest,
268 dev_inspect,
269 );
270 (store, effects, result)
271 } else {
272 (inner_temp_store, effects, execution_result)
273 }
274 } else {
275 (inner_temp_store, effects, execution_result)
276 };
277
278 let loaded_runtime_objects = tracking_store.into_read_objects();
279 let unchanged_loaded_runtime_objects =
280 unchanged_loaded_runtime_objects(&transaction, &effects, &loaded_runtime_objects);
281
282 let object_set = {
283 let objects = {
284 let mut objects = loaded_runtime_objects;
285
286 for o in inner_temp_store
287 .input_objects
288 .into_values()
289 .chain(inner_temp_store.written.into_values())
290 {
291 objects.insert(o);
292 }
293
294 objects
295 };
296
297 let object_keys =
298 get_transaction_object_set(&transaction, &effects, &unchanged_loaded_runtime_objects);
299
300 let mut set = ObjectSet::default();
301 for k in object_keys {
302 if let Some(o) = objects.get(&k) {
303 set.insert(o.clone());
304 }
305 }
306
307 set
308 };
309
310 Ok(SimulateTransactionResult {
311 objects: object_set,
312 events: effects.events_digest().map(|_| inner_temp_store.events),
313 effects,
314 execution_result,
315 mock_gas_id,
316 unchanged_loaded_runtime_objects,
317 suggested_gas_price,
318 })
319}