From d1fb3164b36b1f648b4020aa897899fdb3d1e14d Mon Sep 17 00:00:00 2001 From: Valentin Chaboche Date: Thu, 28 Nov 2024 10:31:18 +0100 Subject: [PATCH] EVM/Node: remember intermediate finalized states --- etherlink/CHANGES_NODE.md | 3 + etherlink/bin_node/lib_dev/evm_context.ml | 141 +++++++++++------- etherlink/bin_node/lib_dev/evm_store.ml | 67 +++++++-- etherlink/bin_node/lib_dev/evm_store.mli | 17 +++ .../migrations/014_pending_confirmations.sql | 4 + etherlink/script-inputs/evm_store_migrations | 1 + .../EVM Node- debug print store schemas.out | 7 +- 7 files changed, 176 insertions(+), 64 deletions(-) create mode 100644 etherlink/bin_node/migrations/014_pending_confirmations.sql diff --git a/etherlink/CHANGES_NODE.md b/etherlink/CHANGES_NODE.md index 587d58cbfd96..8677bba1cc51 100644 --- a/etherlink/CHANGES_NODE.md +++ b/etherlink/CHANGES_NODE.md @@ -14,6 +14,9 @@ (!15629, !15703) - Private RPC `produceBlock` can produce a block without delayed transactions, useful for testing purposes. (!15681) +- Keep a buffer of finalized states if the rollup node is in advance. When + observer catches up, it checks provided blueprint against these finalized + states. (!15748) ## Version 0.8 (2024-11-15) diff --git a/etherlink/bin_node/lib_dev/evm_context.ml b/etherlink/bin_node/lib_dev/evm_context.ml index be00325e4d14..ead57df493ab 100644 --- a/etherlink/bin_node/lib_dev/evm_context.ml +++ b/etherlink/bin_node/lib_dev/evm_context.ml @@ -492,9 +492,65 @@ module State = struct let root_hash = `Hex root_hash in Kernel_download.download ~preimages ~preimages_endpoint ~root_hash () + let blueprint_applied_event ctxt conn evm_state on_success + ({number = Qty number; hash = expected_block_hash} : + Evm_events.Blueprint_applied.t) = + let open Lwt_result_syntax in + let on_success session = + let (Qty finalized) = session.finalized_number in + (* We use [max] to not rely on the order of the EVM events (because + it is possible to see several blueprints applied during on L1 + level). *) + let new_finalized = Z.(max number finalized) in + session.finalized_number <- Qty new_finalized ; + Metrics.set_confirmed_level ~level:new_finalized ; + on_success session + in + let* block_hash_opt = + if ctxt.block_storage_sqlite3 then + Evm_store.Blocks.find_hash_of_number conn (Qty number) + else + let*! bytes = + Evm_state.inspect + evm_state + (Durable_storage_path.Indexes.block_by_number (Nth number)) + in + return (Option.map Ethereum_types.decode_block_hash bytes) + in + match block_hash_opt with + | Some found_block_hash -> + if found_block_hash = expected_block_hash then + let*! () = + Evm_events_follower_events.upstream_blueprint_applied + (number, expected_block_hash) + in + return (evm_state, on_success) + else + let*! () = + Evm_events_follower_events.diverged + (number, expected_block_hash, found_block_hash) + in + tzfail + (Node_error.Diverged + (number, expected_block_hash, Some found_block_hash)) + | None when ctxt.fail_on_missing_blueprint -> + let*! () = + Evm_events_follower_events.missing_blueprint + (number, expected_block_hash) + in + tzfail (Node_error.Diverged (number, expected_block_hash, None)) + | None -> + let*! () = Evm_events_follower_events.rollup_node_ahead (Qty number) in + let* () = + Evm_store.Pending_confirmations.insert + conn + (Qty number) + expected_block_hash + in + return (evm_state, on_success) + let apply_evm_event_unsafe on_success ctxt conn evm_state event = let open Lwt_result_syntax in - let open Ethereum_types in let*! () = Evm_events_follower_events.new_event event in match event with | Evm_events.Upgrade_event upgrade -> @@ -535,55 +591,8 @@ module State = struct evm_state in return (evm_state, on_success) - | Blueprint_applied {number = Qty number; hash = expected_block_hash} -> ( - let on_success session = - let (Qty finalized) = session.finalized_number in - (* We use [max] to not rely on the order of the EVM events (because - it is possible to see several blueprints applied during on L1 - level). *) - let new_finalized = Z.(max number finalized) in - session.finalized_number <- Qty new_finalized ; - Metrics.set_confirmed_level ~level:new_finalized ; - on_success session - in - let* block_hash_opt = - if ctxt.block_storage_sqlite3 then - Evm_store.Blocks.find_hash_of_number conn (Qty number) - else - let*! bytes = - Evm_state.inspect - evm_state - (Durable_storage_path.Indexes.block_by_number (Nth number)) - in - return (Option.map decode_block_hash bytes) - in - match block_hash_opt with - | Some found_block_hash -> - if found_block_hash = expected_block_hash then - let*! () = - Evm_events_follower_events.upstream_blueprint_applied - (number, expected_block_hash) - in - return (evm_state, on_success) - else - let*! () = - Evm_events_follower_events.diverged - (number, expected_block_hash, found_block_hash) - in - tzfail - (Node_error.Diverged - (number, expected_block_hash, Some found_block_hash)) - | None when ctxt.fail_on_missing_blueprint -> - let*! () = - Evm_events_follower_events.missing_blueprint - (number, expected_block_hash) - in - tzfail (Node_error.Diverged (number, expected_block_hash, None)) - | None -> - let*! () = - Evm_events_follower_events.rollup_node_ahead (Qty number) - in - return (evm_state, on_success)) + | Blueprint_applied event -> + blueprint_applied_event ctxt conn evm_state on_success event | New_delayed_transaction delayed_transaction -> let* evm_state = on_new_delayed_transaction ~delayed_transaction evm_state @@ -841,6 +850,30 @@ module State = struct match try_apply with | Apply_success {evm_state; block} -> + let* () = + (* A dirty way to avoid doing this in sequencer mode, to refine. *) + if ctxt.fail_on_missing_blueprint 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 + 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 + tzfail + (Node_error.Diverged + (number, expected_block_hash, Some block.hash)) + in let number_of_transactions = match block.transactions with | TxHash l -> List.length l @@ -1047,6 +1080,11 @@ module State = struct load ~data_dir ~store_perm index in Evm_store.use store @@ fun conn -> + let* () = + let* is_empty = Evm_store.Pending_confirmations.is_empty conn in + when_ (fail_on_missing_blueprint && not is_empty) (fun () -> + failwith "Store has pending confirmation, state is not final") + in let* pending_upgrade = Evm_store.Kernel_upgrades.find_latest_pending conn in let* latest_relationship = Evm_store.L1_l2_levels_relationships.find conn in let finalized_number, l1_level = @@ -1506,7 +1544,8 @@ module Handlers = struct unit tzresult Lwt.t = let open Lwt_result_syntax in match (req, errs) with - | Apply_evm_events _, [Node_error.Diverged _divergence] -> + | Apply_evm_events _, [Node_error.Diverged _divergence] + | Apply_blueprint _, [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}] ) -> diff --git a/etherlink/bin_node/lib_dev/evm_store.ml b/etherlink/bin_node/lib_dev/evm_store.ml index 689a275a0c94..d6163d1b662a 100644 --- a/etherlink/bin_node/lib_dev/evm_store.ml +++ b/etherlink/bin_node/lib_dev/evm_store.ml @@ -220,7 +220,7 @@ module Q = struct You can review the result at [etherlink/tezt/tests/expected/evm_sequencer.ml/EVM Node- debug print store schemas.out]. *) - let version = 13 + let version = 14 let all : Evm_node_migrations.migration list = Evm_node_migrations.migrations version @@ -271,15 +271,6 @@ module Q = struct let clear_before = (level ->. unit) @@ {|DELETE FROM context_hashes WHERE id < ?|} - - let get_finalized = - (unit ->? t2 level context_hash) - @@ {|SELECT id, context_hash FROM context_hashes - WHERE id = ( - SELECT finalized_l2_level FROM l1_l2_levels_relationships - ORDER BY latest_l2_level DESC LIMIT 1 - )|} - (* Using [latest_l2_level] because it is an index *) end module Kernel_upgrades = struct @@ -352,7 +343,7 @@ module Q = struct let get = (unit ->! levels) - @@ {|SELECT latest_l2_level, l1_level, finalized_l2_level FROM l1_l2_levels_relationships ORDER BY latest_l2_level DESC LIMIT 1|} + @@ {|SELECT latest_l2_level, l1_level, finalized_l2_level FROM l1_l2_levels_relationships ORDER BY latest_l2_level DESC LIMIT 1|} let clear_after = (level ->. unit) @@ -508,6 +499,24 @@ module Q = struct (t2 level timestamp ->. unit) @@ {eos|INSERT INTO gc (id, last_split_level, last_split_timestamp) VALUES (1, ?, ?) ON CONFLICT(id) DO UPDATE SET last_split_level = excluded.last_split_level, last_split_timestamp = excluded.last_split_timestamp|eos} end + + module Pending_confirmations = struct + let insert = + (t2 level block_hash ->. unit) + @@ {|INSERT INTO pending_confirmations (level, hash) VALUES (?, ?)|} + + let select_with_level = + (level ->? block_hash) + @@ {|SELECT hash FROM pending_confirmations WHERE level = ?|} + + let delete_with_level = + (level ->. unit) @@ {|DELETE FROM pending_confirmations WHERE level = ?|} + + let clear = (unit ->. unit) @@ {|DELETE FROM pending_confirmations|} + + let count = + (unit ->! level) @@ {|SELECT COUNT(*) FROM pending_confirmations|} + end end module Schemas = struct @@ -621,8 +630,16 @@ module Context_hashes = struct Db.find_opt conn Q.Context_hashes.get_earliest () let find_finalized store = + let open Lwt_result_syntax in with_connection store @@ fun conn -> - Db.find_opt conn Q.Context_hashes.get_finalized () + let* l1_l2_levels = Db.find_opt conn Q.L1_l2_levels_relationships.get () in + match l1_l2_levels with + | None -> return_none + | Some {current_number = Qty current_number; finalized = Qty finalized; _} + -> + let min = Ethereum_types.Qty (Z.min current_number finalized) in + let+ hash = Db.find_opt conn Q.Context_hashes.select min in + Option.map (fun hash -> (min, hash)) hash let clear_after store l2_level = with_connection store @@ fun conn -> @@ -1026,6 +1043,30 @@ module Blocks = struct Db.exec conn Q.Blocks.clear_before level end +module Pending_confirmations = struct + let insert store level hash = + with_connection store @@ fun conn -> + Db.exec conn Q.Pending_confirmations.insert (level, hash) + + let find_with_level store level = + with_connection store @@ fun conn -> + Db.find_opt conn Q.Pending_confirmations.select_with_level level + + let delete_with_level store level = + with_connection store @@ fun conn -> + Db.exec conn Q.Pending_confirmations.delete_with_level level + + let clear store = + with_connection store @@ fun conn -> + Db.exec conn Q.Pending_confirmations.clear () + + let is_empty store = + let open Lwt_result_syntax in + with_connection store @@ fun conn -> + let* (Qty count) = Db.find conn Q.Pending_confirmations.count () in + return Z.(equal zero count) +end + let context_hash_of_block_hash store hash = with_connection store @@ fun conn -> Db.find_opt conn Q.context_hash_of_block_hash hash @@ -1039,6 +1080,8 @@ let reset_after store ~l2_level = let* () = Delayed_transactions.clear_after store l2_level in let* () = Blocks.clear_after store l2_level in let* () = Transactions.clear_after store l2_level in + (* Blocks in [Pending_confirmations] are always after the current head. *) + let* () = Pending_confirmations.clear store in return_unit let reset_before store ~l2_level = diff --git a/etherlink/bin_node/lib_dev/evm_store.mli b/etherlink/bin_node/lib_dev/evm_store.mli index 79504998ef80..92e2ed2f4309 100644 --- a/etherlink/bin_node/lib_dev/evm_store.mli +++ b/etherlink/bin_node/lib_dev/evm_store.mli @@ -183,6 +183,23 @@ module GC : sig conn -> Ethereum_types.quantity -> Time.Protocol.t -> unit tzresult Lwt.t end +module Pending_confirmations : sig + val insert : + conn -> + Ethereum_types.quantity -> + Ethereum_types.block_hash -> + unit tzresult Lwt.t + + val find_with_level : + conn -> + Ethereum_types.quantity -> + Ethereum_types.block_hash option tzresult Lwt.t + + val delete_with_level : conn -> Ethereum_types.quantity -> unit tzresult Lwt.t + + val is_empty : conn -> bool tzresult Lwt.t +end + module L1_l2_levels_relationships : sig type t = { l1_level : int32; diff --git a/etherlink/bin_node/migrations/014_pending_confirmations.sql b/etherlink/bin_node/migrations/014_pending_confirmations.sql new file mode 100644 index 000000000000..5ecf5f730e50 --- /dev/null +++ b/etherlink/bin_node/migrations/014_pending_confirmations.sql @@ -0,0 +1,4 @@ +CREATE TABLE pending_confirmations ( + level serial PRIMARY KEY, + hash VARCHAR(32) NOT NULL +); diff --git a/etherlink/script-inputs/evm_store_migrations b/etherlink/script-inputs/evm_store_migrations index fd31c1f4bd63..ca4f645645d7 100644 --- a/etherlink/script-inputs/evm_store_migrations +++ b/etherlink/script-inputs/evm_store_migrations @@ -12,3 +12,4 @@ fe2dd1f67667ed6cfdce9a6450fc7785d820e08ac6d3e894547f201ec16071e0 etherlink/bin_ 3d2782f7802cc9697ba246e987ec0808c854dcbecb1b073acbd845befa729b8f etherlink/bin_node/migrations/011_create_transactions_table.sql efd7da46af254996433066a6d2a6c692e60f7314130a5ba8ceee9e419e155118 etherlink/bin_node/migrations/012_create_gc_table.sql 003e667c1e68db6cefbe6acee6c56570f19158f138d523384531e8b21c753d31 etherlink/bin_node/migrations/013_unique_index_blocks_transactions.sql +9c934e867806d31d17f4e7ce4e15c8620f015919af32c294e45631914b3679b1 etherlink/bin_node/migrations/014_pending_confirmations.sql diff --git a/etherlink/tezt/tests/expected/evm_sequencer.ml/EVM Node- debug print store schemas.out b/etherlink/tezt/tests/expected/evm_sequencer.ml/EVM Node- debug print store schemas.out index f7b31c3a9394..9586d1265f31 100644 --- a/etherlink/tezt/tests/expected/evm_sequencer.ml/EVM Node- debug print store schemas.out +++ b/etherlink/tezt/tests/expected/evm_sequencer.ml/EVM Node- debug print store schemas.out @@ -63,4 +63,9 @@ CREATE TABLE gc ( last_split_timestamp TIMESTAMP ); -CREATE UNIQUE INDEX block_hash_index ON blocks (hash) +CREATE UNIQUE INDEX block_hash_index ON blocks (hash); + +CREATE TABLE pending_confirmations ( + level serial PRIMARY KEY, + hash VARCHAR(32) NOT NULL +) -- GitLab