Skip to main content

sui_adapter_v3/programmable_transactions/
trace_utils.rs

1// Copyright (c) Mysten Labs, Inc.
2// SPDX-License-Identifier: Apache-2.0
3
4//! This module implements support for tracing related to PTB execution. IMPORTANT:
5//! Bodies of all public functions in this module should be enclosed in a large if statement checking if
6//! tracing is enabled or not to make sure that any errors coming from these functions only manifest itself
7//! when tracing is enabled.
8
9use crate::{
10    execution_mode::ExecutionMode,
11    execution_value::{ObjectContents, ObjectValue, Value},
12    programmable_transactions::context::*,
13};
14use move_core_types::{
15    identifier::Identifier,
16    language_storage::{StructTag, TypeTag},
17};
18use move_trace_format::{
19    format::{Effect, MoveTraceBuilder, RefType, TraceEvent, TypeTagWithRefs},
20    value::{SerializableMoveValue, SimplifiedMoveStruct},
21};
22use move_vm_types::loaded_data::runtime_types::Type;
23use sui_types::{
24    base_types::ObjectID,
25    coin::Coin,
26    error::ExecutionError,
27    execution_status::ExecutionErrorKind,
28    object::bounded_visitor::BoundedVisitor,
29    ptb_trace::{
30        ExtMoveValue, ExtMoveValueInfo, ExternalEvent, PTBCommandInfo, PTBEvent, SummaryEvent,
31    },
32    transaction::Command,
33};
34use sui_verifier::INIT_FN_NAME;
35
36// External trace events are serialized with `serde_json::value::to_raw_value` (the streaming
37// serializer) rather than `serde_json::json!`/`to_value`, so that integer values outside
38// `serde_json::Number`'s range (e.g. `u128`, `u256`) are preserved exactly instead of panicking.
39//
40// `v3` is a frozen execution snapshot that would ordinarily not be modified. This is not a
41// behavioural fix for `v3`; it is a mechanical update forced by `move-trace-format` changing
42// `TraceEvent::External` from `Box<Value>` to `Box<RawValue>`, and is needed only so this cut keeps
43// building. Tracing is a side channel that never influences the effects or gas a transaction
44// produces, and `trace_builder_opt` is `None` during validator and fullnode execution, so this
45// cannot affect consensus, state sync, or the effects a replay reproduces. It changes only the
46// trace *output*, and only when this cut is dispatched to (i.e. a `--trace` replay of a `v3`-era
47// transaction) -- and even then only in the case that previously panicked; other traces serialize
48// to identical bytes.
49
50/// Inserts Move call start event into the trace. As is the case for all other public functions in this module,
51/// its body is (and must be) enclosed in an if statement checking if tracing is enabled.
52pub fn trace_move_call_start(trace_builder_opt: &mut Option<MoveTraceBuilder>) {
53    if let Some(trace_builder) = trace_builder_opt {
54        trace_builder.push_event(TraceEvent::External(
55            serde_json::value::to_raw_value(&PTBEvent::MoveCallStart).unwrap(),
56        ));
57    }
58}
59
60/// Inserts Move call end event into the trace. As is the case for all other public functions in this module,
61/// its body is (and must be) enclosed in an if statement checking if tracing is enabled.
62pub fn trace_move_call_end(trace_builder_opt: &mut Option<MoveTraceBuilder>) {
63    if let Some(trace_builder) = trace_builder_opt {
64        trace_builder.push_event(TraceEvent::External(
65            serde_json::value::to_raw_value(&PTBEvent::MoveCallEnd).unwrap(),
66        ));
67    }
68}
69
70/// Inserts transfer event into the trace. As is the case for all other public functions in this module,
71/// its body is (and must be) enclosed in an if statement checking if tracing is enabled.
72pub fn trace_transfer(
73    context: &mut ExecutionContext<'_, '_, '_>,
74    trace_builder_opt: &mut Option<MoveTraceBuilder>,
75    obj_values: &[ObjectValue],
76) -> Result<(), ExecutionError> {
77    if let Some(trace_builder) = trace_builder_opt {
78        let mut to_transfer = vec![];
79        for (idx, v) in obj_values.iter().enumerate() {
80            let obj_info = move_value_info_from_obj_value(context, v)?;
81            to_transfer.push(ExtMoveValue::Single {
82                name: format!("obj{idx}"),
83                info: obj_info,
84            });
85        }
86        trace_builder.push_event(TraceEvent::External(
87            serde_json::value::to_raw_value(&PTBEvent::ExternalEvent(ExternalEvent {
88                description: "TransferObjects: obj0...objN => ()".to_string(),
89                name: "Transfer".to_string(),
90                values: to_transfer,
91            }))
92            .map_err(|e| make_invariant_violation!("Failed to serialize PTB trace event: {}", e))?,
93        ));
94    }
95    Ok(())
96}
97
98/// Inserts PTB summary event into the trace. As is the case for all other public functions in this module,
99/// its body is (and must be) enclosed in an if statement checking if tracing is enabled.
100pub fn trace_ptb_summary<Mode: ExecutionMode>(
101    context: &mut ExecutionContext<'_, '_, '_>,
102    trace_builder_opt: &mut Option<MoveTraceBuilder>,
103    commands: &[Command],
104) -> Result<(), ExecutionError> {
105    if let Some(trace_builder) = trace_builder_opt {
106        let events = commands
107            .iter()
108            .map(|c| match c {
109                Command::MoveCall(move_call) => {
110                    let pkg = move_call.package.to_string();
111                    let module = move_call.module.clone();
112                    let function = move_call.function.clone();
113                    Ok(vec![PTBCommandInfo::MoveCall {
114                        pkg,
115                        module,
116                        function,
117                    }])
118                }
119                Command::TransferObjects(..) => Ok(vec![PTBCommandInfo::ExternalEvent(
120                    "TransferObjects".to_string(),
121                )]),
122                Command::SplitCoins(..) => Ok(vec![PTBCommandInfo::ExternalEvent(
123                    "SplitCoins".to_string(),
124                )]),
125                Command::MergeCoins(..) => Ok(vec![PTBCommandInfo::ExternalEvent(
126                    "MergeCoins".to_string(),
127                )]),
128                Command::Publish(module_bytes, _) => {
129                    let mut events = vec![];
130                    events.push(PTBCommandInfo::ExternalEvent("Publish".to_string()));
131                    // Not ideal but it only runs when tracing is enabled so overhead
132                    // should be insignificant
133                    let modules = context.deserialize_modules(module_bytes)?;
134                    events.extend(modules.into_iter().find_map(|m| {
135                        for fdef in &m.function_defs {
136                            let fhandle = m.function_handle_at(fdef.function);
137                            let fname = m.identifier_at(fhandle.name);
138                            if fname == INIT_FN_NAME {
139                                return Some(PTBCommandInfo::MoveCall {
140                                    pkg: m.address().to_string(),
141                                    module: m.name().to_string(),
142                                    function: INIT_FN_NAME.to_string(),
143                                });
144                            }
145                        }
146                        None
147                    }));
148                    Ok(events)
149                }
150                Command::MakeMoveVec(..) => Ok(vec![PTBCommandInfo::ExternalEvent(
151                    "MakeMoveVec".to_string(),
152                )]),
153                Command::Upgrade(..) => {
154                    Ok(vec![PTBCommandInfo::ExternalEvent("Upgrade".to_string())])
155                }
156            })
157            .collect::<Result<Vec<Vec<PTBCommandInfo>>, ExecutionError>>()?
158            .into_iter()
159            .flatten()
160            .collect();
161        trace_builder.push_event(TraceEvent::External(
162            serde_json::value::to_raw_value(&PTBEvent::Summary(SummaryEvent {
163                name: "PTBSummary".to_string(),
164                events,
165            }))
166            .map_err(|e| make_invariant_violation!("Failed to serialize PTB trace event: {}", e))?,
167        ));
168    }
169
170    Ok(())
171}
172
173/// Inserts split coins event into the trace. As is the case for all other public functions in this module,
174/// its body is (and must be) enclosed in an if statement checking if tracing is enabled.
175pub fn trace_split_coins(
176    context: &mut ExecutionContext<'_, '_, '_>,
177    trace_builder_opt: &mut Option<MoveTraceBuilder>,
178    coin_type: &Type,
179    input_coin: &Coin,
180    split_coin_values: &[Value],
181) -> Result<(), ExecutionError> {
182    if let Some(trace_builder) = trace_builder_opt {
183        let type_tag_with_refs = trace_type_to_type_tag_with_refs(context, coin_type)?;
184        let mut split_coin_move_values = vec![];
185        for coin_val in split_coin_values {
186            let Value::Object(ObjectValue {
187                contents: ObjectContents::Coin(coin),
188                ..
189            }) = coin_val
190            else {
191                invariant_violation!("Expected result of split coins PTB command to be a coin");
192            };
193            split_coin_move_values.push(
194                coin_move_value_info(
195                    type_tag_with_refs.clone(),
196                    *coin.id.object_id(),
197                    coin.balance.value(),
198                )?
199                .value,
200            );
201        }
202
203        let input = coin_move_value_info(
204            type_tag_with_refs.clone(),
205            *input_coin.id.object_id(),
206            input_coin.value(),
207        )?;
208        trace_builder.push_event(TraceEvent::External(
209            serde_json::value::to_raw_value(&PTBEvent::ExternalEvent(ExternalEvent {
210                description: "SplitCoins: input => result".to_string(),
211                name: "SplitCoins".to_string(),
212                values: vec![
213                    ExtMoveValue::Single {
214                        name: "input".to_string(),
215                        info: input,
216                    },
217                    ExtMoveValue::Vector {
218                        name: "result".to_string(),
219                        type_: type_tag_with_refs.clone(),
220                        value: split_coin_move_values,
221                    },
222                ],
223            }))
224            .map_err(|e| make_invariant_violation!("Failed to serialize PTB trace event: {}", e))?,
225        ));
226    }
227    Ok(())
228}
229
230/// Inserts merge coins event into the trace. As is the case for all other public functions in this module,
231/// its body is (and must be) enclosed in an if statement checking if tracing is enabled.
232pub fn trace_merge_coins(
233    context: &mut ExecutionContext<'_, '_, '_>,
234    trace_builder_opt: &mut Option<MoveTraceBuilder>,
235    coin_type: &Type,
236    input_infos: &[(u64, ObjectID)],
237    target_coin: &Coin,
238) -> Result<(), ExecutionError> {
239    if let Some(trace_builder) = trace_builder_opt {
240        let type_tag_with_refs = trace_type_to_type_tag_with_refs(context, coin_type)?;
241        let mut input_coin_move_values = vec![];
242        let mut to_merge = 0;
243        for (balance, id) in input_infos {
244            input_coin_move_values.push(coin_move_value_info(
245                type_tag_with_refs.clone(),
246                *id,
247                *balance,
248            )?);
249            to_merge += balance;
250        }
251        let merge_target = coin_move_value_info(
252            type_tag_with_refs.clone(),
253            *target_coin.id.object_id(),
254            target_coin.value() - to_merge,
255        )?;
256        let mut values = vec![ExtMoveValue::Single {
257            name: "merge_target".to_string(),
258            info: merge_target,
259        }];
260        for (idx, input_value) in input_coin_move_values.into_iter().enumerate() {
261            values.push(ExtMoveValue::Single {
262                name: format!("coin{idx}"),
263                info: input_value,
264            });
265        }
266        let merge_result = coin_move_value_info(
267            type_tag_with_refs.clone(),
268            *target_coin.id.object_id(),
269            target_coin.value(),
270        )?;
271        values.push(ExtMoveValue::Single {
272            name: "merge_result".to_string(),
273            info: merge_result,
274        });
275        trace_builder.push_event(TraceEvent::External(
276            serde_json::value::to_raw_value(&PTBEvent::ExternalEvent(ExternalEvent {
277                description: "MergeCoins: merge_target, coin0...coinN => mergeresult".to_string(),
278                name: "MergeCoins".to_string(),
279                values,
280            }))
281            .map_err(|e| make_invariant_violation!("Failed to serialize PTB trace event: {}", e))?,
282        ));
283    }
284    Ok(())
285}
286
287/// Inserts make move vec event into the trace. As is the case for all other public functions in this module,
288/// its body is (and must be) enclosed in an if statement checking if tracing is enabled.
289pub fn trace_make_move_vec(
290    context: &mut ExecutionContext<'_, '_, '_>,
291    trace_builder_opt: &mut Option<MoveTraceBuilder>,
292    move_values: Vec<ExtMoveValueInfo>,
293    type_: &Type,
294) -> Result<(), ExecutionError> {
295    if let Some(trace_builder) = trace_builder_opt {
296        let type_tag_with_refs = trace_type_to_type_tag_with_refs(context, type_)?;
297        trace_builder.push_event(TraceEvent::External(
298            serde_json::value::to_raw_value(&PTBEvent::ExternalEvent(ExternalEvent {
299                description: "MakeMoveVec: vector".to_string(),
300                name: "MakeMoveVec".to_string(),
301                values: vec![ExtMoveValue::Vector {
302                    name: "vector".to_string(),
303                    type_: type_tag_with_refs,
304                    value: move_values
305                        .into_iter()
306                        .map(|move_value| move_value.value)
307                        .collect(),
308                }],
309            }))
310            .map_err(|e| make_invariant_violation!("Failed to serialize PTB trace event: {}", e))?,
311        ));
312    }
313    Ok(())
314}
315
316/// Inserts publish event into the trace. As is the case for all other public functions in this module,
317/// its body is (and must be) enclosed in an if statement checking if tracing is enabled.
318pub fn trace_publish_event(
319    trace_builder_opt: &mut Option<MoveTraceBuilder>,
320) -> Result<(), ExecutionError> {
321    if let Some(trace_builder) = trace_builder_opt {
322        trace_builder.push_event(TraceEvent::External(
323            serde_json::value::to_raw_value(&PTBEvent::ExternalEvent(ExternalEvent {
324                description: "Publish: ()".to_string(),
325                name: "Publish".to_string(),
326                values: vec![],
327            }))
328            .map_err(|e| make_invariant_violation!("Failed to serialize PTB trace event: {}", e))?,
329        ));
330    }
331    Ok(())
332}
333
334/// Inserts upgrade event into the trace. As is the case for all other public functions in this module,
335/// its body is (and must be) enclosed in an if statement checking if tracing is enabled.
336pub fn trace_upgrade_event(
337    trace_builder_opt: &mut Option<MoveTraceBuilder>,
338) -> Result<(), ExecutionError> {
339    if let Some(trace_builder) = trace_builder_opt {
340        trace_builder.push_event(TraceEvent::External(
341            serde_json::value::to_raw_value(&PTBEvent::ExternalEvent(ExternalEvent {
342                description: "Upgrade: ()".to_string(),
343                name: "Upgrade".to_string(),
344                values: vec![],
345            }))
346            .map_err(|e| make_invariant_violation!("Failed to serialize PTB trace event: {}", e))?,
347        ));
348    }
349    Ok(())
350}
351
352/// Inserts execution error event into the trace. As is the case for all other public functions in this module,
353/// its body is (and must be) enclosed in an if statement checking if tracing is enabled.
354pub fn trace_execution_error(trace_builder_opt: &mut Option<MoveTraceBuilder>, msg: String) {
355    if let Some(trace_builder) = trace_builder_opt {
356        trace_builder.push_event(TraceEvent::Effect(Box::new(Effect::ExecutionError(msg))));
357    }
358}
359
360/// Adds `ExtMoveValueInfo` to the mutable vector passed as an argument.
361/// As is the case for all other public functions in this module,
362/// its body is (and must be) enclosed in an if statement checking if tracing is enabled.
363pub fn add_move_value_info_from_value(
364    context: &mut ExecutionContext<'_, '_, '_>,
365    trace_builder_opt: &mut Option<MoveTraceBuilder>,
366    move_values: &mut Vec<ExtMoveValueInfo>,
367    type_: &Type,
368    value: &Value,
369) -> Result<(), ExecutionError> {
370    if trace_builder_opt.is_some()
371        && let Some(move_value_info) = move_value_info_from_value(context, type_, value)?
372    {
373        move_values.push(move_value_info);
374    }
375    Ok(())
376}
377
378/// Adds `ExtMoveValueInfo` to the mutable vector passed as an argument.
379/// As is the case for all other public functions in this module,
380/// its body is (and must be) enclosed in an if statement checking if tracing is enabled.
381pub fn add_move_value_info_from_obj_value(
382    context: &mut ExecutionContext<'_, '_, '_>,
383    trace_builder_opt: &mut Option<MoveTraceBuilder>,
384    move_values: &mut Vec<ExtMoveValueInfo>,
385    obj_val: &ObjectValue,
386) -> Result<(), ExecutionError> {
387    if trace_builder_opt.is_some() {
388        let move_value_info = move_value_info_from_obj_value(context, obj_val)?;
389        move_values.push(move_value_info);
390    }
391    Ok(())
392}
393
394/// Adds coin object info to the mutable vector passed as an argument.
395/// As is the case for all other public functions in this module,
396/// its body is (and must be) enclosed in an if statement checking if tracing is enabled.
397pub fn add_coin_obj_info(
398    trace_builder_opt: &mut Option<MoveTraceBuilder>,
399    coin_infos: &mut Vec<(u64, ObjectID)>,
400    balance: u64,
401    id: ObjectID,
402) {
403    if trace_builder_opt.is_some() {
404        coin_infos.push((balance, id));
405    }
406}
407
408/// Creates `ExtMoveValueInfo` from raw bytes.
409fn move_value_info_from_raw_bytes(
410    context: &mut ExecutionContext<'_, '_, '_>,
411    type_: &Type,
412    bytes: &[u8],
413) -> Result<ExtMoveValueInfo, ExecutionError> {
414    let type_tag_with_refs = trace_type_to_type_tag_with_refs(context, type_)?;
415    let layout = context
416        .vm
417        .get_runtime()
418        .type_to_fully_annotated_layout(type_)
419        .map_err(|e| ExecutionError::new_with_source(ExecutionErrorKind::InvariantViolation, e))?;
420    let move_value = BoundedVisitor::deserialize_value(bytes, &layout)
421        .map_err(|e| ExecutionError::new_with_source(ExecutionErrorKind::InvariantViolation, e))?;
422    let serialized_move_value = SerializableMoveValue::from(move_value);
423    Ok(ExtMoveValueInfo {
424        type_: type_tag_with_refs,
425        value: serialized_move_value,
426    })
427}
428
429/// Creates `ExtMoveValueInfo` from `Value`.
430fn move_value_info_from_value(
431    context: &mut ExecutionContext<'_, '_, '_>,
432    type_: &Type,
433    value: &Value,
434) -> Result<Option<ExtMoveValueInfo>, ExecutionError> {
435    match value {
436        Value::Object(obj_val) => Ok(Some(move_value_info_from_obj_value(context, obj_val)?)),
437        Value::Raw(_, bytes) => Ok(Some(move_value_info_from_raw_bytes(context, type_, bytes)?)),
438        Value::Receiving(_, _, _) => Ok(None),
439    }
440}
441
442/// Creates `ExtMoveValueInfo` from `ObjectValue`.
443fn move_value_info_from_obj_value(
444    context: &mut ExecutionContext<'_, '_, '_>,
445    obj_val: &ObjectValue,
446) -> Result<ExtMoveValueInfo, ExecutionError> {
447    let type_tag_with_refs = trace_type_to_type_tag_with_refs(context, &obj_val.type_)?;
448    match &obj_val.contents {
449        ObjectContents::Coin(coin) => {
450            coin_move_value_info(type_tag_with_refs, *coin.id.object_id(), coin.value())
451        }
452        ObjectContents::Raw(bytes) => {
453            move_value_info_from_raw_bytes(context, &obj_val.type_, bytes)
454        }
455    }
456}
457
458/// Creates `ExtMoveValueInfo` for a coin.
459fn coin_move_value_info(
460    type_tag_with_refs: TypeTagWithRefs,
461    object_id: ObjectID,
462    balance: u64,
463) -> Result<ExtMoveValueInfo, ExecutionError> {
464    let coin_type_tag = match type_tag_with_refs.type_.clone() {
465        TypeTag::Struct(tag) => tag,
466        _ => invariant_violation!("Expected a struct type tag when creating a Move coin value"),
467    };
468    // object.ID
469    let object_id = SerializableMoveValue::Address(object_id.into());
470    let object_id_struct_tag = StructTag {
471        address: coin_type_tag.address,
472        module: Identifier::new("object").unwrap(),
473        name: Identifier::new("ID").unwrap(),
474        type_params: vec![],
475    };
476    let object_id_struct = SimplifiedMoveStruct {
477        type_: object_id_struct_tag,
478        fields: vec![(Identifier::new("value").unwrap(), object_id)],
479    };
480    let serializable_object_id = SerializableMoveValue::Struct(object_id_struct);
481    // object.UID
482    let object_uid_struct_tag = StructTag {
483        address: coin_type_tag.address,
484        module: Identifier::new("object").unwrap(),
485        name: Identifier::new("UID").unwrap(),
486        type_params: vec![],
487    };
488    let object_uid_struct = SimplifiedMoveStruct {
489        type_: object_uid_struct_tag,
490        fields: vec![(Identifier::new("id").unwrap(), serializable_object_id)],
491    };
492    let serializable_object_uid = SerializableMoveValue::Struct(object_uid_struct);
493    // coin.Balance
494    let serializable_value = SerializableMoveValue::U64(balance);
495    let balance_struct_tag = StructTag {
496        address: coin_type_tag.address,
497        module: Identifier::new("balance").unwrap(),
498        name: Identifier::new("Balance").unwrap(),
499        type_params: coin_type_tag.type_params.clone(),
500    };
501    let balance_struct = SimplifiedMoveStruct {
502        type_: balance_struct_tag,
503        fields: vec![(Identifier::new("value").unwrap(), serializable_value)],
504    };
505    let serializable_balance = SerializableMoveValue::Struct(balance_struct);
506    // coin.Coin
507    let coin_obj = SimplifiedMoveStruct {
508        type_: *coin_type_tag,
509        fields: vec![
510            (Identifier::new("id").unwrap(), serializable_object_uid),
511            (Identifier::new("balance").unwrap(), serializable_balance),
512        ],
513    };
514    Ok(ExtMoveValueInfo {
515        type_: type_tag_with_refs,
516        value: SerializableMoveValue::Struct(coin_obj),
517    })
518}
519
520/// Converts a type to type tag format used in tracing.
521fn trace_type_to_type_tag_with_refs(
522    context: &mut ExecutionContext<'_, '_, '_>,
523    type_: &Type,
524) -> Result<TypeTagWithRefs, ExecutionError> {
525    let (deref_type, ref_type) = match type_ {
526        Type::Reference(t) => (t.as_ref(), Some(RefType::Imm)),
527        Type::MutableReference(t) => (t.as_ref(), Some(RefType::Mut)),
528        t => (t, None),
529    };
530    let type_ = context
531        .vm
532        .get_runtime()
533        .get_type_tag(deref_type)
534        .map_err(|e| ExecutionError::new_with_source(ExecutionErrorKind::InvariantViolation, e))?;
535    Ok(TypeTagWithRefs { type_, ref_type })
536}