From f9a01aec245e53c94dd143a3249864e36a209c4c Mon Sep 17 00:00:00 2001 From: Pierrick Couderc Date: Mon, 22 May 2023 15:33:52 +0200 Subject: [PATCH 1/5] EVM/Proxy: transaction_object's block can be null --- src/bin_evm_proxy/ethereum_types.ml | 12 +++++++----- src/bin_evm_proxy/mockup.ml | 14 ++++++++++---- src/bin_evm_proxy/rollup_node.ml | 4 ++-- 3 files changed, 19 insertions(+), 11 deletions(-) diff --git a/src/bin_evm_proxy/ethereum_types.ml b/src/bin_evm_proxy/ethereum_types.ml index 0a080e415da6..f49cae8aa47e 100644 --- a/src/bin_evm_proxy/ethereum_types.ml +++ b/src/bin_evm_proxy/ethereum_types.ml @@ -41,8 +41,10 @@ type address = Address of string [@@ocaml.unboxed] let address_of_string s = Address (String.lowercase_ascii (strip_0x s)) +let address_to_string (Address a) = append_0x a + let address_encoding = - Data_encoding.(conv (fun (Address a) -> append_0x a) address_of_string string) + Data_encoding.(conv address_to_string address_of_string string) (** Ethereum generic quantity, always encoded in hexadecimal. *) type quantity = Qty of Z.t [@@ocaml.unboxed] @@ -424,8 +426,8 @@ let transaction_receipt_encoding = (req "contractAddress" (option address_encoding)))) type transaction_object = { - blockHash : block_hash; - blockNumber : quantity; + blockHash : block_hash option; + blockNumber : quantity option; from : address; gas : quantity; gasPrice : quantity; @@ -500,8 +502,8 @@ let transaction_object_encoding = }) (merge_objs (obj10 - (req "blockHash" block_hash_encoding) - (req "blockNumber" quantity_encoding) + (req "blockHash" (option block_hash_encoding)) + (req "blockNumber" (option quantity_encoding)) (req "from" address_encoding) (req "gas" quantity_encoding) (req "gasPrice" quantity_encoding) diff --git a/src/bin_evm_proxy/mockup.ml b/src/bin_evm_proxy/mockup.ml index 8dcb5c75ce89..67da1d059295 100644 --- a/src/bin_evm_proxy/mockup.ml +++ b/src/bin_evm_proxy/mockup.ml @@ -95,14 +95,20 @@ let block () = uncles = []; } +let bootstrap_address = + address_of_string "0x6F4d14B90C48bEFb49CA3fe6663dEC70731A8bC7" + +let bootstrap_address2 = + address_of_string "0xA5A5bf58c7Dc91cBE5005A7E5c6314998Eda479E" + let transaction_receipt () = { transactionHash = transaction_hash; transactionIndex = qty_f Z.zero; blockHash = block_hash; blockNumber = qty_f @@ Z.of_int !block_height_counter; - from = address_of_string "0x6F4d14B90C48bEFb49CA3fe6663dEC70731A8bC7"; - to_ = Some (address_of_string "0xA5A5bf58c7Dc91cBE5005A7E5c6314998Eda479E"); + from = bootstrap_address; + to_ = Some bootstrap_address2; contractAddress = Some (address_of_string "0x6ce4d79d4e77402e1ef3417fdda433aa744c6e1c"); cumulativeGasUsed = gas_price; @@ -116,8 +122,8 @@ let transaction_receipt () = let transaction_object = { - blockHash = block_hash; - blockNumber = qty_f @@ Z.of_int 42; + blockHash = Some block_hash; + blockNumber = Some (qty_f @@ Z.of_int 42); from = address_of_string "0x6F4d14B90C48bEFb49CA3fe6663dEC70731A8bC7"; gas = qty_f Z.zero; gasPrice = qty_f Z.zero; diff --git a/src/bin_evm_proxy/rollup_node.ml b/src/bin_evm_proxy/rollup_node.ml index a7bdb839464f..8db1ac997062 100644 --- a/src/bin_evm_proxy/rollup_node.ml +++ b/src/bin_evm_proxy/rollup_node.ml @@ -583,8 +583,8 @@ module RPC = struct in return_some { - blockHash = block_hash; - blockNumber = block_number; + blockHash = Some block_hash; + blockNumber = Some block_number; from; gas = gas_used; gasPrice = gas_price; -- GitLab From 50dfb764f97ea68a3374f659ec3c0ec1eaabd90c Mon Sep 17 00:00:00 2001 From: Pierrick Couderc Date: Mon, 22 May 2023 15:38:34 +0200 Subject: [PATCH 2/5] EVM/Proxy: add txpool_content encoding and RPC --- src/bin_evm_proxy/ethereum_types.ml | 99 +++++++++++++++++++++++++++++ src/bin_evm_proxy/mockup.ml | 53 +++++++++++++++ src/bin_evm_proxy/rollup_node.ml | 7 ++ src/bin_evm_proxy/rollup_node.mli | 3 + src/bin_evm_proxy/rpc_encodings.ml | 15 +++++ src/bin_evm_proxy/rpc_encodings.mli | 3 + src/bin_evm_proxy/services.ml | 3 + 7 files changed, 183 insertions(+) diff --git a/src/bin_evm_proxy/ethereum_types.ml b/src/bin_evm_proxy/ethereum_types.ml index f49cae8aa47e..fab62c31a7db 100644 --- a/src/bin_evm_proxy/ethereum_types.ml +++ b/src/bin_evm_proxy/ethereum_types.ml @@ -567,3 +567,102 @@ let call_encoding = (opt "gasPrice" quantity_encoding) (opt "value" quantity_encoding) (req "data" (option hash_encoding))) + +(** The txpool encoding can be found in + https://geth.ethereum.org/docs/interacting-with-geth/rpc/ns-txpool#txpool-content. + + Basically, `txpool_content` is a map associating addresses to counters and + transactions. In JSON, it is encoded as an object associating addresses as + fields to objects that contain counters as field and transaction objects as + values. I.e., the `txpool_` encodes it as: + + ``` + { "address1" : + { "counter1" : , + "counter2" : , + ... + }, + "address2" : + { "counter1" : , + "counter2" : , + ... + }, + ... + } + ``` + + As such, the encoding uses Ezjsonm representation directly to encode and + decode the txpool. +*) +module MapMake (Key : sig + include Stdlib.Map.OrderedType + + val to_string : t -> string + + val of_string : string -> t +end) : sig + include Map.S with type key = Key.t + + val associative_array_encoding : 'a Data_encoding.t -> 'a t Data_encoding.t +end = struct + module Instance = Map.Make (Key) + + let associative_array_encoding value_encoding = + let open Data_encoding in + conv + (fun map -> + let bindings = Instance.bindings map in + let fields = + List.map + (fun (name, value) -> + (Key.to_string name, Json.construct value_encoding value)) + bindings + in + `O fields) + (function + | `O fields -> + let bindings = + List.filter_map + (fun (name, value) -> + try + Some (Key.of_string name, Json.destruct value_encoding value) + with _ -> None) + fields + |> List.to_seq + in + Instance.of_seq bindings + | _ -> Instance.empty) + Json.encoding + + include Instance +end + +module NonceMap = MapMake (Z) + +module Address = struct + type t = address + + let compare (Address h) (Address h') = String.compare h h' + + let to_string = address_to_string + + let of_string = address_of_string +end + +module AddressMap = MapMake (Address) + +type txpool = { + pending : transaction_object NonceMap.t AddressMap.t; + queued : transaction_object NonceMap.t AddressMap.t; +} + +let txpool_encoding = + let open Data_encoding in + let field_encoding = + AddressMap.associative_array_encoding + (NonceMap.associative_array_encoding transaction_object_encoding) + in + conv + (fun {pending; queued} -> (pending, queued)) + (fun (pending, queued) -> {pending; queued}) + (obj2 (req "pending" field_encoding) (req "queued" field_encoding)) diff --git a/src/bin_evm_proxy/mockup.ml b/src/bin_evm_proxy/mockup.ml index 67da1d059295..a2bb36e12c61 100644 --- a/src/bin_evm_proxy/mockup.ml +++ b/src/bin_evm_proxy/mockup.ml @@ -162,3 +162,56 @@ let nth_block ~full_transaction_object:_ _n = return (block ()) let transaction_receipt _tx_hash = return (transaction_receipt ()) let transaction_object _tx_hash = return (Some transaction_object) + +let tx_pending = + { + blockHash = None; + blockNumber = None; + from = bootstrap_address; + gas = qty_f Z.zero; + gasPrice = qty_f Z.zero; + hash = + Hash "af953a2d01f55cfe080c0c94150a60105e8ac3d51153058a1f03dd239dd08586"; + input = Some (Hash ""); + nonce = qty_f Z.zero; + to_ = Some bootstrap_address2; + transactionIndex = qty_f Z.zero; + value = qty_f Z.zero; + v = qty_f Z.zero; + r = hash_f @@ "00"; + s = hash_f @@ "00"; + } + +let tx_queued = + { + blockHash = None; + blockNumber = None; + from = bootstrap_address2; + gas = qty_f Z.zero; + gasPrice = qty_f Z.zero; + hash = + Hash "af953a2d01f55cfe080c0c94150a60105e8ac3d51153058a1f03dd239dd08588"; + input = Some (Hash "0x"); + nonce = qty_f Z.zero; + to_ = Some bootstrap_address; + transactionIndex = qty_f Z.zero; + value = qty_f Z.zero; + v = qty_f Z.zero; + r = hash_f @@ "00"; + s = hash_f @@ "00"; + } + +let txpool _ = + return + { + pending = + AddressMap.add + bootstrap_address + (NonceMap.add Z.zero tx_pending NonceMap.empty) + AddressMap.empty; + queued = + AddressMap.add + bootstrap_address2 + (NonceMap.add Z.zero tx_queued NonceMap.empty) + AddressMap.empty; + } diff --git a/src/bin_evm_proxy/rollup_node.ml b/src/bin_evm_proxy/rollup_node.ml index 8db1ac997062..bbcb82836311 100644 --- a/src/bin_evm_proxy/rollup_node.ml +++ b/src/bin_evm_proxy/rollup_node.ml @@ -598,6 +598,9 @@ module RPC = struct r; s; } + + let txpool _ () = + Lwt.return_ok {pending = AddressMap.empty; queued = AddressMap.empty} end module type S = sig @@ -626,6 +629,8 @@ module type S = sig val transaction_object : Ethereum_types.hash -> Ethereum_types.transaction_object option tzresult Lwt.t + + val txpool : unit -> Ethereum_types.txpool tzresult Lwt.t end module Make (Base : sig @@ -650,4 +655,6 @@ end) : S = struct let transaction_receipt = RPC.transaction_receipt Base.base let transaction_object = RPC.transaction_object Base.base + + let txpool = RPC.txpool Base.base end diff --git a/src/bin_evm_proxy/rollup_node.mli b/src/bin_evm_proxy/rollup_node.mli index 8129c5fb20d9..d89089548d34 100644 --- a/src/bin_evm_proxy/rollup_node.mli +++ b/src/bin_evm_proxy/rollup_node.mli @@ -78,6 +78,9 @@ module type S = sig val transaction_object : Ethereum_types.hash -> Ethereum_types.transaction_object option tzresult Lwt.t + + (** [txpool ()] returns the pending and queued transactions. *) + val txpool : unit -> Ethereum_types.txpool tzresult Lwt.t end (** Instantiate a module of type {!S} that communicates with a rollup diff --git a/src/bin_evm_proxy/rpc_encodings.ml b/src/bin_evm_proxy/rpc_encodings.ml index 8cc2952775c2..8edd4c86ee05 100644 --- a/src/bin_evm_proxy/rpc_encodings.ml +++ b/src/bin_evm_proxy/rpc_encodings.ml @@ -406,6 +406,20 @@ module Get_estimate_gas = MethodMaker (struct let method_ = "eth_estimateGas" end) +module Txpool_content = MethodMaker (struct + open Ethereum_types + + type input = unit + + type output = txpool + + let input_encoding = Data_encoding.unit + + let output_encoding = txpool_encoding + + let method_ = "txpool_content" +end) + let methods : (module METHOD) list = [ (module Network_id); @@ -424,6 +438,7 @@ let methods : (module METHOD) list = (module Send_raw_transaction); (module Eth_call); (module Get_estimate_gas); + (module Txpool_content); ] module Input = struct diff --git a/src/bin_evm_proxy/rpc_encodings.mli b/src/bin_evm_proxy/rpc_encodings.mli index 80501dfc1c95..cd4b6a653283 100644 --- a/src/bin_evm_proxy/rpc_encodings.mli +++ b/src/bin_evm_proxy/rpc_encodings.mli @@ -249,3 +249,6 @@ module Get_estimate_gas : METHOD with type m_input = Ethereum_types.call and type m_output = Ethereum_types.quantity + +module Txpool_content : + METHOD with type m_input = unit and type m_output = Ethereum_types.txpool diff --git a/src/bin_evm_proxy/services.ml b/src/bin_evm_proxy/services.ml index 184de541a5e0..7be91ae0428d 100644 --- a/src/bin_evm_proxy/services.ml +++ b/src/bin_evm_proxy/services.ml @@ -127,6 +127,9 @@ let dispatch_input | Eth_call.Input _ -> return (Eth_call.Output (Ok Mockup.call)) | Get_estimate_gas.Input _ -> return (Get_estimate_gas.Output (Ok Mockup.gas_price)) + | Txpool_content.Input _ -> + let* txpool = Rollup_node_rpc.txpool () in + return (Txpool_content.Output (Ok txpool)) | _ -> Error_monad.failwith "Unsupported method\n%!" in return (output, id) -- GitLab From 9e5223a0854f955762bd193b689530b98cebd98b Mon Sep 17 00:00:00 2001 From: Pierrick Couderc Date: Thu, 25 May 2023 15:16:40 +0200 Subject: [PATCH 3/5] EVM/Test: add Evm_proxy_server mockup mode. --- tezt/lib_tezos/evm_proxy_server.ml | 14 ++++++++++++-- tezt/lib_tezos/evm_proxy_server.mli | 4 ++++ 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/tezt/lib_tezos/evm_proxy_server.ml b/tezt/lib_tezos/evm_proxy_server.ml index 9da6cc5f8ca3..1ff34b654acb 100644 --- a/tezt/lib_tezos/evm_proxy_server.ml +++ b/tezt/lib_tezos/evm_proxy_server.ml @@ -29,7 +29,7 @@ module Parameters = struct mutable pending_ready : unit option Lwt.u list; rpc_addr : string; rpc_port : int; - rollup_node : Sc_rollup_node.t; + rollup_node : Sc_rollup_node.t option; runner : Runner.t option; } @@ -95,7 +95,11 @@ let wait_for_ready proxy_server = check_event proxy_server event_ready_name promise let create ?runner ?rpc_addr ?rpc_port rollup_node = - let rollup_node_endpoint = Sc_rollup_node.endpoint rollup_node in + let rollup_node_endpoint = + match rollup_node with + | None -> "mockup" + | Some rollup_node -> Sc_rollup_node.endpoint rollup_node + in let arguments, rpc_addr, rpc_port = connection_arguments ?rpc_addr ?rpc_port rollup_node_endpoint in @@ -107,6 +111,12 @@ let create ?runner ?rpc_addr ?rpc_port rollup_node = on_event proxy_server (handle_event proxy_server) ; proxy_server +let mockup ?runner ?rpc_addr ?rpc_port () = + create ?runner ?rpc_addr ?rpc_port None + +let create ?runner ?rpc_addr ?rpc_port rollup_node = + create ?runner ?rpc_addr ?rpc_port (Some rollup_node) + let run proxy_server = let* () = run diff --git a/tezt/lib_tezos/evm_proxy_server.mli b/tezt/lib_tezos/evm_proxy_server.mli index 524c8bc2e809..df3b19153d5e 100644 --- a/tezt/lib_tezos/evm_proxy_server.mli +++ b/tezt/lib_tezos/evm_proxy_server.mli @@ -38,6 +38,10 @@ type t val create : ?runner:Runner.t -> ?rpc_addr:string -> ?rpc_port:int -> Sc_rollup_node.t -> t +(** [mockup ?runner ?rpc_addr ?rpc_port ()] is like [create] but doesn't + communicate with a [rollup_node] and serves mockup values. *) +val mockup : ?runner:Runner.t -> ?rpc_addr:string -> ?rpc_port:int -> unit -> t + (** [run proxy_server] launches the EVM proxy server with the arguments given during {!create}. *) val run : t -> unit Lwt.t -- GitLab From b292a86d66eb35836bc2ea8f1982cbb1c594c684 Mon Sep 17 00:00:00 2001 From: Pierrick Couderc Date: Tue, 23 May 2023 17:26:31 +0200 Subject: [PATCH 4/5] EVM/Test: test RPC `txpool_content` availability --- tezt/lib_tezos/evm_proxy_server.ml | 28 ++++++++++++++++++++++++++++ tezt/lib_tezos/evm_proxy_server.mli | 8 ++++++++ tezt/tests/evm_rollup.ml | 22 +++++++++++++++++++++- 3 files changed, 57 insertions(+), 1 deletion(-) diff --git a/tezt/lib_tezos/evm_proxy_server.ml b/tezt/lib_tezos/evm_proxy_server.ml index 1ff34b654acb..b418a4e5f4ea 100644 --- a/tezt/lib_tezos/evm_proxy_server.ml +++ b/tezt/lib_tezos/evm_proxy_server.ml @@ -182,3 +182,31 @@ let fetch_contract_code evm_proxy_server contract_address = } in return JSON.(code |-> "result" |> as_string) + +type txpool_slot = {address : string; transactions : (int64 * JSON.t) list} + +let txpool_content evm_proxy_server = + let* txpool = + call_evm_rpc + evm_proxy_server + {method_ = "txpool_content"; parameters = `A []} + in + Log.info "Result: %s" (JSON.encode txpool) ; + let txpool = JSON.(txpool |-> "result") in + let parse field = + let open JSON in + let pool = txpool |-> field in + (* `|->` returns `Null if the field does not exists, and `Null is + interpreted as the empty list by `as_object`. As such, we must ensure the + field exists. *) + if is_null pool then Test.fail "%s must exists" field + else + pool |> as_object + |> List.map (fun (address, transactions) -> + let transactions = + transactions |> as_object + |> List.map (fun (nonce, tx) -> (Int64.of_string nonce, tx)) + in + {address; transactions}) + in + return (parse "pending", parse "queued") diff --git a/tezt/lib_tezos/evm_proxy_server.mli b/tezt/lib_tezos/evm_proxy_server.mli index df3b19153d5e..ac2c6188ee40 100644 --- a/tezt/lib_tezos/evm_proxy_server.mli +++ b/tezt/lib_tezos/evm_proxy_server.mli @@ -76,3 +76,11 @@ val batch_evm_rpc : t -> request list -> JSON.t Lwt.t (** [fetch_contract_code proxy_server contract] returns the code associated to the given contract in the rollup. *) val fetch_contract_code : t -> string -> string Lwt.t + +(** A slot in the transaction pool associates an address to a mapping of nonces + to transactions. *) +type txpool_slot = {address : string; transactions : (int64 * JSON.t) list} + +(** [txpool_content proxy_server] returns the transaction hash and nonce + contained in the `pending` and `queued` pools. *) +val txpool_content : t -> (txpool_slot list * txpool_slot list) Lwt.t diff --git a/tezt/tests/evm_rollup.ml b/tezt/tests/evm_rollup.ml index 393b500d7af0..618ddbf9dd49 100644 --- a/tezt/tests/evm_rollup.ml +++ b/tezt/tests/evm_rollup.ml @@ -160,6 +160,11 @@ let setup_past_genesis ?originator_key ?rollup_operator_key protocol = let* _level = next_evm_level ~sc_rollup_node ~node ~client in return full_setup +let setup_mockup () = + let evm_proxy_server = Evm_proxy_server.mockup () in + let* () = Evm_proxy_server.run evm_proxy_server in + return evm_proxy_server + let test_evm_proxy_server_connection = Protocol.register_test ~__FILE__ @@ -519,6 +524,20 @@ let test_chunked_transaction = ~title:"Check L2 chunked transfers are applied" @@ transfer ~data:("0x" ^ String.make 12_000 'a') +let test_rpc_txpool_content = + Protocol.register_test + ~__FILE__ + ~tags:["evm"; "txpool_content"] + ~title:"Check RPC txpool_content is available" + @@ fun _protocol -> + let* evm_proxy_server = setup_mockup () in + (* The content of the txpool is not relevant for now, this test only checks + the the RPC is correct, i.e. an object containing both the `pending` and + `queued` fields, containing the correct objects: addresses pointing to a + mapping of nonces to transactions. *) + let* _result = Evm_proxy_server.txpool_content evm_proxy_server in + unit + let register_evm_proxy_server ~protocols = test_originate_evm_kernel protocols ; test_evm_proxy_server_connection protocols ; @@ -530,6 +549,7 @@ let register_evm_proxy_server ~protocols = test_l2_blocks_progression protocols ; test_l2_transfer protocols ; test_chunked_transaction protocols ; - test_l2_deploy protocols + test_l2_deploy protocols ; + test_rpc_txpool_content protocols let register ~protocols = register_evm_proxy_server ~protocols -- GitLab From a3c80a42da6f6ae8a16a9c6b64320f255359088f Mon Sep 17 00:00:00 2001 From: Pierrick Couderc Date: Wed, 24 May 2023 09:53:42 +0200 Subject: [PATCH 5/5] EVM/Proxy: transaction_object.input is not optional --- src/bin_evm_proxy/ethereum_types.ml | 6 ++++-- src/bin_evm_proxy/mockup.ml | 8 ++++---- src/bin_evm_proxy/rollup_node.ml | 2 +- 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/src/bin_evm_proxy/ethereum_types.ml b/src/bin_evm_proxy/ethereum_types.ml index fab62c31a7db..c528da50eb03 100644 --- a/src/bin_evm_proxy/ethereum_types.ml +++ b/src/bin_evm_proxy/ethereum_types.ml @@ -138,6 +138,8 @@ let hash_to_bytes (Hash h) = Hex.to_bytes_exn (`Hex h) |> Bytes.to_string let hash_encoding = Data_encoding.(conv hash_to_string hash_of_string string) +let empty_hash = Hash "0x" + (** Ethereum block hash representation from RPCs. *) type block = { number : block_height option; @@ -432,7 +434,7 @@ type transaction_object = { gas : quantity; gasPrice : quantity; hash : hash; - input : hash option; + input : hash; nonce : quantity; to_ : address option; transactionIndex : quantity; @@ -508,7 +510,7 @@ let transaction_object_encoding = (req "gas" quantity_encoding) (req "gasPrice" quantity_encoding) (req "hash" hash_encoding) - (req "input" (option hash_encoding)) + (req "input" hash_encoding) (req "nonce" quantity_encoding) (req "to" (option address_encoding)) (req "transactionIndex" quantity_encoding)) diff --git a/src/bin_evm_proxy/mockup.ml b/src/bin_evm_proxy/mockup.ml index a2bb36e12c61..1aac57b86a53 100644 --- a/src/bin_evm_proxy/mockup.ml +++ b/src/bin_evm_proxy/mockup.ml @@ -128,7 +128,7 @@ let transaction_object = gas = qty_f Z.zero; gasPrice = qty_f Z.zero; hash = transaction_hash; - input = None; + input = empty_hash; nonce = qty_f Z.zero; to_ = Some (address_of_string "0xA5A5bf58c7Dc91cBE5005A7E5c6314998Eda479E"); transactionIndex = qty_f Z.zero; @@ -138,7 +138,7 @@ let transaction_object = s = hash_f @@ "00"; } -let call = hash_f "0x" +let call = empty_hash let return = Lwt_result_syntax.return @@ -172,7 +172,7 @@ let tx_pending = gasPrice = qty_f Z.zero; hash = Hash "af953a2d01f55cfe080c0c94150a60105e8ac3d51153058a1f03dd239dd08586"; - input = Some (Hash ""); + input = empty_hash; nonce = qty_f Z.zero; to_ = Some bootstrap_address2; transactionIndex = qty_f Z.zero; @@ -191,7 +191,7 @@ let tx_queued = gasPrice = qty_f Z.zero; hash = Hash "af953a2d01f55cfe080c0c94150a60105e8ac3d51153058a1f03dd239dd08588"; - input = Some (Hash "0x"); + input = empty_hash; nonce = qty_f Z.zero; to_ = Some bootstrap_address; transactionIndex = qty_f Z.zero; diff --git a/src/bin_evm_proxy/rollup_node.ml b/src/bin_evm_proxy/rollup_node.ml index bbcb82836311..d3c24facc325 100644 --- a/src/bin_evm_proxy/rollup_node.ml +++ b/src/bin_evm_proxy/rollup_node.ml @@ -589,7 +589,7 @@ module RPC = struct gas = gas_used; gasPrice = gas_price; hash; - input; + input = Option.value ~default:(Hash "") input; nonce; to_; transactionIndex = index; -- GitLab