1use crate::{
5 execution_mode::ExecutionMode,
6 gas_charger::GasCharger,
7 object_runtime, sp,
8 static_programmable_transactions::{
9 env::Env,
10 execution::{
11 context::{Context, CtxValue, GasCoinTransfer},
12 trace_utils,
13 },
14 typing::{ast as T, verify::input_arguments::is_coin_send_funds},
15 },
16};
17use move_core_types::account_address::AccountAddress;
18use move_trace_format::format::MoveTraceBuilder;
19use mysten_common::ZipDebugEqIteratorExt;
20use std::{
21 cell::RefCell,
22 collections::BTreeMap,
23 rc::Rc,
24 sync::Arc,
25 time::{Duration, Instant},
26};
27use sui_types::{
28 base_types::TxContext,
29 error::ExecutionError,
30 execution::{ExecutionTiming, ResultWithTimings},
31 execution_status::{ExecutionErrorKind, PackageUpgradeError},
32 metrics::ExecutionMetrics,
33 move_package::MovePackage,
34 object::Owner,
35};
36use tracing::instrument;
37
38pub fn execute<'env, 'pc, 'vm, 'state, 'linkage, 'extension, Mode: ExecutionMode>(
39 env: &'env mut Env<'pc, 'vm, 'state, 'linkage, 'extension>,
40 metrics: Arc<ExecutionMetrics>,
41 tx_context: Rc<RefCell<TxContext>>,
42 gas_charger: &mut GasCharger,
43 ast: T::Transaction,
44 trace_builder_opt: &mut Option<MoveTraceBuilder>,
45) -> ResultWithTimings<Mode::ExecutionResults, ExecutionError>
46where
47 'pc: 'state,
48 'env: 'state,
49{
50 let original_command_len = ast.original_command_len;
51 let mut indexed_timings = IndexedExecutionTimings::new(original_command_len);
52 let result = execute_inner::<Mode>(
53 &mut indexed_timings,
54 env,
55 metrics,
56 tx_context,
57 gas_charger,
58 ast,
59 trace_builder_opt,
60 );
61 let timings = indexed_timings.into_coalesced();
62 debug_assert!(
63 timings.len() <= original_command_len,
64 "coalesced timings length {} exceeds original command length {}",
65 timings.len(),
66 original_command_len
67 );
68
69 match result {
70 Ok(result) => Ok((result, timings)),
71 Err(e) => {
72 trace_utils::trace_execution_error(trace_builder_opt, e.to_string());
73 Err((e, timings))
74 }
75 }
76}
77
78fn execute_inner<'env, 'pc, 'vm, 'state, 'linkage, 'extension, Mode: ExecutionMode>(
79 timings: &mut IndexedExecutionTimings,
80 env: &'env mut Env<'pc, 'vm, 'state, 'linkage, 'extension>,
81 metrics: Arc<ExecutionMetrics>,
82 tx_context: Rc<RefCell<TxContext>>,
83 gas_charger: &mut GasCharger,
84 ast: T::Transaction,
85 trace_builder_opt: &mut Option<MoveTraceBuilder>,
86) -> Result<Mode::ExecutionResults, ExecutionError>
87where
88 'pc: 'state,
89{
90 debug_assert_eq!(gas_charger.move_gas_status().stack_height_current(), 0);
91 let T::Transaction {
92 gas_payment,
93 bytes,
94 objects,
95 withdrawals,
96 pure,
97 receiving,
98 withdrawal_compatibility_conversions: _,
99 original_command_len: _,
100 commands,
101 } = ast;
102 let mut context = Context::new(
103 env,
104 metrics,
105 tx_context,
106 gas_charger,
107 gas_payment,
108 bytes,
109 objects,
110 withdrawals,
111 pure,
112 receiving,
113 )?;
114
115 trace_utils::trace_ptb_summary(&mut context, trace_builder_opt, &commands)?;
116
117 let mut mode_results = Mode::empty_results();
118 for sp!(annotated_index, c) in commands {
119 let annotated_index = annotated_index as usize;
120 let start = Instant::now();
121 if let Err(err) =
122 execute_command::<Mode>(&mut context, &mut mode_results, c, trace_builder_opt)
123 {
124 let loaded_runtime_objects = object_runtime!(context)?.loaded_runtime_objects();
126 drop(context);
128 env.state_view
131 .save_loaded_runtime_objects(loaded_runtime_objects);
132 timings.error(annotated_index, start.elapsed());
133 return Err(err.with_command_index(annotated_index));
134 };
135 timings.executed(annotated_index, start.elapsed());
136 }
137 let loaded_runtime_objects = object_runtime!(context)?.loaded_runtime_objects();
143 let wrapped_object_containers = object_runtime!(context)?.wrapped_object_containers();
146 let generated_object_ids = object_runtime!(context)?.generated_object_ids();
148
149 let finished = context.finish::<Mode>();
151 env.state_view
153 .save_loaded_runtime_objects(loaded_runtime_objects);
154 env.state_view
155 .save_wrapped_object_containers(wrapped_object_containers);
156 env.state_view.record_execution_results(finished?)?;
157 env.state_view
158 .record_generated_object_ids(generated_object_ids);
159 Ok(mode_results)
160}
161
162#[instrument(level = "trace", skip_all)]
164fn execute_command<Mode: ExecutionMode>(
165 context: &mut Context,
166 mode_results: &mut Mode::ExecutionResults,
167 c: T::Command_,
168 trace_builder_opt: &mut Option<MoveTraceBuilder>,
169) -> Result<(), ExecutionError> {
170 let T::Command_ {
171 command,
172 result_type,
173 drop_values,
174 consumed_shared_objects: _,
175 } = c;
176 assert_invariant!(
177 context.gas_charger.move_gas_status().stack_height_current() == 0,
178 "stack height did not start at 0"
179 );
180 let is_move_call = matches!(command, T::Command__::MoveCall(_));
181 let num_args = command.arguments_len();
182 let mut args_to_update = vec![];
183 let result = match command {
184 T::Command__::MoveCall(move_call) => {
185 trace_utils::trace_move_call_start(trace_builder_opt);
186 let T::MoveCall {
187 function,
188 arguments,
189 } = *move_call;
190 let is_gas_coin_send_funds = is_coin_send_funds(&function)
192 && arguments.first().is_some_and(|arg| {
193 matches!(
194 &arg.value.0,
195 T::Argument__::Use(T::Usage::Move(T::Location::GasCoin))
196 )
197 });
198 if Mode::TRACK_EXECUTION {
199 args_to_update.extend(
200 arguments
201 .iter()
202 .filter(|arg| matches!(&arg.value.1, T::Type::Reference(true, _)))
203 .cloned(),
204 )
205 }
206 let arguments: Vec<CtxValue> = context.arguments(arguments)?;
207 if is_gas_coin_send_funds {
208 assert_invariant!(arguments.len() == 2, "coin::send_funds should have 2 args");
209 let recipient = arguments.last().unwrap().to_address()?;
210 context.record_gas_coin_transfer(GasCoinTransfer::SendFunds { recipient })?;
211 }
212 let res = context.vm_move_call(function, arguments, trace_builder_opt);
213 trace_utils::trace_move_call_end(trace_builder_opt);
214 res?
215 }
216 T::Command__::TransferObjects(objects, recipient) => {
217 let has_gas_coin_move = objects.iter().any(|arg| {
219 matches!(
220 &arg.value.0,
221 T::Argument__::Use(T::Usage::Move(T::Location::GasCoin))
222 )
223 });
224 if has_gas_coin_move {
225 context.record_gas_coin_transfer(GasCoinTransfer::TransferObjects)?;
226 }
227 let object_tys = objects
228 .iter()
229 .map(|sp!(_, (_, ty))| ty.clone())
230 .collect::<Vec<_>>();
231 let object_values: Vec<CtxValue> = context.arguments(objects)?;
232 let recipient: AccountAddress = context.argument(recipient)?;
233 assert_invariant!(
234 object_values.len() == object_tys.len(),
235 "object values and types mismatch"
236 );
237 trace_utils::trace_transfer(context, trace_builder_opt, &object_values, &object_tys)?;
238 for (object_value, ty) in object_values.into_iter().zip_debug_eq(object_tys) {
239 let recipient = Owner::AddressOwner(recipient.into());
241 context.transfer_object(recipient, ty, object_value)?;
242 }
243 vec![]
244 }
245 T::Command__::SplitCoins(ty, coin, amounts) => {
246 let mut trace_values = vec![];
247 if Mode::TRACK_EXECUTION {
249 args_to_update.push(coin.clone());
250 }
251 let coin_ref: CtxValue = context.argument(coin)?;
252 let amount_values: Vec<u64> = context.arguments(amounts)?;
253 let mut total: u64 = 0;
254 for amount in &amount_values {
255 let Some(new_total) = total.checked_add(*amount) else {
256 return Err(ExecutionError::from_kind(
257 ExecutionErrorKind::CoinBalanceOverflow,
258 ));
259 };
260 total = new_total;
261 }
262 trace_utils::add_move_value_info_from_ctx_value(
263 context,
264 trace_builder_opt,
265 &mut trace_values,
266 &ty,
267 &coin_ref,
268 )?;
269 let coin_value = context.copy_value(&coin_ref)?.coin_ref_value()?;
270 fp_ensure!(
271 coin_value >= total,
272 ExecutionError::new_with_source(
273 ExecutionErrorKind::InsufficientCoinBalance,
274 format!("balance: {coin_value} required: {total}")
275 )
276 );
277 coin_ref.coin_ref_subtract_balance(total)?;
278 let amounts = amount_values
279 .into_iter()
280 .map(|a| context.new_coin(a))
281 .collect::<Result<Vec<_>, _>>()?;
282 trace_utils::trace_split_coins(
283 context,
284 trace_builder_opt,
285 &ty,
286 trace_values,
287 &amounts,
288 total,
289 )?;
290
291 amounts
292 }
293 T::Command__::MergeCoins(ty, target, coins) => {
294 let mut trace_values = vec![];
295 if Mode::TRACK_EXECUTION {
297 args_to_update.push(target.clone());
298 }
299 let target_ref: CtxValue = context.argument(target)?;
300 trace_utils::add_move_value_info_from_ctx_value(
301 context,
302 trace_builder_opt,
303 &mut trace_values,
304 &ty,
305 &target_ref,
306 )?;
307 let coins = context.arguments(coins)?;
308 let amounts = coins
309 .into_iter()
310 .map(|coin| {
311 trace_utils::add_move_value_info_from_ctx_value(
312 context,
313 trace_builder_opt,
314 &mut trace_values,
315 &ty,
316 &coin,
317 )?;
318 context.destroy_coin(coin)
319 })
320 .collect::<Result<Vec<_>, _>>()?;
321 let mut additional: u64 = 0;
322 for amount in amounts {
323 let Some(new_additional) = additional.checked_add(amount) else {
324 return Err(ExecutionError::from_kind(
325 ExecutionErrorKind::CoinBalanceOverflow,
326 ));
327 };
328 additional = new_additional;
329 }
330 let target_value = context.copy_value(&target_ref)?.coin_ref_value()?;
331 fp_ensure!(
332 target_value.checked_add(additional).is_some(),
333 ExecutionError::from_kind(ExecutionErrorKind::CoinBalanceOverflow,)
334 );
335 target_ref.coin_ref_add_balance(additional)?;
336 trace_utils::trace_merge_coins(
337 context,
338 trace_builder_opt,
339 &ty,
340 trace_values,
341 additional,
342 )?;
343 vec![]
344 }
345 T::Command__::MakeMoveVec(ty, items) => {
346 let items: Vec<CtxValue> = context.arguments(items)?;
347 trace_utils::trace_make_move_vec(context, trace_builder_opt, &items, &ty)?;
348 vec![CtxValue::vec_pack(ty, items)?]
349 }
350 T::Command__::Publish(module_bytes, dep_ids, linkage) => {
351 trace_utils::trace_publish_event(trace_builder_opt)?;
352 let modules =
353 context.deserialize_modules(&module_bytes, false)?;
354
355 let original_id = context.publish_and_init_package::<Mode>(
356 modules,
357 &dep_ids,
358 linkage,
359 trace_builder_opt,
360 )?;
361
362 if <Mode>::packages_are_predefined() {
363 std::vec![]
365 } else {
366 std::vec![context.new_upgrade_cap(original_id)?]
367 }
368 }
369 T::Command__::Upgrade(
370 module_bytes,
371 dep_ids,
372 current_package_id,
373 upgrade_ticket,
374 linkage,
375 ) => {
376 trace_utils::trace_upgrade_event(trace_builder_opt)?;
377 let upgrade_ticket = context
378 .argument::<CtxValue>(upgrade_ticket)?
379 .into_upgrade_ticket()?;
380 if current_package_id != upgrade_ticket.package.bytes {
382 return Err(ExecutionError::from_kind(
383 ExecutionErrorKind::PackageUpgradeError {
384 upgrade_error: PackageUpgradeError::PackageIDDoesNotMatch {
385 package_id: current_package_id,
386 ticket_id: upgrade_ticket.package.bytes,
387 },
388 },
389 ));
390 }
391 let modules = context.deserialize_modules(&module_bytes, true)?;
393
394 let computed_digest = MovePackage::compute_digest_for_modules_and_deps(
395 &module_bytes,
396 &dep_ids,
397 true,
398 )
399 .to_vec();
400 if computed_digest != upgrade_ticket.digest {
401 return Err(ExecutionError::from_kind(
402 ExecutionErrorKind::PackageUpgradeError {
403 upgrade_error: PackageUpgradeError::DigestDoesNotMatch {
404 digest: computed_digest,
405 },
406 },
407 ));
408 }
409
410 let upgraded_package_id = context.upgrade(
411 modules,
412 &dep_ids,
413 current_package_id,
414 upgrade_ticket.policy,
415 linkage,
416 )?;
417
418 vec![context.upgrade_receipt(upgrade_ticket, upgraded_package_id)]
419 }
420 };
421 if Mode::TRACK_EXECUTION {
422 let argument_updates = context.argument_updates(args_to_update)?;
423 let command_result = context.tracked_results(&result, &result_type)?;
424 Mode::finish_command_v2(mode_results, argument_updates, command_result)?;
425 }
426 assert_invariant!(
427 result.len() == drop_values.len(),
428 "result values and drop values mismatch"
429 );
430 context.charge_command(is_move_call, num_args, result.len())?;
431 let result = result
432 .into_iter()
433 .zip_debug_eq(drop_values)
434 .map(|(value, drop)| if !drop { Some(value) } else { None })
435 .collect::<Vec<_>>();
436 context.result(result)?;
437 assert_invariant!(
438 context.gas_charger.move_gas_status().stack_height_current() == 0,
439 "stack height did not end at 0"
440 );
441 Ok(())
442}
443
444struct IndexedExecutionTimings {
446 max_allowed_index: usize,
449 executed_commands: BTreeMap<usize, Duration>,
452 error_command: Option<(usize, Duration)>,
455}
456
457impl IndexedExecutionTimings {
458 fn new(original_command_len: usize) -> Self {
459 let max_allowed_index = original_command_len.saturating_sub(1);
460 Self {
461 max_allowed_index,
462 executed_commands: BTreeMap::new(),
463 error_command: None,
464 }
465 }
466
467 fn executed(&mut self, annotated_index: usize, duration: Duration) {
469 debug_assert!(
470 self.error_command.is_none(),
471 "command executed after an error occurred"
472 );
473 let index = annotated_index.min(self.max_allowed_index);
474 let existing = self
475 .executed_commands
476 .entry(index)
477 .or_insert(Duration::ZERO);
478 *existing = existing.saturating_add(duration);
479 }
480
481 fn error(&mut self, annotated_index: usize, duration: Duration) {
483 debug_assert!(self.error_command.is_none(), "multiple errors recorded");
484 let index = annotated_index.min(self.max_allowed_index);
485 debug_assert!(
486 self.executed_commands
487 .last_key_value()
488 .is_none_or(|(last, _)| *last <= index),
489 "execution timings recorded for command index {:?} after error at index {}",
490 self.executed_commands
491 .last_key_value()
492 .map(|(last, _)| *last),
493 index,
494 );
495
496 let existing_opt = self.executed_commands.remove(&index);
497 let total_duration = existing_opt
498 .unwrap_or(Duration::ZERO)
499 .saturating_add(duration);
500 self.error_command = Some((index, total_duration));
501 }
502
503 fn into_coalesced(self) -> Vec<ExecutionTiming> {
508 let Self {
509 max_allowed_index,
510 executed_commands,
511 error_command,
512 } = self;
513
514 let max_executed_index = executed_commands.keys().last().copied();
515 let error_index = error_command.as_ref().map(|(idx, _)| *idx);
516 let max_used_index = match (max_executed_index, error_index) {
517 (Some(exec), Some(err)) => exec.max(err),
518 (Some(idx), None) | (None, Some(idx)) => idx,
519 (None, None) => return vec![],
520 };
521 debug_assert!(
522 max_used_index <= max_allowed_index,
523 "max used index {} exceeds max allowed index {}",
524 max_used_index,
525 max_allowed_index
526 );
527 let size = max_used_index.saturating_add(1);
528
529 let mut coalesced = vec![ExecutionTiming::Success(Duration::ZERO); size];
534 for (index, duration) in executed_commands {
535 let Some(entry) = coalesced.get_mut(index) else {
536 debug_assert!(
537 false,
538 "failed to initialize coalesced timings at index {}",
539 index
540 );
541 continue;
542 };
543 debug_assert!(matches!(entry, ExecutionTiming::Success(d) if d.is_zero()));
544 *entry = ExecutionTiming::Success(duration);
545 }
546
547 if let Some((index, error_duration)) = error_command {
548 debug_assert!(
549 index == coalesced.len().saturating_sub(1),
550 "error index should be last"
551 );
552 if let Some(entry) = coalesced.get_mut(index) {
553 debug_assert!(matches!(entry, ExecutionTiming::Success(d) if d.is_zero()));
554 *entry = ExecutionTiming::Abort(error_duration);
555 } else {
556 debug_assert!(
557 false,
558 "failed to initialize coalesced timings at index {}",
559 index
560 );
561 };
562 }
563
564 coalesced
565 }
566}