From 4603224e4b30de7187c539cc5b7790f56d52c2f4 Mon Sep 17 00:00:00 2001 From: arnaud Date: Wed, 31 Jul 2024 15:48:38 +0200 Subject: [PATCH 1/6] EVM/Node: Da fee computation outside of simulator --- etherlink/bin_node/lib_dev/fees.ml | 27 +++++++++++++++++++++++ etherlink/bin_node/lib_dev/fees.mli | 15 +++++++++++++ etherlink/bin_node/lib_dev/simulator.ml | 29 ++++--------------------- 3 files changed, 46 insertions(+), 25 deletions(-) create mode 100644 etherlink/bin_node/lib_dev/fees.ml create mode 100644 etherlink/bin_node/lib_dev/fees.mli diff --git a/etherlink/bin_node/lib_dev/fees.ml b/etherlink/bin_node/lib_dev/fees.ml new file mode 100644 index 000000000000..4527a4777018 --- /dev/null +++ b/etherlink/bin_node/lib_dev/fees.ml @@ -0,0 +1,27 @@ +(*****************************************************************************) +(* *) +(* SPDX-License-Identifier: MIT *) +(* Copyright (c) 2023 Nomadic Labs *) +(* Copyright (c) 2024 Functori *) +(* *) +(*****************************************************************************) + +let gas_for_fees ?da_fee_per_byte ~gas_price tx_data = + (* The computation of the gas_for_fees comes from the kernel in fees.rs + in the gas_for_fees function *) + (* Constants defined in the kernel: *) + let assumed_tx_encoded_size = 150 in + let default_da_fee_per_byte = + (* 4 * 10^12, 4 mutez *) + Ethereum_types.quantity_of_z (Z.of_string "4_000_000_000_000") + in + let (Qty da_fee_per_byte) = + Option.value ~default:default_da_fee_per_byte da_fee_per_byte + in + (* Computation of da fee based on da fee per byte and variable tx data. *) + let da_fee da_fee_per_byte tx_data = + let size = Bytes.length tx_data + assumed_tx_encoded_size |> Z.of_int in + Z.mul da_fee_per_byte size + in + let fees = da_fee da_fee_per_byte tx_data in + Z.div fees gas_price diff --git a/etherlink/bin_node/lib_dev/fees.mli b/etherlink/bin_node/lib_dev/fees.mli new file mode 100644 index 000000000000..ec345d53395c --- /dev/null +++ b/etherlink/bin_node/lib_dev/fees.mli @@ -0,0 +1,15 @@ +(*****************************************************************************) +(* *) +(* SPDX-License-Identifier: MIT *) +(* Copyright (c) 2023 Nomadic Labs *) +(* Copyright (c) 2024 Functori *) +(* *) +(*****************************************************************************) + +(** [gas_for_fees ~da_fee_per_byte ~gas_price tx_data] returns the DA fees, + i.e. the gas unit necessary for the data availability. + + The whole point of this function is to avoid an unncessary call + to the WASM PVM to improve the performances. *) +val gas_for_fees : + ?da_fee_per_byte:Ethereum_types.quantity -> gas_price:Z.t -> bytes -> Z.t diff --git a/etherlink/bin_node/lib_dev/simulator.ml b/etherlink/bin_node/lib_dev/simulator.ml index 39996571b8fd..36b87c8b00d5 100644 --- a/etherlink/bin_node/lib_dev/simulator.ml +++ b/etherlink/bin_node/lib_dev/simulator.ml @@ -136,33 +136,13 @@ module Make (SimulationBackend : SimulationBackend) = struct The whole point of this function is to avoid an unncessary call to the WASM PVM to improve the performances. *) - let gas_for_fees simulation_state tx_data = + let gas_for_fees simulation_state tx_data : (Z.t, tztrace) result Lwt.t = let open Lwt_result_syntax in - (* Constants defined in the kernel: *) - let assumed_tx_encoded_size = 150 in - let default_da_fee_per_byte = - (* 4 * 10^12, 4 mutez *) - Ethereum_types.quantity_of_z (Z.of_string "4_000_000_000_000") - in - - (* Computation of da fee based on da fee per byte and variable tx data. *) - let da_fee da_fee_per_byte tx_data = - let size = Bytes.length tx_data + assumed_tx_encoded_size |> Z.of_int in - Z.mul da_fee_per_byte size - in - let read_qty path = let+ bytes = SimulationBackend.read simulation_state ~path in Option.map Ethereum_types.decode_number_le bytes in - - let* (Qty da_fee_per_byte) = - let+ da_fee_per_byte_opt = - read_qty Durable_storage_path.da_fee_per_byte - in - Option.value ~default:default_da_fee_per_byte da_fee_per_byte_opt - in - + let* da_fee_per_byte = read_qty Durable_storage_path.da_fee_per_byte in let* (Qty gas_price) = let path = Durable_storage_path.base_fee_per_gas in let* gas_price_opt = read_qty path in @@ -173,9 +153,8 @@ module Make (SimulationBackend : SimulationBackend) = struct failwith "Internal error: base fee per gas is not found at %s" path | Some gas_price -> return gas_price in - - let fees = da_fee da_fee_per_byte tx_data in - return (Z.div fees gas_price) + let da_fee = Fees.gas_for_fees ?da_fee_per_byte ~gas_price tx_data in + return da_fee let check_node_da_fees ~previous_gas ~node_da_fees ~simulation_version simulation_state call = -- GitLab From 87a6ee5587ccdf8f7dfc0e11b4763e9876013aa4 Mon Sep 17 00:00:00 2001 From: arnaud Date: Fri, 2 Aug 2024 16:41:30 +0200 Subject: [PATCH 2/6] EVM/Node: Compute gas for da_fees with access list --- etherlink/bin_node/lib_dev/fees.ml | 19 +++++++++++++++++-- etherlink/bin_node/lib_dev/fees.mli | 10 +++++++--- 2 files changed, 24 insertions(+), 5 deletions(-) diff --git a/etherlink/bin_node/lib_dev/fees.ml b/etherlink/bin_node/lib_dev/fees.ml index 4527a4777018..358e01b0a1c5 100644 --- a/etherlink/bin_node/lib_dev/fees.ml +++ b/etherlink/bin_node/lib_dev/fees.ml @@ -6,7 +6,7 @@ (* *) (*****************************************************************************) -let gas_for_fees ?da_fee_per_byte ~gas_price tx_data = +let gas_for_fees ?da_fee_per_byte ~gas_price ?(access_list = []) tx_data = (* The computation of the gas_for_fees comes from the kernel in fees.rs in the gas_for_fees function *) (* Constants defined in the kernel: *) @@ -20,7 +20,22 @@ let gas_for_fees ?da_fee_per_byte ~gas_price tx_data = in (* Computation of da fee based on da fee per byte and variable tx data. *) let da_fee da_fee_per_byte tx_data = - let size = Bytes.length tx_data + assumed_tx_encoded_size |> Z.of_int in + (* Compute access_list size for da_fees *) + let size_address = Z.of_int 20 in + let size_slot = Z.of_int 32 in + let access_list_size = + List.fold_left + (fun acc (_, slots) -> + let nb_slots = Z.of_int (List.length slots) in + let slots_cost = Z.mul size_slot nb_slots in + Z.add (Z.add acc slots_cost) size_address) + Z.zero + access_list + in + let size_wo_access_list = + Bytes.length tx_data + assumed_tx_encoded_size |> Z.of_int + in + let size = Z.add size_wo_access_list access_list_size in Z.mul da_fee_per_byte size in let fees = da_fee da_fee_per_byte tx_data in diff --git a/etherlink/bin_node/lib_dev/fees.mli b/etherlink/bin_node/lib_dev/fees.mli index ec345d53395c..8458d08375ed 100644 --- a/etherlink/bin_node/lib_dev/fees.mli +++ b/etherlink/bin_node/lib_dev/fees.mli @@ -6,10 +6,14 @@ (* *) (*****************************************************************************) -(** [gas_for_fees ~da_fee_per_byte ~gas_price tx_data] returns the DA fees, - i.e. the gas unit necessary for the data availability. +(** [gas_for_fees ~da_fee_per_byte ~gas_price ?access_list tx_data] returns + the DA fees, i.e. the gas unit necessary for the data availability. The whole point of this function is to avoid an unncessary call to the WASM PVM to improve the performances. *) val gas_for_fees : - ?da_fee_per_byte:Ethereum_types.quantity -> gas_price:Z.t -> bytes -> Z.t + ?da_fee_per_byte:Ethereum_types.quantity -> + gas_price:Z.t -> + ?access_list:Transaction.access_list_item list -> + bytes -> + Z.t -- GitLab From 8a6d78e4204ef8cdcb8d84713cf80e01dbdd4fcb Mon Sep 17 00:00:00 2001 From: arnaud Date: Tue, 6 Aug 2024 14:16:50 +0200 Subject: [PATCH 3/6] EVM/Node: Add path for maximum_gas_per_transaction --- etherlink/bin_node/lib_dev/durable_storage_path.ml | 2 ++ etherlink/bin_node/lib_dev/durable_storage_path.mli | 2 ++ 2 files changed, 4 insertions(+) diff --git a/etherlink/bin_node/lib_dev/durable_storage_path.ml b/etherlink/bin_node/lib_dev/durable_storage_path.ml index e6592c0e690c..e976d9e2c464 100644 --- a/etherlink/bin_node/lib_dev/durable_storage_path.ml +++ b/etherlink/bin_node/lib_dev/durable_storage_path.ml @@ -48,6 +48,8 @@ let sequencer_pool_address = EVM.make "/sequencer_pool_address" let sequencer_key = EVM.make "/sequencer" +let maximum_gas_per_transaction = EVM.make "/maximum_gas_per_transaction" + module Accounts = struct let accounts = World_state.make "/eth_accounts" diff --git a/etherlink/bin_node/lib_dev/durable_storage_path.mli b/etherlink/bin_node/lib_dev/durable_storage_path.mli index f018ea1fb0b9..0a2b913aa230 100644 --- a/etherlink/bin_node/lib_dev/durable_storage_path.mli +++ b/etherlink/bin_node/lib_dev/durable_storage_path.mli @@ -36,6 +36,8 @@ val sequencer_pool_address : path val sequencer_key : path +val maximum_gas_per_transaction : path + (** Paths related to accounts. *) module Accounts : sig (** Path to the account's balance. *) -- GitLab From 4d212fe1ae1e077d537cbae0cd3799247a5c9bcf Mon Sep 17 00:00:00 2001 From: arnaud Date: Wed, 31 Jul 2024 16:19:34 +0200 Subject: [PATCH 4/6] EVM/Node: Validate gas_limit with da_fees --- etherlink/bin_node/lib_dev/validate.ml | 40 ++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/etherlink/bin_node/lib_dev/validate.ml b/etherlink/bin_node/lib_dev/validate.ml index 4020f44a9722..efe911671aea 100644 --- a/etherlink/bin_node/lib_dev/validate.ml +++ b/etherlink/bin_node/lib_dev/validate.ml @@ -38,6 +38,45 @@ let validate_nonce (module Backend_rpc : Services_backend_sig.S) if transaction.nonce >= nonce then return (Ok ()) else return (Error "Nonce too low") +let validate_gas_limit (module Backend_rpc : Services_backend_sig.S) + (transaction : Transaction.transaction) : + (unit, string) result tzresult Lwt.t = + let open Lwt_result_syntax in + (* Constants defined in the kernel: *) + let gas_limit = transaction.gas_limit in + let* (Qty maximum_gas_limit) = + let default_maximum_gas_per_transaction = + Ethereum_types.quantity_of_z (Z.of_string "30_000_000") + in + let+ bytes = + Backend_rpc.Reader.read Durable_storage_path.maximum_gas_per_transaction + in + let kernel_maximum_gas_per_transaction = + Option.map Ethereum_types.decode_number_le bytes + in + Option.value + kernel_maximum_gas_per_transaction + ~default:default_maximum_gas_per_transaction + in + let* da_fee_per_byte = + let+ bytes = Backend_rpc.Reader.read Durable_storage_path.da_fee_per_byte in + Option.map Ethereum_types.decode_number_le bytes + in + let gas_for_da_fees = + Fees.gas_for_fees + ?da_fee_per_byte + ~access_list:transaction.access_list + ~gas_price:transaction.max_fee_per_gas + transaction.data + in + let** execution_gas_limit = + if Z.gt gas_limit gas_for_da_fees then + return (Ok (Z.sub gas_limit gas_for_da_fees)) + else return (Error "Invalid gas_limit for da_fees") + in + if Z.leq execution_gas_limit maximum_gas_limit then return (Ok ()) + else return (Error "Gas limit for execution is too high") + let validate_max_fee_per_gas (module Backend_rpc : Services_backend_sig.S) (transaction : Transaction.transaction) = let open Lwt_result_syntax in @@ -61,6 +100,7 @@ let validate backend_rpc transaction ~caller = let** () = validate_nonce backend_rpc transaction caller in let** () = validate_max_fee_per_gas backend_rpc transaction in let** () = validate_pay_for_fees backend_rpc transaction caller in + let** () = validate_gas_limit backend_rpc transaction in return (Ok ()) let valid_transaction_object ~backend_rpc ~decode ~hash tx_raw = -- GitLab From 55fbd9485b068ec888492dcc8a3cc240d17ba92a Mon Sep 17 00:00:00 2001 From: arnaud Date: Fri, 2 Aug 2024 15:01:37 +0200 Subject: [PATCH 5/6] EVM/Tezt: Add da_fee_per_byte in sandbox sequencer for testing --- etherlink/tezt/lib/helpers.ml | 4 +++- etherlink/tezt/lib/helpers.mli | 1 + 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/etherlink/tezt/lib/helpers.ml b/etherlink/tezt/lib/helpers.ml index aeca527620b3..547912ee5b63 100644 --- a/etherlink/tezt/lib/helpers.ml +++ b/etherlink/tezt/lib/helpers.ml @@ -392,7 +392,8 @@ let find_and_execute_withdrawal ~withdrawal_level ~commitment_period in return withdrawal_level -let init_sequencer_sandbox ?patch_config ?(kernel = Constant.WASM.evm_kernel) +let init_sequencer_sandbox ?da_fee_per_byte ?patch_config + ?(kernel = Constant.WASM.evm_kernel) ?(bootstrap_accounts = List.map (fun account -> account.Eth_account.address) @@ -403,6 +404,7 @@ let init_sequencer_sandbox ?patch_config ?(kernel = Constant.WASM.evm_kernel) let*! () = Evm_node.make_kernel_installer_config + ?da_fee_per_byte ~output:output_config ~bootstrap_accounts () diff --git a/etherlink/tezt/lib/helpers.mli b/etherlink/tezt/lib/helpers.mli index 338aff5a3fd9..9aba7332ebed 100644 --- a/etherlink/tezt/lib/helpers.mli +++ b/etherlink/tezt/lib/helpers.mli @@ -214,6 +214,7 @@ val find_and_execute_withdrawal : (** Runs a sequencer in mode sandbox, with no connection needed to a rollup node. *) val init_sequencer_sandbox : + ?da_fee_per_byte:Wei.t -> ?patch_config:(JSON.t -> JSON.t) -> ?kernel:Uses.t -> ?bootstrap_accounts:string list -> -- GitLab From c1b568ac31a906ea60a00c6867983631052f0b9e Mon Sep 17 00:00:00 2001 From: arnaud Date: Fri, 2 Aug 2024 15:17:06 +0200 Subject: [PATCH 6/6] EVM/Tezt: Test to see if the da_fees are taken into account --- etherlink/tezt/tests/validate.ml | 79 ++++++++++++++++++++++++++++++-- 1 file changed, 76 insertions(+), 3 deletions(-) diff --git a/etherlink/tezt/tests/validate.ml b/etherlink/tezt/tests/validate.ml index fb9cca07b111..12a56c4d0ebe 100644 --- a/etherlink/tezt/tests/validate.ml +++ b/etherlink/tezt/tests/validate.ml @@ -16,7 +16,7 @@ let tag = function | Eip1559 -> "Eip1559" | Eip2930 -> "Eip2930" -let register ~title ~tags f tx_types = +let register ?da_fee_per_byte ~title ~tags f tx_types = List.iter (fun tx_type -> let tag_tx = tag tx_type in @@ -40,7 +40,9 @@ let register ~title ~tags f tx_types = ~node_transaction_validation:true () in - let* sequencer = Helpers.init_sequencer_sandbox ~patch_config () in + let* sequencer = + Helpers.init_sequencer_sandbox ?da_fee_per_byte ~patch_config () + in f sequencer tx_type) tx_types @@ -361,6 +363,76 @@ let test_validate_pay_for_fees = unit +let test_validate_gas_limit = + register + ~da_fee_per_byte:(Wei.of_string "4_000_000_000_000") + ~title:"Validate gas limit" + ~tags:["da_fee"; "gas_limit"] + @@ fun sequencer tx_type -> + let source = Eth_account.bootstrap_accounts.(0) in + let make_tx_gas_limit ~legacy ~gas = + Cast.craft_tx + ~source_private_key:source.private_key + ~chain_id:1337 + ~nonce:0 + ~gas_price:1_000_000_000 + ~legacy + ~gas + ~address:"0xd77420f73b4612a7a99dba8c2afd30a1886b0344" + ~value:Wei.zero + () + in + (* 30_000 gas limit is enough to cover execution gas limit but not the gas for da_fees *) + let* not_enough_gas_limit = + match tx_type with + | Legacy -> make_tx_gas_limit ~legacy:true ~gas:100_000 + | Eip1559 -> make_tx_gas_limit ~legacy:false ~gas:100_000 + | Eip2930 -> + return + "0x01f86882053980843b9aca00830186a094d77420f73b4612a7a99dba8c2afd30a1886b03448080c001a015d91492a6fac0b1d507e16bffe6ba1de4544bbc9d139d32e97422d7afcfa05ea038f69526e800a06e50587006b924f1d48c732315add8bf3d6bfb79a2fd29278d" + in + let*@? err = + Rpc.send_raw_transaction ~raw_tx:not_enough_gas_limit sequencer + in + Check.((err.message = "Invalid gas_limit for da_fees") string) + ~error_msg: + "The transaction has not enough gas to pay da_fees, it should fail" ; + (* 300_000_000 gas limit is above the maximum gas_limit for a transaction (even a block) *) + let* gas_limit_too_high = + match tx_type with + | Legacy -> make_tx_gas_limit ~legacy:true ~gas:300_000_000 + | Eip1559 -> make_tx_gas_limit ~legacy:false ~gas:300_000_000 + | Eip2930 -> + return + "0x01f86982053980843b9aca008411e1a30094d77420f73b4612a7a99dba8c2afd30a1886b03448080c001a026ce5062285cd3ade072bd279e56a5ce0679cd56c8cfaf434f5d2b9a1d211c8ea06ebd07be2e0231557a0f6a3766667faa711f0675469da46e3dceb045d5558fd5" + in + let*@? err = Rpc.send_raw_transaction ~raw_tx:gas_limit_too_high sequencer in + Check.((err.message = "Gas limit for execution is too high") string) + ~error_msg:"Gas limit too high for execution, it should fail" ; + (* This transaction should work as it covers the gas for da_fee and not above the limit *) + let* valid_transaction = + match tx_type with + | Legacy -> make_tx_gas_limit ~legacy:true ~gas:1_000_000 + | Eip1559 -> make_tx_gas_limit ~legacy:false ~gas:1_000_000 + | Eip2930 -> + return + "0x01f86882053980843b9aca00830f424094d77420f73b4612a7a99dba8c2afd30a1886b03448080c080a0d901759695e31fd26bfb4cee10022251d74bec021bceed65449705491b148ea1a0788c6b2da1784a2cda4d25afb18873c17a7ff989d31a6d67a74966e495fdd77a" + in + let*@ _ok = Rpc.send_raw_transaction ~raw_tx:valid_transaction sequencer in + + (* This tx is the same as the valid_transaction in eip2930 but with some random entry for access_list *) + let not_enough_access_list_tx = + "0x01f902bd82053980843b9aca00830f424094d77420f73b4612a7a99dba8c2afd30a1886b03448080f90253f89b9402704ed8b5a8e817f354d59432e115e0d8053394f884a00000000000000000000000000000000000000000000000000000000000000001a00000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000002a0fe4d1297c5434445a55041cf44037c0799556cc55064da684dc6eed1a5dccabff8bc944585fe77225b41b697c938b018e2ac67ac5a20c0f8a5a00000000000000000000000000000000000000000000000000000000000000079a00000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000004a00000000000000000000000000000000000000000000000000000000000000001a0a00e9f45e9f0c328446d13a90db1b8ff531c4946ba6a4294a1ec03159cc44b19f87a94c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2f863a09c7d93c4e4b5ea55e1466a741eef69e5430c31615a1970eebbd883a9864ed2dca09ede93be0d8fc6a5eb9cf1c7345a85b7519d8487a727aef0c2f00ab966aa7716a01ea3275ac863f4decf8615eb5ddf70a19af62b291bfd8b6e6747fceb19ae4484f87a942260fac5e5542a773aa44fbcfedf7c193bc2c599f863a0dc276a4f120117ad5ae6415d1c724b4f3a0e81f0ee6466e1392ca121b63123f2a00000000000000000000000000000000000000000000000000000000000000005a038137cdf9f165d9fe3ae438081fac96e39615491dfc8ca4a0e150d98de492a7d01a0af5ec7d4ba53e8ff408f1aef5f1a701b51c1bc5b9331ee7c194b5209b47ca121a07e853bc9d24fe2937843fc1feab758bc0aed3648816661e7e07ab3937650a380" + in + let*@? err = + Rpc.send_raw_transaction ~raw_tx:not_enough_access_list_tx sequencer + in + Check.((err.message = "Invalid gas_limit for da_fees") string) + ~error_msg: + "The transaction has not enough gas to pay da_fees for access_list, it \ + should fail" ; + unit + let () = let all_types = [Legacy; Eip1559; Eip2930] in test_validate_compressed_sig [Legacy] ; @@ -368,4 +440,5 @@ let () = test_validate_chain_id all_types ; test_validate_nonce all_types ; test_validate_max_fee_per_gas all_types ; - test_validate_pay_for_fees [Legacy; Eip1559] + test_validate_pay_for_fees [Legacy; Eip1559] ; + test_validate_gas_limit all_types -- GitLab