From e1f45918b3e6927f8fdba4b482c4c8f26068e1ce Mon Sep 17 00:00:00 2001 From: Luciano Freitas Date: Fri, 11 Apr 2025 17:15:56 +0200 Subject: [PATCH 1/6] Tezlink: import header service --- .../bin_node/lib_dev/tezlink/tezlink_imports.ml | 4 ++++ .../bin_node/lib_dev/tezlink/tezos_services.ml | 13 ++++++++++--- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/etherlink/bin_node/lib_dev/tezlink/tezlink_imports.ml b/etherlink/bin_node/lib_dev/tezlink/tezlink_imports.ml index d2795cee6404..2b26d16e7e09 100644 --- a/etherlink/bin_node/lib_dev/tezlink/tezlink_imports.ml +++ b/etherlink/bin_node/lib_dev/tezlink/tezlink_imports.ml @@ -10,3 +10,7 @@ module Imported_protocol_plugin = Tezos_protocol_plugin_021_PsQuebec module Imported_protocol_parameters = Tezos_protocol_021_PsQuebec_parameters module Imported_env = Tezos_protocol_environment_021_PsQuebec module Alpha_context = Imported_protocol.Alpha_context +module Block_services = + Tezos_shell_services.Block_services.Make + (Imported_protocol) + (Imported_protocol) diff --git a/etherlink/bin_node/lib_dev/tezlink/tezos_services.ml b/etherlink/bin_node/lib_dev/tezlink/tezos_services.ml index 07a67b027e6a..9a02032702da 100644 --- a/etherlink/bin_node/lib_dev/tezlink/tezos_services.ml +++ b/etherlink/bin_node/lib_dev/tezlink/tezos_services.ml @@ -109,9 +109,7 @@ type chain = Tezos_shell_services.Chain_services.chain type tezlink_rpc_context = {block : block; chain : chain} (** Builds a [tezlink_rpc_context] from paths parameters. *) -let make_env (chain : Tezos_shell_services.Chain_services.chain) - (block : Tezos_shell_services.Block_services.block) : - tezlink_rpc_context Lwt.t = +let make_env (chain : chain) (block : block) : tezlink_rpc_context Lwt.t = Lwt.return {block; chain} module Tezlink_version = struct @@ -247,6 +245,15 @@ module Imported_services = struct let chain_id : ([`GET], chain, chain, unit, unit, Chain_id.t) Tezos_rpc.Service.t = import_service Tezos_shell_services.Chain_services.S.chain_id + let _header : + ( [`GET], + tezlink_rpc_context, + tezlink_rpc_context, + unit, + unit, + Block_services.block_header ) + Tezos_rpc.Service.t = + import_service Block_services.S.header end let chain_directory_path = Tezos_shell_services.Chain_services.path -- GitLab From 11b3bff2a04da31eacb3d3c99e879551f52478e2 Mon Sep 17 00:00:00 2001 From: Luciano Freitas Date: Thu, 17 Apr 2025 12:24:54 +0200 Subject: [PATCH 2/6] Tezlink: implement header rpc Co-authored-by: Arnaud Bihan --- .../bin_node/lib_dev/services_backend_sig.ml | 2 ++ .../bin_node/lib_dev/tezlink_services_impl.ml | 17 ++++++++++++++++- 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/etherlink/bin_node/lib_dev/services_backend_sig.ml b/etherlink/bin_node/lib_dev/services_backend_sig.ml index da09b3dfd336..ca8203d67c32 100644 --- a/etherlink/bin_node/lib_dev/services_backend_sig.ml +++ b/etherlink/bin_node/lib_dev/services_backend_sig.ml @@ -196,6 +196,8 @@ module Make (Backend : Backend) (Executor : Evm_execution.S) : S = struct include Backend.Reader let block_param_to_block_number = Backend.block_param_to_block_number + + let tez_nth_block = Block_storage.tez_nth_block end) let block_param_to_block_number = Backend.block_param_to_block_number diff --git a/etherlink/bin_node/lib_dev/tezlink_services_impl.ml b/etherlink/bin_node/lib_dev/tezlink_services_impl.ml index ab1f1fcfd83f..8c2dc3a951d6 100644 --- a/etherlink/bin_node/lib_dev/tezlink_services_impl.ml +++ b/etherlink/bin_node/lib_dev/tezlink_services_impl.ml @@ -4,7 +4,6 @@ (* Copyright (c) 2025 Nomadic Labs *) (* *) (*****************************************************************************) - open Tezos_types module Path = struct @@ -68,6 +67,8 @@ module type Backend = sig val block_param_to_block_number : Ethereum_types.Block_parameter.extended -> Ethereum_types.quantity tzresult Lwt.t + + val tez_nth_block : Z.t -> L2_types.Tezos_block.t tzresult Lwt.t end module Make (Backend : Backend) : Tezlink_backend_sig.S = struct @@ -182,4 +183,18 @@ module Make (Backend : Backend) : Tezlink_backend_sig.S = struct (* TODO: #7831 !17664 we type [chain], even though we don't use it, to satisfy the compiler. *) let counter (chain : [> `Main]) = counter read chain + + let _header chain block = + let open Lwt_result_syntax in + (* TODO: #7831 + take chain and block into account + For the moment this implementation only supports the main chain and head block, once + the rpc support of tezlink is more stable, we can add support for other chains and blocks. *) + ignore (chain, block) ; + + let* (Qty block_number) = + Backend.block_param_to_block_number (Block_parameter Latest) + in + + Backend.tez_nth_block block_number end -- GitLab From 85187ccd95c75e760eddf3e9ee092bd07060ceda Mon Sep 17 00:00:00 2001 From: Luciano Freitas Date: Tue, 29 Apr 2025 11:46:42 +0200 Subject: [PATCH 3/6] Tezlink: block header conversions --- .../lib_dev/tezlink/tezos_services.ml | 87 +++++++++++++++++++ 1 file changed, 87 insertions(+) diff --git a/etherlink/bin_node/lib_dev/tezlink/tezos_services.ml b/etherlink/bin_node/lib_dev/tezlink/tezos_services.ml index 9a02032702da..532e44566117 100644 --- a/etherlink/bin_node/lib_dev/tezlink/tezos_services.ml +++ b/etherlink/bin_node/lib_dev/tezlink/tezos_services.ml @@ -9,6 +9,49 @@ include Tezlink_imports type level = Tezos_types.level +(* Provides mock values necessary for constructing L1 types that contain fields + that are either irrelevant to the L2 or are not yet supported. *) +module Mock = struct + let proto_level = 1 + + let validation_passes = 4 + + (* TODO #7866 + When blocks are populated, this mock value will be unnecessary, + being replaced by actual data. *) + let operations_hash = + Operation_list_list_hash.of_bytes_exn (Bytes.make 32 '\000') + + let fitness = [Bytes.make 32 '\255'] + + (* TODO #7866 + When blocks are populated, this mock value will be unnecessary, + being replaced by actual data. *) + let context = Context_hash.of_bytes_exn (Bytes.make 32 '\255') + + let contents : Imported_protocol.Block_header_repr.contents = + { + payload_hash = Imported_protocol.Block_payload_hash.zero; + payload_round = Imported_protocol.Round_repr.zero; + seed_nonce_hash = None; + proof_of_work_nonce = + Bytes.make + Imported_protocol.Constants_repr.proof_of_work_nonce_size + '\000'; + per_block_votes = + { + liquidity_baking_vote = Per_block_vote_pass; + adaptive_issuance_vote = Per_block_vote_pass; + }; + } + + let signature : Imported_protocol.Alpha_context.signature = + Unknown (Bytes.make Tezos_crypto.Signature.Ed25519.size '\000') + + let protocol_data : Imported_protocol.Block_header_repr.protocol_data = + {contents; signature} +end + (* Module importing, amending, and converting, protocol types. Those types might be difficult to actually build, so we define conversion function from local types to protocol types. *) @@ -32,6 +75,50 @@ module Protocol_types = struct l2_chain_id |> Z.to_int32 |> Bytes.set_int32_be bytes 0 ; Chain_id.of_bytes_exn bytes + module Protocol_data = struct + let get_mock_protocol_data = + Tezos_types.convert_using_serialization + ~name:"protocol_data" + ~dst:Tezlink_imports.Imported_protocol.block_header_data_encoding + ~src:Imported_protocol.Block_header_repr.protocol_data_encoding + Mock.protocol_data + end + + let ethereum_to_tezos_block_hash hash = + hash |> Ethereum_types.block_hash_to_bytes |> Block_hash.of_string_exn + + module Block_header = struct + let tezlink_block_to_shell_header (block : L2_types.Tezos_block.t) : + Block_header.shell_header = + let open Mock in + let (Ethereum_types.Qty number) = block.number in + let (Ethereum_types.Qty timestamp) = block.timestamp in + let predecessor = ethereum_to_tezos_block_hash block.parent_hash in + { + level = Z.to_int32 number; + proto_level; + predecessor; + timestamp = Time.Protocol.of_seconds @@ Z.to_int64 timestamp; + validation_passes; + operations_hash; + fitness; + context; + } + + let _tezlink_block_to_block_header ~l2_chain_id + ((block : L2_types.Tezos_block.t), chain) : + Block_services.block_header tzresult = + let open Result_syntax in + let chain_id = tezlink_to_tezos_chain_id_exn ~l2_chain_id chain in + let* protocol_data = Protocol_data.get_mock_protocol_data in + let hash = ethereum_to_tezos_block_hash block.hash in + let shell = tezlink_block_to_shell_header block in + let block_header : Block_services.block_header = + {chain_id; hash; shell; protocol_data} + in + return block_header + end + module Level = struct type t = Alpha_context.Level.t -- GitLab From f3b917b675616ddbd35734c33f75765d8902a6f0 Mon Sep 17 00:00:00 2001 From: Luciano Freitas Date: Tue, 22 Apr 2025 14:24:10 +0200 Subject: [PATCH 4/6] Tezlink: register header service --- .../lib_dev/tezlink/tezlink_backend_sig.ml | 3 +++ .../lib_dev/tezlink/tezos_services.ml | 19 +++++++++++++++---- .../bin_node/lib_dev/tezlink_services_impl.ml | 2 +- 3 files changed, 19 insertions(+), 5 deletions(-) diff --git a/etherlink/bin_node/lib_dev/tezlink/tezlink_backend_sig.ml b/etherlink/bin_node/lib_dev/tezlink/tezlink_backend_sig.ml index b65dbd8bdc60..abe9dcbe89ab 100644 --- a/etherlink/bin_node/lib_dev/tezlink/tezlink_backend_sig.ml +++ b/etherlink/bin_node/lib_dev/tezlink/tezlink_backend_sig.ml @@ -29,4 +29,7 @@ module type S = sig val counter : [> `Main] -> [> `Head of 'a] -> Tezos_types.Contract.t -> Z.t tzresult Lwt.t + + val header : + [> `Main] -> [> `Head of 'a] -> L2_types.Tezos_block.t tzresult Lwt.t end diff --git a/etherlink/bin_node/lib_dev/tezlink/tezos_services.ml b/etherlink/bin_node/lib_dev/tezlink/tezos_services.ml index 532e44566117..6fe5a6345872 100644 --- a/etherlink/bin_node/lib_dev/tezlink/tezos_services.ml +++ b/etherlink/bin_node/lib_dev/tezlink/tezos_services.ml @@ -105,7 +105,7 @@ module Protocol_types = struct context; } - let _tezlink_block_to_block_header ~l2_chain_id + let tezlink_block_to_block_header ~l2_chain_id ((block : L2_types.Tezos_block.t), chain) : Block_services.block_header tzresult = let open Result_syntax in @@ -332,7 +332,8 @@ module Imported_services = struct let chain_id : ([`GET], chain, chain, unit, unit, Chain_id.t) Tezos_rpc.Service.t = import_service Tezos_shell_services.Chain_services.S.chain_id - let _header : + + let header : ( [`GET], tezlink_rpc_context, tezlink_rpc_context, @@ -358,7 +359,8 @@ let version () = Lwt_result_syntax.return Tezlink_version.mock (** Builds the directory registering services under `/chains/
/blocks//...`. *) -let register_block_services (module Backend : Tezlink_backend_sig.S) base_dir = +let register_block_services ~l2_chain_id + (module Backend : Tezlink_backend_sig.S) base_dir = let dir = Tezos_rpc.Directory.empty |> register_with_conversion @@ -385,6 +387,15 @@ let register_block_services (module Backend : Tezlink_backend_sig.S) base_dir = ~service:Imported_services.constants ~impl:(fun {block; chain} () () -> Backend.constants chain block) ~convert_output:Tezlink_constants.convert + |> register_with_conversion + ~service:Imported_services.header + ~impl:(fun {chain; block} () () -> + let open Lwt_result_syntax in + let* header = Backend.header chain block in + Lwt_result_syntax.return (header, chain)) + ~convert_output: + (Protocol_types.Block_header.tezlink_block_to_block_header + ~l2_chain_id) in Tezos_rpc.Directory.prefix block_directory_path @@ -413,7 +424,7 @@ let register_chain_services ~l2_chain_id (** Builds the root directory. *) let build_dir ~l2_chain_id backend = Tezos_rpc.Directory.empty - |> register_block_services backend + |> register_block_services ~l2_chain_id backend |> register_chain_services ~l2_chain_id backend |> register ~service:Imported_services.version ~impl:(fun () () () -> version ()) diff --git a/etherlink/bin_node/lib_dev/tezlink_services_impl.ml b/etherlink/bin_node/lib_dev/tezlink_services_impl.ml index 8c2dc3a951d6..d7c14a8c702a 100644 --- a/etherlink/bin_node/lib_dev/tezlink_services_impl.ml +++ b/etherlink/bin_node/lib_dev/tezlink_services_impl.ml @@ -184,7 +184,7 @@ module Make (Backend : Backend) : Tezlink_backend_sig.S = struct we type [chain], even though we don't use it, to satisfy the compiler. *) let counter (chain : [> `Main]) = counter read chain - let _header chain block = + let header chain block = let open Lwt_result_syntax in (* TODO: #7831 take chain and block into account -- GitLab From 6e9b8a2c2679da45cdd68705b02a9b3cd5349c4d Mon Sep 17 00:00:00 2001 From: Luciano Freitas Date: Mon, 14 Apr 2025 09:54:21 +0200 Subject: [PATCH 5/6] Tezlink: test header rpc --- etherlink/tezt/lib/test_helpers.ml | 39 +++++++++++++++++++++++++++ etherlink/tezt/lib/test_helpers.mli | 9 +++++++ etherlink/tezt/tests/evm_sequencer.ml | 39 +++++++++++++++++++++++++++ 3 files changed, 87 insertions(+) diff --git a/etherlink/tezt/lib/test_helpers.ml b/etherlink/tezt/lib/test_helpers.ml index dddffbaeb3b6..d2bbcc6c075a 100644 --- a/etherlink/tezt/lib/test_helpers.ml +++ b/etherlink/tezt/lib/test_helpers.ml @@ -113,6 +113,45 @@ let check_chain_id ~expected_chain_id ~chain_id = string ~error_msg:"Expected %R as chain_id but got %L") +let check_header ~previous_header ~current_header ~chain_id ~current_timestamp = + Check.( + ((JSON.(current_header |-> "level" |> as_int) + - JSON.(previous_header |-> "level" |> as_int)) + = 1) + int) + ~error_msg: + "The difference in level between consecutive blocks should be %R but got \ + %L" ; + + Check.( + (JSON.(previous_header |-> "hash") = JSON.(current_header |-> "predecessor")) + json) + ~error_msg:"Expected predecessor hash of current header to be %L but got %R" ; + + (match chain_id with + | Some chain_id -> + Check.( + (JSON.(previous_header |-> "chain_id") + = JSON.(current_header |-> "chain_id")) + json) + ~error_msg: + "Expected blocks to be in the same chain, but the current block is \ + in chain %R, while the previous block is in chain %L" ; + + check_chain_id + ~expected_chain_id:chain_id + ~chain_id:JSON.(current_header |-> "chain_id" |> as_string) + | _ -> ()) ; + + match current_timestamp with + | Some current_timestamp -> + Check.( + (JSON.(current_header |-> "timestamp" |> as_string) = current_timestamp) + string) + ~error_msg: + "Expected the timestamp of the current_block to be %R, but got %L" + | _ -> () + let next_evm_level ~evm_node ~sc_rollup_node ~client = match Evm_node.mode evm_node with | Proxy -> diff --git a/etherlink/tezt/lib/test_helpers.mli b/etherlink/tezt/lib/test_helpers.mli index 12ec2a31b44e..c80dc98a57c4 100644 --- a/etherlink/tezt/lib/test_helpers.mli +++ b/etherlink/tezt/lib/test_helpers.mli @@ -78,6 +78,15 @@ val produce_block : Evm_node.t -> (int, Rpc.error) result Lwt.t +(** [check_header ~previous_header ~current_header] checks that two + consecutive headers are consistent. *) +val check_header : + previous_header:JSON.t -> + current_header:JSON.t -> + chain_id:int option -> + current_timestamp:string option -> + unit + (** [next_evm_level ~evm_node ~sc_rollup_node ~client] moves [evm_node] to the next L2 level. *) val next_evm_level : diff --git a/etherlink/tezt/tests/evm_sequencer.ml b/etherlink/tezt/tests/evm_sequencer.ml index 316e52a25e21..cd9278eb9639 100644 --- a/etherlink/tezt/tests/evm_sequencer.ml +++ b/etherlink/tezt/tests/evm_sequencer.ml @@ -725,6 +725,44 @@ let test_tezlink_chain_id = check_chain_id ~expected_chain_id ~chain_id ; unit +let test_tezlink_header = + register_tezlink_test ~title:"Test of the header rpc" ~tags:["rpc"; "header"] + @@ fun {sequencer; client; l2_chains; _} _protocol -> + let chain_id = + match l2_chains with + | [l2_chain] -> Some l2_chain.l2_chain_id + | _ -> Test.fail ~__LOC__ "Expected one l2 chain" + in + + let endpoint = + Client.( + Foreign_endpoint + {(Evm_node.rpc_endpoint_record sequencer) with path = "tezlink"}) + in + + let*@ n = Rpc.produce_block sequencer in + let* () = Evm_node.wait_for_blueprint_applied sequencer n in + let* block_1 = + Client.RPC.call ~hooks ~endpoint client @@ RPC.get_chain_block_header () + in + + let current_timestamp = + Tezos_base.Time.( + System.now () |> System.to_protocol |> Protocol.to_notation) + in + let*@ n = Rpc.produce_block ~timestamp:current_timestamp sequencer in + let* () = Evm_node.wait_for_blueprint_applied sequencer n in + let* block_2 = + Client.RPC.call ~hooks ~endpoint client @@ RPC.get_chain_block_header () + in + + return + @@ check_header + ~previous_header:block_1 + ~current_header:block_2 + ~chain_id + ~current_timestamp:(Some current_timestamp) + let test_make_l2_kernel_installer_config chain_family = Protocol.register_test ~__FILE__ @@ -13533,6 +13571,7 @@ let () = test_tezlink_counter [Alpha] ; test_tezlink_protocols [Alpha] ; test_tezlink_version [Alpha] ; + test_tezlink_header [Alpha] ; test_tezlink_constants [Alpha] ; test_tezlink_produceBlock [Alpha] ; test_tezlink_chain_id [Alpha] ; -- GitLab From eeb88f7be9e897beb3374fba94addf8a285e582f Mon Sep 17 00:00:00 2001 From: Luciano Freitas Date: Mon, 12 May 2025 08:55:23 +0200 Subject: [PATCH 6/6] Tezt: update regressions --- .../evm_sequencer.ml/Alpha- Test the -describe endpoint.out | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/etherlink/tezt/tests/expected/evm_sequencer.ml/Alpha- Test the -describe endpoint.out b/etherlink/tezt/tests/expected/evm_sequencer.ml/Alpha- Test the -describe endpoint.out index 70e289d38359..9414bba2e45d 100644 --- a/etherlink/tezt/tests/expected/evm_sequencer.ml/Alpha- Test the -describe endpoint.out +++ b/etherlink/tezt/tests/expected/evm_sequencer.ml/Alpha- Test the -describe endpoint.out @@ -39,6 +39,8 @@ Available services: Access the counter of a contract, if any. - GET /tezlink/chains//blocks//context/contracts//manager_key Access the manager of an implicit contract. + - GET /tezlink/chains//blocks//header + The whole block header. - GET /tezlink/chains//blocks//helpers/current_level Returns the level of the interrogated block, or the one of a block located `offset` blocks after it in the chain. For @@ -102,6 +104,8 @@ Available services: Access the counter of a contract, if any. - GET /chains//blocks//context/contracts//manager_key Access the manager of an implicit contract. + - GET /chains//blocks//header + The whole block header. - GET /chains//blocks//helpers/current_level Returns the level of the interrogated block, or the one of a block located `offset` blocks after it in the chain. For instance, the -- GitLab