From 3e59bb967e6d3b35ea5efaf73dfbd1e37e05c4d6 Mon Sep 17 00:00:00 2001 From: Valentin Chaboche Date: Thu, 21 Nov 2024 12:44:41 +0100 Subject: [PATCH] EVM/Node: support [with_delayed_transactions] in produceBlock Allows to produce blocks on a speculative branch without delayed transactions, useful for testing. --- etherlink/CHANGES_NODE.md | 2 + etherlink/bin_node/lib_dev/block_producer.ml | 45 ++++++++++++------ etherlink/bin_node/lib_dev/block_producer.mli | 14 ++++-- etherlink/bin_node/lib_dev/rpc_encodings.ml | 18 +++++++- etherlink/bin_node/lib_dev/rpc_encodings.mli | 7 ++- etherlink/bin_node/lib_dev/services.ml | 13 +++++- etherlink/tezt/lib/evm_node.ml | 8 ++++ etherlink/tezt/lib/evm_node.mli | 2 + etherlink/tezt/lib/rpc.ml | 18 ++++++-- etherlink/tezt/lib/rpc.mli | 17 +++++-- etherlink/tezt/tests/evm_sequencer.ml | 46 ++++++++++++++++++- 11 files changed, 159 insertions(+), 31 deletions(-) diff --git a/etherlink/CHANGES_NODE.md b/etherlink/CHANGES_NODE.md index 56560c46a5ab..c5b6e4354d2d 100644 --- a/etherlink/CHANGES_NODE.md +++ b/etherlink/CHANGES_NODE.md @@ -12,6 +12,8 @@ - Use a single RPC (if the rollup node supports it) when fetching EVM events. (!15629) +- Private RPC `produceBlock` can produce a block without delayed transactions, + useful for testing purposes. (!15681) ## Version 0.8 (2024-11-15) diff --git a/etherlink/bin_node/lib_dev/block_producer.ml b/etherlink/bin_node/lib_dev/block_producer.ml index 1bdde4f3be4d..c1d8b2d53be1 100644 --- a/etherlink/bin_node/lib_dev/block_producer.ml +++ b/etherlink/bin_node/lib_dev/block_producer.ml @@ -77,7 +77,7 @@ end module Request = struct type ('a, 'b) t = - | Produce_block : (Time.Protocol.t * bool) -> (int, tztrace) t + | Produce_block : (bool * Time.Protocol.t * bool) -> (int, tztrace) t type view = View : _ t -> view @@ -90,15 +90,17 @@ module Request = struct case (Tag 0) ~title:"Produce_block" - (obj3 + (obj4 (req "request" (constant "produce_block")) + (req "with_delayed_transactions" bool) (req "timestamp" Time.Protocol.encoding) (req "force" bool)) (function - | View (Produce_block (timestamp, force)) -> - Some ((), timestamp, force)) - (fun ((), timestamp, force) -> - View (Produce_block (timestamp, force))); + | View (Produce_block (with_delayed_transactions, timestamp, force)) + -> + Some ((), with_delayed_transactions, timestamp, force)) + (fun ((), with_delayed_transactions, timestamp, force) -> + View (Produce_block (with_delayed_transactions, timestamp, force))); ] let pp _ppf (View _) = () @@ -213,19 +215,26 @@ let produce_block_if_needed ~cctxt ~smart_rollup_address ~sequencer_key ~force return n else return 0 -let head_info_and_delayed_transactions maximum_number_of_chunks = +let head_info_and_delayed_transactions ~with_delayed_transactions + maximum_number_of_chunks = let open Lwt_result_syntax in (* We need to first fetch the delayed transactions then requests the head info. If the order is swapped, we might face a race condition where the delayed transactions are fetched from state more recent than head info. *) let* delayed_hashes, remaining_cumulative_size = - take_delayed_transactions maximum_number_of_chunks + if with_delayed_transactions then + take_delayed_transactions maximum_number_of_chunks + else + return + ( [], + Sequencer_blueprint.maximum_usable_space_in_blueprint + maximum_number_of_chunks ) in let*! head_info = Evm_context.head_info () in return (head_info, delayed_hashes, remaining_cumulative_size) let produce_block ~cctxt ~smart_rollup_address ~sequencer_key ~force ~timestamp - ~maximum_number_of_chunks = + ~maximum_number_of_chunks ~with_delayed_transactions = let open Lwt_result_syntax in let* is_locked = Tx_pool.is_locked () in if is_locked then @@ -233,7 +242,9 @@ let produce_block ~cctxt ~smart_rollup_address ~sequencer_key ~force ~timestamp return 0 else let* head_info, delayed_hashes, remaining_cumulative_size = - head_info_and_delayed_transactions maximum_number_of_chunks + head_info_and_delayed_transactions + ~with_delayed_transactions + maximum_number_of_chunks in let is_going_to_upgrade = match head_info.pending_upgrade with @@ -274,7 +285,7 @@ module Handlers = struct fun w request -> let state = Worker.state w in match request with - | Request.Produce_block (timestamp, force) -> + | Request.Produce_block (with_delayed_transactions, timestamp, force) -> protect @@ fun () -> let { cctxt; @@ -291,6 +302,7 @@ module Handlers = struct ~force ~timestamp ~maximum_number_of_chunks + ~with_delayed_transactions type launch_error = error trace @@ -348,10 +360,17 @@ let shutdown () = let* () = Block_producer_events.shutdown () in Worker.shutdown w -let produce_block ~force ~timestamp = +let produce_block ~with_delayed_transactions ~force ~timestamp = let open Lwt_result_syntax in let*? worker = Lazy.force worker in Worker.Queue.push_request_and_wait worker - (Request.Produce_block (timestamp, force)) + (Request.Produce_block (with_delayed_transactions, timestamp, force)) |> handle_request_error + +module Internal_for_tests = struct + let produce_block ~with_delayed_transactions = + produce_block ~with_delayed_transactions +end + +let produce_block = produce_block ~with_delayed_transactions:true diff --git a/etherlink/bin_node/lib_dev/block_producer.mli b/etherlink/bin_node/lib_dev/block_producer.mli index 457eaf7add09..e7684b285f98 100644 --- a/etherlink/bin_node/lib_dev/block_producer.mli +++ b/etherlink/bin_node/lib_dev/block_producer.mli @@ -18,9 +18,17 @@ val start : parameters -> unit tzresult Lwt.t (** [shutdown ()] stops the events follower. *) val shutdown : unit -> unit Lwt.t -(** [produce_block ~force ~timestamp] takes the transactions - in the tx pool and produces a block from it, returns the number of +(** [produce_block ~force ~timestamp] takes the transactions in the tx + pool and produces a block from it, returns the number of transaction in the block. The block is not produced if the list of - transactions is empty and [force] is set to [false]. *) + transactions is empty and [force] is set to [false].*) val produce_block : force:bool -> timestamp:Time.Protocol.t -> int tzresult Lwt.t + +module Internal_for_tests : sig + val produce_block : + with_delayed_transactions:bool -> + force:bool -> + timestamp:Time.Protocol.t -> + int tzresult Lwt.t +end diff --git a/etherlink/bin_node/lib_dev/rpc_encodings.ml b/etherlink/bin_node/lib_dev/rpc_encodings.ml index 0dea4c11ef0a..c70e1fd49369 100644 --- a/etherlink/bin_node/lib_dev/rpc_encodings.ml +++ b/etherlink/bin_node/lib_dev/rpc_encodings.ml @@ -679,12 +679,26 @@ module Get_logs = struct type ('input, 'output) method_ += Method : (input, output) method_ end +type produce_block_input = { + timestamp : Time.Protocol.t option; + with_delayed_transactions : bool; +} + module Produce_block = struct - type input = Time.Protocol.t + type input = produce_block_input type output = Ethereum_types.quantity - let input_encoding = Time.Protocol.encoding + let input_encoding = + let open Data_encoding in + conv + (fun {timestamp; with_delayed_transactions} -> + (timestamp, with_delayed_transactions)) + (fun (timestamp, with_delayed_transactions) -> + {timestamp; with_delayed_transactions}) + (obj2 + (opt "timestamp" Time.Protocol.encoding) + (dft "with_delayed_transactions" bool true)) let output_encoding = Ethereum_types.quantity_encoding diff --git a/etherlink/bin_node/lib_dev/rpc_encodings.mli b/etherlink/bin_node/lib_dev/rpc_encodings.mli index ac1669296c42..8564844961e9 100644 --- a/etherlink/bin_node/lib_dev/rpc_encodings.mli +++ b/etherlink/bin_node/lib_dev/rpc_encodings.mli @@ -259,9 +259,14 @@ module Web3_sha3 : module Get_logs : METHOD with type input = Filter.t and type output = Filter.changes list +type produce_block_input = { + timestamp : Time.Protocol.t option; + with_delayed_transactions : bool; +} + module Produce_block : METHOD - with type input = Time.Protocol.t + with type input = produce_block_input and type output = Ethereum_types.quantity module Produce_proposal : diff --git a/etherlink/bin_node/lib_dev/services.ml b/etherlink/bin_node/lib_dev/services.ml index 79619d153667..851718525750 100644 --- a/etherlink/bin_node/lib_dev/services.ml +++ b/etherlink/bin_node/lib_dev/services.ml @@ -770,11 +770,20 @@ let dispatch_private_request (rpc : Configuration.rpc) | Method (Produce_block.Method, _) when block_production <> `Single_node -> unsupported () | Method (Produce_block.Method, module_) -> - let f (timestamp : Time.Protocol.t option) = + let f input = let open Lwt_result_syntax in + let timestamp, with_delayed_transactions = + match input with + | Some {timestamp; with_delayed_transactions} -> + (timestamp, with_delayed_transactions) + | None -> (None, true) + in let timestamp = Option.value timestamp ~default:(Misc.now ()) in let* nb_transactions = - Block_producer.produce_block ~force:true ~timestamp + Block_producer.Internal_for_tests.produce_block + ~with_delayed_transactions + ~force:true + ~timestamp in rpc_ok (Ethereum_types.quantity_of_z @@ Z.of_int nb_transactions) in diff --git a/etherlink/tezt/lib/evm_node.ml b/etherlink/tezt/lib/evm_node.ml index a36583cf061a..083e29b50442 100644 --- a/etherlink/tezt/lib/evm_node.ml +++ b/etherlink/tezt/lib/evm_node.ml @@ -565,6 +565,14 @@ let wait_for_gc_finished ?gc_level ?head_level evm_node = else None | None, None -> Some (event_gc_level, event_gc_level) +let wait_for_processed_l1_level ?level evm_node = + wait_for_event evm_node ~event:"evm_context_processed_l1_level.v0" + @@ fun json -> + let event_level = JSON.(json |> as_int) in + match level with + | None -> Some () + | Some level -> if level = event_level then Some () else None + let create ?(path = Uses.path Constant.octez_evm_node) ?name ?runner ?(mode = Proxy) ?data_dir ?rpc_addr ?rpc_port ?restricted_rpcs endpoint = let arguments, rpc_addr, rpc_port = diff --git a/etherlink/tezt/lib/evm_node.mli b/etherlink/tezt/lib/evm_node.mli index f5f1cb8c8fb6..46d6c516b63e 100644 --- a/etherlink/tezt/lib/evm_node.mli +++ b/etherlink/tezt/lib/evm_node.mli @@ -224,6 +224,8 @@ val wait_for_rollup_node_follower_connection_acquired : val wait_for_rollup_node_follower_disabled : ?timeout:float -> t -> unit Lwt.t +val wait_for_processed_l1_level : ?level:int -> t -> unit Lwt.t + module Config_file : sig (** Node configuration files. *) diff --git a/etherlink/tezt/lib/rpc.ml b/etherlink/tezt/lib/rpc.ml index 66cf9ca49685..b03939d769e9 100644 --- a/etherlink/tezt/lib/rpc.ml +++ b/etherlink/tezt/lib/rpc.ml @@ -70,10 +70,18 @@ module Request = struct parameters = `A [`String block; `Bool full_tx_objects]; } - let produceBlock ?timestamp () = - let parameters = - match timestamp with None -> `Null | Some timestamp -> `String timestamp + let produceBlock ?with_delayed_transactions ?timestamp () = + let with_delayed_transactions = + match with_delayed_transactions with + | Some false -> [("with_delayed_transactions", `Bool false)] + | _ -> [] + in + let timestamp = + match timestamp with + | Some timestamp -> [("timestamp", `String timestamp)] + | None -> [] in + let parameters = `O (with_delayed_transactions @ timestamp) in {method_ = "produceBlock"; parameters} let stateValue path = {method_ = "stateValue"; parameters = `String path} @@ -282,12 +290,12 @@ module Syntax = struct | Error err -> f err end -let produce_block ?timestamp evm_node = +let produce_block ?with_delayed_transactions ?timestamp evm_node = let* json = Evm_node.call_evm_rpc ~private_:true evm_node - (Request.produceBlock ?timestamp ()) + (Request.produceBlock ?with_delayed_transactions ?timestamp ()) in return @@ decode_or_error diff --git a/etherlink/tezt/lib/rpc.mli b/etherlink/tezt/lib/rpc.mli index dccc8d7c871b..4455f452b8ba 100644 --- a/etherlink/tezt/lib/rpc.mli +++ b/etherlink/tezt/lib/rpc.mli @@ -24,7 +24,11 @@ module Request : sig val eth_getBlockByNumber : block:string -> full_tx_objects:bool -> Evm_node.request - val produceBlock : ?timestamp:string -> unit -> Evm_node.request + val produceBlock : + ?with_delayed_transactions:bool -> + ?timestamp:string -> + unit -> + Evm_node.request val produceProposal : ?timestamp:string -> unit -> Evm_node.request @@ -94,9 +98,14 @@ module Syntax : sig ('a option, error) result Lwt.t -> ('a -> 'c Lwt.t) -> 'c Lwt.t end -(** [produce_block ?timestamp evm_node] calls the private RPC [produceBlock]. If - provided the block will have timestamp [timestamp] (in RFC3339) format. *) -val produce_block : ?timestamp:string -> Evm_node.t -> (int, error) result Lwt.t +(** [produce_block ?with_delayed_transactions ?timestamp evm_node] + calls the private RPC [produceBlock]. If provided the block will + have timestamp [timestamp] (in RFC3339) format. *) +val produce_block : + ?with_delayed_transactions:bool -> + ?timestamp:string -> + Evm_node.t -> + (int, error) result Lwt.t (** [produce_proposal ?timestamp evm_node] calls the private RPC [produceProposal]. If provided the block will have timestamp [timestamp] (in RFC3339) format. *) diff --git a/etherlink/tezt/tests/evm_sequencer.ml b/etherlink/tezt/tests/evm_sequencer.ml index 4c4b844dab72..c7c1759a3dbc 100644 --- a/etherlink/tezt/tests/evm_sequencer.ml +++ b/etherlink/tezt/tests/evm_sequencer.ml @@ -7017,6 +7017,49 @@ let test_inconsistent_da_fees = timeout" ; unit +let test_produce_block_with_no_delayed_transactions = + register_all + ~time_between_blocks:Nothing + ~tags:["evm"; "delayed_transaction"] + ~title:"Produce block with no delayed transactions" + @@ fun {client; l1_contracts; sc_rollup_address; sc_rollup_node; sequencer; _} + _protocol -> + (* Send a random transaction in the delayed inbox. *) + let* tx1 = + Cast.craft_tx + ~source_private_key:Eth_account.bootstrap_accounts.(0).private_key + ~chain_id:1337 + ~nonce:0 + ~gas_price:21_000 + ~gas:21_000 + ~value:(Wei.of_string "10") + ~address:"0x0000000000000000000000000000000000000000" + () + in + let* _hash = + send_raw_transaction_to_delayed_inbox + ~sc_rollup_node + ~client + ~l1_contracts + ~sc_rollup_address + tx1 + in + (* Finalize the transaction so the sequencer sees the event. *) + let* l1_level = Client.level client in + let wait_for = + Evm_node.wait_for_processed_l1_level ~level:l1_level sequencer + in + let* _ = next_rollup_node_level ~sc_rollup_node ~client in + let* _ = next_rollup_node_level ~sc_rollup_node ~client in + let* () = wait_for in + + let*@ n = Rpc.produce_block ~with_delayed_transactions:false sequencer in + Check.((n = 0) int) ~error_msg:"Block should be empty but got %L transactions" ; + let*@ n = Rpc.produce_block sequencer in + Check.((n = 1) int) ~error_msg:"Block should have one transaction but got %L" ; + + unit + let protocols = Protocol.all let () = @@ -7118,4 +7161,5 @@ let () = test_trace_transaction_calltracer_failed_create protocols ; test_configuration_service [Protocol.Alpha] ; test_overwrite_simulation_tick_limit protocols ; - test_inconsistent_da_fees protocols + test_inconsistent_da_fees protocols ; + test_produce_block_with_no_delayed_transactions protocols -- GitLab