diff --git a/etherlink/CHANGES_NODE.md b/etherlink/CHANGES_NODE.md index 2516b5a0cf7cf5bb0fe609d6490763e4641c5f08..b65582254c5bec7f1b99dd5b3e5f6f0f07832553 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 c3cec196a46310791fe72cf4eaa623324eb7cdde..6835e093bfbd7a6683b246e1e55134458e20fef4 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 08292ed433c42ecbff74383696cb60b0b8122c53..a3e53277b9b947d6e6c517403c1a867b4b3f42a0 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 d438723263f56cc63e8800d0773b43874f77174d..a5d754e2a456dd34aed258d7c50c491dddd498b6 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 c7230502ab64e1f83972c829c66245f1ccf0371a..ab6a14cc2e93237ee5a43366980a8ac5c050fa37 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 7183b9e9d8fd25cfeeb86ed5c39a279e1bb6d6a0..79619d15366713b9e8789cbfa44c72681d98bd6a 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 2d934ab7297078b78d1c36ffa7f9086894f1b3d4..bc003c62fd633e38ece0f05fbfa89145ea923c1f 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 3d81105a0013ee184f5739f9a73fd67c7c70283c..a064573f15d5c3913192e2ad0a3c75551911ca35 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 19a6d7bc2c07a5e929eda1dcb9fcdd2a39b83430..d90c8b0397d243d015b5cd1955beeb9bf8750f11 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 50420c89e3dfe056fb0233d3396f93931b124a59..85394d5c77c5dbb88bedbec263b16332b8749832 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 bbc42031112849dc1b0bc0393b5053d35676397d..be3579c67c7c22ddc652f1e69cb866cfec8de75b 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,