diff --git a/etherlink/bin_node/lib_dev/services_backend_sig.ml b/etherlink/bin_node/lib_dev/services_backend_sig.ml index da09b3dfd3361dfc4ad7c5e2a7095ab9d7c31d72..ca8203d67c3225f5d6864f61b8ca0a15f2d19412 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/tezlink_backend_sig.ml b/etherlink/bin_node/lib_dev/tezlink/tezlink_backend_sig.ml index b65dbd8bdc607c48a4351a173a67af65ea8cccf0..abe9dcbe89ab9f089f768f3c609c4444d89c00a6 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/tezlink_imports.ml b/etherlink/bin_node/lib_dev/tezlink/tezlink_imports.ml index d2795cee64047185c793ca56572302bca09b28a4..2b26d16e7e092e9195470059ae51de7ff84f8879 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 07a67b027e6a1977e14f13d69b728352e457b6c7..6fe5a634587253df6c373e2c76dffd2697276660 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 @@ -109,9 +196,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 +332,16 @@ 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 @@ -264,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 @@ -291,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 @@ -319,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 ab1f1fcfd83f47fb7294910268cc1e3648bd4ada..d7c14a8c702a62a819408cfd23c766e251ac4975 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 diff --git a/etherlink/tezt/lib/test_helpers.ml b/etherlink/tezt/lib/test_helpers.ml index dddffbaeb3b6826b3d67909da02d8f23e4066f79..d2bbcc6c075ae5f68ef5c789bd7e456f9cc1b566 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 12ec2a31b44ebab53714a82d7f4c66da7841cade..c80dc98a57c40287a527b19263be7b046e9274bf 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 316e52a25e218cedae89fd36f8532e94415defb0..cd9278eb9639a2dea13c786b66fb9bd7e4c193a9 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] ; 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 70e289d383591d217c3429ef4709113fdf10313e..9414bba2e45de2668c99499da02c1e958ad4bf2d 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