From 9e859d32b1bac6dbaccad46a05086486925abbbf Mon Sep 17 00:00:00 2001 From: Valentin Chaboche Date: Mon, 9 Dec 2024 14:06:15 +0100 Subject: [PATCH] EVM/Node/GC: keep a number of chunks --- etherlink/CHANGES_NODE.md | 4 + etherlink/bin_node/config/configuration.ml | 19 +-- etherlink/bin_node/config/configuration.mli | 2 +- etherlink/bin_node/lib_dev/evm_context.ml | 145 +++++++----------- etherlink/bin_node/lib_dev/evm_store.ml | 70 ++++++--- etherlink/bin_node/lib_dev/evm_store.mli | 18 ++- .../bin_node/migrations/015_irmin_chunks.sql | 6 + etherlink/script-inputs/evm_store_migrations | 1 + etherlink/tezt/lib/evm_node.ml | 7 +- etherlink/tezt/lib/evm_node.mli | 2 +- .../EVM Node- debug print store schemas.out | 13 +- .../EVM Node- describe config.out | 6 +- etherlink/tezt/tests/gc.ml | 50 +++++- 13 files changed, 185 insertions(+), 158 deletions(-) create mode 100644 etherlink/bin_node/migrations/015_irmin_chunks.sql diff --git a/etherlink/CHANGES_NODE.md b/etherlink/CHANGES_NODE.md index 9b0d2f3aa441..29292a09c531 100644 --- a/etherlink/CHANGES_NODE.md +++ b/etherlink/CHANGES_NODE.md @@ -34,6 +34,10 @@ a single timestamp per simulation. (!15889) - Internal error in the EVM context emits an event instead of silently failing. (!15824) +- Garbage collector experimental feature keeps a number of chunks + rather than keeping "number of seconds" of history. For example, if + `split_frequency_in_seconds` is set to 86_400 (1 day) and `number_of_chunks` + is set to 7, the node will keep 7 days of history. (!15928) ## Verson 0.10 (2024-12-02) diff --git a/etherlink/bin_node/config/configuration.ml b/etherlink/bin_node/config/configuration.ml index b6ec612e9576..49d83f2be3a5 100644 --- a/etherlink/bin_node/config/configuration.ml +++ b/etherlink/bin_node/config/configuration.ml @@ -34,10 +34,7 @@ type kernel_execution_config = { type garbage_collector = { split_frequency_in_seconds : int; - (* Split frequency in seconds is not really necessary, only history to keep - in seconds is. We keep the field for now to easily test different setups. - But the split frequency should be in close relationship to history. *) - history_to_keep_in_seconds : int; + number_of_chunks : int; } type rpc_server = Resto | Dream @@ -674,20 +671,20 @@ let observer_encoding = let garbage_collector_encoding = let open Data_encoding in conv - (fun {split_frequency_in_seconds; history_to_keep_in_seconds} -> - (split_frequency_in_seconds, history_to_keep_in_seconds)) - (fun (split_frequency_in_seconds, history_to_keep_in_seconds) -> - {split_frequency_in_seconds; history_to_keep_in_seconds}) + (fun {split_frequency_in_seconds; number_of_chunks} -> + (split_frequency_in_seconds, number_of_chunks)) + (fun (split_frequency_in_seconds, number_of_chunks) -> + {split_frequency_in_seconds; number_of_chunks}) (obj2 (req "split_frequency_in_seconds" ~description:"Frequency of irmin context split in days" int31) (req - "history_to_keep_in_seconds" + "number_of_chunks" ~description: - "Number of seconds kept in the irmin context, the rest is garbage \ - collected, e.g. 86_400 will keep 1 day of history." + "Number of irmin context chunks kept, the rest is garbage \ + collected." int31)) let rpc_server_encoding = diff --git a/etherlink/bin_node/config/configuration.mli b/etherlink/bin_node/config/configuration.mli index 60a2ee300d9e..7d0f88140d13 100644 --- a/etherlink/bin_node/config/configuration.mli +++ b/etherlink/bin_node/config/configuration.mli @@ -56,7 +56,7 @@ type blueprints_publisher_config = { type garbage_collector = { split_frequency_in_seconds : int; - history_to_keep_in_seconds : int; + number_of_chunks : int; } (** RPC server implementation. *) diff --git a/etherlink/bin_node/lib_dev/evm_context.ml b/etherlink/bin_node/lib_dev/evm_context.ml index 20aaa51d3290..20bd652e8a35 100644 --- a/etherlink/bin_node/lib_dev/evm_context.ml +++ b/etherlink/bin_node/lib_dev/evm_context.ml @@ -41,8 +41,6 @@ type session_state = { mutable evm_state : Evm_state.t; mutable last_split_block : (Ethereum_types.quantity * Time.Protocol.t) option; (** Garbage collector session related information. *) - mutable last_gc_block : (Ethereum_types.quantity * Time.Protocol.t) option; - (** Garbage collector session related information. *) } type history = @@ -184,6 +182,36 @@ module State = struct let* () = Evm_store.Context_hashes.store store number checkpoint in return context + let gc ctxt conn gc_level head_level = + let open Lwt_result_syntax in + let*! () = Evm_context_events.gc_started ~gc_level ~head_level in + let start_timestamp = Time.System.now () in + let* hash = Evm_store.Context_hashes.find conn gc_level in + let*? hash = + Option.to_result + ~none: + [ + error_of_fmt + "Context hash of GC candidate %a is missing from store" + Ethereum_types.pp_quantity + gc_level; + ] + hash + in + let* () = Evm_store.reset_before conn ~l2_level:gc_level in + let*! () = Irmin_context.gc ctxt.index hash in + let gc_waiter () = + let open Lwt_syntax in + let* () = Irmin_context.wait_gc_completion ctxt.index in + let stop_timestamp = Time.System.now () in + Evm_context_events.gc_finished + ~gc_level + ~head_level + (Ptime.diff stop_timestamp start_timestamp) + in + Lwt.dont_wait gc_waiter Evm_context_events.gc_waiter_failed ; + return_unit + let maybe_split_context ctxt conn timestamp level = let open Lwt_result_syntax in match ctxt.history with @@ -206,70 +234,23 @@ module State = struct in if split then ( Irmin_context.split ctxt.index ; - let* () = Evm_store.GC.update_last_split conn level timestamp in + let* () = Evm_store.Irmin_chunks.insert conn level timestamp in let*! () = Evm_context_events.gc_split level timestamp in + let* number_of_chunks = Evm_store.Irmin_chunks.count conn in + let max_number_of_chunks = + parameters.Configuration.number_of_chunks + in + let* () = + if number_of_chunks > max_number_of_chunks then + let* gc_level, _gc_timestamp = + Evm_store.Irmin_chunks.oldest conn + in + gc ctxt conn gc_level level + else return_unit + in return_some (level, timestamp)) else return_none - let maybe_gc ctxt conn timestamp level = - let open Lwt_result_syntax in - match ctxt.history with - | Archive -> return_none - | Full {parameters} -> ( - match ctxt.session.last_gc_block with - | None -> - (* The GC was never called, we do nothing and consider - that the GC was done. *) - let* () = Evm_store.GC.update_last_gc conn level timestamp in - return_some (level, timestamp) - | Some (last_gc_level, last_gc_timestamp) -> - let gc = - let timestamp = Time.Protocol.to_seconds timestamp in - let last_gc_timestamp = - Time.Protocol.to_seconds last_gc_timestamp - in - Compare.Int64.( - timestamp - >= Int64.( - add - last_gc_timestamp - (of_int parameters.history_to_keep_in_seconds))) - in - if gc then ( - let*! () = - Evm_context_events.gc_started - ~gc_level:last_gc_level - ~head_level:level - in - let start_timestamp = Time.System.now () in - let* hash = Evm_store.Context_hashes.find conn last_gc_level in - let*? hash = - Option.to_result - ~none: - [ - error_of_fmt - "Context hash of GC candidate %a is missing from store" - Ethereum_types.pp_quantity - last_gc_level; - ] - hash - in - let* () = Evm_store.reset_before conn ~l2_level:last_gc_level in - let*! () = Irmin_context.gc ctxt.index hash in - let* () = Evm_store.GC.update_last_gc conn level timestamp in - let gc_waiter () = - let open Lwt_syntax in - let* () = Irmin_context.wait_gc_completion ctxt.index in - let stop_timestamp = Time.System.now () in - Evm_context_events.gc_finished - ~gc_level:last_gc_level - ~head_level:level - (Ptime.diff stop_timestamp start_timestamp) - in - Lwt.dont_wait gc_waiter Evm_context_events.gc_waiter_failed ; - return_some (level, timestamp)) - else return_none) - let commit_next_head (ctxt : t) conn timestamp evm_state = let open Lwt_result_syntax in let* context = @@ -282,10 +263,7 @@ module State = struct let* split_info = maybe_split_context ctxt conn timestamp ctxt.session.next_blueprint_number in - let* gc_info = - maybe_gc ctxt conn timestamp ctxt.session.next_blueprint_number - in - return (context, split_info, gc_info) + return (context, split_info) let replace_current_commit (ctxt : t) conn evm_state = let (Qty next) = ctxt.session.next_blueprint_number in @@ -672,16 +650,10 @@ module State = struct ctxt.session.next_blueprint_number in - let* context, split_info, gc_info = + let* context, split_info = commit_next_head ctxt conn timestamp evm_state in - return - ( evm_state, - context, - block, - applied_kernel_upgrade, - split_info, - gc_info ) + return (evm_state, context, block, applied_kernel_upgrade, split_info) | Apply_failure (* Did not produce a block *) -> let*! () = if ctxt.fail_on_missing_blueprint then @@ -690,8 +662,8 @@ module State = struct in tzfail (Cannot_apply_blueprint {local_state_level = Z.pred next}) - let on_new_head ?split_info ?gc_info ctxt ~applied_upgrade evm_state context - block blueprint_with_events = + let on_new_head ?split_info ctxt ~applied_upgrade evm_state context block + blueprint_with_events = let open Lwt_syntax in let (Qty level) = ctxt.session.next_blueprint_number in ctxt.session.evm_state <- evm_state ; @@ -702,9 +674,6 @@ module State = struct (fun (split_level, split_timestamp) -> ctxt.session.last_split_block <- Some (split_level, split_timestamp)) split_info ; - Option.iter - (fun gc_info -> ctxt.session.last_gc_block <- Some gc_info) - gc_info ; Broadcast.notify @@ Broadcast.Blueprint blueprint_with_events ; if applied_upgrade then ctxt.session.pending_upgrade <- None ; let* head_info in @@ -749,12 +718,7 @@ module State = struct let rec apply_blueprint ?conn ?(events = []) ctxt timestamp payload delayed_transactions = let open Lwt_result_syntax in - let* ( evm_state, - context, - current_block, - applied_kernel_upgrade, - split_info, - gc_info ) = + let* evm_state, context, current_block, applied_kernel_upgrade, split_info = let kont conn = let* () = apply_evm_events conn ctxt events in apply_blueprint_store_unsafe @@ -781,7 +745,6 @@ module State = struct @@ fun () -> on_new_head ?split_info - ?gc_info ctxt ~applied_upgrade:applied_kernel_upgrade evm_state @@ -1156,13 +1119,12 @@ module State = struct initial kernel" in - let* history, last_split_block, last_gc_block = + let* history, last_split_block = match garbage_collector with | Some parameters -> - let* last_split_opt = Evm_store.GC.last_split conn in - let* last_gc_opt = Evm_store.GC.last_gc conn in - return (Full {parameters}, last_split_opt, last_gc_opt) - | None -> return (Archive, None, None) + let* last_split_opt = Evm_store.Irmin_chunks.latest conn in + return (Full {parameters}, last_split_opt) + | None -> return (Archive, None) in let ctxt = @@ -1182,7 +1144,6 @@ module State = struct pending_upgrade; evm_state; last_split_block; - last_gc_block; }; store; fail_on_missing_blueprint; diff --git a/etherlink/bin_node/lib_dev/evm_store.ml b/etherlink/bin_node/lib_dev/evm_store.ml index a7f40adb1d7f..0f367192c5d7 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 = 14 + let version = 15 let all : Evm_node_migrations.migration list = Evm_node_migrations.migrations version @@ -486,22 +486,26 @@ module Q = struct (block_hash ->? context_hash) @@ {eos|SELECT c.context_hash from Context_hashes c JOIN Blocks b on c.id = b.level WHERE hash = ?|eos} - module GC = struct - let select_last_gc = - (unit ->? t2 level timestamp) - @@ {|SELECT last_gc_level, last_gc_timestamp FROM gc|} - - let update_last_gc = + module Irmin_chunks = struct + let insert = (t2 level timestamp ->. unit) - @@ {eos|INSERT INTO gc (id, last_gc_level, last_gc_timestamp) VALUES (1, ?, ?) ON CONFLICT(id) DO UPDATE SET last_gc_level = excluded.last_gc_level, last_gc_timestamp = excluded.last_gc_timestamp|eos} + @@ {|INSERT INTO irmin_chunks (level, timestamp) VALUES (?, ?)|} + + let oldest = + (unit ->! t2 level timestamp) + @@ {|SELECT level, timestamp from irmin_chunks ORDER BY level ASC LIMIT 1|} - let select_last_split = + let latest = (unit ->? t2 level timestamp) - @@ {|SELECT last_split_level, last_split_timestamp FROM gc|} + @@ {|SELECT level, timestamp from irmin_chunks ORDER BY level DESC LIMIT 1|} - let update_last_split = - (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} + let count = (unit ->! int) @@ {|SELECT COUNT(*) FROM irmin_chunks|} + + let clear_after = + (level ->. unit) @@ {|DELETE FROM irmin_chunks WHERE level > ?|} + + let clear_before_included = + (level ->. unit) @@ {|DELETE FROM irmin_chunks WHERE level <= ?|} end module Pending_confirmations = struct @@ -958,21 +962,31 @@ module Transactions = struct Db.exec conn Q.Transactions.clear_before level end -module GC = struct - let last_gc store = - with_connection store @@ fun conn -> Db.find_opt conn Q.GC.select_last_gc () +module Irmin_chunks = struct + let insert conn level timestamp = + with_connection conn @@ fun conn -> + Db.exec conn Q.Irmin_chunks.insert (level, timestamp) - let update_last_gc store level timestamp = - with_connection store @@ fun conn -> - Db.exec conn Q.GC.update_last_gc (level, timestamp) + let oldest conn = + with_connection conn @@ fun conn -> Db.find conn Q.Irmin_chunks.oldest () - let last_split store = + let latest conn = + with_connection conn @@ fun conn -> + Db.find_opt conn Q.Irmin_chunks.latest () + + let count conn = + let open Lwt_result_syntax in + with_connection conn @@ fun conn -> + let* count = Db.find_opt conn Q.Irmin_chunks.count () in + return (Option.value ~default:0 count) + + let clear_after store level = with_connection store @@ fun conn -> - Db.find_opt conn Q.GC.select_last_split () + Db.exec conn Q.Irmin_chunks.clear_after level - let update_last_split store level timestamp = + let clear_before_included store level = with_connection store @@ fun conn -> - Db.exec conn Q.GC.update_last_split (level, timestamp) + Db.exec conn Q.Irmin_chunks.clear_before_included level end module Blocks = struct @@ -1088,6 +1102,9 @@ let reset_after store ~l2_level = let* () = Transactions.clear_after store l2_level in (* Blocks in [Pending_confirmations] are always after the current head. *) let* () = Pending_confirmations.clear store in + (* If splits were produced on the resetted branch, they will be garbage + collected by the GC anyway later. *) + let* () = Irmin_chunks.clear_after store l2_level in return_unit let reset_before store ~l2_level = @@ -1099,4 +1116,11 @@ let reset_before store ~l2_level = let* () = Delayed_transactions.clear_before store l2_level in let* () = Blocks.clear_before store l2_level in let* () = Transactions.clear_before store l2_level in + (* {!reset_before} is called when garbage collector is trigerred. + Garbage collector is trigerred when the maximum number of splits + is reached, [l2_level] was the pointer to the oldest split. + + If it wasn't included, the garbage collector would keep the maximum + number of splits plus an additional one. *) + let* () = Irmin_chunks.clear_before_included store l2_level in return_unit diff --git a/etherlink/bin_node/lib_dev/evm_store.mli b/etherlink/bin_node/lib_dev/evm_store.mli index 92e2ed2f4309..40f714efb7ec 100644 --- a/etherlink/bin_node/lib_dev/evm_store.mli +++ b/etherlink/bin_node/lib_dev/evm_store.mli @@ -169,18 +169,20 @@ module Transactions : sig conn -> Ethereum_types.quantity -> Transaction_receipt.t list tzresult Lwt.t end -module GC : sig - val last_gc : - conn -> (Ethereum_types.quantity * Time.Protocol.t) option tzresult Lwt.t +module Irmin_chunks : sig + val insert : + conn -> + Ethereum_types.quantity -> + Time.Protocol.t -> + unit Error_monad.tzresult Lwt.t - val update_last_gc : - conn -> Ethereum_types.quantity -> Time.Protocol.t -> unit tzresult Lwt.t + val oldest : + conn -> (Ethereum_types.quantity * Time.Protocol.t) tzresult Lwt.t - val last_split : + val latest : conn -> (Ethereum_types.quantity * Time.Protocol.t) option tzresult Lwt.t - val update_last_split : - conn -> Ethereum_types.quantity -> Time.Protocol.t -> unit tzresult Lwt.t + val count : conn -> int tzresult Lwt.t end module Pending_confirmations : sig diff --git a/etherlink/bin_node/migrations/015_irmin_chunks.sql b/etherlink/bin_node/migrations/015_irmin_chunks.sql new file mode 100644 index 000000000000..778e6a3a8316 --- /dev/null +++ b/etherlink/bin_node/migrations/015_irmin_chunks.sql @@ -0,0 +1,6 @@ +DROP TABLE gc; + +CREATE TABLE irmin_chunks ( + level INTEGER, + timestamp TIMESTAMP +); diff --git a/etherlink/script-inputs/evm_store_migrations b/etherlink/script-inputs/evm_store_migrations index ca4f645645d7..8e7fad59d0d6 100644 --- a/etherlink/script-inputs/evm_store_migrations +++ b/etherlink/script-inputs/evm_store_migrations @@ -13,3 +13,4 @@ fe2dd1f67667ed6cfdce9a6450fc7785d820e08ac6d3e894547f201ec16071e0 etherlink/bin_ 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 +bd8fc14f18c1ca562e8b8e412a72bd36b071e28e84e8e8624b1f6581e70c144d etherlink/bin_node/migrations/015_irmin_chunks.sql diff --git a/etherlink/tezt/lib/evm_node.ml b/etherlink/tezt/lib/evm_node.ml index 77ac098f26b0..8dbacbe9ec62 100644 --- a/etherlink/tezt/lib/evm_node.ml +++ b/etherlink/tezt/lib/evm_node.ml @@ -1171,7 +1171,7 @@ let endpoint = rpc_endpoint ?local:None type garbage_collector = { split_frequency_in_seconds : int; - history_to_keep_in_seconds : int; + number_of_chunks : int; } type rpc_server = Resto | Dream @@ -1221,13 +1221,12 @@ let patch_config_with_experimental_feature |> optional_json_put ~name:"garbage_collector" garbage_collector - (fun {split_frequency_in_seconds; history_to_keep_in_seconds} -> + (fun {split_frequency_in_seconds; number_of_chunks} -> `O [ ( "split_frequency_in_seconds", `Float (Int.to_float split_frequency_in_seconds) ); - ( "history_to_keep_in_seconds", - `Float (Int.to_float history_to_keep_in_seconds) ); + ("number_of_chunks", `Float (Int.to_float number_of_chunks)); ]) |> optional_json_put ~name:"rpc_server" rpc_server (function | Resto -> `String "resto" diff --git a/etherlink/tezt/lib/evm_node.mli b/etherlink/tezt/lib/evm_node.mli index 6356f1a36065..13f9b47082dd 100644 --- a/etherlink/tezt/lib/evm_node.mli +++ b/etherlink/tezt/lib/evm_node.mli @@ -253,7 +253,7 @@ val spawn_init_config : ?extra_arguments:string list -> t -> Process.t type garbage_collector = { split_frequency_in_seconds : int; - history_to_keep_in_seconds : int; + number_of_chunks : int; } type rpc_server = Resto | Dream 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 9586d1265f31..2ce3a2e17df0 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 @@ -55,17 +55,14 @@ CREATE TABLE transactions ( object_fields BLOB NOT NULL ); -CREATE TABLE gc ( - id INTEGER PRIMARY KEY, - last_gc_level INTEGER, - last_gc_timestamp TIMESTAMP, - last_split_level INTEGER, - last_split_timestamp TIMESTAMP -); - CREATE UNIQUE INDEX block_hash_index ON blocks (hash); CREATE TABLE pending_confirmations ( level serial PRIMARY KEY, hash VARCHAR(32) NOT NULL +); + +CREATE TABLE irmin_chunks ( + level INTEGER, + timestamp TIMESTAMP ) diff --git a/etherlink/tezt/tests/expected/evm_sequencer.ml/EVM Node- describe config.out b/etherlink/tezt/tests/expected/evm_sequencer.ml/EVM Node- describe config.out index bc72b1a6727b..a0c819be16e1 100644 --- a/etherlink/tezt/tests/expected/evm_sequencer.ml/EVM Node- describe config.out +++ b/etherlink/tezt/tests/expected/evm_sequencer.ml/EVM Node- describe config.out @@ -139,10 +139,10 @@ "split_frequency_in_seconds": integer ∈ [-2^30, 2^30] /* Frequency of irmin context split in days */, - "history_to_keep_in_seconds": + "number_of_chunks": integer ∈ [-2^30, 2^30] - /* Number of seconds kept in the irmin context, the rest is - garbage collected, e.g. 86_400 will keep 1 day of history. */ }, + /* Number of irmin context chunks kept, the rest is garbage + collected. */ }, "next_wasm_runtime"?: boolean /* Enable or disable the experimental WASM runtime that is expected diff --git a/etherlink/tezt/tests/gc.ml b/etherlink/tezt/tests/gc.ml index 921b3257572c..10a62f6fbff0 100644 --- a/etherlink/tezt/tests/gc.ml +++ b/etherlink/tezt/tests/gc.ml @@ -22,9 +22,8 @@ open Rpc.Syntax let register ?genesis_timestamp ?(garbage_collector = - Evm_node. - {split_frequency_in_seconds = 100; history_to_keep_in_seconds = 100}) - ~title ~tags f = + Evm_node.{split_frequency_in_seconds = 100; number_of_chunks = 5}) ~title + ~tags f = Test.register ~__FILE__ ~title @@ -56,7 +55,7 @@ let test_gc_boundaries () = Client.(At (Time.of_notation_exn "2020-01-01T00:00:00Z")) in let garbage_collector = - Evm_node.{split_frequency_in_seconds = 1; history_to_keep_in_seconds = 3} + Evm_node.{split_frequency_in_seconds = 1; number_of_chunks = 3} in register ~genesis_timestamp @@ -69,23 +68,60 @@ let test_gc_boundaries () = Evm_node.wait_for_gc_finished ~gc_level:6 ~head_level:9 sequencer in (* Produce blocks, one per second. Enough to trigger a garbage collector. *) + let*@ level = Rpc.block_number sequencer in + let level = Int32.to_int level in let* _ = fold 9 () (fun i () -> + let level = level + i + 1 in let timestamp = sf "2020-01-01T00:00:0%dZ" (i + 1) in - let*@ _ = Rpc.produce_block ~timestamp sequencer in + let wait_for_split = Evm_node.wait_for_split ~level sequencer in + let wait_for_gc = + if level > 3 then + Some + (Evm_node.wait_for_gc_finished + ~gc_level:(level - 3) + ~head_level:level + sequencer) + else None + in + let* _ = wait_for_split + and* _ = + match wait_for_gc with + | None -> unit + | Some wait_for_gc -> + let* _ = wait_for_gc in + unit + and* res = Rpc.produce_block ~timestamp sequencer in + (* Just a sanity check to make sure it's a success, but it should + be obviously true as it would have failed on the wait for events. *) + assert (Result.is_ok res) ; unit) in - let* _ = wait_for_split in - let* _ = wait_for_gc in + let* _ = wait_for_split and* _ = wait_for_gc in (* The GC happenned at level 6, therefore the block 6 is supposed to be the earliest block. *) let*@ block = Rpc.get_block_by_number ~block:"earliest" sequencer in Check.((block.number = 6l) int32) ~error_msg:"Earliest block should be 6, but got %L" ; + let*@ _balance = + Rpc.get_balance + ~address:"0xB53dc01974176E5dFf2298C5a94343c2585E3c54" + ~block:(Number 6) + sequencer + in (* The block storage should also have cleaned everything behind 6. *) let*@? err = Rpc.get_block_by_number ~block:"5" sequencer in Check.(err.message =~ rex "Block 5 not found") ~error_msg:"The block 5 should be missing" ; + let*@? err = + Rpc.get_balance + ~address:"0xB53dc01974176E5dFf2298C5a94343c2585E3c54" + ~block:(Number 5) + sequencer + in + Check.(err.message =~ rex "No state available for block 5") + ~error_msg:"The state for block 5 should be missing" ; + unit let () = test_gc_boundaries () -- GitLab