From 2425d5f60e303129026bb7423893d016cbccc93b Mon Sep 17 00:00:00 2001 From: Thomas Letan Date: Fri, 18 Jul 2025 08:38:04 +0200 Subject: [PATCH] EVM Node: RPC responses revert to lower-case addresses * What This patch reverts to the behavior exhibited before commit db1459fd9a2ac14e174f52e0b50604ae863c2d66. Specifically, This change reverts the behavior of the EVM node's RPC endpoints to output Ethereum addresses in lowercase hexadecimal format, instead of the EIP-55 checksummed format. * Why Returning EIP-55 checksummed addresses directly from RPC responses is not standard behavior for most EVM-compatible nodes. Major clients like Geth return lowercase addresses, leaving it to client-side applications to apply checksums for display. Adhering to this de facto standard improves compatibility with the wider Ethereum tooling ecosystem. * How The Ethereum_types.Address.to_string function has been simplified back to output only lowercase hex strings. The previous EIP-55 checksumming logic was moved to a new, explicitly named function, to_eip55_string. This new function is used where checksums are still beneficial for direct user display, like in the `show kms key info` command. All RPC-related types and encodings were updated accordingly. The test suite has been reverted to assert against the new lowercase address format in RPC responses. --- etherlink/CHANGES_NODE.md | 3 + .../lib_dev/encodings/ethereum_types.ml | 56 ++++++++++--------- .../lib_dev/encodings/ethereum_types.mli | 4 +- etherlink/bin_node/lib_dev/evm_store.ml | 2 +- etherlink/bin_node/main.ml | 2 +- etherlink/tezt/tests/evm_rollup.ml | 40 +++++++------ etherlink/tezt/tests/evm_sequencer.ml | 34 +++++++---- 7 files changed, 83 insertions(+), 58 deletions(-) diff --git a/etherlink/CHANGES_NODE.md b/etherlink/CHANGES_NODE.md index bc6f240264eb..1c67936b7115 100644 --- a/etherlink/CHANGES_NODE.md +++ b/etherlink/CHANGES_NODE.md @@ -13,6 +13,9 @@ the store (!18713). ### RPCs changes +- Revert outputting EIP-55 encoded addresses in the RPC responses. This is not + a standard behavior for nodes of EVM-compatible chains. (!18757) + ### Metrics changes ### Command-line interface changes diff --git a/etherlink/bin_node/lib_dev/encodings/ethereum_types.ml b/etherlink/bin_node/lib_dev/encodings/ethereum_types.ml index 95442b79fd17..ef2ce3545ac0 100644 --- a/etherlink/bin_node/lib_dev/encodings/ethereum_types.ml +++ b/etherlink/bin_node/lib_dev/encodings/ethereum_types.ml @@ -70,27 +70,7 @@ type address = Address of hex [@@ocaml.unboxed] let address_of_string s = Address (hex_of_string (String.lowercase_ascii s)) -let address_to_string (Address (Hex address)) = - (* Implementation compliant with EIP-55 - See https://eips.ethereum.org/EIPS/eip-55 *) - let hexchar_to_int c = - if '0' <= c && c <= '9' then Char.code c - Char.code '0' - else if 'A' <= c && c <= 'F' then Char.code c - Char.code 'A' + 10 - else if 'a' <= c && c <= 'f' then Char.code c - Char.code 'a' + 10 - else raise (Invalid_argument "address_to_string: not a valid address") - in - - let hash = Digestif.KECCAK_256.(to_hex @@ digest_string address) in - let address = Bytes.of_string address in - - Bytes.iteri - (fun i c -> - if 'a' <= c && c <= 'f' then - let hash_nibble = hexchar_to_int (String.get hash i) in - if hash_nibble >= 8 then Bytes.set address i (Char.uppercase_ascii c)) - address ; - - "0x" ^ Bytes.to_string address +let address_to_string (Address a) = hex_to_string a let address_encoding = Data_encoding.(conv address_to_string address_of_string string) @@ -564,7 +544,7 @@ type 'transaction_object block = { transactionRoot : hash; stateRoot : hash; receiptRoot : hash; - miner : address; + miner : hex; difficulty : quantity; totalDifficulty : quantity; extraData : hex; @@ -626,8 +606,8 @@ let block_from_rlp_v0 bytes = have that, potentially this could be the sequencer. *) let miner = decode_option - ~default:(Address (Hex "0000000000000000000000000000000000000000")) - decode_address + ~default:(Hex "0000000000000000000000000000000000000000") + decode_hex miner in let transactionRoot = @@ -726,8 +706,8 @@ let block_from_rlp_v1 bytes = have that, potentially this could be the sequencer. *) let miner = decode_option - ~default:(Address (Hex "0000000000000000000000000000000000000000")) - decode_address + ~default:(Hex "0000000000000000000000000000000000000000") + decode_hex miner in let transactionRoot = @@ -948,7 +928,7 @@ let block_encoding transaction_object_encoding = (req "transactionsRoot" hash_encoding) (req "stateRoot" hash_encoding) (req "receiptsRoot" hash_encoding) - (req "miner" address_encoding)) + (req "miner" hex_encoding)) (obj10 (req "difficulty" quantity_encoding) (req "totalDifficulty" quantity_encoding) @@ -1099,6 +1079,28 @@ module Address = struct let to_string = address_to_string let of_string = address_of_string + + let to_eip55_string (Address (Hex address)) = + (* Implementation compliant with EIP-55 + See https://eips.ethereum.org/EIPS/eip-55 *) + let hexchar_to_int c = + if '0' <= c && c <= '9' then Char.code c - Char.code '0' + else if 'A' <= c && c <= 'F' then Char.code c - Char.code 'A' + 10 + else if 'a' <= c && c <= 'f' then Char.code c - Char.code 'a' + 10 + else raise (Invalid_argument "address_to_string: not a valid address") + in + + let hash = Digestif.KECCAK_256.(to_hex @@ digest_string address) in + let address = Bytes.of_string address in + + Bytes.iteri + (fun i c -> + if 'a' <= c && c <= 'f' then + let hash_nibble = hexchar_to_int (String.get hash i) in + if hash_nibble >= 8 then Bytes.set address i (Char.uppercase_ascii c)) + address ; + + "0x" ^ Bytes.to_string address end module AddressMap = MapMake (Address) diff --git a/etherlink/bin_node/lib_dev/encodings/ethereum_types.mli b/etherlink/bin_node/lib_dev/encodings/ethereum_types.mli index 9f855b92a66a..f89aea18e9ad 100644 --- a/etherlink/bin_node/lib_dev/encodings/ethereum_types.mli +++ b/etherlink/bin_node/lib_dev/encodings/ethereum_types.mli @@ -184,7 +184,7 @@ type 'transaction_object block = { transactionRoot : hash; stateRoot : hash; receiptRoot : hash; - miner : address; + miner : hex; difficulty : quantity; totalDifficulty : quantity; extraData : hex; @@ -302,6 +302,8 @@ module Address : sig val to_string : t -> string val of_string : string -> t + + val to_eip55_string : t -> string end (** [timestamp_to_bytes timestamp] transforms the timestamp to bytes diff --git a/etherlink/bin_node/lib_dev/evm_store.ml b/etherlink/bin_node/lib_dev/evm_store.ml index 0f01f797692b..1ff9f88d05f6 100644 --- a/etherlink/bin_node/lib_dev/evm_store.ml +++ b/etherlink/bin_node/lib_dev/evm_store.ml @@ -125,7 +125,7 @@ module Legacy_encodings = struct (req "transactionsRoot" hash_encoding) (req "stateRoot" hash_encoding) (req "receiptsRoot" hash_encoding) - (req "miner" address_encoding)) + (req "miner" hex_encoding)) (obj10 (req "difficulty" quantity_encoding) (req "totalDifficulty" quantity_encoding) diff --git a/etherlink/bin_node/main.ml b/etherlink/bin_node/main.ml index 1372799cd91e..98e2cd12a543 100644 --- a/etherlink/bin_node/main.ml +++ b/etherlink/bin_node/main.ml @@ -1456,7 +1456,7 @@ let show_kms_key_info_command = fprintf fmt "@ Ethereum address: %s" - (Ethereum_types.Address.to_string addr))) + (Ethereum_types.Address.to_eip55_string addr))) eth_opt ; return_unit | None -> diff --git a/etherlink/tezt/tests/evm_rollup.ml b/etherlink/tezt/tests/evm_rollup.ml index aca0083086d4..e3b3d5daeaca 100644 --- a/etherlink/tezt/tests/evm_rollup.ml +++ b/etherlink/tezt/tests/evm_rollup.ml @@ -770,8 +770,9 @@ let deploy_with_base_checks {contract; expected_address} full_evm_setup = let endpoint = Evm_node.endpoint evm_node in let sender = Eth_account.bootstrap_accounts.(0) in let* contract_address, tx = deploy ~contract ~sender full_evm_setup in + let address = String.lowercase_ascii contract_address in Check.( - (contract_address = expected_address) + (address = expected_address) string ~error_msg:"Expected address to be %R but was %L.") ; let* code_in_kernel = @@ -1235,7 +1236,7 @@ let test_l2_deploy_simple_storage = deploy_with_base_checks { contract = simple_storage_resolved; - expected_address = "0xd77420F73B4612a7A99DBA8c2AFd30a1886b0344"; + expected_address = "0xd77420f73b4612a7a99dba8c2afd30a1886b0344"; } evm_setup @@ -1369,7 +1370,8 @@ let test_l2_deploy_erc20 = (* deploy the contract *) let* address, tx = deploy ~contract:erc20_resolved ~sender evm_setup in Check.( - (address = "0xd77420F73B4612a7A99DBA8c2AFd30a1886b0344") + (String.lowercase_ascii address + = "0xd77420f73b4612a7a99dba8c2afd30a1886b0344") string ~error_msg:"Expected address to be %R but was %L.") ; @@ -1488,7 +1490,7 @@ let test_deploy_contract_with_push0 = deploy_with_base_checks { contract = shanghai_storage_resolved; - expected_address = "0xd77420F73B4612a7A99DBA8c2AFd30a1886b0344"; + expected_address = "0xd77420f73b4612a7a99dba8c2afd30a1886b0344"; } evm_setup @@ -1537,7 +1539,7 @@ let test_log_index = ~gas_price:1_000_000_000 ~gas:27_638 ~value:Wei.zero - ~address:"0xd77420F73B4612a7A99DBA8c2AFd30a1886b0344" + ~address:"0xd77420f73b4612a7a99dba8c2afd30a1886b0344" ~signature:"emitBoth(uint256)" ~arguments:["100"] () @@ -1550,7 +1552,7 @@ let test_log_index = ~gas_price:1_000_000_000 ~gas:25_664 ~value:Wei.zero - ~address:"0xd77420F73B4612a7A99DBA8c2AFd30a1886b0344" + ~address:"0xd77420f73b4612a7a99dba8c2afd30a1886b0344" ~signature:"emitA(uint256)" ~arguments:["10"] () @@ -1915,7 +1917,7 @@ let test_rpc_txpool_content = ~gas_price:100_000 ~gas:23_300 ~value:(Wei.of_string "100") - ~address:"0x11D3C9168db9d12a3C591061D555870969b43dC9" + ~address:"0x11d3c9168db9d12a3c591061d555870969b43dc9" () in let* tx2 = @@ -1926,7 +1928,7 @@ let test_rpc_txpool_content = ~gas_price:100_000 ~gas:23_300 ~value:(Wei.of_string "100") - ~address:"0x11D3C9168db9d12a3C591061D555870969b43dC9" + ~address:"0x11d3c9168db9d12a3c591061d555870969b43dc9" () in let*@ tx_hash1 = Rpc.send_raw_transaction ~raw_tx:tx1 evm_node in @@ -1959,12 +1961,12 @@ let test_rpc_txpool_content = let transaction_addr_queued = List.nth txpool_queued 0 in Check.( (transaction_addr_pending.address - = "0x6ce4d79d4E77402e1ef3417Fdda433aA744C6e1c") + = "0x6ce4d79d4e77402e1ef3417fdda433aa744c6e1c") string) ~error_msg:"Expected caller of transaction_1 to be %R, got %L." ; Check.( (transaction_addr_queued.address - = "0x6ce4d79d4E77402e1ef3417Fdda433aA744C6e1c") + = "0x6ce4d79d4e77402e1ef3417fdda433aa744c6e1c") string) ~error_msg:"Expected caller of transaction_2 to be %R, got %L." ; let num_pending_transaction_addr_1 = @@ -1994,13 +1996,13 @@ let test_rpc_txpool_content = ~transaction_content:pending_transaction_addr_1_content ~blockHash:"null" ~blockNumber:"null" - ~from:"0x6ce4d79d4E77402e1ef3417Fdda433aA744C6e1c" + ~from:"0x6ce4d79d4e77402e1ef3417fdda433aa744c6e1c" ~gas:"0x5b04" ~gasPrice:"0x186a0" ~hash:"0xed148f664807dfcb3c7095de22a6c63e72ae4f9d503549c525f5014327b51693" ~input:"0x" ~nonce:"0x0" - ~to_:"0x11D3C9168db9d12a3C591061D555870969b43dC9" + ~to_:"0x11d3c9168db9d12a3c591061d555870969b43dc9" ~transactionIndex:"null" ~value:"0x64" (* TODO: https://gitlab.com/tezos/tezos/-/issues/7194 @@ -2016,13 +2018,13 @@ let test_rpc_txpool_content = ~transaction_content:queued_transaction_addr_1_content ~blockHash:"null" ~blockNumber:"null" - ~from:"0x6ce4d79d4E77402e1ef3417Fdda433aA744C6e1c" + ~from:"0x6ce4d79d4e77402e1ef3417fdda433aa744c6e1c" ~gas:"0x5b04" ~gasPrice:"0x186a0" ~hash:"0x38b2831803a0f9a82bcc68f79bf167311b0adfe9e3d111a7f9579cdfcbae0f0f" ~input:"0x" ~nonce:"0x1" - ~to_:"0x11D3C9168db9d12a3C591061D555870969b43dC9" + ~to_:"0x11d3c9168db9d12a3c591061d555870969b43dc9" ~transactionIndex:"null" ~value:"0x64" (* TODO: https://gitlab.com/tezos/tezos/-/issues/7194 @@ -2369,7 +2371,7 @@ let test_estimate_gas_additionnal_field = let eth_call = [ ( "from", - Ezjsonm.encode_string @@ "0x6ce4d79d4E77402e1ef3417Fdda433aA744C6e1c" + Ezjsonm.encode_string @@ "0x6ce4d79d4e77402e1ef3417fdda433aa744c6e1c" ); ("data", Ezjsonm.encode_string @@ "0x" ^ data); ("value", Ezjsonm.encode_string @@ "0x0"); @@ -2398,7 +2400,8 @@ let test_eth_call_storage_contract = in let* () = check_tx_succeeded ~endpoint ~tx in Check.( - (address = "0xd77420F73B4612a7A99DBA8c2AFd30a1886b0344") + (String.lowercase_ascii address + = "0xd77420f73b4612a7a99dba8c2afd30a1886b0344") string ~error_msg:"Expected address to be %R but was %L.") ; @@ -4370,8 +4373,9 @@ let test_rpc_getLogs = let* erc20_resolved = erc20 evm_version in (* deploy the contract *) let* address, _tx = deploy ~contract:erc20_resolved ~sender evm_setup in + let address = String.lowercase_ascii address in Check.( - (address = "0xd77420F73B4612a7A99DBA8c2AFd30a1886b0344") + (address = "0xd77420f73b4612a7a99dba8c2afd30a1886b0344") string ~error_msg:"Expected address to be %R but was %L.") ; (* minting / burning *) @@ -4615,7 +4619,7 @@ let test_l2_revert_returns_unused_gas = ~gas_price:65_536 ~gas:100_000 ~value:Wei.zero - ~address:"0xd77420F73B4612a7A99DBA8c2AFd30a1886b0344" + ~address:"0xd77420f73b4612a7a99dba8c2afd30a1886b0344" ~signature:"run()" () in diff --git a/etherlink/tezt/tests/evm_sequencer.ml b/etherlink/tezt/tests/evm_sequencer.ml index 8f7b695b2be9..dc9a59444370 100644 --- a/etherlink/tezt/tests/evm_sequencer.ml +++ b/etherlink/tezt/tests/evm_sequencer.ml @@ -8075,7 +8075,8 @@ let test_trace_delegate_call = sequencer in Check.( - JSON.(top_call |-> "to" |> as_string = delegator_address) + JSON.( + top_call |-> "to" |> as_string = String.lowercase_ascii delegator_address) string ~error_msg:"Top call should have been traced as coming from %R but was %L") ; Check.( @@ -8084,11 +8085,15 @@ let test_trace_delegate_call = ~error_msg:"Top call should have some subcalls") ; let delegatecall = JSON.(top_call |-> "calls" |> as_list |> List.hd) in Check.( - JSON.(delegatecall |-> "from" |> as_string = delegator_address) + JSON.( + delegatecall |-> "from" |> as_string + = String.lowercase_ascii delegator_address) string ~error_msg:"Delegate call should be traced as coming from %R but was %L") ; Check.( - JSON.(delegatecall |-> "to" |> as_string = delegated_address) + JSON.( + delegatecall |-> "to" |> as_string + = String.lowercase_ascii delegated_address) string ~error_msg: "Delegate call should be traced as going to delegated contract %R but \ @@ -8767,7 +8772,9 @@ let test_trace_transaction_calltracer_deposit = unit let test_miner = - let sequencer_pool_address = "0x8aaD6553Cf769Aa7b89174bE824ED0e53768ed70" in + let sequencer_pool_address = + String.lowercase_ascii "0x8aaD6553Cf769Aa7b89174bE824ED0e53768ed70" + in register_all ~__FILE__ ~tags:["evm"; "miner"; "coinbase"] @@ -8775,7 +8782,7 @@ let test_miner = ~sequencer_pool_address @@ fun {sequencer; evm_version; _} _protocol -> let*@ block = Rpc.get_block_by_number ~block:"latest" sequencer in - Check.((block.miner = sequencer_pool_address) string) + Check.((String.lowercase_ascii block.miner = sequencer_pool_address) string) ~error_msg: "Block miner should be the sequencer pool address, expected %R got %L" ; (* We deploy a contract that stores the block coinbase in its storage, and @@ -8801,7 +8808,10 @@ let test_miner = ~method_call:"getStorageCoinbase()" () in - Check.((String.trim storage_coinbase = sequencer_pool_address) string) + Check.( + (String.lowercase_ascii @@ String.trim storage_coinbase + = sequencer_pool_address) + string) ~error_msg: "Stored coinbase should be the sequencer pool address, expected %R got %L" ; let* view_coinbase = @@ -8812,7 +8822,10 @@ let test_miner = ~method_call:"getStorageCoinbase()" () in - Check.((String.trim view_coinbase = sequencer_pool_address) string) + Check.( + (String.lowercase_ascii @@ String.trim view_coinbase + = sequencer_pool_address) + string) ~error_msg: "Viewed coinbase should be the sequencer pool address, expected %R got %L" ; @@ -10452,11 +10465,11 @@ let test_tx_pool_replacing_transactions_on_limit () = in let txpool_content () = let*@ pending, queued = Rpc.txpool_content sequencer in - let sender = sender.address in + let sender = String.lowercase_ascii sender.address in let pending = List.find_map (fun Rpc.{address; transactions} -> - if address = sender then + if String.lowercase_ascii address = sender then Some (List.map fst transactions |> List.sort compare) else None) pending @@ -10464,7 +10477,7 @@ let test_tx_pool_replacing_transactions_on_limit () = let queued = List.find_map (fun Rpc.{address; transactions} -> - if address = sender then + if String.lowercase_ascii address = sender then Some (List.map fst transactions |> List.sort compare) else None) queued @@ -11237,6 +11250,7 @@ let test_websocket_logs_event = ~bin:erc20.bin) sequencer in + let address = String.lowercase_ascii address in let transfer_event_topic = let h = Tezos_crypto.Hacl.Hash.Keccak_256.digest -- GitLab