From 65b6ce33aa4e47c7756616b99ee58ed76a711d19 Mon Sep 17 00:00:00 2001 From: Pierrick Couderc Date: Wed, 7 Feb 2024 16:41:09 +0100 Subject: [PATCH 1/4] EVM/Node: services can return data with errors --- etherlink/bin_node/lib_dev/services.ml | 154 ++++++++++++------------- 1 file changed, 74 insertions(+), 80 deletions(-) diff --git a/etherlink/bin_node/lib_dev/services.ml b/etherlink/bin_node/lib_dev/services.ml index f91c272133f6..8635f74bcabc 100644 --- a/etherlink/bin_node/lib_dev/services.ml +++ b/etherlink/bin_node/lib_dev/services.ml @@ -114,7 +114,9 @@ let encode : let build : type input output. (module METHOD with type input = input and type output = output) -> - f:(input option -> (output, string) Either.t tzresult Lwt.t) -> + f: + (input option -> + (output, string * Ethereum_types.hash option) Either.t tzresult Lwt.t) -> Data_encoding.json option -> JSONRPC.value Lwt.t = fun (module Method) ~f parameters -> @@ -128,9 +130,20 @@ let build : | Ok value -> ( match value with | Left output -> Ok (encode (module Method) output) - | Right message -> Error JSONRPC.{code = -32000; message; data = None}) + | Right (message, data) -> + let data = + Option.map + (Data_encoding.Json.construct Ethereum_types.hash_encoding) + data + in + Error JSONRPC.{code = -32000; message; data}) -let missing_parameter = Either.Right "Missing parameters" +let rpc_ok result = Lwt_result_syntax.return (Either.Left result) + +let rpc_error ?data message = + Lwt_result_syntax.return (Either.Right (message, data)) + +let missing_parameter () = rpc_error "Missing parameters" let dispatch_request (config : 'a Configuration.t) ((module Backend_rpc : Services_backend_sig.S), _) @@ -159,57 +172,54 @@ let dispatch_request (config : 'a Configuration.t) }) (* Ethereum JSON-RPC API methods we support *) | Method (Accounts.Method, module_) -> - let f (_ : unit option) = - let open Lwt_result_syntax in - return (Either.Left []) - in + let f (_ : unit option) = rpc_ok [] in build ~f module_ parameters | Method (Network_id.Method, module_) -> let f (_ : unit option) = let open Lwt_result_syntax in let* (Qty chain_id) = Backend_rpc.chain_id () in - return (Either.Left (Z.to_string chain_id)) + rpc_ok (Z.to_string chain_id) in build ~f module_ parameters | Method (Chain_id.Method, module_) -> let f (_ : unit option) = let open Lwt_result_syntax in let* chain_id = Backend_rpc.chain_id () in - return (Either.Left chain_id) + rpc_ok chain_id in build ~f module_ parameters | Method (Get_balance.Method, module_) -> let f input = let open Lwt_result_syntax in match input with - | None -> return missing_parameter + | None -> missing_parameter () | Some (address, _block_param) -> let* balance = Backend_rpc.balance address in - return (Either.Left balance) + rpc_ok balance in build ~f module_ parameters | Method (Get_storage_at.Method, module_) -> let f input = let open Lwt_result_syntax in match input with - | None -> return missing_parameter + | None -> missing_parameter () | Some (address, position, _block_param) -> let* value = Backend_rpc.storage_at address position in - return (Either.Left value) + rpc_ok value in build ~f module_ parameters | Method (Block_number.Method, module_) -> let f (_ : unit option) = let open Lwt_result_syntax in let* block_number = Backend_rpc.current_block_number () in - return (Either.Left block_number) + rpc_ok block_number in build ~f module_ parameters | Method (Get_block_by_number.Method, module_) -> let f input = let open Lwt_result_syntax in match input with - | None -> return missing_parameter + | None -> missing_parameter () | Some (block_param, full_transaction_object) -> let* block = get_block_by_number @@ -217,67 +227,67 @@ let dispatch_request (config : 'a Configuration.t) block_param (module Backend_rpc) in - return (Either.Left block) + rpc_ok block in build ~f module_ parameters | Method (Get_block_by_hash.Method, module_) -> let f input = let open Lwt_result_syntax in match input with - | None -> return missing_parameter + | None -> missing_parameter () | Some (block_hash, full_transaction_object) -> let* block = Backend_rpc.block_by_hash ~full_transaction_object block_hash in - return (Either.Left block) + rpc_ok block in build ~f module_ parameters | Method (Get_code.Method, module_) -> let f input = let open Lwt_result_syntax in match input with - | None -> return missing_parameter + | None -> missing_parameter () | Some (address, _) -> let* code = Backend_rpc.code address in - return (Either.Left code) + rpc_ok code in build ~f module_ parameters | Method (Gas_price.Method, module_) -> let f (_ : unit option) = let open Lwt_result_syntax in let* base_fee = Backend_rpc.base_fee_per_gas () in - return (Either.Left base_fee) + rpc_ok base_fee in build ~f module_ parameters | Method (Get_transaction_count.Method, module_) -> let f input = let open Lwt_result_syntax in match input with - | None -> return missing_parameter + | None -> missing_parameter () | Some (address, _) -> let* nonce = Tx_pool.nonce address in - return (Either.Left nonce) + rpc_ok nonce in build ~f module_ parameters | Method (Get_block_transaction_count_by_hash.Method, module_) -> let f input = let open Lwt_result_syntax in match input with - | None -> return missing_parameter + | None -> missing_parameter () | Some block_hash -> let* block = Backend_rpc.block_by_hash ~full_transaction_object:false block_hash in - return (Either.Left (block_transaction_count block)) + rpc_ok (block_transaction_count block) in build ~f module_ parameters | Method (Get_block_transaction_count_by_number.Method, module_) -> let f input = let open Lwt_result_syntax in match input with - | None -> return missing_parameter + | None -> missing_parameter () | Some block_param -> let* block = get_block_by_number @@ -285,52 +295,50 @@ let dispatch_request (config : 'a Configuration.t) block_param (module Backend_rpc) in - return (Either.Left (block_transaction_count block)) + rpc_ok (block_transaction_count block) in build ~f module_ parameters | Method (Get_uncle_count_by_block_hash.Method, module_) -> let f input = - let open Lwt_result_syntax in match input with - | None -> return missing_parameter - | Some _block_param -> return (Either.Left (Qty Z.zero)) + | None -> missing_parameter () + | Some _block_param -> rpc_ok (Qty Z.zero) in build ~f module_ parameters | Method (Get_uncle_count_by_block_number.Method, module_) -> let f input = - let open Lwt_result_syntax in match input with - | None -> return missing_parameter - | Some _block_param -> return (Either.Left (Qty Z.zero)) + | None -> missing_parameter () + | Some _block_param -> rpc_ok (Qty Z.zero) in build ~f module_ parameters | Method (Get_transaction_receipt.Method, module_) -> let f input = let open Lwt_result_syntax in match input with - | None -> return missing_parameter + | None -> missing_parameter () | Some tx_hash -> let* receipt = Backend_rpc.transaction_receipt tx_hash in - return (Either.Left receipt) + rpc_ok receipt in build ~f module_ parameters | Method (Get_transaction_by_hash.Method, module_) -> let f input = let open Lwt_result_syntax in match input with - | None -> return missing_parameter + | None -> missing_parameter () | Some tx_hash -> let* transaction_object = Backend_rpc.transaction_object tx_hash in - return (Either.Left transaction_object) + rpc_ok transaction_object in build ~f module_ parameters | Method (Get_transaction_by_block_hash_and_index.Method, module_) -> let f input = let open Lwt_result_syntax in match input with - | None -> return missing_parameter + | None -> missing_parameter () | Some (block_hash, Qty index) -> let* block = Backend_rpc.block_by_hash @@ -343,14 +351,14 @@ let dispatch_request (config : 'a Configuration.t) (Z.to_int index) (module Backend_rpc) in - return (Either.Left transaction_object) + rpc_ok transaction_object in build ~f module_ parameters | Method (Get_transaction_by_block_number_and_index.Method, module_) -> let f input = let open Lwt_result_syntax in match input with - | None -> return missing_parameter + | None -> missing_parameter () | Some (block_number, Qty index) -> let* block = get_block_by_number @@ -364,82 +372,74 @@ let dispatch_request (config : 'a Configuration.t) (Z.to_int index) (module Backend_rpc) in - return (Either.Left transaction_object) + rpc_ok transaction_object in build ~f module_ parameters | Method (Get_uncle_by_block_hash_and_index.Method, module_) -> let f input = - let open Lwt_result_syntax in match input with - | None -> return missing_parameter + | None -> missing_parameter () | Some (_block_hash, _index) -> (* A block cannot have uncles. *) - return (Either.Left None) + rpc_ok None in build ~f module_ parameters | Method (Get_uncle_by_block_number_and_index.Method, module_) -> let f input = - let open Lwt_result_syntax in match input with - | None -> return missing_parameter + | None -> missing_parameter () | Some (_block_number, _index) -> (* A block cannot have uncles. *) - return (Either.Left None) + rpc_ok None in build ~f module_ parameters | Method (Send_raw_transaction.Method, module_) -> let f input = let open Lwt_result_syntax in match input with - | None -> return missing_parameter + | None -> missing_parameter () | Some tx_raw -> ( let* tx_hash = Tx_pool.add (Ethereum_types.hex_to_bytes tx_raw) in match tx_hash with - | Ok tx_hash -> return (Either.Left tx_hash) + | Ok tx_hash -> rpc_ok tx_hash | Error reason -> (* TODO: https://gitlab.com/tezos/tezos/-/issues/6229 *) - return (Either.Right reason)) + rpc_error reason) in build ~f module_ parameters | Method (Eth_call.Method, module_) -> let f input = let open Lwt_result_syntax in match input with - | None -> return missing_parameter + | None -> missing_parameter () | Some (call, _) -> ( let* call_result = Backend_rpc.simulate_call call in match call_result with - | Ok (Ok {value = Some value; gas_used = _}) -> - return (Either.Left value) + | Ok (Ok {value = Some value; gas_used = _}) -> rpc_ok value | Ok (Ok {value = None; gas_used = _}) -> - return (Either.Left (hash_of_string "")) - | Ok (Error _reason) -> - return (Either.Right "execution reverted:") + rpc_ok (hash_of_string "") + | Ok (Error _reason) -> rpc_error "execution reverted:" | Error reason -> (* TODO: https://gitlab.com/tezos/tezos/-/issues/6229 *) - return (Either.Right reason)) + rpc_error reason) in build ~f module_ parameters | Method (Get_estimate_gas.Method, module_) -> let f input = let open Lwt_result_syntax in match input with - | None -> return missing_parameter + | None -> missing_parameter () | Some (call, _) -> ( let* result = Backend_rpc.estimate_gas call in match result with - | Ok (Ok {value = _; gas_used = Some gas}) -> - return (Either.Left gas) + | Ok (Ok {value = _; gas_used = Some gas}) -> rpc_ok gas | Ok (Ok {value = _; gas_used = None}) -> - return - (Either.Right - "Simulation failed before execution, cannot estimate \ - gas.") - | Ok (Error _reason) -> - return (Either.Right "execution reverted:") + rpc_error + "Simulation failed before execution, cannot estimate gas." + | Ok (Error _reason) -> rpc_error "execution reverted:" | Error reason -> (* TODO: https://gitlab.com/tezos/tezos/-/issues/6229 *) - return (Either.Right reason)) + rpc_error reason) in build ~f module_ parameters | Method (Txpool_content.Method, module_) -> @@ -452,30 +452,26 @@ let dispatch_request (config : 'a Configuration.t) in build ~f module_ parameters | Method (Web3_clientVersion.Method, module_) -> - let f (_ : unit option) = - let open Lwt_result_syntax in - return (Either.Left client_version) - in + let f (_ : unit option) = rpc_ok client_version in build ~f module_ parameters | Method (Web3_sha3.Method, module_) -> let f input = - let open Lwt_result_syntax in match input with - | None -> return missing_parameter + | None -> missing_parameter () | Some data -> let open Ethereum_types in let (Hex h) = data in let bytes = Hex.to_bytes_exn (`Hex h) in let hash_bytes = Tezos_crypto.Hacl.Hash.Keccak_256.digest bytes in let hash = Hex.of_bytes hash_bytes |> Hex.show in - return (Either.Left (Hash (Hex hash))) + rpc_ok (Hash (Hex hash)) in build ~f module_ parameters | Method (Get_logs.Method, module_) -> let f input = let open Lwt_result_syntax in match input with - | None -> return missing_parameter + | None -> missing_parameter () | Some filter -> let* logs = Filter_helpers.get_logs @@ -483,7 +479,7 @@ let dispatch_request (config : 'a Configuration.t) (module Backend_rpc) filter in - return (Either.Left logs) + rpc_ok logs in build ~f module_ parameters (* Internal RPC methods *) @@ -491,7 +487,7 @@ let dispatch_request (config : 'a Configuration.t) let f (_ : unit option) = let open Lwt_result_syntax in let* kernel_version = Backend_rpc.kernel_version () in - return (Either.Left kernel_version) + rpc_ok kernel_version in build ~f module_ parameters | _ -> Stdlib.failwith "The pattern matching of methods is not exhaustive" @@ -527,9 +523,7 @@ let dispatch_private_request (_config : 'a Configuration.t) let open Lwt_result_syntax in let timestamp = Option.value timestamp ~default:(Helpers.now ()) in let* nb_transactions = Tx_pool.produce_block ~force:true ~timestamp in - return - (Either.Left - (Ethereum_types.quantity_of_z @@ Z.of_int nb_transactions)) + rpc_ok (Ethereum_types.quantity_of_z @@ Z.of_int nb_transactions) in build ~f module_ parameters | _ -> Stdlib.failwith "The pattern matching of methods is not exhaustive" -- GitLab From 927638d0d4e9139439832f20295b9f2bbcf1bf9e Mon Sep 17 00:00:00 2001 From: Pierrick Couderc Date: Wed, 7 Feb 2024 17:06:13 +0100 Subject: [PATCH 2/4] EVM/Node: refactor `missing_parameter` check pattern --- etherlink/bin_node/lib_dev/services.ml | 396 ++++++++++--------------- 1 file changed, 154 insertions(+), 242 deletions(-) diff --git a/etherlink/bin_node/lib_dev/services.ml b/etherlink/bin_node/lib_dev/services.ml index 8635f74bcabc..e005dfc7f768 100644 --- a/etherlink/bin_node/lib_dev/services.ml +++ b/etherlink/bin_node/lib_dev/services.ml @@ -145,15 +145,21 @@ let rpc_error ?data message = let missing_parameter () = rpc_error "Missing parameters" +let expect_input input f = + match input with None -> missing_parameter () | Some v -> f v + +let build_with_input method_ ~f parameters = + build method_ ~f:(fun input -> expect_input input f) parameters + let dispatch_request (config : 'a Configuration.t) ((module Backend_rpc : Services_backend_sig.S), _) ({method_; parameters; id} : JSONRPC.request) : JSONRPC.response Lwt.t = - let open Lwt_syntax in + let open Lwt_result_syntax in let open Ethereum_types in - let* value = + let*! value = match map_method_name method_ with | Unknown -> - return + Lwt.return (Error JSONRPC. { @@ -162,7 +168,7 @@ let dispatch_request (config : 'a Configuration.t) data = Some (`String method_); }) | Unsupported -> - return + Lwt.return (Error JSONRPC. { @@ -183,316 +189,222 @@ let dispatch_request (config : 'a Configuration.t) build ~f module_ parameters | Method (Chain_id.Method, module_) -> let f (_ : unit option) = - let open Lwt_result_syntax in let* chain_id = Backend_rpc.chain_id () in rpc_ok chain_id in build ~f module_ parameters | Method (Get_balance.Method, module_) -> - let f input = - let open Lwt_result_syntax in - match input with - | None -> missing_parameter () - | Some (address, _block_param) -> - let* balance = Backend_rpc.balance address in - rpc_ok balance + let f (address, _block_param) = + let* balance = Backend_rpc.balance address in + rpc_ok balance in - build ~f module_ parameters + build_with_input ~f module_ parameters | Method (Get_storage_at.Method, module_) -> - let f input = - let open Lwt_result_syntax in - match input with - | None -> missing_parameter () - | Some (address, position, _block_param) -> - let* value = Backend_rpc.storage_at address position in - rpc_ok value + let f (address, position, _block_param) = + let* value = Backend_rpc.storage_at address position in + rpc_ok value in - build ~f module_ parameters + build_with_input ~f module_ parameters | Method (Block_number.Method, module_) -> let f (_ : unit option) = - let open Lwt_result_syntax in let* block_number = Backend_rpc.current_block_number () in rpc_ok block_number in build ~f module_ parameters | Method (Get_block_by_number.Method, module_) -> - let f input = - let open Lwt_result_syntax in - match input with - | None -> missing_parameter () - | Some (block_param, full_transaction_object) -> - let* block = - get_block_by_number - ~full_transaction_object - block_param - (module Backend_rpc) - in - rpc_ok block + let f (block_param, full_transaction_object) = + let* block = + get_block_by_number + ~full_transaction_object + block_param + (module Backend_rpc) + in + rpc_ok block in - build ~f module_ parameters + build_with_input ~f module_ parameters | Method (Get_block_by_hash.Method, module_) -> - let f input = - let open Lwt_result_syntax in - match input with - | None -> missing_parameter () - | Some (block_hash, full_transaction_object) -> - let* block = - Backend_rpc.block_by_hash ~full_transaction_object block_hash - in - rpc_ok block + let f (block_hash, full_transaction_object) = + let* block = + Backend_rpc.block_by_hash ~full_transaction_object block_hash + in + rpc_ok block in - build ~f module_ parameters + build_with_input ~f module_ parameters | Method (Get_code.Method, module_) -> - let f input = - let open Lwt_result_syntax in - match input with - | None -> missing_parameter () - | Some (address, _) -> - let* code = Backend_rpc.code address in - rpc_ok code + let f (address, _) = + let* code = Backend_rpc.code address in + rpc_ok code in - build ~f module_ parameters + build_with_input ~f module_ parameters | Method (Gas_price.Method, module_) -> let f (_ : unit option) = - let open Lwt_result_syntax in let* base_fee = Backend_rpc.base_fee_per_gas () in rpc_ok base_fee in build ~f module_ parameters | Method (Get_transaction_count.Method, module_) -> - let f input = - let open Lwt_result_syntax in - match input with - | None -> missing_parameter () - | Some (address, _) -> - let* nonce = Tx_pool.nonce address in - rpc_ok nonce + let f (address, _) = + let* nonce = Tx_pool.nonce address in + rpc_ok nonce in - build ~f module_ parameters + build_with_input ~f module_ parameters | Method (Get_block_transaction_count_by_hash.Method, module_) -> - let f input = - let open Lwt_result_syntax in - match input with - | None -> missing_parameter () - | Some block_hash -> - let* block = - Backend_rpc.block_by_hash - ~full_transaction_object:false - block_hash - in - rpc_ok (block_transaction_count block) + let f block_hash = + let* block = + Backend_rpc.block_by_hash ~full_transaction_object:false block_hash + in + rpc_ok (block_transaction_count block) in - build ~f module_ parameters + build_with_input ~f module_ parameters | Method (Get_block_transaction_count_by_number.Method, module_) -> - let f input = - let open Lwt_result_syntax in - match input with - | None -> missing_parameter () - | Some block_param -> - let* block = - get_block_by_number - ~full_transaction_object:false - block_param - (module Backend_rpc) - in - rpc_ok (block_transaction_count block) + let f block_param = + let* block = + get_block_by_number + ~full_transaction_object:false + block_param + (module Backend_rpc) + in + rpc_ok (block_transaction_count block) in - build ~f module_ parameters + build_with_input ~f module_ parameters | Method (Get_uncle_count_by_block_hash.Method, module_) -> - let f input = - match input with - | None -> missing_parameter () - | Some _block_param -> rpc_ok (Qty Z.zero) - in - build ~f module_ parameters + let f _block_param = rpc_ok (Qty Z.zero) in + build_with_input ~f module_ parameters | Method (Get_uncle_count_by_block_number.Method, module_) -> - let f input = - match input with - | None -> missing_parameter () - | Some _block_param -> rpc_ok (Qty Z.zero) - in - build ~f module_ parameters + let f _block_param = rpc_ok (Qty Z.zero) in + build_with_input ~f module_ parameters | Method (Get_transaction_receipt.Method, module_) -> - let f input = - let open Lwt_result_syntax in - match input with - | None -> missing_parameter () - | Some tx_hash -> - let* receipt = Backend_rpc.transaction_receipt tx_hash in - rpc_ok receipt + let f tx_hash = + let* receipt = Backend_rpc.transaction_receipt tx_hash in + rpc_ok receipt in - build ~f module_ parameters + build_with_input ~f module_ parameters | Method (Get_transaction_by_hash.Method, module_) -> - let f input = - let open Lwt_result_syntax in - match input with - | None -> missing_parameter () - | Some tx_hash -> - let* transaction_object = - Backend_rpc.transaction_object tx_hash - in - rpc_ok transaction_object + let f tx_hash = + let* transaction_object = Backend_rpc.transaction_object tx_hash in + rpc_ok transaction_object in - build ~f module_ parameters + build_with_input ~f module_ parameters | Method (Get_transaction_by_block_hash_and_index.Method, module_) -> - let f input = - let open Lwt_result_syntax in - match input with - | None -> missing_parameter () - | Some (block_hash, Qty index) -> - let* block = - Backend_rpc.block_by_hash - ~full_transaction_object:false - block_hash - in - let* transaction_object = - get_transaction_from_index - block - (Z.to_int index) - (module Backend_rpc) - in - rpc_ok transaction_object + let f (block_hash, Qty index) = + let* block = + Backend_rpc.block_by_hash ~full_transaction_object:false block_hash + in + let* transaction_object = + get_transaction_from_index + block + (Z.to_int index) + (module Backend_rpc) + in + rpc_ok transaction_object in - build ~f module_ parameters + build_with_input ~f module_ parameters | Method (Get_transaction_by_block_number_and_index.Method, module_) -> - let f input = - let open Lwt_result_syntax in - match input with - | None -> missing_parameter () - | Some (block_number, Qty index) -> - let* block = - get_block_by_number - ~full_transaction_object:false - block_number - (module Backend_rpc) - in - let* transaction_object = - get_transaction_from_index - block - (Z.to_int index) - (module Backend_rpc) - in - rpc_ok transaction_object + let f (block_number, Qty index) = + let* block = + get_block_by_number + ~full_transaction_object:false + block_number + (module Backend_rpc) + in + let* transaction_object = + get_transaction_from_index + block + (Z.to_int index) + (module Backend_rpc) + in + rpc_ok transaction_object in - build ~f module_ parameters + build_with_input ~f module_ parameters | Method (Get_uncle_by_block_hash_and_index.Method, module_) -> - let f input = - match input with - | None -> missing_parameter () - | Some (_block_hash, _index) -> - (* A block cannot have uncles. *) - rpc_ok None + let f (_block_hash, _index) = + (* A block cannot have uncles. *) + rpc_ok None in - build ~f module_ parameters + build_with_input ~f module_ parameters | Method (Get_uncle_by_block_number_and_index.Method, module_) -> - let f input = - match input with - | None -> missing_parameter () - | Some (_block_number, _index) -> - (* A block cannot have uncles. *) - rpc_ok None + let f (_block_number, _index) = + (* A block cannot have uncles. *) + rpc_ok None in - build ~f module_ parameters + build_with_input ~f module_ parameters | Method (Send_raw_transaction.Method, module_) -> - let f input = - let open Lwt_result_syntax in - match input with - | None -> missing_parameter () - | Some tx_raw -> ( - let* tx_hash = Tx_pool.add (Ethereum_types.hex_to_bytes tx_raw) in - match tx_hash with - | Ok tx_hash -> rpc_ok tx_hash - | Error reason -> - (* TODO: https://gitlab.com/tezos/tezos/-/issues/6229 *) - rpc_error reason) + let f tx_raw = + let* tx_hash = Tx_pool.add (Ethereum_types.hex_to_bytes tx_raw) in + match tx_hash with + | Ok tx_hash -> rpc_ok tx_hash + | Error reason -> + (* TODO: https://gitlab.com/tezos/tezos/-/issues/6229 *) + rpc_error reason in - build ~f module_ parameters + build_with_input ~f module_ parameters | Method (Eth_call.Method, module_) -> - let f input = - let open Lwt_result_syntax in - match input with - | None -> missing_parameter () - | Some (call, _) -> ( - let* call_result = Backend_rpc.simulate_call call in - match call_result with - | Ok (Ok {value = Some value; gas_used = _}) -> rpc_ok value - | Ok (Ok {value = None; gas_used = _}) -> - rpc_ok (hash_of_string "") - | Ok (Error _reason) -> rpc_error "execution reverted:" - | Error reason -> - (* TODO: https://gitlab.com/tezos/tezos/-/issues/6229 *) - rpc_error reason) + let f (call, _) = + let* call_result = Backend_rpc.simulate_call call in + match call_result with + | Ok (Ok {value = Some value; gas_used = _}) -> rpc_ok value + | Ok (Ok {value = None; gas_used = _}) -> rpc_ok (hash_of_string "") + | Ok (Error _reason) -> rpc_error "execution reverted:" + | Error reason -> + (* TODO: https://gitlab.com/tezos/tezos/-/issues/6229 *) + rpc_error reason in - build ~f module_ parameters + build_with_input ~f module_ parameters | Method (Get_estimate_gas.Method, module_) -> - let f input = - let open Lwt_result_syntax in - match input with - | None -> missing_parameter () - | Some (call, _) -> ( - let* result = Backend_rpc.estimate_gas call in - match result with - | Ok (Ok {value = _; gas_used = Some gas}) -> rpc_ok gas - | Ok (Ok {value = _; gas_used = None}) -> - rpc_error - "Simulation failed before execution, cannot estimate gas." - | Ok (Error _reason) -> rpc_error "execution reverted:" - | Error reason -> - (* TODO: https://gitlab.com/tezos/tezos/-/issues/6229 *) - rpc_error reason) + let f (call, _) = + let* result = Backend_rpc.estimate_gas call in + match result with + | Ok (Ok {value = _; gas_used = Some gas}) -> rpc_ok gas + | Ok (Ok {value = _; gas_used = None}) -> + rpc_error + "Simulation failed before execution, cannot estimate gas." + | Ok (Error _reason) -> rpc_error "execution reverted:" + | Error reason -> + (* TODO: https://gitlab.com/tezos/tezos/-/issues/6229 *) + rpc_error reason in - build ~f module_ parameters + build_with_input ~f module_ parameters | Method (Txpool_content.Method, module_) -> let f (_ : unit option) = - let open Lwt_result_syntax in - return - (Either.Left - Ethereum_types. - {pending = AddressMap.empty; queued = AddressMap.empty}) + rpc_ok + Ethereum_types. + {pending = AddressMap.empty; queued = AddressMap.empty} in build ~f module_ parameters | Method (Web3_clientVersion.Method, module_) -> let f (_ : unit option) = rpc_ok client_version in build ~f module_ parameters | Method (Web3_sha3.Method, module_) -> - let f input = - match input with - | None -> missing_parameter () - | Some data -> - let open Ethereum_types in - let (Hex h) = data in - let bytes = Hex.to_bytes_exn (`Hex h) in - let hash_bytes = Tezos_crypto.Hacl.Hash.Keccak_256.digest bytes in - let hash = Hex.of_bytes hash_bytes |> Hex.show in - rpc_ok (Hash (Hex hash)) + let f data = + let open Ethereum_types in + let (Hex h) = data in + let bytes = Hex.to_bytes_exn (`Hex h) in + let hash_bytes = Tezos_crypto.Hacl.Hash.Keccak_256.digest bytes in + let hash = Hex.of_bytes hash_bytes |> Hex.show in + rpc_ok (Hash (Hex hash)) in - build ~f module_ parameters + build_with_input ~f module_ parameters | Method (Get_logs.Method, module_) -> - let f input = - let open Lwt_result_syntax in - match input with - | None -> missing_parameter () - | Some filter -> - let* logs = - Filter_helpers.get_logs - config.log_filter - (module Backend_rpc) - filter - in - rpc_ok logs + let f filter = + let* logs = + Filter_helpers.get_logs + config.log_filter + (module Backend_rpc) + filter + in + rpc_ok logs in - build ~f module_ parameters + build_with_input ~f module_ parameters (* Internal RPC methods *) | Method (Kernel_version.Method, module_) -> let f (_ : unit option) = - let open Lwt_result_syntax in let* kernel_version = Backend_rpc.kernel_version () in rpc_ok kernel_version in build ~f module_ parameters | _ -> Stdlib.failwith "The pattern matching of methods is not exhaustive" in - return JSONRPC.{value; id} + Lwt.return JSONRPC.{value; id} let dispatch_private_request (_config : 'a Configuration.t) ((module Backend_rpc : Services_backend_sig.S), _) -- GitLab From 62ea02094ba6d25fae2b702433746f032fac041c Mon Sep 17 00:00:00 2001 From: Pierrick Couderc Date: Wed, 7 Feb 2024 16:42:52 +0100 Subject: [PATCH 3/4] EVM/Node: revert reason is outputted as data --- etherlink/bin_node/lib_dev/services.ml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/etherlink/bin_node/lib_dev/services.ml b/etherlink/bin_node/lib_dev/services.ml index e005dfc7f768..f89f93852a3d 100644 --- a/etherlink/bin_node/lib_dev/services.ml +++ b/etherlink/bin_node/lib_dev/services.ml @@ -344,7 +344,7 @@ let dispatch_request (config : 'a Configuration.t) match call_result with | Ok (Ok {value = Some value; gas_used = _}) -> rpc_ok value | Ok (Ok {value = None; gas_used = _}) -> rpc_ok (hash_of_string "") - | Ok (Error _reason) -> rpc_error "execution reverted:" + | Ok (Error reason) -> rpc_error ~data:reason "execution reverted" | Error reason -> (* TODO: https://gitlab.com/tezos/tezos/-/issues/6229 *) rpc_error reason @@ -358,7 +358,7 @@ let dispatch_request (config : 'a Configuration.t) | Ok (Ok {value = _; gas_used = None}) -> rpc_error "Simulation failed before execution, cannot estimate gas." - | Ok (Error _reason) -> rpc_error "execution reverted:" + | Ok (Error reason) -> rpc_error ~data:reason "execution reverted" | Error reason -> (* TODO: https://gitlab.com/tezos/tezos/-/issues/6229 *) rpc_error reason -- GitLab From e5cbe02b9ad4620eabd23a67595a75d35daba861 Mon Sep 17 00:00:00 2001 From: Pierrick Couderc Date: Thu, 8 Feb 2024 20:48:53 +0100 Subject: [PATCH 4/4] EVM/Test: the node propagates revert's reason in error's data field --- .../kernel_evm/solidy_examples/error.sol | 41 ++++++++++ etherlink/tezt/lib/eth_cli.ml | 6 ++ etherlink/tezt/lib/eth_cli.mli | 4 + etherlink/tezt/lib/rpc.ml | 36 ++++++++- etherlink/tezt/lib/rpc.mli | 7 +- .../tezt/tests/evm_kernel_inputs/error.abi | 77 +++++++++++++++++++ .../tezt/tests/evm_kernel_inputs/error.bin | 1 + etherlink/tezt/tests/evm_rollup.ml | 36 ++++++++- 8 files changed, 201 insertions(+), 7 deletions(-) create mode 100644 etherlink/kernel_evm/solidy_examples/error.sol create mode 100644 etherlink/tezt/tests/evm_kernel_inputs/error.abi create mode 100644 etherlink/tezt/tests/evm_kernel_inputs/error.bin diff --git a/etherlink/kernel_evm/solidy_examples/error.sol b/etherlink/kernel_evm/solidy_examples/error.sol new file mode 100644 index 000000000000..2219c48183ca --- /dev/null +++ b/etherlink/kernel_evm/solidy_examples/error.sol @@ -0,0 +1,41 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +contract Error { + function testRequire(uint _i) public pure { + // Require should be used to validate conditions such as: + // - inputs + // - conditions before execution + // - return values from calls to other functions + require(_i > 10, "Input must be greater than 10"); + } + + function testRevert(uint _i) public pure { + // Revert is useful when the condition to check is complex. + // This code does the exact same thing as the example above + if (_i <= 10) { + revert("Input must be greater than 10"); + } + } + + uint public num; + + function testAssert() public view { + // Assert should only be used to test for internal errors, + // and to check invariants. + + // Here we assert that num is always equal to 0 + // since it is impossible to update the value of num + assert(num == 0); + } + + // custom error + error InsufficientBalance(uint balance, uint withdrawAmount); + + function testCustomError(uint _withdrawAmount) public view { + uint bal = address(this).balance; + if (bal < _withdrawAmount) { + revert InsufficientBalance({balance: bal, withdrawAmount: _withdrawAmount}); + } + } +} diff --git a/etherlink/tezt/lib/eth_cli.ml b/etherlink/tezt/lib/eth_cli.ml index d8e122184d00..5bce5ff498e9 100644 --- a/etherlink/tezt/lib/eth_cli.ml +++ b/etherlink/tezt/lib/eth_cli.ml @@ -143,3 +143,9 @@ let get_receipt ~endpoint ~tx = spawn_command_and_read_json_opt ["transaction:get"; tx; "--network"; endpoint] (fun j -> JSON.(j |-> "receipt") |> Transaction.transaction_receipt_of_json) + +let encode_method ~abi_label ~method_ = + let* data = + spawn_command_and_read_string ["method:encode"; abi_label; method_] + in + return (String.trim data) diff --git a/etherlink/tezt/lib/eth_cli.mli b/etherlink/tezt/lib/eth_cli.mli index cad304d69a93..73b563399409 100644 --- a/etherlink/tezt/lib/eth_cli.mli +++ b/etherlink/tezt/lib/eth_cli.mli @@ -136,3 +136,7 @@ val block_number : endpoint:string -> int Lwt.t mined transaction if it exists. *) val get_receipt : endpoint:string -> tx:string -> Transaction.transaction_receipt option Lwt.t + +(** [encode_method ~abi_label ~method_] returns the data corresponding to + [method_] call considering the given [abi]. *) +val encode_method : abi_label:string -> method_:string -> string Lwt.t diff --git a/etherlink/tezt/lib/rpc.ml b/etherlink/tezt/lib/rpc.ml index 637a1d6eab3a..21460ddea324 100644 --- a/etherlink/tezt/lib/rpc.ml +++ b/etherlink/tezt/lib/rpc.ml @@ -6,19 +6,32 @@ (* *) (*****************************************************************************) -type error = {code : int; message : string} +type error = {code : int; message : string; data : string option} -let pp_error ppf {code; message} = - Format.fprintf ppf "{code: %d, message: %S}" code message +let pp_error ppf {code; message; data} = + let pp_data ppf = function + | None -> Format.fprintf ppf "null" + | Some data -> Format.fprintf ppf "%s" data + in + Format.fprintf + ppf + "{code: %d, message: %S; data: %a}" + code + message + pp_data + data let decode_or_error decode json = match JSON.(json |-> "error" |> as_opt) with | Some json -> let code = JSON.(json |-> "code" |> as_int) in let message = JSON.(json |-> "message" |> as_string) in - Error {code; message} + let data = JSON.(json |-> "data" |> as_opt |> Option.map as_string) in + Error {code; message; data} | None -> Ok (decode json) +let eth_call_obj ~to_ ~data = `O [("to", `String to_); ("data", `String data)] + module Request = struct open Evm_node @@ -55,6 +68,12 @@ module Request = struct } let tez_kernelVersion = {method_ = "tez_kernelVersion"; parameters = `Null} + + let eth_call ~to_ ~data = + { + method_ = "eth_call"; + parameters = `A [eth_call_obj ~to_ ~data; `String "latest"]; + } end let block_number evm_node = @@ -160,3 +179,12 @@ let tez_kernelVersion evm_node = @@ decode_or_error (fun response -> Evm_node.extract_result response |> JSON.as_string) response + +let call ~to_ ~data evm_node = + let* response = + Evm_node.call_evm_rpc evm_node (Request.eth_call ~to_ ~data) + in + return + @@ decode_or_error + (fun response -> Evm_node.extract_result response |> JSON.as_string) + response diff --git a/etherlink/tezt/lib/rpc.mli b/etherlink/tezt/lib/rpc.mli index e1c0f369e61e..419c48538a58 100644 --- a/etherlink/tezt/lib/rpc.mli +++ b/etherlink/tezt/lib/rpc.mli @@ -6,7 +6,7 @@ (* *) (*****************************************************************************) -type error = {code : int; message : string} +type error = {code : int; message : string; data : string option} module Request : sig val eth_blockNumber : Evm_node.request @@ -78,3 +78,8 @@ val get_transaction_count : (** [tez_kernelVersion evm_node] calls [tez_kernelVersion]. Returns the kernel commit hash. *) val tez_kernelVersion : Evm_node.t -> (string, error) result Lwt.t + +(** [call ~to_ ~data] call [eth_call] with [to] and [data] as argument (on block + latest) *) +val call : + to_:string -> data:string -> Evm_node.t -> (string, error) result Lwt.t diff --git a/etherlink/tezt/tests/evm_kernel_inputs/error.abi b/etherlink/tezt/tests/evm_kernel_inputs/error.abi new file mode 100644 index 000000000000..32fa8e7f0763 --- /dev/null +++ b/etherlink/tezt/tests/evm_kernel_inputs/error.abi @@ -0,0 +1,77 @@ +[ + { + "inputs": [ + { + "internalType": "uint256", + "name": "balance", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "withdrawAmount", + "type": "uint256" + } + ], + "name": "InsufficientBalance", + "type": "error" + }, + { + "inputs": [], + "name": "num", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "testAssert", + "outputs": [], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_withdrawAmount", + "type": "uint256" + } + ], + "name": "testCustomError", + "outputs": [], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_i", + "type": "uint256" + } + ], + "name": "testRequire", + "outputs": [], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_i", + "type": "uint256" + } + ], + "name": "testRevert", + "outputs": [], + "stateMutability": "pure", + "type": "function" + } +] diff --git a/etherlink/tezt/tests/evm_kernel_inputs/error.bin b/etherlink/tezt/tests/evm_kernel_inputs/error.bin new file mode 100644 index 000000000000..a0a4beb716d1 --- /dev/null +++ b/etherlink/tezt/tests/evm_kernel_inputs/error.bin @@ -0,0 +1 @@ +608060405234801561000f575f80fd5b506103548061001d5f395ff3fe608060405234801561000f575f80fd5b5060043610610055575f3560e01c806320987767146100595780632b813bc0146100755780634e70b1dc1461007f57806375f7286c1461009d578063b8bd717f146100b9575b5f80fd5b610073600480360381019061006e91906101ff565b6100d5565b005b61007d61011b565b005b61008761012e565b6040516100949190610239565b60405180910390f35b6100b760048036038101906100b291906101ff565b610133565b005b6100d360048036038101906100ce91906101ff565b610182565b005b600a8111610118576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161010f906102ac565b60405180910390fd5b50565b5f80541461012c5761012b6102ca565b5b565b5f5481565b5f4790508181101561017e5780826040517fcf4791810000000000000000000000000000000000000000000000000000000081526004016101759291906102f7565b60405180910390fd5b5050565b600a81116101c5576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016101bc906102ac565b60405180910390fd5b50565b5f80fd5b5f819050919050565b6101de816101cc565b81146101e8575f80fd5b50565b5f813590506101f9816101d5565b92915050565b5f60208284031215610214576102136101c8565b5b5f610221848285016101eb565b91505092915050565b610233816101cc565b82525050565b5f60208201905061024c5f83018461022a565b92915050565b5f82825260208201905092915050565b7f496e707574206d7573742062652067726561746572207468616e2031300000005f82015250565b5f610296601d83610252565b91506102a182610262565b602082019050919050565b5f6020820190508181035f8301526102c38161028a565b9050919050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52600160045260245ffd5b5f60408201905061030a5f83018561022a565b610317602083018461022a565b939250505056fea26469706673582212201d215bdc0afa47c0fb4594c7f47afbe7b6faf4f49536efb831ef603e17e5d04a64736f6c63430008180033 \ No newline at end of file diff --git a/etherlink/tezt/tests/evm_rollup.ml b/etherlink/tezt/tests/evm_rollup.ml index 1cc0c4fc78ec..1e2d83a84372 100644 --- a/etherlink/tezt/tests/evm_rollup.ml +++ b/etherlink/tezt/tests/evm_rollup.ml @@ -1092,6 +1092,15 @@ let recursive = bin = kernel_inputs_path ^ "/recursive.bin"; } +(** The info for the "error.sol" contract. + See [etherlink/kernel_evm/solidity_examples/error.sol] *) +let error = + { + label = "error"; + abi = kernel_inputs_path ^ "/error.abi"; + bin = kernel_inputs_path ^ "/error.bin"; + } + (** Test that the contract creation works. *) let test_l2_deploy_simple_storage = register_proxy @@ -4476,7 +4485,9 @@ let test_estimate_gas_out_of_ticks = ("to", `String loop_address); ] in - let*@? {message; code = _} = Rpc.estimate_gas estimateGas evm_node in + let*@? {message; code = _; data = _} = + Rpc.estimate_gas estimateGas evm_node + in Check.(message =~ rex "The transaction would exhaust all the ticks") ~error_msg:"The estimate gas should fail with out of ticks message." ; unit @@ -4721,6 +4732,26 @@ let test_blockhash_opcode = the BLOCKHASH opcode, got %L, but %R was expected." ; unit +let test_revert_is_correctly_propagated = + register_both + ~tags:["evm"; "revert"] + ~title:"Check that the node propagates reverts reason correctly." + @@ fun ~protocol:_ ~evm_setup:({evm_node; _} as evm_setup) -> + let sender = Eth_account.bootstrap_accounts.(0) in + let* error_address, _tx = deploy ~contract:error ~sender evm_setup in + let* data = + Eth_cli.encode_method ~abi_label:error.label ~method_:"testRevert(0)" + in + let* call = Rpc.call ~to_:error_address ~data evm_node in + match call with + | Ok _ -> Test.fail "Call should have reverted" + | Error {data = None; _} -> + Test.fail "Call should have reverted with a reason" + | Error {data = Some _reason; _} -> + (* TODO: #6893 + eth-cli cannot decode an encoded string using Ethereum format. *) + unit + let register_evm_node ~protocols = test_originate_evm_kernel protocols ; test_evm_node_connection protocols ; @@ -4802,7 +4833,8 @@ let register_evm_node ~protocols = test_transaction_exhausting_ticks_is_rejected protocols ; test_reveal_storage protocols ; test_call_recursive_contract_estimate_gas protocols ; - test_blockhash_opcode protocols + test_blockhash_opcode protocols ; + test_revert_is_correctly_propagated protocols let () = register_evm_node ~protocols:[Alpha] ; -- GitLab