From 5e72222e8939f7327423aec782aa1b31b4585d1a Mon Sep 17 00:00:00 2001 From: Thomas Letan Date: Thu, 26 Sep 2024 10:53:06 +0200 Subject: [PATCH] EVM Node: Add the experimental feature `overwrite_simulation_tick_limit` This can be useful to execute calls that will not be injected in transactions (similarly to what the Uniswap V3 frontend does to prepare swaps). However, it can lead to confusing UX for users, where eth_estimateGas fails when eth_call succeeded. --- etherlink/CHANGES_NODE.md | 9 ++ etherlink/bin_node/config/configuration.ml | 23 ++++- etherlink/bin_node/config/configuration.mli | 1 + etherlink/bin_node/lib_dev/evm_ro_context.ml | 5 ++ etherlink/bin_node/lib_dev/rollup_node.ml | 3 + etherlink/bin_node/lib_dev/services.ml | 7 +- .../bin_node/lib_dev/services_backend_sig.ml | 1 + etherlink/bin_node/lib_dev/simulator.ml | 14 ++- etherlink/tezt/tests/evm_sequencer.ml | 87 ++++++++++++++++++- .../Alpha- Configuration RPC.out | 9 +- .../EVM Node- describe config.out | 9 +- 11 files changed, 158 insertions(+), 10 deletions(-) diff --git a/etherlink/CHANGES_NODE.md b/etherlink/CHANGES_NODE.md index 2516b5a0cf7c..b65582254c5b 100644 --- a/etherlink/CHANGES_NODE.md +++ b/etherlink/CHANGES_NODE.md @@ -4,6 +4,15 @@ ### Features +#### RPCs + +- **Experimental:** Introduce the experimental feature + `ovewrite_simulation_tick_limit`. When enabled, the `eth_call` RPC is no + longer subject to the kernel tick limit. This can be useful to execute calls + that will not be injected in transactions (similarly to what the Uniswap V3 + frontend does to prepare swaps). However, it can lead to confusing UX for + users, where eth_estimateGas fails when eth_call succeeded. (!15078) + ### Bug fixes ### Internals diff --git a/etherlink/bin_node/config/configuration.ml b/etherlink/bin_node/config/configuration.ml index c3cec196a463..6835e093bfbd 100644 --- a/etherlink/bin_node/config/configuration.ml +++ b/etherlink/bin_node/config/configuration.ml @@ -34,6 +34,7 @@ type experimental_features = { enable_send_raw_transaction : bool; node_transaction_validation : bool; block_storage_sqlite3 : bool; + overwrite_simulation_tick_limit : bool; } type sequencer = { @@ -122,6 +123,7 @@ let default_experimental_features = drop_duplicate_on_injection = false; node_transaction_validation = false; block_storage_sqlite3 = false; + overwrite_simulation_tick_limit = false; } let default_data_dir = Filename.concat (Sys.getenv "HOME") ".octez-evm-node" @@ -650,22 +652,26 @@ let experimental_features_encoding = enable_send_raw_transaction; node_transaction_validation; block_storage_sqlite3; + overwrite_simulation_tick_limit; } -> ( drop_duplicate_on_injection, enable_send_raw_transaction, node_transaction_validation, - block_storage_sqlite3 )) + block_storage_sqlite3, + overwrite_simulation_tick_limit )) (fun ( drop_duplicate_on_injection, enable_send_raw_transaction, node_transaction_validation, - block_storage_sqlite3 ) -> + block_storage_sqlite3, + overwrite_simulation_tick_limit ) -> { drop_duplicate_on_injection; enable_send_raw_transaction; node_transaction_validation; block_storage_sqlite3; + overwrite_simulation_tick_limit; }) - (obj4 + (obj5 (dft ~description: "Request the rollup node to filter messages it has already \ @@ -688,6 +694,17 @@ let experimental_features_encoding = "Store the blocks and transactions in a sqlite3 database and \ removes them from the durable storage" bool + false) + (dft + "overwrite_simulation_tick_limit" + ~description: + "When enabled, the eth_call method is not subject to the tick \ + limit. This can be useful to execute calls that will not be \ + injected in transactions (similarly to what the Uniswap V3 \ + frontend does to prepare swaps). However, it can lead to \ + confusing UX for users, where eth_estimateGas fails when eth_call \ + succeeded." + bool false)) let proxy_encoding = diff --git a/etherlink/bin_node/config/configuration.mli b/etherlink/bin_node/config/configuration.mli index 08292ed433c4..a3e53277b9b9 100644 --- a/etherlink/bin_node/config/configuration.mli +++ b/etherlink/bin_node/config/configuration.mli @@ -56,6 +56,7 @@ type experimental_features = { enable_send_raw_transaction : bool; node_transaction_validation : bool; block_storage_sqlite3 : bool; + overwrite_simulation_tick_limit : bool; } type sequencer = { diff --git a/etherlink/bin_node/lib_dev/evm_ro_context.ml b/etherlink/bin_node/lib_dev/evm_ro_context.ml index d438723263f5..a5d754e2a456 100644 --- a/etherlink/bin_node/lib_dev/evm_ro_context.ml +++ b/etherlink/bin_node/lib_dev/evm_ro_context.ml @@ -195,6 +195,11 @@ struct module SimulatorBackend = struct include Reader + let modify ~key ~value state = + let open Lwt_result_syntax in + let*! state = Evm_state.modify ~key ~value state in + return state + let simulate_and_read ?state_override simulate_state ~input = let open Lwt_result_syntax in let config = diff --git a/etherlink/bin_node/lib_dev/rollup_node.ml b/etherlink/bin_node/lib_dev/rollup_node.ml index c7230502ab64..ab6a14cc2e93 100644 --- a/etherlink/bin_node/lib_dev/rollup_node.ml +++ b/etherlink/bin_node/lib_dev/rollup_node.ml @@ -131,6 +131,9 @@ end) : Services_backend_sig.Backend = struct module SimulatorBackend = struct include Reader + let modify ~key:_ ~value:_ _state = + failwith "Unsupported primitive for the proxy mode" + let simulate_and_read ?state_override:_ _state ~input = let open Lwt_result_syntax in let* json = diff --git a/etherlink/bin_node/lib_dev/services.ml b/etherlink/bin_node/lib_dev/services.ml index 7183b9e9d8fd..79619d153667 100644 --- a/etherlink/bin_node/lib_dev/services.ml +++ b/etherlink/bin_node/lib_dev/services.ml @@ -610,7 +610,12 @@ let dispatch_request (rpc : Configuration.rpc) (config : Configuration.t) | Eth_call.Method -> let f (call, block_param, state_override) = let* call_result = - Backend_rpc.simulate_call call block_param state_override + Backend_rpc.simulate_call + ~overwrite_tick_limit: + config.experimental_features.overwrite_simulation_tick_limit + call + block_param + state_override in match call_result with | Ok (Ok {value = Some value; gas_used = _}) -> rpc_ok value diff --git a/etherlink/bin_node/lib_dev/services_backend_sig.ml b/etherlink/bin_node/lib_dev/services_backend_sig.ml index 2d934ab72970..bc003c62fd63 100644 --- a/etherlink/bin_node/lib_dev/services_backend_sig.ml +++ b/etherlink/bin_node/lib_dev/services_backend_sig.ml @@ -103,6 +103,7 @@ module type S = sig context [block_param] (optionally updated with [state_override]) and returns the result. *) val simulate_call : + overwrite_tick_limit:bool -> Ethereum_types.call -> Ethereum_types.Block_parameter.extended -> Ethereum_types.state_override -> diff --git a/etherlink/bin_node/lib_dev/simulator.ml b/etherlink/bin_node/lib_dev/simulator.ml index 3d81105a0013..a064573f15d5 100644 --- a/etherlink/bin_node/lib_dev/simulator.ml +++ b/etherlink/bin_node/lib_dev/simulator.ml @@ -9,6 +9,8 @@ module type SimulationBackend = sig include Durable_storage.READER + val modify : key:string -> value:string -> state -> state tzresult Lwt.t + val simulate_and_read : ?state_override:Ethereum_types.state_override -> state -> @@ -70,10 +72,20 @@ module Make (SimulationBackend : SimulationBackend) = struct return `V0 else return `V1 - let simulate_call call block_param state_override = + let simulate_call ~overwrite_tick_limit call block_param state_override = let open Lwt_result_syntax in let* simulation_state = SimulationBackend.get_state ~block:block_param () in let* simulation_version = simulation_version simulation_state in + let* simulation_state = + if overwrite_tick_limit then + SimulationBackend.modify + simulation_state + ~key:"/evm/maximum_allowed_ticks" + ~value: + Data_encoding.( + Binary.to_string_exn Little_endian.int64 1_000_000_000_000L) + else return simulation_state + in let* bytes = call_simulation ~state_override diff --git a/etherlink/tezt/tests/evm_sequencer.ml b/etherlink/tezt/tests/evm_sequencer.ml index 19a6d7bc2c07..d90c8b0397d2 100644 --- a/etherlink/tezt/tests/evm_sequencer.ml +++ b/etherlink/tezt/tests/evm_sequencer.ml @@ -4736,6 +4736,90 @@ let test_trace_transaction = check_trace_result ~sequencer:trace_result ~rpc:trace_result_rpc ; unit +let test_overwrite_simulation_tick_limit = + register_all + ~kernels:Kernel.all + ~tags:["evm"; "rpc"; "overwrite_tick_limit"; "eth_call"] + ~title:"Can overwrite tick limit in eth_call with the experimental feature" + ~time_between_blocks:Nothing + ~maximum_allowed_ticks: + (* We set a smaller tick limit to make it easier to trigger the + OutOfTick error *) + 500_000_000L + @@ fun {sequencer; sc_rollup_node; _} _protocol -> + let* observer = + run_new_observer_node + ~sc_rollup_node + ~patch_config: + JSON.( + update + "experimental_features" + (put + ( "overwrite_simulation_tick_limit", + annotate ~origin:"" (`Bool true) ))) + sequencer + in + + let* recursive = Solidity_contracts.recursive () in + let* () = Eth_cli.add_abi ~label:recursive.label ~abi:recursive.abi () in + (* Deploy the contract. *) + let* recursive_address, _tx_hash = + send_transaction_to_sequencer + (fun () -> + Eth_cli.deploy + ~source_private_key:Eth_account.bootstrap_accounts.(0).private_key + ~endpoint:(Evm_node.endpoint sequencer) + ~abi:recursive.abi + ~bin:recursive.bin) + sequencer + in + + let* has_failed = + Lwt.catch + (fun () -> + let* _ = + Eth_cli.contract_call + ~endpoint:Evm_node.(endpoint sequencer) + ~abi_label:recursive.label + ~address:recursive_address + ~method_call:"call(200)" + () + in + return false) + (fun _ -> return true) + in + + if not has_failed then + Test.fail "The sequencer should have refused the `eth_call`" ; + + let* has_failed = + Lwt.catch + (fun () -> + let* _ = + Eth_cli.contract_call + ~endpoint:Evm_node.(endpoint observer) + ~abi_label:recursive.label + ~address:recursive_address + ~method_call:"call(200)" + () + in + return false) + (fun _ -> return true) + in + + if has_failed then + Test.fail "The observer shouldn’t have refused the `eth_call`" ; + + (* `eth_call` is supposed to work, but not gas estimation. *) + let* data = Cast.calldata ~args:["200"] "call(uint256)" in + let*@? (_ : Rpc.error) = + Rpc.estimate_gas + [("to", `String recursive_address); ("data", `String data)] + observer + in + + unit + let test_trace_transaction_on_invalid_transaction = register_all ~kernels:Kernel.all @@ -6552,4 +6636,5 @@ let () = test_tx_pool_replacing_transactions protocols ; test_da_fees_after_execution protocols ; test_trace_transaction_calltracer_failed_create protocols ; - test_configuration_service [Protocol.Alpha] + test_configuration_service [Protocol.Alpha] ; + test_overwrite_simulation_tick_limit protocols diff --git a/etherlink/tezt/tests/expected/evm_sequencer.ml/Alpha- Configuration RPC.out b/etherlink/tezt/tests/expected/evm_sequencer.ml/Alpha- Configuration RPC.out index 50420c89e3df..85394d5c77c5 100644 --- a/etherlink/tezt/tests/expected/evm_sequencer.ml/Alpha- Configuration RPC.out +++ b/etherlink/tezt/tests/expected/evm_sequencer.ml/Alpha- Configuration RPC.out @@ -14,7 +14,8 @@ "drop_duplicate_on_injection": true, "enable_send_raw_transaction": true, "node_transaction_validation": true, - "block_storage_sqlite3": true + "block_storage_sqlite3": true, + "overwrite_simulation_tick_limit": false }, "proxy": { "finalized_view": false, @@ -61,7 +62,8 @@ "drop_duplicate_on_injection": true, "enable_send_raw_transaction": true, "node_transaction_validation": true, - "block_storage_sqlite3": true + "block_storage_sqlite3": true, + "overwrite_simulation_tick_limit": false }, "proxy": { "finalized_view": false, @@ -101,7 +103,8 @@ "drop_duplicate_on_injection": true, "enable_send_raw_transaction": true, "node_transaction_validation": true, - "block_storage_sqlite3": true + "block_storage_sqlite3": true, + "overwrite_simulation_tick_limit": false }, "proxy": { "finalized_view": false, diff --git a/etherlink/tezt/tests/expected/evm_sequencer.ml/EVM Node- describe config.out b/etherlink/tezt/tests/expected/evm_sequencer.ml/EVM Node- describe config.out index bbc420311128..be3579c67c7c 100644 --- a/etherlink/tezt/tests/expected/evm_sequencer.ml/EVM Node- describe config.out +++ b/etherlink/tezt/tests/expected/evm_sequencer.ml/EVM Node- describe config.out @@ -126,7 +126,14 @@ "block_storage_sqlite3"?: boolean /* Store the blocks and transactions in a sqlite3 database and - removes them from the durable storage */ }, + removes them from the durable storage */, + "overwrite_simulation_tick_limit"?: + boolean + /* When enabled, the eth_call method is not subject to the tick + limit. This can be useful to execute calls that will not be + injected in transactions (similarly to what the Uniswap V3 + frontend does to prepare swaps). However, it can lead to confusing + UX for users, where eth_estimateGas fails when eth_call succeeded. */ }, "proxy"?: { "finalized_view"?: boolean, "evm_node_endpoint"?: $unistring, -- GitLab