Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Sui Custom Tracer #1

Open
Poytr1 opened this issue May 23, 2024 · 0 comments
Open

Sui Custom Tracer #1

Poytr1 opened this issue May 23, 2024 · 0 comments
Assignees

Comments

@Poytr1
Copy link
Collaborator

Poytr1 commented May 23, 2024

Design Document for Sui Custom Tracer

Summary

This document outlines the design and implementation details for a custom tracer for Sui. The goal is to create a flexible and minimally intrusive tracing framework that integrates seamlessly into the existing Sui infrastructure while maintaining compatibility with Sentio's custom functionalities.

Changes We've Made

Entry Point of Sentio's Trace Service: Sui Trace Server

  • Developed a trace service that depends on sui_replay to invoke the trace API.
  • Modified sui_replay to use executor for transaction replay and call trace retrieval.
  • Altered the download_latest_object method in sui_replay, replacing block_on with a thread channel to convert the asynchronous method to synchronous, ensuring it operates correctly within the API service (replay.rs).
  • Added call_trace to the PTB context and introduced execution_mode::DevCallTrace.
  • Implemented dev_transaction_call_trace in sui_executor to call execute_transaction_to_effects::<execution_mode::DevCallTrace>, which in turn calls the VM's call trace method.
  • Integrated the call_trace method into the VM runtime down to the interpreter level.

Background

The Sentio debugger feature (e.g., example transaction) obtains call traces of historical transactions to compute fund flows. The current changes to Sentio's SUI fork are tightly coupled with Sentio's requirements and include custom call trace data structures and methods within the Move VM. These modifications lead to duplicated code and hinder upstream contributions. Periodic merges from upstream are required, causing maintenance challenges. The objective is to refactor and redesign the solution to be more generic, facilitating integration with Sentio's call trace requirements and existing SUI features like gas profiling.

For reference, Geth's custom tracer API can be reviewed here.

Requirements

  • Minimize intrusiveness to existing Move VM code.
  • Maintain feature parity for Sentio's call trace feature and SUI gas profiler.
  • Extensibility to support instruction-level debugging.
  • Include in the debug build.
  • Enable or disable tracers via cargo build feature flags.
  • Initial design targets CLI usage (phase 1), with potential API implementation (persisting log files generated by the tracer).
  • Trace API support is covered in a separate section, which can be considered as phase 2.

High-Level Flow

332865156-205ebd50-e662-4f63-8700-5d091522ce3d.png

Detailed Design

Changes We Propose - Phase 1 (CLI Support)

  • Add a new tracer trait for customized tracers.
  • Add two new methods into the GasMeter to initialize and retrieve the customized tracers.
  • Add a new module move_tracer with several useful macros to control the on/off state of each tracer.
  • Add logic to invoke the customized tracers within the interpreter's execution logic.

Tracer Trait

trait Tracer {
    fn on_instruction(&self) {}
    fn on_function_call(&self) {}
    fn on_function_generics_call(&self) {}
    fn on_function_return(&self) {}
    fn dump_file(&self) {}
}

Each tracer will have its own feature flag (e.g., gas_profiler). Tracers will be added to the tracer pool only when their feature flag is enabled.

Modifications to GasStatus

pub struct GasStatus {
    // ...
    tracers: Vec<Box<dyn Tracer>>,
}

impl GasStatus {
    fn get_tracers_mut(&mut self) -> &mut Vec<Box<dyn Tracer>> {
        &mut self.tracers
    }

    fn add_tracer(&mut self, tracer: Box<dyn Tracer>) {
        self.tracers.push(tracer);
    }
}

Initialization

move_vm_tracer::tracer_feature_enabled! {
    use move_vm_tracer::Tracer;
    gas_meter.add_tracer(Box::new(GasProfilerTracer::init_default_cfg(
        function_name.to_string(),
        gas_meter.remaining_gas().into(),
    )));
}

Macros

#[macro_export]
macro_rules! trace_open_frame {
    ($gas_meter:expr, $frame_name:expr) => {
        #[cfg(feature = "sui-tracer")]
        {
            let gas_rem = $gas_meter.remaining_gas().into();
            move_vm_tracer::trace_open_frame_impl!(
                $gas_meter.get_tracers_mut(),
                $frame_name,
                gas_rem
            );
        }
    };
}

#[macro_export]
macro_rules! trace_open_frame_impl {
    ($tracers:expr, $frame_name:expr, $gas_rem:expr) => {
        #[cfg(feature = "sui-tracer")]
        {
            for tracer in $tracers {
                tracer.on_function_call();
            }
        }
    };
}

#[macro_export]
macro_rules! trace_close_frame {
    ($gas_meter:expr, $frame_name:expr) => {
        #[cfg(feature = "sui-tracer")]
        {
            let gas_rem = $gas_meter.remaining_gas().into();
            move_vm_tracer::trace_close_frame_impl!(
                $gas_meter.get_tracers_mut(),
                $frame_name,
                gas_rem
            );
        }
    };
}

#[macro_export]
macro_rules! trace_close_frame_impl {
    ($tracers:expr, $frame_name:expr, $gas_rem:expr) => {
        #[cfg(feature = "sui-tracer")]
        {
            for tracer in $tracers {
                tracer.on_function_return();
            }
        }
    };
}

