From ad71ac96b394c54cf00ff5bbbc6c2264f77d6b06 Mon Sep 17 00:00:00 2001 From: Valentin Chaboche Date: Thu, 14 Nov 2024 10:08:56 +0100 Subject: [PATCH] EVM/Node: check out of sync in apply_evm_events --- etherlink/CHANGES_NODE.md | 3 + etherlink/bin_node/lib_dev/evm_context.ml | 101 +++++++++++++----- .../bin_node/lib_dev/evm_context_events.ml | 14 +++ .../bin_node/lib_dev/evm_events_follower.ml | 85 +++++---------- 4 files changed, 118 insertions(+), 85 deletions(-) diff --git a/etherlink/CHANGES_NODE.md b/etherlink/CHANGES_NODE.md index 5ead857506be..92c0bac6adf1 100644 --- a/etherlink/CHANGES_NODE.md +++ b/etherlink/CHANGES_NODE.md @@ -6,6 +6,9 @@ ### Bug fixes +- Fixes a race condition where rollup node follower would process start + processing a block before finishing the processing of its predecessor. (!15620) + ### Internals - Adds the duration of the blueprint application to the diff --git a/etherlink/bin_node/lib_dev/evm_context.ml b/etherlink/bin_node/lib_dev/evm_context.ml index 541d110299a1..0c62ce0df927 100644 --- a/etherlink/bin_node/lib_dev/evm_context.ml +++ b/etherlink/bin_node/lib_dev/evm_context.ml @@ -664,37 +664,72 @@ module State = struct let apply_evm_events ?finalized_level (ctxt : t) events = let open Lwt_result_syntax in - let* context, evm_state, on_success = - with_store_transaction ctxt @@ fun conn -> - let* on_success, ctxt, evm_state = - List.fold_left_es - (fun (on_success, ctxt, evm_state) event -> - let* evm_state, on_success = - apply_evm_event_unsafe on_success ctxt conn evm_state event + let* needs_process = + match finalized_level with + | None -> + (* We are not taking events from a rollup block, we don't need to check + if it's the last known l1 block successor. *) + return_true + | Some rollup_block_level -> ( + let* last_known_l1_block = + let* level = + Evm_store.use ctxt.store @@ fun conn -> + Evm_store.L1_l2_levels_relationships.find conn in - return (on_success, ctxt, evm_state)) - (ignore, ctxt, ctxt.session.evm_state) - events - in - let* _ = - Option.map_es - (fun l1_level -> - let l2_level = current_blueprint_number ctxt in - Evm_store.L1_l2_levels_relationships.store - conn - ~latest_l2_level:l2_level - ~l1_level - ~finalized_l2_level:ctxt.session.finalized_number) - finalized_level - in - let* ctxt = replace_current_commit ctxt conn evm_state in - return (ctxt, evm_state, on_success) + match level with + | Some {l1_level; _} -> return_some l1_level + | None -> return_none + in + match last_known_l1_block with + | None -> return_true + | Some last_known_l1_block -> + let level_expected = Int32.succ last_known_l1_block in + if Int32.equal rollup_block_level level_expected then return_true + else if Compare.Int32.(rollup_block_level < level_expected) then + let*! () = + Evm_context_events.unexpected_l1_block + ~expected_level:level_expected + ~provided_level:rollup_block_level + in + return false + else + tzfail + (Node_error.Out_of_sync + {level_received = rollup_block_level; level_expected})) in - on_success ctxt.session ; - on_modified_head ctxt evm_state context ; - let*! head_info in - head_info := session_to_head_info ctxt.session ; - return_unit + if needs_process then ( + let* context, evm_state, on_success = + with_store_transaction ctxt @@ fun conn -> + let* on_success, ctxt, evm_state = + List.fold_left_es + (fun (on_success, ctxt, evm_state) event -> + let* evm_state, on_success = + apply_evm_event_unsafe on_success ctxt conn evm_state event + in + return (on_success, ctxt, evm_state)) + (ignore, ctxt, ctxt.session.evm_state) + events + in + let* _ = + Option.map_es + (fun l1_level -> + let l2_level = current_blueprint_number ctxt in + Evm_store.L1_l2_levels_relationships.store + conn + ~latest_l2_level:l2_level + ~l1_level + ~finalized_l2_level:ctxt.session.finalized_number) + finalized_level + in + let* ctxt = replace_current_commit ctxt conn evm_state in + return (ctxt, evm_state, on_success) + in + on_success ctxt.session ; + on_modified_head ctxt evm_state context ; + let*! head_info in + head_info := session_to_head_info ctxt.session ; + return_unit) + else assert false type error += Cannot_apply_blueprint of {local_state_level : Z.t} @@ -1591,6 +1626,14 @@ module Handlers = struct match (req, errs) with | Apply_evm_events _, [Node_error.Diverged _divergence] -> Lwt_exit.exit_and_raise Node_error.exit_code_when_diverge + | ( Apply_evm_events _, + [Node_error.Out_of_sync {level_expected; level_received}] ) -> + let*! () = + Evm_events_follower_events.out_of_sync + ~expected:level_expected + ~received:level_received + in + Lwt_exit.exit_and_raise Node_error.exit_code_when_out_of_sync | _ -> return_unit end diff --git a/etherlink/bin_node/lib_dev/evm_context_events.ml b/etherlink/bin_node/lib_dev/evm_context_events.ml index cf62edfdfad0..9ef62cfd4924 100644 --- a/etherlink/bin_node/lib_dev/evm_context_events.ml +++ b/etherlink/bin_node/lib_dev/evm_context_events.ml @@ -86,6 +86,17 @@ let gc_waiter_failed = ~msg:"[Warning] Garbage collector waiter failed with an exception:" ("exn", Data_encoding.string) +let unexpected_l1_block = + declare_2 + ~section + ~name:"evm_context_unexpected_l1_block" + ~level:Warning + ~msg: + "[Warning] Apply EVM events got a block for level {provided_level} but \ + is older than expected level {expected_level}." + ("expected_level", Data_encoding.int32) + ("provided_level", Data_encoding.int32) + let ready () = emit ready () let shutdown () = emit shutdown () @@ -102,3 +113,6 @@ let gc_finished ~gc_level ~head_level duration = let gc_waiter_failed exn = emit__dont_wait__use_with_care gc_waiter_failed (Printexc.to_string exn) + +let unexpected_l1_block ~expected_level ~provided_level = + emit unexpected_l1_block (expected_level, provided_level) diff --git a/etherlink/bin_node/lib_dev/evm_events_follower.ml b/etherlink/bin_node/lib_dev/evm_events_follower.ml index db0414f40e9f..424453998456 100644 --- a/etherlink/bin_node/lib_dev/evm_events_follower.ml +++ b/etherlink/bin_node/lib_dev/evm_events_follower.ml @@ -93,54 +93,36 @@ let on_new_head ({rollup_node_endpoint; keep_alive; filter_event} as state : Types.state) rollup_block_lvl = let open Lwt_result_syntax in - let* last_known_l1_block = Evm_context.last_known_l1_level () in - let needs_processing = - match last_known_l1_block with - | None -> `Process - | Some last_known_l1_block -> - let level_expected = Int32.succ last_known_l1_block in - if Int32.equal rollup_block_lvl level_expected then `Process - else if Compare.Int32.(rollup_block_lvl < level_expected) then `Ignore - else - `Exit - (Node_error.Out_of_sync - {level_received = rollup_block_lvl; level_expected}) + let* nb_of_events_bytes = + read_from_rollup_node + ~keep_alive + Durable_storage_path.Evm_events.length + rollup_block_lvl + rollup_node_endpoint in - - match needs_processing with - | `Ignore -> return_unit - | `Process -> ( - let* nb_of_events_bytes = - read_from_rollup_node - ~keep_alive - Durable_storage_path.Evm_events.length - rollup_block_lvl - rollup_node_endpoint + match nb_of_events_bytes with + | None -> Evm_context.new_last_known_l1_level rollup_block_lvl + | Some nb_of_events_bytes -> + let (Qty nb_of_events) = + Ethereum_types.decode_number_le nb_of_events_bytes + in + let nb_of_events = Z.to_int nb_of_events in + let* events = + List.init_ep + ~when_negative_length: + (error_of_fmt + "Internal error: the rollup node advertised a negative length \ + for the events stream") + nb_of_events + (fetch_event state rollup_block_lvl) + in + let events = + List.filter_map + (function + | Some event when filter_event event -> Some event | _ -> None) + events in - match nb_of_events_bytes with - | None -> Evm_context.new_last_known_l1_level rollup_block_lvl - | Some nb_of_events_bytes -> - let (Qty nb_of_events) = - Ethereum_types.decode_number_le nb_of_events_bytes - in - let nb_of_events = Z.to_int nb_of_events in - let* events = - List.init_ep - ~when_negative_length: - (error_of_fmt - "Internal error: the rollup node advertised a negative \ - length for the events stream") - nb_of_events - (fetch_event state rollup_block_lvl) - in - let events = - List.filter_map - (function - | Some event when filter_event event -> Some event | _ -> None) - events - in - Evm_context.apply_evm_events ~finalized_level:rollup_block_lvl events) - | `Exit err -> fail [err] + Evm_context.apply_evm_events ~finalized_level:rollup_block_lvl events module Handlers = struct type self = worker @@ -172,16 +154,7 @@ module Handlers = struct unit tzresult Lwt.t = fun _w _ req errs -> let open Lwt_result_syntax in - match (req, errs) with - | ( Request.New_rollup_node_block _, - Node_error.Out_of_sync {level_expected; level_received} :: _ ) -> - let*! () = - Evm_events_follower_events.out_of_sync - ~expected:level_expected - ~received:level_received - in - Lwt_exit.exit_and_raise Node_error.exit_code_when_out_of_sync - | _ -> return_unit + match (req, errs) with _ -> return_unit let on_completion _ _ _ _ = Lwt.return_unit -- GitLab