From 5bd4c19f67748aac8969a122a05661149c22030c Mon Sep 17 00:00:00 2001 From: Pierrick Couderc Date: Mon, 6 May 2024 16:50:28 +0200 Subject: [PATCH 1/4] EVM/Node: move "one or two" encoding to an helper module --- .../bin_node/lib_dev/encodings/helpers.ml | 26 +++++++++++++++++ etherlink/bin_node/lib_dev/rpc_encodings.ml | 28 +++---------------- 2 files changed, 30 insertions(+), 24 deletions(-) create mode 100644 etherlink/bin_node/lib_dev/encodings/helpers.ml diff --git a/etherlink/bin_node/lib_dev/encodings/helpers.ml b/etherlink/bin_node/lib_dev/encodings/helpers.ml new file mode 100644 index 000000000000..6f3697c2d347 --- /dev/null +++ b/etherlink/bin_node/lib_dev/encodings/helpers.ml @@ -0,0 +1,26 @@ +(*****************************************************************************) +(* *) +(* SPDX-License-Identifier: MIT *) +(* Copyright (c) 2024 Nomadic Labs *) +(* *) +(*****************************************************************************) + +let encoding_with_optional_second_param encoding second_param_encoding + default_second_param = + let open Data_encoding in + let encoding = if is_tup encoding then encoding else tup1 encoding in + union + [ + case + ~title:"with_second_param" + (Tag 0) + (merge_tups encoding (tup1 second_param_encoding)) + (fun (t, second_param) -> Some (t, second_param)) + (fun (t, second_param) -> (t, second_param)); + case + ~title:"without_second_param" + (Tag 1) + encoding + (fun (t, _) -> Some t) + (fun t -> (t, default_second_param)); + ] diff --git a/etherlink/bin_node/lib_dev/rpc_encodings.ml b/etherlink/bin_node/lib_dev/rpc_encodings.ml index e70e298e95e5..3746af043f99 100644 --- a/etherlink/bin_node/lib_dev/rpc_encodings.ml +++ b/etherlink/bin_node/lib_dev/rpc_encodings.ml @@ -136,37 +136,17 @@ module type METHOD = sig type ('input, 'output) method_ += Method : (input, output) method_ end -let encoding_with_optional_block_param block_param_encoding default_block_param - encoding = - let open Data_encoding in - let encoding = if is_tup encoding then encoding else tup1 encoding in - union - [ - case - ~title:"with_block_param" - (Tag 0) - (merge_tups encoding (tup1 block_param_encoding)) - (fun (t, block_param) -> Some (t, block_param)) - (fun (t, block_param) -> (t, block_param)); - case - ~title:"without_block_param" - (Tag 1) - encoding - (fun (t, _) -> Some t) - (fun t -> (t, default_block_param)); - ] - let encoding_with_optional_extended_block_param encoding = - encoding_with_optional_block_param + Evm_node_lib_dev_encoding.Helpers.encoding_with_optional_second_param + encoding Ethereum_types.Block_parameter.extended_encoding Ethereum_types.Block_parameter.(Block_parameter Latest) - encoding let encoding_with_optional_block_param encoding = - encoding_with_optional_block_param + Evm_node_lib_dev_encoding.Helpers.encoding_with_optional_second_param + encoding Ethereum_types.Block_parameter.encoding Ethereum_types.Block_parameter.Latest - encoding module Kernel_version = struct type input = unit -- GitLab From aad8adfb729bf80356a19788dc2a933a5b6be417 Mon Sep 17 00:00:00 2001 From: Pierrick Couderc Date: Mon, 6 May 2024 17:06:36 +0200 Subject: [PATCH 2/4] EVM/Node: tracer configuration --- .../lib_dev/encodings/tracer_types.ml | 97 +++++++++++++++++++ .../lib_dev/encodings/tracer_types.mli | 36 +++++++ 2 files changed, 133 insertions(+) create mode 100644 etherlink/bin_node/lib_dev/encodings/tracer_types.ml create mode 100644 etherlink/bin_node/lib_dev/encodings/tracer_types.mli diff --git a/etherlink/bin_node/lib_dev/encodings/tracer_types.ml b/etherlink/bin_node/lib_dev/encodings/tracer_types.ml new file mode 100644 index 000000000000..b84ed46fe7b4 --- /dev/null +++ b/etherlink/bin_node/lib_dev/encodings/tracer_types.ml @@ -0,0 +1,97 @@ +(*****************************************************************************) +(* *) +(* SPDX-License-Identifier: MIT *) +(* Copyright (c) 2023 Nomadic Labs *) +(* *) +(*****************************************************************************) + +type tracer_config = { + enable_return_data : bool; + enable_memory : bool; + disable_stack : bool; + disable_storage : bool; +} + +let default_tracer_config = + { + enable_return_data = false; + enable_memory = false; + disable_stack = false; + disable_storage = false; + } + +let tracer_config_encoding = + let open Data_encoding in + conv + (fun {enable_return_data; enable_memory; disable_stack; disable_storage} -> + ( enable_return_data, + enable_memory, + disable_stack, + disable_storage, + false, + 5 )) + (fun ( enable_return_data, + enable_memory, + disable_stack, + disable_storage, + _, + _ ) -> + {enable_return_data; enable_memory; disable_stack; disable_storage}) + (obj6 + (dft "enableReturnData" bool default_tracer_config.enable_return_data) + (dft "enableMemory" bool default_tracer_config.enable_memory) + (dft "disableStack" bool default_tracer_config.disable_stack) + (dft "disableStorage" bool default_tracer_config.disable_storage) + (dft "debug" bool false) + (dft "limit" int31 5)) + +type tracer_kind = StructLogger + +(* TODO: (#7212) make it return an error so that we can return an understadable + error to the user. *) +(* Cannot be made a string_enum due to `"data_encoding.string_enum: cannot have a + single case, use constant instead"`. *) +let tracer_kind_encoding = + Data_encoding.( + conv + (fun StructLogger -> ()) + (fun () -> StructLogger) + (constant "structLogger")) + +type config = { + tracer : tracer_kind; + tracer_config : tracer_config; + timeout : Time.System.Span.t; + reexec : int64; +} + +(* Default config is derived from the specification of the RPC: + https://geth.ethereum.org/docs/interacting-with-geth/rpc/ns-debug#debugtracetransaction. *) +let default_config = + { + tracer = StructLogger; + tracer_config = default_tracer_config; + timeout = Time.System.Span.of_seconds_exn 5.; + reexec = 128L; + } + +let config_encoding = + let open Data_encoding in + conv + (fun {tracer; tracer_config; timeout; reexec} -> + (tracer, tracer_config, timeout, reexec)) + (fun (tracer, tracer_config, timeout, reexec) -> + {tracer; tracer_config; timeout; reexec}) + (obj4 + (dft "tracer" tracer_kind_encoding default_config.tracer) + (dft "tracerConfig" tracer_config_encoding default_config.tracer_config) + (dft "timeout" Time.System.Span.encoding default_config.timeout) + (dft "reexec" int64 default_config.reexec)) + +type input = Ethereum_types.hash * config + +let input_encoding = + Helpers.encoding_with_optional_second_param + Ethereum_types.hash_encoding + config_encoding + default_config diff --git a/etherlink/bin_node/lib_dev/encodings/tracer_types.mli b/etherlink/bin_node/lib_dev/encodings/tracer_types.mli new file mode 100644 index 000000000000..fcaa47442295 --- /dev/null +++ b/etherlink/bin_node/lib_dev/encodings/tracer_types.mli @@ -0,0 +1,36 @@ +(*****************************************************************************) +(* *) +(* SPDX-License-Identifier: MIT *) +(* Copyright (c) 2024 Nomadic Labs *) +(* *) +(*****************************************************************************) + +type tracer_config = { + enable_return_data : bool; + enable_memory : bool; + disable_stack : bool; + disable_storage : bool; +} + +val default_tracer_config : tracer_config + +val tracer_config_encoding : tracer_config Data_encoding.t + +type tracer_kind = StructLogger + +val tracer_kind_encoding : tracer_kind Data_encoding.t + +type config = { + tracer : tracer_kind; + tracer_config : tracer_config; + timeout : Time.System.Span.t; + reexec : int64; +} + +val default_config : config + +val config_encoding : config Data_encoding.t + +type input = Ethereum_types.hash * config + +val input_encoding : (Ethereum_types.hash * config) Data_encoding.t -- GitLab From 82bdba72b12a5ca896dcb23720def16b29d01dbf Mon Sep 17 00:00:00 2001 From: Pierrick Couderc Date: Tue, 7 May 2024 11:02:00 +0200 Subject: [PATCH 3/4] EVM/Node: add boilerplate for debug_traceTransaction RPC --- etherlink/bin_node/lib_dev/rpc_encodings.ml | 15 +++++++++++++++ etherlink/bin_node/lib_dev/rpc_encodings.mli | 3 +++ etherlink/bin_node/lib_dev/services.ml | 15 ++++++++++++++- 3 files changed, 32 insertions(+), 1 deletion(-) diff --git a/etherlink/bin_node/lib_dev/rpc_encodings.ml b/etherlink/bin_node/lib_dev/rpc_encodings.ml index 3746af043f99..6b2dcb09d183 100644 --- a/etherlink/bin_node/lib_dev/rpc_encodings.ml +++ b/etherlink/bin_node/lib_dev/rpc_encodings.ml @@ -706,6 +706,20 @@ module Replay_block = struct type ('input, 'output) method_ += Method : (input, output) method_ end +module Trace_transaction = struct + type input = Tracer_types.input + + type output = unit + + let input_encoding = Tracer_types.input_encoding + + let output_encoding = Data_encoding.unit + + let method_ = "debug_traceTransaction" + + type ('input, 'output) method_ += Method : (input, output) method_ +end + type map_result = | Method : ('input, 'output) method_ @@ -751,6 +765,7 @@ let supported_methods : (module METHOD) list = (module Durable_state_value); (module Eth_max_priority_fee_per_gas); (module Replay_block); + (module Trace_transaction); ] let unsupported_methods : string list = diff --git a/etherlink/bin_node/lib_dev/rpc_encodings.mli b/etherlink/bin_node/lib_dev/rpc_encodings.mli index e0e55bce1e46..0605ec97933e 100644 --- a/etherlink/bin_node/lib_dev/rpc_encodings.mli +++ b/etherlink/bin_node/lib_dev/rpc_encodings.mli @@ -277,6 +277,9 @@ module Replay_block : with type input = Ethereum_types.quantity and type output = Ethereum_types.block +module Trace_transaction : + METHOD with type input = Tracer_types.input and type output = unit + type map_result = | Method : ('input, 'output) method_ diff --git a/etherlink/bin_node/lib_dev/services.ml b/etherlink/bin_node/lib_dev/services.ml index 64484d81ea2e..74b0b7e81942 100644 --- a/etherlink/bin_node/lib_dev/services.ml +++ b/etherlink/bin_node/lib_dev/services.ml @@ -405,7 +405,20 @@ let dispatch_request (config : Configuration.t) | Method (Eth_max_priority_fee_per_gas.Method, module_) -> let f (_ : unit option) = rpc_ok @@ Qty Z.zero in build ~f module_ parameters - | _ -> Stdlib.failwith "The pattern matching of methods is not exhaustive" + | Method (Trace_transaction.Method, module_) -> + let f (_ : Tracer_types.input option) = + return + (Error + JSONRPC. + { + code = -32000; + message = "Method not implemented"; + data = Some (`String method_); + }) + in + build ~f module_ parameters + | Method (_, _) -> + Stdlib.failwith "The pattern matching of methods is not exhaustive" in Lwt.return JSONRPC.{value; id} -- GitLab From bf760b95c7aa3326ed3ae958aca5bf44c8cf0c54 Mon Sep 17 00:00:00 2001 From: Pierrick Couderc Date: Tue, 7 May 2024 17:01:02 +0200 Subject: [PATCH 4/4] EVM/Tests: test debug_traceTransaction exists --- etherlink/tezt/lib/rpc.ml | 29 ++++++++++ etherlink/tezt/lib/rpc.mli | 9 ++++ etherlink/tezt/tests/evm_sequencer.ml | 76 ++++++++++++++++++++++++++- 3 files changed, 113 insertions(+), 1 deletion(-) diff --git a/etherlink/tezt/lib/rpc.ml b/etherlink/tezt/lib/rpc.ml index a8a25dc7a16e..491630057dd2 100644 --- a/etherlink/tezt/lib/rpc.ml +++ b/etherlink/tezt/lib/rpc.ml @@ -137,6 +137,24 @@ module Request = struct } let txpool_content = {method_ = "txpool_content"; parameters = `A []} + + let trace_transaction ~transaction_hash ?tracer ?tracer_config () = + let config = + match tracer with + | Some tracer -> [("tracer", `String tracer)] + | None -> [] + in + let config = + match tracer_config with + | None -> config + | Some tracer_config -> ("tracerConfig", `O tracer_config) :: config + in + let parameters = + match config with + | [] -> `A [`String transaction_hash] + | config -> `A [`String transaction_hash; `O config] + in + {method_ = "debug_traceTransaction"; parameters} end let net_version evm_node = @@ -357,3 +375,14 @@ let txpool_content evm_node = let txpool = Evm_node.extract_result response in (parse txpool "pending", parse txpool "queued")) response + +let trace_transaction ~transaction_hash ?tracer ?tracer_config evm_node = + let* response = + Evm_node.call_evm_rpc + evm_node + (Request.trace_transaction ~transaction_hash ?tracer ?tracer_config ()) + in + return + @@ decode_or_error + (fun response -> Evm_node.extract_result response |> ignore) + response diff --git a/etherlink/tezt/lib/rpc.mli b/etherlink/tezt/lib/rpc.mli index 2a8c5c2fb02b..e092d78f96b9 100644 --- a/etherlink/tezt/lib/rpc.mli +++ b/etherlink/tezt/lib/rpc.mli @@ -161,3 +161,12 @@ type txpool_slot = {address : string; transactions : (int64 * JSON.t) list} contained in the `pending` and `queued` pools. *) val txpool_content : Evm_node.t -> (txpool_slot list * txpool_slot list, error) result Lwt.t + +(** [trace_transaction ~transaction_hash evm_node] replays the given transaction + in the same execution context. Doesn't return the trace for now. *) +val trace_transaction : + transaction_hash:string -> + ?tracer:string -> + ?tracer_config:(string * JSON.u) list -> + Evm_node.t -> + (unit, error) result Lwt.t diff --git a/etherlink/tezt/tests/evm_sequencer.ml b/etherlink/tezt/tests/evm_sequencer.ml index 48820b5fde74..59de4deaa278 100644 --- a/etherlink/tezt/tests/evm_sequencer.ml +++ b/etherlink/tezt/tests/evm_sequencer.ml @@ -3431,6 +3431,79 @@ let test_txpool_content_empty_with_legacy_encoding = Test.fail "The transaction pool RPC returns more than a single transaction" +let test_trace_transaction = + register_both + ~tags:["evm"; "rpc"; "trace"] + ~title:"Sequencer can run debug_traceTransaction" + @@ fun {sc_rollup_node; sequencer; client; proxy; _} _protocol -> + (* Transfer funds to a random address. *) + let address = "0xB7A97043983f24991398E5a82f63F4C58a417185" in + let* transaction_hash = + send_transaction + (Eth_cli.transaction_send + ~source_private_key:Eth_account.bootstrap_accounts.(0).private_key + ~to_public_key:address + ~value:(Wei.of_eth_int 10) + ~endpoint:(Evm_node.endpoint sequencer)) + sequencer + in + (* Block few levels to ensure we are replaying on an old block. *) + let* () = + repeat 2 (fun () -> + next_evm_level ~evm_node:sequencer ~sc_rollup_node ~client) + in + let* () = bake_until_sync ~sequencer ~sc_rollup_node ~proxy ~client () in + (* Check tracing without options works *) + let* trace_result = Rpc.trace_transaction ~transaction_hash sequencer in + (match trace_result with + | Ok _ -> Test.fail "debug_traceTransaction should have failed" + | Error {message; _} -> + Check.( + (message = "Method not implemented") + string + ~error_msg:"debug_traceTransaction is not implemented")) ; + (* Check tracing with a tracer without config *) + let* trace_result = + Rpc.trace_transaction ~transaction_hash ~tracer:"structLogger" sequencer + in + (match trace_result with + | Ok _ -> Test.fail "debug_traceTransaction should have failed" + | Error {message; _} -> + Check.( + (message = "Method not implemented") + string + ~error_msg:"debug_traceTransaction is not implemented")) ; + (* Check tracing with a tracer and a config *) + let* trace_result = + Rpc.trace_transaction + ~transaction_hash + ~tracer:"structLogger" + ~tracer_config:[("enableMemory", `Bool true)] + sequencer + in + (match trace_result with + | Ok _ -> Test.fail "debug_traceTransaction should have failed" + | Error {message; _} -> + Check.( + (message = "Method not implemented") + string + ~error_msg:"debug_traceTransaction is not implemented")) ; + (* Check tracing without a tracer and a config *) + let* trace_result = + Rpc.trace_transaction + ~transaction_hash + ~tracer_config:[("enableMemory", `Bool true)] + sequencer + in + (match trace_result with + | Ok _ -> Test.fail "debug_traceTransaction should have failed" + | Error {message; _} -> + Check.( + (message = "Method not implemented") + string + ~error_msg:"debug_traceTransaction is not implemented")) ; + unit + let protocols = Protocol.all let () = @@ -3479,4 +3552,5 @@ let () = test_preimages_endpoint protocols ; test_store_smart_rollup_address protocols ; test_replay_rpc protocols ; - test_txpool_content_empty_with_legacy_encoding protocols + test_txpool_content_empty_with_legacy_encoding protocols ; + test_trace_transaction protocols -- GitLab