// Instruction level macros go here

Usage During Execution

match exit_code {
    ExitCode::Call(fh_idx) => {
        let func = resolver.function_from_handle(fh_idx);
        #[cfg(debug_assertions)]
        let func_name = func.pretty_string();
        trace_open_frame!(gas_meter, func_name.clone());
    }
    ExitCode::Return => {
        let non_ref_vals = current_frame
            .locals
            .drop_all_values()
            .map(|(_idx, val)| val);

        gas_meter
            .charge_drop_frame(non_ref_vals.into_iter())
            .map_err(|e| self.set_location(e))?;

        trace_close_frame!(gas_meter, current_frame.function.pretty_string());
    }
}

Limitations - Phase 1

The current design is tailored for CLI use and is not optimized for API services. For API support in a separate service based on the replay method, additional changes are needed. The existing method dev_inspect_transaction in the executor returns only transaction effects, and tracer output is written to a separate log file. Implementing an API would require adding a new method like dev_transaction_call_trace, which may not be accepted upstream. A potential solution involves invoking the CLI and using an asynchronous approach to fetch local log files in the API service and obtain trace results, which is to be determined.

We will discuss the API version in the next section.

Open Questions - Phase 1

  • Tracers are placed in the same place as the gas profiler, i.e., GasMeter, which may not be ideal. To avoid changing the signature by introducing a new parameter in the VM and interpreter, are there any alternative approaches? One approach is to add a new container for the GasMeter and pass the object of this new container into the VM.

Changes We Propose - Phase 2 (API Support)

  • Add a new API sui_devTraceTransactionBlock like sui_devInspectTransactionBlock.
  • Add a new execution mode DevTrace into the execution engine.
  • Add an optional trace result in the VM result.
  • Extend the tracer trait to obtain the trace result.
  • Implement tracer-related logic in the PTB executor if needed.

API Specification: sui_devTraceTransactionBlock

Description

This endpoint replays a historic transaction block and returns the VM tracing result, providing detailed information about the execution of the transaction block.

Parameters
Index Type Description
0 string Required. Sender address.
1 string Required. BCS encoded TransactionKind (as opposed to TransactionData, which includes gasBudget and gasPrice).
2 number Optional. Gas is not charged, but gas usage is still calculated. Defaults to use reference gas price.
3 number Optional. The epoch to perform the call. Will be set from the system state object

if not provided. |
| 4 | string | Optional. Tracer name, native tracer by default. |
| 5 | null | Optional. Additional arguments for tracer. |

Example Request Body
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "sui_devTraceTransactionBlock",
  "params": [
    "0xd70420418b84502e506794227f897237764dde8d79a01ab2104bf742a277a2ab",
    "AAACACBnxtMcbJcOVn8D72fYEaT4Q2ZbjePygvpIs+AQO6m77QEAagYVO5/EhuEB8OnicDrIZm0GrsxN3355JqNhlwxlpbECAAAAAAAAACDoQ3EipycU+/EOvBcDPFtMkZiSbdzWAw3CwdmQCAtBWAEBAQEBAAEAAC9cVD1xauQ9RT3rOxmbva8bxwMMdoL4dwPc5DEkj+3gASxDgF0Nb1QCp60Npb3sVJx83qBrxKHTOaIlIe6pM7iJAgAAAAAAAAAgnvsgc1pPauyCE27/c+aBnHN3fSsxRAWdEJYzYFOryNAvXFQ9cWrkPUU96zsZm72vG8cDDHaC+HcD3OQxJI/t4AoAAAAAAAAAoIYBAAAAAAAA",
    1000,
    8888,
    null,
    null
  ]
}
Response
Name Type Description
vm_trace object Trace result
Example Response Body
{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "vm_trace": {
      "steps": [
        {
          "step": 1,
          "pc": 0,
          "instr": "Pop",
          "locals": ["0x0"]
        },
        {
          "step": 2,
          "pc": 1,
          "instr": "CastU8",
          "locals": ["0x0", "0x1"]
        }
        // More steps...
      ],
      "gas_used": 21000,
      "error": null
    }
  }
}

Tracer Trait

trait Tracer {
    fn on_instruction(&self) {}
    fn on_function_call(&self) {}
    fn on_function_generics_call(&self) {}
    fn on_function_return(&self) {}
    fn dump_file(&self) {}
    fn into_result(&self) -> serde_json::Value // Customizable tracer result
}

Definition of the New VM Result

#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd)]
pub struct TraceResult {
    traces: serde_json::Value,
}

pub type VMResultWithTraces<T> = ::std::result::Result<T, (VMError, Vec<TraceResult>)>;

Open Questions - Phase 2

  • Does the new API need to be proposed and reviewed by the community?
  • Do the Move VM changes need to be merged into the official Move VM, or is it specific to SUI?

Backward Compatibility

VM traces should be available for all the historical transactions. And in the execution layer, changes will be added in the latest version.
In the Move VM layer, since the tracer related code is behind the feature flag and will be no included in the release build, so it would not affect the normal transaction execution.

Conclusion

This design aims to create a modular and maintainable tracing framework for the Sui blockchain, addressing both current requirements and potential future enhancements. The proposed approach minimizes disruption to existing codebases and provides a path for integrating advanced debugging and profiling features.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants