From 4e26b38e6b0732339e69d3cfdea33c16eafe75da Mon Sep 17 00:00:00 2001 From: Thomas Letan Date: Sun, 9 Jun 2024 17:24:17 +0200 Subject: [PATCH] EVM Node: Propagate kernel upgrades through blueprints Currently, EVM observers get their kernel upgrade events via the rollup node, but these upgrades are applied when blueprints come from their EVM endpoint. This works well enough when (1) the cooldown period of the upgrade is large enough, and (2) observers are online and synced when it is injected. This does not work so well for offline observers, which needs to catchup two streams (EVM endpoint and rollup node). In that case, nothing ensures the upgrade event will be fetched before the blueprint activating it to be applied. The EVM node does not have a way to recover from this easily. With this patch, we change the source from which EVM nodes in observer mode fetch the upgrade. The upgrades are propagated through the blueprints responsible for activating them. It would arguably be better to propagate them sooner, but the change would be more intrusive and widespread (e.g., changes in the threshold encryption support modules would be necessary). --- etherlink/bin_node/lib_dev/blueprint_types.ml | 10 +++++-- .../bin_node/lib_dev/blueprint_types.mli | 1 + .../lib_dev/encodings/ethereum_types.mli | 2 ++ etherlink/bin_node/lib_dev/evm_context.ml | 30 +++++++++++-------- etherlink/bin_node/lib_dev/evm_store.ml | 9 ++++++ etherlink/bin_node/lib_dev/evm_store.mli | 5 ++++ etherlink/bin_node/lib_dev/observer.ml | 11 +++++-- etherlink/tezt/tests/evm_sequencer.ml | 28 +++++++++++++---- 8 files changed, 73 insertions(+), 23 deletions(-) diff --git a/etherlink/bin_node/lib_dev/blueprint_types.ml b/etherlink/bin_node/lib_dev/blueprint_types.ml index c38dee573a98..ae4049010da3 100644 --- a/etherlink/bin_node/lib_dev/blueprint_types.ml +++ b/etherlink/bin_node/lib_dev/blueprint_types.ml @@ -15,6 +15,7 @@ type t = { type with_events = { delayed_transactions : Ethereum_types.Delayed_transaction.t list; + kernel_upgrade : Ethereum_types.Upgrade.t option; blueprint : t; } @@ -39,10 +40,13 @@ let encoding = let with_events_encoding = let open Data_encoding in conv - (fun {delayed_transactions; blueprint} -> (delayed_transactions, blueprint)) - (fun (delayed_transactions, blueprint) -> {delayed_transactions; blueprint}) - (obj2 + (fun {delayed_transactions; kernel_upgrade; blueprint} -> + (delayed_transactions, kernel_upgrade, blueprint)) + (fun (delayed_transactions, kernel_upgrade, blueprint) -> + {delayed_transactions; kernel_upgrade; blueprint}) + (obj3 (req "delayed_transactions" (list Ethereum_types.Delayed_transaction.encoding)) + (opt "kernel_upgrade" Ethereum_types.Upgrade.encoding) (req "blueprint" encoding)) diff --git a/etherlink/bin_node/lib_dev/blueprint_types.mli b/etherlink/bin_node/lib_dev/blueprint_types.mli index 0500e328a422..9264327b5645 100644 --- a/etherlink/bin_node/lib_dev/blueprint_types.mli +++ b/etherlink/bin_node/lib_dev/blueprint_types.mli @@ -18,6 +18,7 @@ type t = { type with_events = { delayed_transactions : Ethereum_types.Delayed_transaction.t list; (** The delayed transactions to apply before applying the blueprint. *) + kernel_upgrade : Ethereum_types.Upgrade.t option; blueprint : t; (** The blueprint to execute. *) } diff --git a/etherlink/bin_node/lib_dev/encodings/ethereum_types.mli b/etherlink/bin_node/lib_dev/encodings/ethereum_types.mli index 00d1b0b891ac..11aac70a3792 100644 --- a/etherlink/bin_node/lib_dev/encodings/ethereum_types.mli +++ b/etherlink/bin_node/lib_dev/encodings/ethereum_types.mli @@ -342,6 +342,8 @@ module Upgrade : sig val of_bytes : bytes -> t option val to_bytes : t -> bytes + + val encoding : t Data_encoding.t end module Sequencer_upgrade : sig diff --git a/etherlink/bin_node/lib_dev/evm_context.ml b/etherlink/bin_node/lib_dev/evm_context.ml index e23579d62dfe..64083f28ac5d 100644 --- a/etherlink/bin_node/lib_dev/evm_context.ml +++ b/etherlink/bin_node/lib_dev/evm_context.ml @@ -470,13 +470,13 @@ module State = struct match ctxt.session.pending_upgrade with | None -> None | Some upgrade -> - if Time.Protocol.(upgrade.timestamp <= timestamp) then Some upgrade.hash + if Time.Protocol.(upgrade.timestamp <= timestamp) then Some upgrade else None let check_upgrade ctxt evm_state = let open Lwt_result_syntax in function - | Some root_hash -> + | Some Ethereum_types.Upgrade.({hash = root_hash; _} as kernel_upgrade) -> let*! bytes = Evm_state.inspect evm_state Durable_storage_path.kernel_root_hash in @@ -498,8 +498,9 @@ module State = struct Events.failed_upgrade root_hash ctxt.session.next_blueprint_number in - return_true - | None -> return_false + (* Even if the upgrade failed, we consider it has been applied. *) + return_some kernel_upgrade + | None -> return_none (** [apply_blueprint_store_unsafe ctxt payload delayed_transactions] applies the blueprint [payload] on the head of [ctxt], and commit the resulting @@ -546,13 +547,11 @@ module State = struct {number = Qty blueprint_number; timestamp; payload} in - let root_hash_candidate = check_pending_upgrade ctxt timestamp in - let* applied_upgrade = - check_upgrade ctxt evm_state root_hash_candidate - in + let upgrade_candidate = check_pending_upgrade ctxt timestamp in + let* kernel_upgrade = check_upgrade ctxt evm_state upgrade_candidate in let* () = - when_ applied_upgrade @@ fun () -> + when_ Option.(is_some kernel_upgrade) @@ fun () -> Evm_store.Kernel_upgrades.record_apply conn ctxt.session.next_blueprint_number @@ -578,7 +577,7 @@ module State = struct ( evm_state, context, current_block_hash, - applied_upgrade, + kernel_upgrade, delayed_transactions ) | Apply_success _ (* Produced a block, but not of the expected height *) | Apply_failure (* Did not produce a block *) -> @@ -605,7 +604,7 @@ module State = struct let* ( evm_state, context, current_block_hash, - applied_upgrade, + kernel_upgrade, delayed_transactions ) = with_store_transaction ctxt @@ fun conn -> apply_blueprint_store_unsafe @@ -618,12 +617,13 @@ module State = struct let*! () = on_new_head ctxt - ~applied_upgrade + ~applied_upgrade:Option.(is_some kernel_upgrade) evm_state context current_block_hash { delayed_transactions; + kernel_upgrade; blueprint = {number = ctxt.session.next_blueprint_number; timestamp; payload}; } @@ -844,13 +844,17 @@ module State = struct let open Lwt_result_syntax in Evm_store.use ctxt.store @@ fun conn -> let* blueprint = Evm_store.Blueprints.find conn level in + let* kernel_upgrade = + Evm_store.Kernel_upgrades.find_applied_before conn level + in match blueprint with | None -> return None | Some blueprint -> let* delayed_transactions = Evm_store.Delayed_transactions.at_level conn level in - return_some Blueprint_types.{delayed_transactions; blueprint} + return_some + Blueprint_types.{delayed_transactions; kernel_upgrade; blueprint} let messages_of_level ~levels_to_hashes ~l2_blocks ~messages level = let open Lwt_result_syntax in diff --git a/etherlink/bin_node/lib_dev/evm_store.ml b/etherlink/bin_node/lib_dev/evm_store.ml index 6e33383c6113..dce9c8aa1970 100644 --- a/etherlink/bin_node/lib_dev/evm_store.ml +++ b/etherlink/bin_node/lib_dev/evm_store.ml @@ -285,6 +285,11 @@ module Q = struct LIMIT 1 |} + let find_applied_before = + (level ->? upgrade) + @@ {|SELECT root_hash, activation_timestamp + FROM kernel_upgrades WHERE applied_before = ?|} + let record_apply = (level ->. unit) @@ {| @@ -523,6 +528,10 @@ module Kernel_upgrades = struct with_connection store @@ fun conn -> Db.find_opt conn Q.Kernel_upgrades.get_latest_unapplied () + let find_applied_before store level = + with_connection store @@ fun conn -> + Db.find_opt conn Q.Kernel_upgrades.find_applied_before level + let record_apply store level = with_connection store @@ fun conn -> Db.exec conn Q.Kernel_upgrades.record_apply level diff --git a/etherlink/bin_node/lib_dev/evm_store.mli b/etherlink/bin_node/lib_dev/evm_store.mli index 19bfcad09b31..7f103d07de0d 100644 --- a/etherlink/bin_node/lib_dev/evm_store.mli +++ b/etherlink/bin_node/lib_dev/evm_store.mli @@ -86,6 +86,11 @@ module Kernel_upgrades : sig val find_latest_pending : conn -> Ethereum_types.Upgrade.t option tzresult Lwt.t + val find_applied_before : + conn -> + Ethereum_types.quantity -> + Ethereum_types.Upgrade.t option tzresult Lwt.t + val record_apply : conn -> Ethereum_types.quantity -> unit tzresult Lwt.t val clear_after : conn -> Ethereum_types.quantity -> unit tzresult Lwt.t diff --git a/etherlink/bin_node/lib_dev/observer.ml b/etherlink/bin_node/lib_dev/observer.ml index a152805c53a9..fc5de1fc249b 100644 --- a/etherlink/bin_node/lib_dev/observer.ml +++ b/etherlink/bin_node/lib_dev/observer.ml @@ -108,7 +108,8 @@ end) : Services_backend_sig.Backend = struct end let on_new_blueprint next_blueprint_number - ({delayed_transactions; blueprint} : Blueprint_types.with_events) = + ({delayed_transactions; kernel_upgrade; blueprint} : + Blueprint_types.with_events) = let open Lwt_result_syntax in let (Qty level) = blueprint.number in let (Qty number) = next_blueprint_number in @@ -118,6 +119,11 @@ let on_new_blueprint next_blueprint_number (fun delayed_transaction -> Ethereum_types.Evm_events.New_delayed_transaction delayed_transaction) delayed_transactions + @ + match kernel_upgrade with + | Some kernel_upgrade -> + [Ethereum_types.Evm_events.Upgrade_event kernel_upgrade] + | None -> [] in let* () = Evm_context.apply_evm_events events in let delayed_transactions = @@ -357,7 +363,8 @@ let main ?kernel_path ~data_dir ~(config : Configuration.t) () = rollup_node_endpoint; keep_alive = config.keep_alive; filter_event = - (function New_delayed_transaction _ -> false | _ -> true); + (function + | New_delayed_transaction _ | Upgrade_event _ -> false | _ -> true); } in let () = diff --git a/etherlink/tezt/tests/evm_sequencer.ml b/etherlink/tezt/tests/evm_sequencer.ml index 4d32283fc85c..2deb1a15a980 100644 --- a/etherlink/tezt/tests/evm_sequencer.ml +++ b/etherlink/tezt/tests/evm_sequencer.ml @@ -1920,8 +1920,7 @@ let test_self_upgrade_kernel = in let* () = bake_until_sync ~sc_rollup_node ~client ~sequencer ~proxy () - and* _upgrade_info = Evm_node.wait_for_pending_upgrade sequencer - and* _upgrade_info_observer = Evm_node.wait_for_pending_upgrade observer in + and* _upgrade_info = Evm_node.wait_for_pending_upgrade sequencer in let* () = check_head_consistency @@ -1976,6 +1975,7 @@ let test_upgrade_kernel_auto_sync = client; sequencer; proxy; + observer; _; } _protocol -> @@ -1989,6 +1989,10 @@ let test_upgrade_kernel_auto_sync = | None -> unit in + (* Kill the observer to demonstrate the sequencer propagates the upgrade on + replay. *) + let* () = Evm_node.terminate observer in + (* Sends the upgrade to L1, but not to the sequencer. *) let _, to_use = Kernel.to_uses_and_tags to_ in let* () = @@ -2030,7 +2034,8 @@ let test_upgrade_kernel_auto_sync = Rpc.produce_block ~timestamp:"2020-01-01T00:00:15Z" sequencer in unit) - in + and* _upgrade = Evm_node.wait_for_successful_upgrade sequencer in + let* () = bake_until_sync ~sc_rollup_node ~client ~sequencer ~proxy () in let* () = @@ -2051,6 +2056,20 @@ let test_upgrade_kernel_auto_sync = | None -> unit in + (* Start the observer again and wait for a successful upgrade *) + let* () = Evm_node.run observer in + let* _upgrade = Evm_node.wait_for_successful_upgrade observer in + + let* () = Evm_node.wait_for_blueprint_applied observer 4 in + + let* () = + check_head_consistency + ~left:sequencer + ~right:observer + ~error_msg:"The head should be the same after the upgrade" + () + in + unit let test_delayed_transfer_timeout = @@ -3377,8 +3396,7 @@ let test_txpool_content_empty_with_legacy_encoding = in let* () = bake_until_sync ~sc_rollup_node ~client ~sequencer ~proxy () - and* _upgrade_info = Evm_node.wait_for_pending_upgrade sequencer - and* _upgrade_info_observer = Evm_node.wait_for_pending_upgrade observer in + and* _upgrade_info = Evm_node.wait_for_pending_upgrade sequencer in (* Produce a block after activation timestamp, both the rollup node and the sequencer will upgrade to itself. *) -- GitLab