diff --git a/etherlink/bin_evm_node/evm_node.ml b/etherlink/bin_evm_node/evm_node.ml index 62454b1ff383519d51f57332e3d2ef3f5ffee2b0..1fe0f1312340a15185e403080f9017dfb95509fd 100644 --- a/etherlink/bin_evm_node/evm_node.ml +++ b/etherlink/bin_evm_node/evm_node.ml @@ -833,9 +833,34 @@ let make_upgrade_command = Printf.printf "%s" Hex.(of_bytes payload |> show) ; return_unit) +let init_from_rollup_node_command = + let open Tezos_clic in + let rollup_node_data_dir_param = + Tezos_clic.param + ~name:"rollup-node-data-dir" + ~desc:(Format.sprintf "The path to the rollup node data directory.") + Params.string + in + command + ~desc: + "initialises the EVM node data-dir using the data-dir of a rollup node." + (args1 data_dir_arg) + (prefixes ["init"; "from"; "rollup"; "node"] + @@ rollup_node_data_dir_param @@ stop) + (fun data_dir rollup_node_data_dir () -> + Evm_node_lib_dev.Sequencer_context.init_from_rollup_node + ~data_dir + ~rollup_node_data_dir) + (* List of program commands *) let commands = - [proxy_command; sequencer_command; chunker_command; make_upgrade_command] + [ + proxy_command; + sequencer_command; + chunker_command; + make_upgrade_command; + init_from_rollup_node_command; + ] let global_options = Tezos_clic.no_options diff --git a/etherlink/bin_evm_node/lib_dev/sequencer_context.ml b/etherlink/bin_evm_node/lib_dev/sequencer_context.ml index 49580d291b38ab6e788e842a789719a47dfa8111..47bf88d36936c6c53575900e55c9f93dc80b3638 100644 --- a/etherlink/bin_evm_node/lib_dev/sequencer_context.ml +++ b/etherlink/bin_evm_node/lib_dev/sequencer_context.ml @@ -168,6 +168,60 @@ let init ~data_dir ~kernel ~preimages ~smart_rollup_address ~secret_key = return ctxt +let init_from_rollup_node ~data_dir ~rollup_node_data_dir = + let open Lwt_result_syntax in + let* checkpoint = + let l2_head_path = + Filename.Infix.(rollup_node_data_dir // "storage" // "l2_head") + in + Lwt_io.with_file ~flags:[Unix.O_RDONLY; O_CLOEXEC] ~mode:Input l2_head_path + @@ fun channel -> + let*! raw_data = Lwt_io.read channel in + let Sc_rollup_block.{header = {context; _}; _} = + Data_encoding.Binary.of_string_exn Sc_rollup_block.encoding raw_data + in + Smart_rollup_context_hash.to_bytes context + |> Context_hash.of_bytes_exn |> return + in + let rollup_node_context_dir = + Filename.Infix.(rollup_node_data_dir // "context") + in + let* rollup_node_index = + Irmin_context.load ~cache_size:100_000 Read_only rollup_node_context_dir + in + let*! rollup_node_context = + Irmin_context.checkout_exn rollup_node_index checkpoint + in + let evm_context_dir = store_path ~data_dir in + let*! () = Lwt_utils_unix.create_dir evm_context_dir in + let* () = + Irmin_context.export_snapshot + rollup_node_context + checkpoint + ~path:evm_context_dir + in + let* evm_node_index = + Irmin_context.load ~cache_size:100_000 Read_write evm_context_dir + in + let*! evm_node_context = + Irmin_context.checkout_exn evm_node_index checkpoint + in + let*! evm_state = Irmin_context.PVMState.get evm_node_context in + let* current_blueprint_number = + let*! current_blueprint_number_opt = + Sequencer_state.inspect + evm_state + Durable_storage_path.Block.current_number + in + match current_blueprint_number_opt with + | Some bytes -> return (Bytes.to_string bytes |> Z.of_bits) + | None -> failwith "The blueprint number was not found" + in + let next_blueprint_number = + Ethereum_types.Qty Z.(add one current_blueprint_number) + in + store_metadata ~data_dir {checkpoint; next_blueprint_number} + let execute_and_inspect ~input ctxt = let open Lwt_result_syntax in let config = execution_config ctxt in diff --git a/etherlink/bin_evm_node/lib_dev/sequencer_context.mli b/etherlink/bin_evm_node/lib_dev/sequencer_context.mli index f54972fbccfdaf983484ec4363c68257c1fe1d5c..02e8aa8121760bfb81edffdfc27f529fbea45bbc 100644 --- a/etherlink/bin_evm_node/lib_dev/sequencer_context.mli +++ b/etherlink/bin_evm_node/lib_dev/sequencer_context.mli @@ -29,6 +29,14 @@ val init : secret_key:Signature.secret_key -> t tzresult Lwt.t +(** [init_from_rollup_node ~data_dir + ~rollup_node_data_dir ~inspect_current_blueprint_number] + initialises the irmin context and metadata of the evm using the + latest known evm state of the given rollup + node. *) +val init_from_rollup_node : + data_dir:string -> rollup_node_data_dir:string -> unit tzresult Lwt.t + (** [commit ctxt evm_state] updates the [evm_state] in [ctxt], commits to disk the changes, and update the checkpoint. *) val commit : t -> Sequencer_state.t -> t tzresult Lwt.t diff --git a/etherlink/tezt/tests/evm_sequencer.ml b/etherlink/tezt/tests/evm_sequencer.ml index fe9fad2d7955aa4a02f0306c286c47a455d55041..9b7c86a1be7ef7e90ace970346f0eb82cc1fdd38 100644 --- a/etherlink/tezt/tests/evm_sequencer.ml +++ b/etherlink/tezt/tests/evm_sequencer.ml @@ -570,6 +570,58 @@ let test_delayed_transfer_is_included = ~error_msg:"Expected a bigger balance" ; unit +(** test to initialise a sequencer data dir based on a rollup node + data dir *) +let test_init_from_rollup_node_data_dir = + Protocol.register_test + ~__FILE__ + ~tags:["evm"; "rollup_node"; "init"] + ~uses:(fun _protocol -> + [ + Constant.octez_smart_rollup_node; + Constant.octez_evm_node; + Constant.smart_rollup_installer; + Constant.WASM.evm_kernel; + ]) + ~title:"Init evm node sequencer data dir from a rollup node data dir" + @@ fun protocol -> + let* {sc_rollup_node; evm_node; client; _} = + setup_sequencer ~time_between_blocks:Nothing protocol + in + (* a sequencer is needed to produce an initial block *) + let* () = + repeat 5 (fun () -> + let* _l2_lvl = Rpc.produce_block evm_node in + let* _lvl = Client.bake_for_and_wait client in + let* _lvl = Sc_rollup_node.wait_sync ~timeout:30. sc_rollup_node in + unit) + in + let* () = Evm_node.terminate evm_node in + let* proxy_node = + Evm_node.init + ~mode:(Proxy {devmode = false}) + (Sc_rollup_node.endpoint sc_rollup_node) + in + let evm_node' = + Evm_node.create + ~mode:(Evm_node.mode evm_node) + (Sc_rollup_node.endpoint sc_rollup_node) + in + let* () = Evm_node.init_from_rollup_node_data_dir evm_node' sc_rollup_node in + let* () = Evm_node.run evm_node' in + let*@ rollup_node_head = Rpc.get_block_by_number ~block:"latest" proxy_node in + let*@ sequencer_head = Rpc.get_block_by_number ~block:"latest" evm_node' in + Check.((sequencer_head.number = rollup_node_head.number) int32) + ~error_msg:"block number is not equal (sequencer: %L; rollup: %R)" ; + let* _l2_lvl = Rpc.produce_block evm_node in + let* _lvl = Client.bake_for_and_wait client in + let* _lvl = Sc_rollup_node.wait_sync ~timeout:30. sc_rollup_node in + let*@ rollup_node_head = Rpc.get_block_by_number ~block:"latest" proxy_node in + let*@ sequencer_head = Rpc.get_block_by_number ~block:"latest" evm_node' in + Check.((sequencer_head.number = rollup_node_head.number) int32) + ~error_msg:"block number is not equal (sequencer: %L; rollup: %R)" ; + unit + let () = test_persistent_state [Alpha] ; test_publish_blueprints [Alpha] ; @@ -578,4 +630,5 @@ let () = test_send_transaction_to_delayed_inbox [Alpha] ; test_send_deposit_to_delayed_inbox [Alpha] ; test_rpc_produceBlock [Alpha] ; - test_delayed_transfer_is_included [Alpha] + test_delayed_transfer_is_included [Alpha] ; + test_init_from_rollup_node_data_dir [Alpha] diff --git a/src/lib_layer2_store/irmin_context.ml b/src/lib_layer2_store/irmin_context.ml index 6e9313865812d9e864923b96ba1a4ebbcbddbde7..4c4ba7aa835dbd2c39996b8003418739cb4e9f65 100644 --- a/src/lib_layer2_store/irmin_context.ml +++ b/src/lib_layer2_store/irmin_context.ml @@ -195,6 +195,22 @@ let is_gc_finished index = IStore.Gc.is_finished index.repo let index context = context.index +let export_snapshot {index = {path = _; repo}; _} context_hash ~path = + let open Lwt_result_syntax in + let*! commit_opt = + IStore.Commit.of_hash repo (hash_to_istore_hash context_hash) + in + match commit_opt with + | None -> + failwith + "Cannot export context snapshot: unknown context hash %a" + Context_hash.pp + context_hash + | Some commit -> + let h = IStore.Commit.key commit in + let*! () = IStore.create_one_commit_store repo h path in + return_unit + module Proof (Hash : sig type t diff --git a/src/lib_layer2_store/irmin_context.mli b/src/lib_layer2_store/irmin_context.mli index 1b17f64fd36f8e738c5460d45ba545272ab4ad54..1342030d7d95b7de36c55525899ec4bd014bab32 100644 --- a/src/lib_layer2_store/irmin_context.mli +++ b/src/lib_layer2_store/irmin_context.mli @@ -125,6 +125,16 @@ val is_gc_finished : [> `Write] index -> bool GC run is currently ongoing. *) val wait_gc_completion : [> `Write] index -> unit Lwt.t +(** [export_snapshot index context_hash ~path] exports the context corresponding + to [context_hash], if found in [index], into the given folder path. As the + export uses the GC's behaviour to extract a single commit into a standalone + fresh store, it is not possible to export a snapshot while a GC is + running. This call will hang until the GC has finished. + + Note: there is no associated [import_snapshot] function as the import + consist in copying the exported Irmin store. *) +val export_snapshot : _ t -> hash -> path:string -> unit tzresult Lwt.t + (** Module for generating and verifying proofs for a context *) module Proof (Hash : sig type t diff --git a/tezt/lib_tezos/evm_node.ml b/tezt/lib_tezos/evm_node.ml index a2b3eb1ba49ddc83a6edcdf4cf4b3582fe1e3b80..1355020ba7c55cecc788499baabc77f0929cbb64 100644 --- a/tezt/lib_tezos/evm_node.ml +++ b/tezt/lib_tezos/evm_node.ml @@ -343,6 +343,16 @@ let init ?runner ?mode ?data_dir ?rpc_addr ?rpc_port rollup_node = let* () = run evm_node in return evm_node +let init_from_rollup_node_data_dir evm_node rollup_node = + let rollup_node_data_dir = Sc_rollup_node.data_dir rollup_node in + let process = + spawn_command + evm_node + (["init"; "from"; "rollup"; "node"; rollup_node_data_dir] + @ data_dir evm_node) + in + Process.check process + type request = {method_ : string; parameters : JSON.u} let request_to_JSON {method_; parameters} : JSON.u = diff --git a/tezt/lib_tezos/evm_node.mli b/tezt/lib_tezos/evm_node.mli index c9e36b9c510cdc1273cd27584812646e2b0815dc..b845ffb7dc7cfbd027b62c916fb24b352f501318 100644 --- a/tezt/lib_tezos/evm_node.mli +++ b/tezt/lib_tezos/evm_node.mli @@ -158,3 +158,8 @@ val txpool_content : t -> (txpool_slot list * txpool_slot list) Lwt.t RFC3399 format). *) val upgrade_payload : root_hash:string -> activation_timestamp:string -> string Lwt.t + +(** [init_from_rollup_node_data_dir evm_node rollup_node] initialises + the data dir of the evm node by importing the evm state from a + rollup node data dir. *) +val init_from_rollup_node_data_dir : t -> Sc_rollup_node.t -> unit Lwt.t