From aaa6bc893fde9bc6f6dd4ad1490720fafa3c3fce Mon Sep 17 00:00:00 2001 From: Thomas Plisson Date: Fri, 28 Nov 2025 15:40:23 +0100 Subject: [PATCH 1/7] L2 Node: Evm state assemble_block function --- .../bin_node/lib_dev/durable_storage_path.ml | 6 + .../bin_node/lib_dev/durable_storage_path.mli | 7 +- etherlink/bin_node/lib_dev/evm_state.ml | 104 +++++++++++++----- etherlink/bin_node/lib_dev/evm_state.mli | 18 +++ 4 files changed, 109 insertions(+), 26 deletions(-) diff --git a/etherlink/bin_node/lib_dev/durable_storage_path.ml b/etherlink/bin_node/lib_dev/durable_storage_path.ml index 2a5db3836f82..dd58395a28da 100644 --- a/etherlink/bin_node/lib_dev/durable_storage_path.ml +++ b/etherlink/bin_node/lib_dev/durable_storage_path.ml @@ -40,6 +40,12 @@ module Single_tx = struct let input_tx = single_tx_path ^ "/input_tx" end +module Assemble_block = struct + let path = World_state.make "/assemble_block" + + let input = path ^ "/input" +end + let etherlink_root = World_state.make "" let root_of_chain_family (type f) (chain_family : f L2_types.chain_family) = diff --git a/etherlink/bin_node/lib_dev/durable_storage_path.mli b/etherlink/bin_node/lib_dev/durable_storage_path.mli index a348166672a4..aea0196dff33 100644 --- a/etherlink/bin_node/lib_dev/durable_storage_path.mli +++ b/etherlink/bin_node/lib_dev/durable_storage_path.mli @@ -50,11 +50,16 @@ val sequencer_key : path val maximum_gas_per_transaction : path -(** Kernel communication canal for individual transaction execution *) +(** Kernel communication canal for individual transaction execution (instant confirmations) *) module Single_tx : sig val input_tx : path end +(** Kernel communication canal for block assembling (instant confirmations) *) +module Assemble_block : sig + val input : path +end + (** Paths related to accounts. *) module Accounts : sig (** Path to the account's info. Should be used in place of `balance`, `nonce` and `code` *) diff --git a/etherlink/bin_node/lib_dev/evm_state.ml b/etherlink/bin_node/lib_dev/evm_state.ml index 6eb745c06805..1bd66c5bbe08 100644 --- a/etherlink/bin_node/lib_dev/evm_state.ml +++ b/etherlink/bin_node/lib_dev/evm_state.ml @@ -348,32 +348,10 @@ type apply_result = } | Apply_failure -let apply_unsigned_chunks ~pool ?wasm_pvm_fallback ?log_file ?profile ~data_dir - ~chain_family ~config ~native_execution_policy evm_state - (chunks : Sequencer_blueprint.unsigned_chunked_blueprint) = +let retrieve_block_result ~data_dir ~chain_family ~before_height ?log_file + ?profile evm_state = let open Lwt_result_syntax in let root = Durable_storage_path.root_of_chain_family chain_family in - let*! (Qty before_height) = current_block_height ~root evm_state in - let* evm_state = - store_blueprint_chunks - ~blueprint_number:(Z.succ before_height) - evm_state - chunks - in - let* evm_state = - execute - ~pool - ~native_execution:(native_execution_policy = Configuration.Always) - ?wasm_pvm_fallback - ?profile - ~data_dir - ~wasm_entrypoint:Tezos_scoru_wasm.Constants.wasm_entrypoint - ~config - ?log_file - evm_state - `Skip_stage_one - in - let root = Durable_storage_path.root_of_chain_family chain_family in let* storage_version = storage_version evm_state in let* block = if not (Storage_version.legacy_storage_compatible ~storage_version) then @@ -390,7 +368,6 @@ let apply_unsigned_chunks ~pool ?wasm_pvm_fallback ?log_file ?profile ~data_dir in return (Option.map (L2_types.block_from_bytes ~chain_family) bytes) in - let export_gas_used (Qty gas) = match (profile, log_file) with | Some Configuration.Minimal, Some log_file -> @@ -416,6 +393,83 @@ let apply_unsigned_chunks ~pool ?wasm_pvm_fallback ?log_file ?profile ~data_dir else return Apply_failure | _ -> return Apply_failure +let michelson_disabled (type f) ~(chain_family : f L2_types.chain_family) () = + let open Lwt_result_syntax in + match chain_family with + | EVM -> return_unit + | Michelson -> + failwith "Assemble block is not supported for Michelson chain family" + +let assemble_block ~pool ~data_dir ~chain_family ~config ~timestamp ~number + ?profile ?log_file evm_state = + let open Lwt_result_syntax in + let* () = michelson_disabled ~chain_family () in + let rlp = + Rlp.List + [ + Value (Ethereum_types.timestamp_to_bytes timestamp); + Value (Ethereum_types.encode_u256_le number); + ] + in + let*! evm_state = + modify + ~key:Durable_storage_path.Assemble_block.input + ~value:(Rlp.encode rlp |> Bytes.to_string) + evm_state + in + let* evm_state = + execute + ~pool + ~native_execution:false + ~data_dir + ~config + ~wasm_entrypoint:"assemble_block" + evm_state + (`Inbox []) + in + let (Qty height) = number in + let before_height = Z.pred height in + retrieve_block_result + ~data_dir + ~chain_family + ~before_height + ?log_file + ?profile + evm_state + +let apply_unsigned_chunks ~pool ?wasm_pvm_fallback ?log_file ?profile ~data_dir + ~chain_family ~config ~native_execution_policy evm_state + (chunks : Sequencer_blueprint.unsigned_chunked_blueprint) = + let open Lwt_result_syntax in + let root = Durable_storage_path.root_of_chain_family chain_family in + let*! (Qty before_height) = current_block_height ~root evm_state in + let* evm_state = + store_blueprint_chunks + ~blueprint_number:(Z.succ before_height) + evm_state + chunks + in + let* evm_state = + execute + ~pool + ~native_execution:(native_execution_policy = Configuration.Always) + ?wasm_pvm_fallback + ?profile + ~data_dir + ~wasm_entrypoint:Tezos_scoru_wasm.Constants.wasm_entrypoint + ~config + ?log_file + evm_state + `Skip_stage_one + in + retrieve_block_result + ~data_dir + ~chain_family + ~before_height + ?log_file + ?profile + evm_state + let delete ~kind evm_state path = let open Lwt_syntax in let key = Tezos_scoru_wasm.Durable.key_of_string_exn path in diff --git a/etherlink/bin_node/lib_dev/evm_state.mli b/etherlink/bin_node/lib_dev/evm_state.mli index 7563469c3581..de8178f65bc6 100644 --- a/etherlink/bin_node/lib_dev/evm_state.mli +++ b/etherlink/bin_node/lib_dev/evm_state.mli @@ -121,6 +121,24 @@ val apply_unsigned_chunks : Sequencer_blueprint.unsigned_chunked_blueprint -> apply_result tzresult Lwt.t +(** [assemble_block ~pool ~data_dir ~chain_family ~config ~timestamp ~number ?profile ?log_file t] + builds an L2 block at height [number] and [timestamp] from the transactions + previously accumulated in durable storage by the kernel instant-confirmation + execution. No blueprint application is (re)performed; the function only + assembles the block from already recorded effects and returns the result + of this operation. *) +val assemble_block : + pool:Lwt_domain.pool -> + data_dir:string -> + chain_family:'a L2_types.chain_family -> + config:Pvm_types.config -> + timestamp:Time.Protocol.t -> + number:Ethereum_types.quantity -> + ?profile:Configuration.profile_mode -> + ?log_file:string -> + t -> + apply_result tzresult Lwt.t + (** [flag_local_exec evm_state] adds a flag telling the kernel it is executed by an EVM node, not a rollup node. *) val flag_local_exec : t -> t Lwt.t -- GitLab From 7b4d64b874af2b06f0c4fe55f00c192e67184a21 Mon Sep 17 00:00:00 2001 From: Thomas Plisson Date: Mon, 1 Dec 2025 08:51:45 +0100 Subject: [PATCH 2/7] L2 Node: Next block info checks for sequencer upgrades --- etherlink/bin_node/lib_dev/evm_context.ml | 132 +++++++++++++--------- 1 file changed, 78 insertions(+), 54 deletions(-) diff --git a/etherlink/bin_node/lib_dev/evm_context.ml b/etherlink/bin_node/lib_dev/evm_context.ml index 042a527ddf22..9d4acdefb702 100644 --- a/etherlink/bin_node/lib_dev/evm_context.ml +++ b/etherlink/bin_node/lib_dev/evm_context.ml @@ -35,6 +35,7 @@ type future_block_state = { evm_state : Evm_state.t; timestamp : Time.Protocol.t; next_tx_index : int32; + applied_sequencer_upgrade : bool; } type session_state = { @@ -889,19 +890,81 @@ module State = struct {l1_level; start_l2_level; end_l2_level} ; return_unit - let next_block_info ctxt (timestamp : Time.Protocol.t) + let handle_sequencer_upgrade ctxt conn ~timestamp ~data_dir ~config = + let open Lwt_result_syntax in + match ctxt.session.future_block_info with + | Some {applied_sequencer_upgrade; _} -> + return (ctxt.session.evm_state, applied_sequencer_upgrade) + | None -> ( + match check_pending_sequencer_upgrade ctxt timestamp with + | Some {sequencer_upgrade; _} -> + let* evm_state = + (* The sequencer upgrade is based on the l1 timestamp + found in the inbox. To activate it it's enough to + trigger a kernel run with a correct timestamp (>= + activation_timstamp) and an empty inbox. The sequencer + upgrade is checked in the kernel just after finishing + to parse the inbox, there is no need for a blueprint to + be applied. + + Also as the application of the sequencer upgrade is + done after parsing the blueprint and it clears the + storage. If we try to do a run with a blueprint : + + - For the previous sequencer, the blueprint will be + validated but removed when the sequencer upgrade is + applied. + + - for the new sequencer, the blueprint will not be + validated. + + Because of all that it's necessary to do a run with no + specified blueprint. *) + Evm_state.execute + ~pool:ctxt.execution_pool + ~execution_timestamp:timestamp + ~native_execution: + (ctxt.configuration.kernel_execution.native_execution_policy + = Configuration.Always) + ~data_dir + ~config + ctxt.session.evm_state + (`Inbox []) + in + let* applied_sequencer_upgrade = + check_sequencer_upgrade ctxt evm_state sequencer_upgrade + in + let* () = + when_ applied_sequencer_upgrade @@ fun () -> + Evm_store.Sequencer_upgrades.record_apply + conn + ctxt.session.next_blueprint_number + in + return (evm_state, applied_sequencer_upgrade) + | None -> return (ctxt.session.evm_state, false)) + + let next_block_info ctxt conn (timestamp : Time.Protocol.t) (number : Ethereum_types.quantity) = - if Ethereum_types.Qty.(number = ctxt.session.next_blueprint_number) then + let open Lwt_result_syntax in + if Ethereum_types.Qty.(number = ctxt.session.next_blueprint_number) then ( + let*! data_dir, config = execution_config in + let* evm_state, applied_sequencer_upgrade = + handle_sequencer_upgrade ctxt conn ~timestamp ~data_dir ~config + in ctxt.session.future_block_state <- - Some {evm_state = ctxt.session.evm_state; timestamp; next_tx_index = 0l} - else ctxt.session.future_block_state <- None + Some + {evm_state; timestamp; next_tx_index = 0l; applied_sequencer_upgrade} ; + return_unit) + else ( + ctxt.session.future_block_state <- None ; + return_unit) let execute_single_transaction ctxt (tx : Broadcast.transaction) hash = let open Lwt_result_syntax in Octez_telemetry.Trace.add_attrs (fun () -> Telemetry.Attributes.[Transaction.hash hash]) ; match ctxt.session.future_block_state with - | Some {evm_state; timestamp; next_tx_index} -> + | Some {evm_state; timestamp; next_tx_index; applied_sequencer_upgrade} -> let* tx = match tx with | Broadcast.Common (Evm tx) -> @@ -957,7 +1020,13 @@ module State = struct Ethereum_types.(Block_hash (Hex (String.make 64 '0')))) in ctxt.session.future_block_state <- - Some {evm_state; timestamp; next_tx_index = Int32.succ next_tx_index} ; + Some + { + evm_state; + timestamp; + next_tx_index = Int32.succ next_tx_index; + applied_sequencer_upgrade; + } ; return_some receipt | None -> return_none @@ -989,52 +1058,7 @@ module State = struct ~l2_chains:ctxt.configuration.experimental_features.l2_chains in let* evm_state, applied_sequencer_upgrade = - match check_pending_sequencer_upgrade ctxt timestamp with - | Some {sequencer_upgrade; _} -> - let* evm_state = - (* The sequencer upgrade is based on the l1 timestamp - found in the inbox. To activate it it's enough to - trigger a kernel run with a correct timestamp (>= - activation_timstamp) and an empty inbox. The sequencer - upgrade is checked in the kernel just after finishing - to parse the inbox, there is no need for a blueprint to - be applied. - - Also as the application of the sequencer upgrade is - done after parsing the blueprint and it clears the - storage. If we try to do a run with a blueprint : - - - For the previous sequencer, the blueprint will be - validated but removed when the sequencer upgrade is - applied. - - - for the new sequencer, the blueprint will not be - validated. - - Because of all that it's necessary to do a run with no - specified blueprint. *) - Evm_state.execute - ~pool:ctxt.execution_pool - ~execution_timestamp:timestamp - ~native_execution: - (ctxt.configuration.kernel_execution.native_execution_policy - = Configuration.Always) - ~data_dir - ~config - ctxt.session.evm_state - (`Inbox []) - in - let* applied_sequencer_upgrade = - check_sequencer_upgrade ctxt evm_state sequencer_upgrade - in - return (evm_state, applied_sequencer_upgrade) - | None -> return (ctxt.session.evm_state, false) - in - let* () = - when_ applied_sequencer_upgrade @@ fun () -> - Evm_store.Sequencer_upgrades.record_apply - conn - ctxt.session.next_blueprint_number + handle_sequencer_upgrade ctxt conn ~timestamp ~data_dir ~config in let* try_apply = @@ -2314,8 +2338,8 @@ module Handlers = struct ~end_l2_level | Next_block_info {timestamp; number} -> let ctxt = Worker.state self in - State.next_block_info ctxt timestamp number ; - return_unit + State.Transaction.run ctxt @@ fun ctxt conn -> + State.next_block_info ctxt conn timestamp number | Execute_single_transaction {tx; hash} -> let ctxt = Worker.state self in State.execute_single_transaction ctxt tx hash -- GitLab From 79b7e3ad12180ee17ddbedf7932fa170c540dfc8 Mon Sep 17 00:00:00 2001 From: Thomas Plisson Date: Mon, 1 Dec 2025 16:56:40 +0100 Subject: [PATCH 3/7] L2 Node: apply_blueprint_store_unsafe calls assemble if future info exists --- etherlink/bin_node/lib_dev/evm_context.ml | 32 +++++++++++++++-------- 1 file changed, 21 insertions(+), 11 deletions(-) diff --git a/etherlink/bin_node/lib_dev/evm_context.ml b/etherlink/bin_node/lib_dev/evm_context.ml index 9d4acdefb702..9633280ea506 100644 --- a/etherlink/bin_node/lib_dev/evm_context.ml +++ b/etherlink/bin_node/lib_dev/evm_context.ml @@ -1065,18 +1065,29 @@ module State = struct Misc.with_timing (fun time -> Lwt.return (time_processed := time)) (fun () -> - Evm_state.apply_unsigned_chunks - ~pool:ctxt.execution_pool - ~native_execution_policy: - ctxt.configuration.kernel_execution.native_execution_policy - ~wasm_pvm_fallback:(not @@ List.is_empty delayed_transactions) - ~data_dir - ~chain_family - ~config - evm_state - chunks) + match ctxt.session.future_block_state with + | Some _ -> + Evm_state.assemble_block + ~pool:ctxt.execution_pool + ~chain_family + ~data_dir + ~config + evm_state + | None -> + Evm_state.apply_unsigned_chunks + ~pool:ctxt.execution_pool + ~native_execution_policy: + ctxt.configuration.kernel_execution.native_execution_policy + ~wasm_pvm_fallback:(not @@ List.is_empty delayed_transactions) + ~data_dir + ~chain_family + ~config + evm_state + chunks) in + ctxt.session.future_block_state <- None ; + match try_apply with | Apply_success {evm_state; block} -> let block_number = L2_types.block_number block in @@ -1394,7 +1405,6 @@ module State = struct {number = ctxt.session.next_blueprint_number; timestamp; payload}; } in - ctxt.session.future_block_state <- None ; return (current_block, execution_gas) in current_block -- GitLab From 1501e36bde5870703dfd24539917b5e42a9fde1d Mon Sep 17 00:00:00 2001 From: Thomas Plisson Date: Mon, 1 Dec 2025 17:01:10 +0100 Subject: [PATCH 4/7] L2 Node: SBL requests use EVM Context session state --- etherlink/bin_node/lib_dev/evm_context.ml | 37 +++++++++++------------ 1 file changed, 18 insertions(+), 19 deletions(-) diff --git a/etherlink/bin_node/lib_dev/evm_context.ml b/etherlink/bin_node/lib_dev/evm_context.ml index 9633280ea506..9b7922d64016 100644 --- a/etherlink/bin_node/lib_dev/evm_context.ml +++ b/etherlink/bin_node/lib_dev/evm_context.ml @@ -31,8 +31,7 @@ type parameters = { tx_container : Services_backend_sig.ex_tx_container; } -type future_block_state = { - evm_state : Evm_state.t; +type future_block_info = { timestamp : Time.Protocol.t; next_tx_index : int32; applied_sequencer_upgrade : bool; @@ -54,7 +53,7 @@ type session_state = { (** A function to be run by the worker at the end of the current {!Transaction.run} call. This can be used to delay some computation only once the commits in Irmin and SQlite have been confirmed. *) - mutable future_block_state : future_block_state option; + mutable future_block_info : future_block_info option; (** This value starts at None to prevent inclusion confirmation handling on an incomplete stream after startup. In the case where we find a divergence between the next_blueprint_number @@ -214,7 +213,7 @@ module State = struct evm_state; last_split_block; post_transaction_run_hook; - future_block_state; + future_block_info; } = { context; @@ -227,7 +226,7 @@ module State = struct evm_state; last_split_block; post_transaction_run_hook; - future_block_state; + future_block_info; } (* [apply session session'] modifies [session] in-place to match the content of [session']. *) @@ -243,7 +242,7 @@ module State = struct evm_state; last_split_block; post_transaction_run_hook; - future_block_state; + future_block_info; } = session.context <- context ; session.storage_version <- storage_version ; @@ -255,7 +254,7 @@ module State = struct session.evm_state <- evm_state ; session.last_split_block <- last_split_block ; session.post_transaction_run_hook <- post_transaction_run_hook ; - session.future_block_state <- future_block_state + session.future_block_info <- future_block_info let session_to_head_info session = { @@ -951,20 +950,20 @@ module State = struct let* evm_state, applied_sequencer_upgrade = handle_sequencer_upgrade ctxt conn ~timestamp ~data_dir ~config in - ctxt.session.future_block_state <- - Some - {evm_state; timestamp; next_tx_index = 0l; applied_sequencer_upgrade} ; + ctxt.session.evm_state <- evm_state ; + ctxt.session.future_block_info <- + Some {timestamp; next_tx_index = 0l; applied_sequencer_upgrade} ; return_unit) else ( - ctxt.session.future_block_state <- None ; + ctxt.session.future_block_info <- None ; return_unit) let execute_single_transaction ctxt (tx : Broadcast.transaction) hash = let open Lwt_result_syntax in Octez_telemetry.Trace.add_attrs (fun () -> Telemetry.Attributes.[Transaction.hash hash]) ; - match ctxt.session.future_block_state with - | Some {evm_state; timestamp; next_tx_index; applied_sequencer_upgrade} -> + match ctxt.session.future_block_info with + | Some {timestamp; next_tx_index; applied_sequencer_upgrade} -> let* tx = match tx with | Broadcast.Common (Evm tx) -> @@ -997,7 +996,7 @@ module State = struct Evm_state.modify ~key:Durable_storage_path.Single_tx.input_tx ~value:(Rlp.encode rlp |> Bytes.to_string) - evm_state + ctxt.session.evm_state in let*! data_dir, config = execution_config in let* evm_state = @@ -1019,10 +1018,10 @@ module State = struct (Transaction_receipt.decode_last_from_list Ethereum_types.(Block_hash (Hex (String.make 64 '0')))) in - ctxt.session.future_block_state <- + ctxt.session.evm_state <- evm_state ; + ctxt.session.future_block_info <- Some { - evm_state; timestamp; next_tx_index = Int32.succ next_tx_index; applied_sequencer_upgrade; @@ -1065,7 +1064,7 @@ module State = struct Misc.with_timing (fun time -> Lwt.return (time_processed := time)) (fun () -> - match ctxt.session.future_block_state with + match ctxt.session.future_block_info with | Some _ -> Evm_state.assemble_block ~pool:ctxt.execution_pool @@ -1086,7 +1085,7 @@ module State = struct chunks) in - ctxt.session.future_block_state <- None ; + ctxt.session.future_block_info <- None ; match try_apply with | Apply_success {evm_state; block} -> @@ -1968,7 +1967,7 @@ module State = struct evm_state; last_split_block; post_transaction_run_hook = None; - future_block_state = None; + future_block_info = None; }; store; signer; -- GitLab From 057286d863973e329200a001335c190b35b7d7eb Mon Sep 17 00:00:00 2001 From: Thomas Plisson Date: Tue, 2 Dec 2025 09:59:24 +0100 Subject: [PATCH 5/7] L2 Node: Broadcast resulting block hash before blueprint message --- etherlink/bin_node/lib_dev/blueprints_follower.ml | 7 +++++++ etherlink/bin_node/lib_dev/broadcast.ml | 14 +++++++++++++- etherlink/bin_node/lib_dev/broadcast.mli | 5 +++++ etherlink/bin_node/lib_dev/evm_context.ml | 1 + etherlink/bin_node/lib_dev/evm_services.ml | 2 +- etherlink/bin_node/lib_dev/rpc.ml | 3 ++- 6 files changed, 29 insertions(+), 3 deletions(-) diff --git a/etherlink/bin_node/lib_dev/blueprints_follower.ml b/etherlink/bin_node/lib_dev/blueprints_follower.ml index 1637692e2401..44bfa59dc004 100644 --- a/etherlink/bin_node/lib_dev/blueprints_follower.ml +++ b/etherlink/bin_node/lib_dev/blueprints_follower.ml @@ -383,6 +383,13 @@ and stream_loop ~multichain ~sbl_callbacks_activated ~instant_confirmations (Qty next_blueprint_number) params monitor + | Ok (Some (Block_hash _hash)) -> + (stream_loop [@tailcall]) + ~multichain + ~sbl_callbacks_activated + (Qty next_blueprint_number) + params + monitor | Ok None | Error [Timeout] -> Evm_services.close_monitor monitor ; (catchup [@tailcall]) diff --git a/etherlink/bin_node/lib_dev/broadcast.ml b/etherlink/bin_node/lib_dev/broadcast.ml index 0bca8f0f42f4..d3415bf8e1bd 100644 --- a/etherlink/bin_node/lib_dev/broadcast.ml +++ b/etherlink/bin_node/lib_dev/broadcast.ml @@ -71,6 +71,7 @@ type message = } | Included_transaction of {tx : transaction; hash : Ethereum_types.hash} | Dropped_transaction of {hash : Ethereum_types.hash; reason : string} + | Block_hash of Ethereum_types.block_hash let message_encoding = let open Data_encoding in @@ -113,7 +114,7 @@ let message_encoding = ~title:"Next_block_info" (Tag 3) (obj3 - (req "kind" (constant "block_timestamp")) + (req "kind" (constant "next_block_info")) (req "timestamp" Time.Protocol.encoding) (req "number" Ethereum_types.quantity_encoding)) (function @@ -141,6 +142,14 @@ let message_encoding = | Dropped_transaction {hash; reason} -> Some ((), hash, reason) | _ -> None) (fun ((), hash, reason) -> Dropped_transaction {hash; reason}); + case + ~title:"Block_hash" + (Tag 6) + (obj2 + (req "kind" (constant "block_hash")) + (req "hash" Ethereum_types.block_hash_encoding)) + (function Block_hash hash -> Some ((), hash) | _ -> None) + (fun ((), hash) -> Block_hash hash); ] (** Stream on which all messages are broadcasted *) @@ -166,6 +175,9 @@ let notify_inclusion tx hash = let notify_dropped ~hash ~reason = Lwt_watcher.notify message_watcher (Dropped_transaction {hash; reason}) +let notify_block_hash hash = + Lwt_watcher.notify message_watcher (Block_hash hash) + type transaction_result = { hash : Ethereum_types.hash; result : (Transaction_receipt.t, string) result; diff --git a/etherlink/bin_node/lib_dev/broadcast.mli b/etherlink/bin_node/lib_dev/broadcast.mli index 15494f1407dc..403b514e69c4 100644 --- a/etherlink/bin_node/lib_dev/broadcast.mli +++ b/etherlink/bin_node/lib_dev/broadcast.mli @@ -34,6 +34,7 @@ type message = } | Included_transaction of {tx : transaction; hash : Ethereum_types.hash} | Dropped_transaction of {hash : Ethereum_types.hash; reason : string} + | Block_hash of Ethereum_types.block_hash val message_encoding : message Data_encoding.t @@ -66,6 +67,10 @@ val notify_inclusion : transaction -> Ethereum_types.hash -> unit to the broadcast stream *) val notify_dropped : hash:Ethereum_types.hash -> reason:string -> unit +(** [notify_inclusion tx] advertizes [hash] as the block hash produced by the latest blueprint + application of the sequencer *) +val notify_block_hash : Ethereum_types.block_hash -> unit + (** Type representing the result of a transaction pre-confirmed execution. *) type transaction_result = { hash : Ethereum_types.hash; diff --git a/etherlink/bin_node/lib_dev/evm_context.ml b/etherlink/bin_node/lib_dev/evm_context.ml index 9b7922d64016..24db7ab23b8f 100644 --- a/etherlink/bin_node/lib_dev/evm_context.ml +++ b/etherlink/bin_node/lib_dev/evm_context.ml @@ -1276,6 +1276,7 @@ module State = struct Some (fun () -> let open Lwt_syntax in + Broadcast.notify_block_hash block_hash ; Broadcast.notify_blueprint blueprint_with_events ; (* We ignore failure. A failure means the prevalidator is not yet started, meaning the call is useless for now. *) diff --git a/etherlink/bin_node/lib_dev/evm_services.ml b/etherlink/bin_node/lib_dev/evm_services.ml index 5ac7beea4d8c..f757630ab6b0 100644 --- a/etherlink/bin_node/lib_dev/evm_services.ml +++ b/etherlink/bin_node/lib_dev/evm_services.ml @@ -230,7 +230,7 @@ let register_broadcast_service find_blueprint get_next_blueprint_number dir = ( Lwt_stream.filter (function | Broadcast.Next_block_info _ | Included_transaction _ - | Dropped_transaction _ -> + | Dropped_transaction _ | Block_hash _ -> false | Blueprint _ | Finalized_levels _ -> true) stream, diff --git a/etherlink/bin_node/lib_dev/rpc.ml b/etherlink/bin_node/lib_dev/rpc.ml index eec104269fbc..738f71f6708e 100644 --- a/etherlink/bin_node/lib_dev/rpc.ml +++ b/etherlink/bin_node/lib_dev/rpc.ml @@ -167,7 +167,7 @@ let main ~evm_node_endpoint ~evm_node_private_endpoint ~next_blueprint_number ~instant_confirmations: config.experimental_features.preconfirmation_stream_enabled - ~on_new_blueprint:(fun (Qty number) blueprint -> + ~on_new_blueprint:(fun (Qty number) blueprint block_hash -> let (Qty level) = blueprint.blueprint.number in if Z.Compare.(number = level) then ( let* () = @@ -175,6 +175,7 @@ let main ~evm_node_endpoint ~evm_node_private_endpoint Evm_ro_context.preload_kernel_from_level ctxt (Qty number) in let* () = Prevalidator.refresh_state () in + Option.iter Broadcast.notify_block_hash block_hash ; Broadcast.notify_blueprint blueprint ; Metrics.set_level ~level:number ; let* () = set_metrics_confirmed_levels ctxt in -- GitLab From e2b67bca907bc02a9e6e78b8de15ae0cf274daec Mon Sep 17 00:00:00 2001 From: Thomas Plisson Date: Tue, 2 Dec 2025 14:02:52 +0100 Subject: [PATCH 6/7] L2 Node: Share block hash through the blueprint follower --- etherlink/bin_courier/main.ml | 2 +- .../bin_floodgate/lib_floodgate/floodgate.ml | 2 +- etherlink/bin_node/lib_dev/blueprints_follower.ml | 15 +++++++++++---- .../bin_node/lib_dev/blueprints_follower.mli | 1 + etherlink/bin_node/lib_dev/observer.ml | 2 +- etherlink/bin_node/lib_dev/sequencer.ml | 2 +- 6 files changed, 16 insertions(+), 8 deletions(-) diff --git a/etherlink/bin_courier/main.ml b/etherlink/bin_courier/main.ml index 339556abb176..2209e9994c5f 100644 --- a/etherlink/bin_courier/main.ml +++ b/etherlink/bin_courier/main.ml @@ -160,7 +160,7 @@ let start_blueprint_follower ~relay_endpoint = ~rpc_timeout:10. ~next_blueprint_number ~instant_confirmations:false - ~on_new_blueprint:(fun number blueprint -> + ~on_new_blueprint:(fun number blueprint _ -> let*! () = Floodgate_events.received_blueprint number in let* () = match Blueprint_decoder.transaction_hashes blueprint with diff --git a/etherlink/bin_floodgate/lib_floodgate/floodgate.ml b/etherlink/bin_floodgate/lib_floodgate/floodgate.ml index 03ab31f2518f..a243400257df 100644 --- a/etherlink/bin_floodgate/lib_floodgate/floodgate.ml +++ b/etherlink/bin_floodgate/lib_floodgate/floodgate.ml @@ -421,7 +421,7 @@ let start_blueprint_follower ~relay_endpoint ~rpc_endpoint = ~rpc_timeout:Network_info.timeout ~next_blueprint_number ~instant_confirmations:false - ~on_new_blueprint:(fun number blueprint -> + ~on_new_blueprint:(fun number blueprint _ -> let*! () = Floodgate_events.received_blueprint number in let* () = match Blueprint_decoder.transaction_hashes blueprint with diff --git a/etherlink/bin_node/lib_dev/blueprints_follower.ml b/etherlink/bin_node/lib_dev/blueprints_follower.ml index 44bfa59dc004..82fbf0dc0498 100644 --- a/etherlink/bin_node/lib_dev/blueprints_follower.ml +++ b/etherlink/bin_node/lib_dev/blueprints_follower.ml @@ -12,6 +12,7 @@ type sbl_callbacks_activated = {sbl_callbacks_activated : bool} type new_blueprint_handler = quantity -> Blueprint_types.with_events -> + Ethereum_types.block_hash option -> [`Restart_from of quantity | `Continue of sbl_callbacks_activated] tzresult Lwt.t @@ -245,7 +246,9 @@ let rec catchup ~multichain ~next_blueprint_number ~first_connection let* fold_result = Blueprints_sequence.fold (fun next_blueprint_number blueprint -> - let* result = params.on_new_blueprint next_blueprint_number blueprint in + let* result = + params.on_new_blueprint next_blueprint_number blueprint None + in match result with | `Restart_from l -> return (`Cut l) | `Continue _ -> @@ -291,7 +294,7 @@ let rec catchup ~multichain ~next_blueprint_number ~first_connection params) and stream_loop ~multichain ~sbl_callbacks_activated ~instant_confirmations - (Qty next_blueprint_number) params monitor = + ?(block_hash = None) (Qty next_blueprint_number) params monitor = let open Lwt_result_syntax in Metrics.stop_bootstrapping () ; let*! candidate = @@ -315,7 +318,9 @@ and stream_loop ~multichain ~sbl_callbacks_activated ~instant_confirmations params monitor | Ok (Some (Blueprint blueprint)) -> ( - let* r = params.on_new_blueprint (Qty next_blueprint_number) blueprint in + let* r = + params.on_new_blueprint (Qty next_blueprint_number) blueprint block_hash + in match r with | `Continue is_sub_block_activated -> (stream_loop [@tailcall]) @@ -383,10 +388,12 @@ and stream_loop ~multichain ~sbl_callbacks_activated ~instant_confirmations (Qty next_blueprint_number) params monitor - | Ok (Some (Block_hash _hash)) -> + | Ok (Some (Block_hash hash)) -> (stream_loop [@tailcall]) ~multichain ~sbl_callbacks_activated + ~instant_confirmations + ~block_hash:(Some hash) (Qty next_blueprint_number) params monitor diff --git a/etherlink/bin_node/lib_dev/blueprints_follower.mli b/etherlink/bin_node/lib_dev/blueprints_follower.mli index 41f83c34379d..b30221385451 100644 --- a/etherlink/bin_node/lib_dev/blueprints_follower.mli +++ b/etherlink/bin_node/lib_dev/blueprints_follower.mli @@ -14,6 +14,7 @@ type sbl_callbacks_activated = {sbl_callbacks_activated : bool} type new_blueprint_handler = quantity -> Blueprint_types.with_events -> + Ethereum_types.block_hash option -> [`Restart_from of quantity | `Continue of sbl_callbacks_activated] tzresult Lwt.t diff --git a/etherlink/bin_node/lib_dev/observer.ml b/etherlink/bin_node/lib_dev/observer.ml index 1e8b902b57ed..4866d50c8949 100644 --- a/etherlink/bin_node/lib_dev/observer.ml +++ b/etherlink/bin_node/lib_dev/observer.ml @@ -25,7 +25,7 @@ let on_new_blueprint (type f) (tx_container : f Services_backend_sig.tx_container) evm_node_endpoint next_blueprint_number (({delayed_transactions; blueprint; _} : Blueprint_types.with_events) as - blueprint_with_events) = + blueprint_with_events) _block_hash = let open Lwt_result_syntax in let (module Tx_container) = Services_backend_sig.tx_container_module tx_container diff --git a/etherlink/bin_node/lib_dev/sequencer.ml b/etherlink/bin_node/lib_dev/sequencer.ml index 9644a81fa25f..907eacf4070b 100644 --- a/etherlink/bin_node/lib_dev/sequencer.ml +++ b/etherlink/bin_node/lib_dev/sequencer.ml @@ -101,7 +101,7 @@ let loop_sequencer (type f) multichain ~rpc_timeout ~next_blueprint_number:head.next_blueprint_number ~instant_confirmations - ~on_new_blueprint:(fun (Qty number) blueprint -> + ~on_new_blueprint:(fun (Qty number) blueprint _ -> let*! {next_blueprint_number = Qty expected_number; _} = Evm_context.head_info () in -- GitLab From aeb3d5a6900499a03360217678008b6b02e2c3d0 Mon Sep 17 00:00:00 2001 From: Thomas Plisson Date: Wed, 3 Dec 2025 09:56:45 +0100 Subject: [PATCH 7/7] L2 Node: Divergence logic --- etherlink/bin_node/lib_dev/events.ml | 23 ++ etherlink/bin_node/lib_dev/events.mli | 9 + etherlink/bin_node/lib_dev/evm_context.ml | 325 ++++++++++-------- etherlink/bin_node/lib_dev/evm_context.mli | 1 + .../bin_node/lib_dev/evm_context_types.ml | 15 +- etherlink/bin_node/lib_dev/observer.ml | 3 +- .../EVM node- list events regression.out | 22 +- 7 files changed, 255 insertions(+), 143 deletions(-) diff --git a/etherlink/bin_node/lib_dev/events.ml b/etherlink/bin_node/lib_dev/events.ml index 2c1bcb184902..5cbfb0f1f320 100644 --- a/etherlink/bin_node/lib_dev/events.ml +++ b/etherlink/bin_node/lib_dev/events.ml @@ -119,6 +119,25 @@ let ignored_preconfirmations = ~level:Warning () +let assemble_block_diverged = + declare_0 + ~section + ~name:"assemble_block_diverged" + ~msg: + "Assemble block diverged, node is going to re-execute the full blueprint" + ~level:Warning + () + +let seq_block_hash_missing = + declare_0 + ~section + ~name:"seq_block_hash_missing" + ~msg: + "Assemble block can not validate its output because sequencer block hash \ + is missing" + ~level:Warning + () + let catching_up_evm_event = Internal_event.Simple.declare_2 ~section @@ -580,6 +599,10 @@ let ignored_periodic_snapshot () = emit ignored_periodic_snapshot () let ignored_preconfirmations () = emit ignored_preconfirmations () +let assemble_block_diverged () = emit assemble_block_diverged () + +let seq_block_hash_missing () = emit seq_block_hash_missing () + let catching_up_evm_event ~from ~to_ = emit catching_up_evm_event (from, to_) let is_ready ~rpc_addr ~rpc_port ~websockets ~backend = diff --git a/etherlink/bin_node/lib_dev/events.mli b/etherlink/bin_node/lib_dev/events.mli index 446d48ef0967..e242bd0b03c7 100644 --- a/etherlink/bin_node/lib_dev/events.mli +++ b/etherlink/bin_node/lib_dev/events.mli @@ -67,6 +67,15 @@ val ignored_periodic_snapshot : unit -> unit Lwt.t be ignoring the all the incoming preconfirmation data. *) val ignored_preconfirmations : unit -> unit Lwt.t +(** [assemble_block_diverged ()] advertises that the assembled block has + diverged from the expected one and that the node will re-execute + the full blueprint to recover consistency. *) +val assemble_block_diverged : unit -> unit Lwt.t + +(** [seq_block_hash_missing ()] advertises that the assembled block cannot + be validated because the sequencer block hash is missing. *) +val seq_block_hash_missing : unit -> unit Lwt.t + (** [catching_up_evm_event ~from ~to_] advertises that the sequencer is catching up on event produced by the evm kernel in the rollup node from L1 level [from] to [to_]. *) diff --git a/etherlink/bin_node/lib_dev/evm_context.ml b/etherlink/bin_node/lib_dev/evm_context.ml index 24db7ab23b8f..ebd9d288eb78 100644 --- a/etherlink/bin_node/lib_dev/evm_context.ml +++ b/etherlink/bin_node/lib_dev/evm_context.ml @@ -1036,8 +1036,8 @@ module State = struct This function expects its connection to the store [conn] to be wrapped in a SQL transaction. *) - let apply_blueprint_store_unsafe ctxt conn timestamp chunks payload - delayed_transactions = + let rec apply_blueprint_store_unsafe ctxt conn timestamp chunks payload + delayed_transactions sequencer_block_hash = let open Lwt_result_syntax in Evm_store.assert_in_transaction conn ; @@ -1065,12 +1065,14 @@ module State = struct (fun time -> Lwt.return (time_processed := time)) (fun () -> match ctxt.session.future_block_info with - | Some _ -> + | Some {timestamp; _} -> Evm_state.assemble_block ~pool:ctxt.execution_pool ~chain_family ~data_dir ~config + ~timestamp + ~number:ctxt.session.next_blueprint_number evm_state | None -> Evm_state.apply_unsigned_chunks @@ -1085,148 +1087,185 @@ module State = struct chunks) in - ctxt.session.future_block_info <- None ; - match try_apply with - | Apply_success {evm_state; block} -> + | Apply_success {evm_state; block} -> ( let block_number = L2_types.block_number block in let block_hash = L2_types.block_hash block in - let* () = - if is_sequencer ctxt then return_unit - else - let* finalized_hash = - Evm_store.Pending_confirmations.find_with_level conn block_number - in - match finalized_hash with - | None -> return_unit - | Some expected_block_hash -> - if expected_block_hash = block_hash then - Evm_store.Pending_confirmations.delete_with_level + match (ctxt.session.future_block_info, sequencer_block_hash) with + (* Future info and sequencer hash are present: + Node could execute txns incrementally and it received the block hash + for verification *) + | Some _, Some sequencer_block_hash + when sequencer_block_hash <> block_hash -> + let*! () = Events.assemble_block_diverged () in + let*! evm_state = Pvm.State.get ctxt.session.context in + ctxt.session.evm_state <- evm_state ; + ctxt.session.future_block_info <- None ; + apply_blueprint_store_unsafe + ctxt + conn + timestamp + chunks + payload + delayed_transactions + None + (* Future info is present but no sequencer hash was received: + Should not happen but if it does we re-apply the full blueprint + as we have no way of checking the assemble validity *) + | Some _, None -> + let*! () = Events.seq_block_hash_missing () in + let*! evm_state = Pvm.State.get ctxt.session.context in + ctxt.session.evm_state <- evm_state ; + ctxt.session.future_block_info <- None ; + apply_blueprint_store_unsafe + ctxt + conn + timestamp + chunks + payload + delayed_transactions + None + (* Any other case is standard procedure *) + | _ -> + ctxt.session.future_block_info <- None ; + let* () = + if is_sequencer ctxt then return_unit + else + let* finalized_hash = + Evm_store.Pending_confirmations.find_with_level conn block_number - else - let Ethereum_types.(Qty number) = block_number in - - let*! () = - Evm_events_follower_events.diverged - (number, expected_block_hash, block_hash) - in - (* If the observer cannot reset to finalized level it must + in + match finalized_hash with + | None -> return_unit + | Some expected_block_hash -> + if expected_block_hash = block_hash then + Evm_store.Pending_confirmations.delete_with_level + conn + block_number + else + let Ethereum_types.(Qty number) = block_number in + + let*! () = + Evm_events_follower_events.diverged + (number, expected_block_hash, block_hash) + in + (* If the observer cannot reset to finalized level it must exit. *) - let exit_error = - Node_error.Diverged - { - level = number; - expected_block_hash; - found_block_hash = Some block_hash; - must_exit = true; - } - in - let* (_ : Evm_state.t) = - reset_to_finalized_level exit_error ctxt conn - in - (* If the observer managed to reset to finalized level it must + let exit_error = + Node_error.Diverged + { + level = number; + expected_block_hash; + found_block_hash = Some block_hash; + must_exit = true; + } + in + let* (_ : Evm_state.t) = + reset_to_finalized_level exit_error ctxt conn + in + (* If the observer managed to reset to finalized level it must not exit. *) - tzfail - (Node_error.Diverged - { - level = number; - expected_block_hash; - found_block_hash = Some block_hash; - must_exit = false; - }) - in - (* Set metrics for the block *) - let () = - match block with - | Eth block -> - let number_of_transactions = - match block.transactions with - | TxHash l -> List.length l - | TxFull l -> List.length l - in - Metrics.set_block - ~time_processed:!time_processed - ~transactions:number_of_transactions ; - Option.iter - (fun baseFeePerGas -> - baseFeePerGas |> Ethereum_types.Qty.to_z - |> Metrics.set_gas_price) - block.baseFeePerGas - | Tez _ -> - (* TODO: https://gitlab.com/tezos/tezos/-/issues/7866 *) - () - in - let* payload in - let* () = - Evm_store.Blueprints.store - conn - {number = block_number; timestamp; payload} - in + tzfail + (Node_error.Diverged + { + level = number; + expected_block_hash; + found_block_hash = Some block_hash; + must_exit = false; + }) + in + (* Set metrics for the block *) + let () = + match block with + | Eth block -> + let number_of_transactions = + match block.transactions with + | TxHash l -> List.length l + | TxFull l -> List.length l + in + Metrics.set_block + ~time_processed:!time_processed + ~transactions:number_of_transactions ; + Option.iter + (fun baseFeePerGas -> + baseFeePerGas |> Ethereum_types.Qty.to_z + |> Metrics.set_gas_price) + block.baseFeePerGas + | Tez _ -> + (* TODO: https://gitlab.com/tezos/tezos/-/issues/7866 *) + () + in + let* payload in + let* () = + Evm_store.Blueprints.store + conn + {number = block_number; timestamp; payload} + in - let* evm_state, receipts, execution_gas = - let* execution_gas, receipts = - match block with - | Eth block -> - let* da_fee_per_byte = - Etherlink_durable_storage.da_fee_per_byte - (read_from_state evm_state) - in - let* (Qty base_fee_per_gas) = - Etherlink_durable_storage.base_fee_per_gas - (read_from_state evm_state) - in - store_block_unsafe - ~da_fee_per_byte - ~base_fee_per_gas - conn - evm_state - block - | Tez block -> - let+ receipts = store_tez_block_unsafe conn block in - (* TODO: support extracting the execution gas *) - (Z.zero, receipts) - in - return (evm_state, receipts, execution_gas) - in - List.iter (Lwt_watcher.notify receipt_watcher) receipts ; + let* evm_state, receipts, execution_gas = + let* execution_gas, receipts = + match block with + | Eth block -> + let* da_fee_per_byte = + Etherlink_durable_storage.da_fee_per_byte + (read_from_state evm_state) + in + let* (Qty base_fee_per_gas) = + Etherlink_durable_storage.base_fee_per_gas + (read_from_state evm_state) + in + store_block_unsafe + ~da_fee_per_byte + ~base_fee_per_gas + conn + evm_state + block + | Tez block -> + let+ receipts = store_tez_block_unsafe conn block in + (* TODO: support extracting the execution gas *) + (Z.zero, receipts) + in + return (evm_state, receipts, execution_gas) + in + List.iter (Lwt_watcher.notify receipt_watcher) receipts ; - let upgrade_candidate = check_pending_upgrade ctxt timestamp in - let* applied_kernel_upgrade = - check_upgrade ctxt evm_state upgrade_candidate - in - let* () = - when_ applied_kernel_upgrade @@ fun () -> - Evm_store.Kernel_upgrades.record_apply - conn - ctxt.session.next_blueprint_number - in + let upgrade_candidate = check_pending_upgrade ctxt timestamp in + let* applied_kernel_upgrade = + check_upgrade ctxt evm_state upgrade_candidate + in + let* () = + when_ applied_kernel_upgrade @@ fun () -> + Evm_store.Kernel_upgrades.record_apply + conn + ctxt.session.next_blueprint_number + in - let* context, split_info = - commit_next_head ctxt conn timestamp evm_state - in + let* context, split_info = + commit_next_head ctxt conn timestamp evm_state + in - Octez_telemetry.Trace.add_attrs (fun () -> - Telemetry.Attributes. - [ - Block.number ctxt.session.next_blueprint_number; - Block.transaction_count (List.length receipts); - Block.execution_gas execution_gas; - ]) ; + Octez_telemetry.Trace.add_attrs (fun () -> + Telemetry.Attributes. + [ + Block.number ctxt.session.next_blueprint_number; + Block.transaction_count (List.length receipts); + Block.execution_gas execution_gas; + ]) ; - let* evm_state = - if ctxt.session.storage_version >= 43 then return evm_state - else Lwt_result.ok (Evm_state.clear_events evm_state) - in + let* evm_state = + if ctxt.session.storage_version >= 43 then return evm_state + else Lwt_result.ok (Evm_state.clear_events evm_state) + in - return - ( evm_state, - context, - block, - applied_kernel_upgrade, - applied_sequencer_upgrade, - split_info, - execution_gas ) + return + ( evm_state, + context, + block, + applied_kernel_upgrade, + applied_sequencer_upgrade, + split_info, + execution_gas )) | Apply_failure (* Did not produce a block *) -> let*! () = if is_sequencer ctxt then @@ -1337,8 +1376,9 @@ module State = struct ctxt.session.evm_state <- cleaned_evm_state ; return_unit - let rec apply_blueprint ?(events = []) ctxt conn timestamp chunks payload - delayed_transactions : 'a L2_types.block tzresult Lwt.t = + let rec apply_blueprint ?(events = []) ?(block_hash = None) ctxt conn + timestamp chunks payload delayed_transactions : + 'a L2_types.block tzresult Lwt.t = let open Lwt_result_syntax in let+ current_block, _execution_gas = Misc.with_timing_f_e (fun (block, execution_gas) -> @@ -1359,6 +1399,7 @@ module State = struct chunks payload delayed_transactions + block_hash in let kernel_upgrade = match ctxt.session.pending_upgrade with @@ -2274,7 +2315,8 @@ module Handlers = struct let ctxt = Worker.state self in State.Transaction.run ctxt @@ fun ctxt conn -> State.apply_evm_events ?finalized_level conn ctxt events - | Apply_blueprint {events; timestamp; chunks; payload; delayed_transactions} + | Apply_blueprint + {events; timestamp; chunks; payload; delayed_transactions; block_hash} -> protect @@ fun () -> (* As defined in [blueprint_storage.rs] *) @@ -2294,6 +2336,7 @@ module Handlers = struct let* block = State.apply_blueprint ?events + ~block_hash ctxt conn timestamp @@ -2683,7 +2726,7 @@ let head_info () = let+ head_info in !head_info -let apply_blueprint ?events timestamp payload delayed_transactions = +let apply_blueprint ?events ?block_hash timestamp payload delayed_transactions = let open Lwt_result_syntax in let*! head = head_info () in let* sequencer = @@ -2715,6 +2758,7 @@ let apply_blueprint ?events timestamp payload delayed_transactions = chunks; payload = return payload; delayed_transactions; + block_hash; }) let apply_chunks ~signer timestamp chunks delayed_transactions = @@ -2745,7 +2789,14 @@ In order to still be able to sign the chunks, we need to use the next sequencer let* confirmed_txs = worker_wait_for_request (Apply_blueprint - {events = None; timestamp; chunks; payload; delayed_transactions}) + { + events = None; + timestamp; + chunks; + payload; + delayed_transactions; + block_hash = None; + }) and* blueprint_chunks and* payload in return (blueprint_chunks, payload, confirmed_txs) diff --git a/etherlink/bin_node/lib_dev/evm_context.mli b/etherlink/bin_node/lib_dev/evm_context.mli index 820854ad658c..83057b1905aa 100644 --- a/etherlink/bin_node/lib_dev/evm_context.mli +++ b/etherlink/bin_node/lib_dev/evm_context.mli @@ -90,6 +90,7 @@ val apply_evm_events' : produces the expected block. *) val apply_blueprint : ?events:Evm_events.t list -> + ?block_hash:Ethereum_types.block_hash -> Time.Protocol.t -> Blueprint_types.payload -> Evm_events.Delayed_transaction.t list -> diff --git a/etherlink/bin_node/lib_dev/evm_context_types.ml b/etherlink/bin_node/lib_dev/evm_context_types.ml index d6591522c88a..e8460f2ab5f5 100644 --- a/etherlink/bin_node/lib_dev/evm_context_types.ml +++ b/etherlink/bin_node/lib_dev/evm_context_types.ml @@ -20,6 +20,7 @@ module Request = struct chunks : Sequencer_blueprint.unsigned_chunked_blueprint; payload : Blueprint_types.payload tzresult Lwt.t; delayed_transactions : Evm_events.Delayed_transaction.t list; + block_hash : Ethereum_types.block_hash option; } -> (Ethereum_types.hash Seq.t, tztrace) t | Last_known_L1_level : (int32 option, tztrace) t @@ -89,7 +90,7 @@ module Request = struct case (Tag 1) ~title:"Apply_blueprint" - (obj5 + (obj6 (req "request" (constant "apply_blueprint")) (opt "events" (list Evm_events.encoding)) (req "timestamp" Time.Protocol.encoding) @@ -98,7 +99,8 @@ module Request = struct Sequencer_blueprint.unsigned_chunked_blueprint_encoding) (req "delayed_transactions" - (list Evm_events.Delayed_transaction.encoding))) + (list Evm_events.Delayed_transaction.encoding)) + (opt "block_hash" Ethereum_types.block_hash_encoding)) (function | View (Apply_blueprint @@ -108,8 +110,15 @@ module Request = struct chunks; payload = _; delayed_transactions; + block_hash; }) -> - Some ((), events, timestamp, chunks, delayed_transactions) + Some + ( (), + events, + timestamp, + chunks, + delayed_transactions, + block_hash ) | _ -> None) (fun _ -> assert false); case diff --git a/etherlink/bin_node/lib_dev/observer.ml b/etherlink/bin_node/lib_dev/observer.ml index 4866d50c8949..fac1f9edceb4 100644 --- a/etherlink/bin_node/lib_dev/observer.ml +++ b/etherlink/bin_node/lib_dev/observer.ml @@ -25,7 +25,7 @@ let on_new_blueprint (type f) (tx_container : f Services_backend_sig.tx_container) evm_node_endpoint next_blueprint_number (({delayed_transactions; blueprint; _} : Blueprint_types.with_events) as - blueprint_with_events) _block_hash = + blueprint_with_events) block_hash = let open Lwt_result_syntax in let (module Tx_container) = Services_backend_sig.tx_container_module tx_container @@ -40,6 +40,7 @@ let on_new_blueprint (type f) let*! res = Evm_context.apply_blueprint ~events + ?block_hash blueprint.timestamp blueprint.payload delayed_transactions diff --git a/etherlink/tezt/tests/expected/evm_rollup.ml/EVM node- list events regression.out b/etherlink/tezt/tests/expected/evm_rollup.ml/EVM node- list events regression.out index c692ebf665cd..f13b4ba1224d 100644 --- a/etherlink/tezt/tests/expected/evm_rollup.ml/EVM node- list events regression.out +++ b/etherlink/tezt/tests/expected/evm_rollup.ml/EVM node- list events regression.out @@ -40,6 +40,14 @@ applied_upgrade: byte sequences. */ string || { "invalid_utf8_string": [ integer ∈ [0, 255] ... ] } +assemble_block_diverged: + description: Assemble block diverged, node is going to re-execute the full blueprint + level: warning + section: evm_node.dev + json format: + { /* assemble_block_diverged version 0 */ + "assemble_block_diverged.v0": any } + background_task_error: description: background task {name} failed with error {error} level: fatal @@ -694,7 +702,8 @@ evm_context_request_failed: "delayed_transactions": [ [ "transaction" || "deposit" || "fa_deposit", $unistring, - /^([a-zA-Z0-9][a-zA-Z0-9])*$/ ] ... ] } + /^([a-zA-Z0-9][a-zA-Z0-9])*$/ ] ... ], + "block_hash"?: $unistring } || { /* Last_known_L1_level */ "request": "last_known_l1_level" } || { /* Patch_state */ @@ -1909,6 +1918,14 @@ sandbox_started: Decimal representation of a big number */ string +seq_block_hash_missing: + description: Assemble block can not validate its output because sequencer block hash is missing + level: warning + section: evm_node.dev + json format: + { /* seq_block_hash_missing version 0 */ + "seq_block_hash_missing.v0": any } + sequencer_disabled_native_execution: description: native execution is disabled for block application in sequencer mode level: warning @@ -2644,7 +2661,8 @@ evm_context_request_failed: "delayed_transactions": [ [ "transaction" || "deposit" || "fa_deposit", $unistring, - /^([a-zA-Z0-9][a-zA-Z0-9])*$/ ] ... ] } + /^([a-zA-Z0-9][a-zA-Z0-9])*$/ ] ... ], + "block_hash"?: $unistring } || { /* Last_known_L1_level */ "request": "last_known_l1_level" } || { /* Patch_state */ -- GitLab