diff --git a/etherlink/CHANGES_KERNEL.md b/etherlink/CHANGES_KERNEL.md index aff63e1db7e0649f22b31b3250441006048459a2..ee9d5bb62fe6f722e3dd54481dadcdd78343e246 100644 --- a/etherlink/CHANGES_KERNEL.md +++ b/etherlink/CHANGES_KERNEL.md @@ -12,6 +12,12 @@ ### Breaking changes +- Sequencer upgrade expect an activation timestamp attached to the new + public key. The new sequencer is activated when the rollup sees a l1 + block with a timestamp greater than or equal to the activation + timestamp. Blueprint in the storage are deleted during the sequencer + upgrade as they can be trusted anymore. (!12038) + ### Internal ## Version 20ab639f09a8c7c76f982383c3d9e1f831f38088 diff --git a/etherlink/bin_node/main.ml b/etherlink/bin_node/main.ml index 8302eb0e941348c7497b2aa5740b73c9e366500d..cf9f122ace6d885564aabf8bc1199c4f4f4fdeef 100644 --- a/etherlink/bin_node/main.ml +++ b/etherlink/bin_node/main.ml @@ -642,6 +642,18 @@ let proxy_command = let* () = wait in return_unit) +let register_wallet ~wallet_dir = + let wallet_ctxt = + new Client_context_unix.unix_io_wallet + ~base_dir:wallet_dir + ~password_filename:None + in + let () = + Client_main_run.register_default_signer + (wallet_ctxt :> Client_context.io_wallet) + in + wallet_ctxt + let sequencer_command = let open Tezos_clic in let open Lwt_result_syntax in @@ -694,15 +706,7 @@ let sequencer_command = rollup_node_endpoint sequencer_str () -> - let wallet_ctxt = - new Client_context_unix.unix_io_wallet - ~base_dir:wallet_dir - ~password_filename:None - in - let () = - Client_main_run.register_default_signer - (wallet_ctxt :> Client_context.io_wallet) - in + let wallet_ctxt = register_wallet ~wallet_dir in let* sequencer = Client_keys.Secret_key.parse_source_string wallet_ctxt sequencer_str in @@ -961,15 +965,7 @@ let chunker_command = | Some k -> Lwt.return k | None -> Lwt.fail_with "missing sequencer key" in - let wallet_ctxt = - new Client_context_unix.unix_io_wallet - ~base_dir:wallet_dir - ~password_filename:None - in - let () = - Client_main_run.register_default_signer - (wallet_ctxt :> Client_context.io_wallet) - in + let wallet_ctxt = register_wallet ~wallet_dir in let+ sequencer_key = Client_keys.Secret_key.parse_source_string wallet_ctxt sequencer_str in @@ -1023,7 +1019,53 @@ let make_upgrade_command = List [Value root_hash_bytes; Value activation_timestamp] in let payload = encode kernel_upgrade in - Printf.printf "%s" Hex.(of_bytes payload |> show) ; + Printf.printf "%s%!" Hex.(of_bytes payload |> show) ; + return_unit) + +let make_sequencer_upgrade_command = + let open Tezos_clic in + let open Lwt_result_syntax in + command + ~desc:"Create bytes payload for the sequencer upgrade entrypoint" + (args1 wallet_dir_arg) + (prefixes + [ + "make"; + "sequencer"; + "upgrade"; + "payload"; + "at"; + "activation"; + "timestamp"; + ] + @@ param + ~name:"activation_timestamp" + ~desc: + "After activation timestamp, the kernel will upgrade to this value" + Params.timestamp + @@ prefix "for" @@ Params.sequencer_key @@ stop) + (fun wallet_dir activation_timestamp sequencer_str () -> + let open Rlp in + let wallet_ctxt = register_wallet ~wallet_dir in + let* _pk_uri, sequencer_pk_opt = + Client_keys.Public_key.parse_source_string wallet_ctxt sequencer_str + in + let activation_timestamp = + Evm_node_lib_dev.Helpers.timestamp_to_bytes activation_timestamp + in + let*? sequencer_pk = + Option.to_result + ~none:[error_of_fmt "invalid format or unknown public key."] + sequencer_pk_opt + in + let sequencer_pk_bytes = + Signature.Public_key.to_b58check sequencer_pk |> String.to_bytes + in + let kernel_upgrade = + List [Value sequencer_pk_bytes; Value activation_timestamp] + in + let payload = encode kernel_upgrade in + Printf.printf "%s%!" Hex.(of_bytes payload |> show) ; return_unit) let init_from_rollup_node_command = @@ -1101,6 +1143,7 @@ let commands = observer_command; chunker_command; make_upgrade_command; + make_sequencer_upgrade_command; init_from_rollup_node_command; dump_to_rlp; ] diff --git a/etherlink/kernel_evm/ethereum/src/rlp_helpers.rs b/etherlink/kernel_evm/ethereum/src/rlp_helpers.rs index 615abe9bfa0c9ef9e8f6a8b2fbfc59d2a1083e8a..b03e08c3e36dceb0ff0e53b10def977384680029 100644 --- a/etherlink/kernel_evm/ethereum/src/rlp_helpers.rs +++ b/etherlink/kernel_evm/ethereum/src/rlp_helpers.rs @@ -10,7 +10,7 @@ use crate::transaction::{ }; use primitive_types::{H256, U256}; use rlp::{Decodable, DecoderError, Encodable, Rlp, RlpIterator, RlpStream}; -use tezos_smart_rollup_encoding::timestamp::Timestamp; +use tezos_smart_rollup_encoding::{public_key::PublicKey, timestamp::Timestamp}; pub fn next<'a, 'v>(decoder: &mut RlpIterator<'a, 'v>) -> Result, DecoderError> { decoder.next().ok_or(DecoderError::RlpIncorrectListLen) @@ -308,3 +308,25 @@ pub fn decode_option_u64_le( _ => Err(DecoderError::RlpIncorrectListLen), } } + +pub fn append_public_key(stream: &mut RlpStream, public_key: &PublicKey) { + let pk_b58 = PublicKey::to_b58check(public_key); + let pk_bytes = String::as_bytes(&pk_b58); + stream.append(&pk_bytes.to_vec()); +} + +pub fn decode_public_key(decoder: &Rlp<'_>) -> Result { + let bytes: Vec = decode_field(decoder, "public_key")?; + + let pk_b58 = String::from_utf8(bytes.to_vec()).map_err(|_| { + DecoderError::Custom( + "Invalid conversion from timestamp vector of bytes to bytes.", + ) + })?; + let pk = PublicKey::from_b58check(&pk_b58).map_err(|_| { + DecoderError::Custom( + "Invalid conversion from timestamp vector of bytes to bytes.", + ) + })?; + Ok(pk) +} diff --git a/etherlink/kernel_evm/kernel/src/block.rs b/etherlink/kernel_evm/kernel/src/block.rs index 3c382c096ff5d5324904c9943663dbe8cadec5e6..b87115af8560bec8ab61aa53095b1bce1f2124ba 100644 --- a/etherlink/kernel_evm/kernel/src/block.rs +++ b/etherlink/kernel_evm/kernel/src/block.rs @@ -270,15 +270,15 @@ pub fn produce( &mut tick_counter, &mut first_block_of_reboot, )? { - ComputationResult::Finished => { - storage::delete_block_in_progress(host)?; - } + ComputationResult::Finished => storage::delete_block_in_progress(host)?, ComputationResult::RebootNeeded => { return Ok(ComputationResult::RebootNeeded) } }, } + upgrade::possible_sequencer_upgrade(host)?; + // Execute stored blueprints while let Some(block_in_progress) = next_bip_from_blueprints( host, diff --git a/etherlink/kernel_evm/kernel/src/blueprint_storage.rs b/etherlink/kernel_evm/kernel/src/blueprint_storage.rs index d0dce0fee472b096cf8a971998d5d48aeeb7e050..03f6f4d33f93c3bd7c48384499744e542b56ebad 100644 --- a/etherlink/kernel_evm/kernel/src/blueprint_storage.rs +++ b/etherlink/kernel_evm/kernel/src/blueprint_storage.rs @@ -350,6 +350,17 @@ pub fn drop_head_blueprint(host: &mut Host) -> Result<(), Error> host.store_delete(&path).map_err(Error::from) } +pub fn clear_all_blueprint(host: &mut Host) -> Result<(), Error> { + if host.store_has(&EVM_BLUEPRINTS)?.is_some() { + let last_blueprint_number = read_last_blueprint_number(host)?; + host.store_delete(&EVM_BLUEPRINTS) + .map_err(StorageError::from)?; + store_last_blueprint_number(host, last_blueprint_number) + } else { + Ok(()) + } +} + #[cfg(test)] mod tests { diff --git a/etherlink/kernel_evm/kernel/src/inbox.rs b/etherlink/kernel_evm/kernel/src/inbox.rs index 69b3e1a8dfbc7945a06b0dd7e994c617e14ca813..0ac9cccbbc74d696adb1b8452253928380691326 100644 --- a/etherlink/kernel_evm/kernel/src/inbox.rs +++ b/etherlink/kernel_evm/kernel/src/inbox.rs @@ -13,7 +13,7 @@ use crate::storage::{ chunked_transaction_path, clear_events, create_chunked_transaction, get_and_increment_deposit_nonce, read_last_info_per_level_timestamp, remove_chunked_transaction, remove_sequencer, store_l1_level, - store_last_info_per_level_timestamp, store_sequencer, store_transaction_chunk, + store_last_info_per_level_timestamp, store_transaction_chunk, }; use crate::upgrade::*; use crate::Error; @@ -315,8 +315,10 @@ pub fn handle_input( } } Input::Upgrade(kernel_upgrade) => store_kernel_upgrade(host, &kernel_upgrade)?, + Input::SequencerUpgrade(sequencer_upgrade) => { + store_sequencer_upgrade(host, &sequencer_upgrade)? + } Input::RemoveSequencer => remove_sequencer(host)?, - Input::NewSequencer(sequencer) => store_sequencer(host, sequencer)?, Input::Info(info) => { // New inbox level detected, remove all previous events. clear_events(host)?; diff --git a/etherlink/kernel_evm/kernel/src/parsing.rs b/etherlink/kernel_evm/kernel/src/parsing.rs index d075a296240ee586a455dfe2552722cba6f6c67f..331b7b96c1ebe5017b168bac1386a44ac3ed78c8 100644 --- a/etherlink/kernel_evm/kernel/src/parsing.rs +++ b/etherlink/kernel_evm/kernel/src/parsing.rs @@ -10,6 +10,7 @@ use crate::{ inbox::{Deposit, Transaction, TransactionContent}, sequencer_blueprint::{SequencerBlueprint, UnsignedSequencerBlueprint}, upgrade::KernelUpgrade, + upgrade::SequencerUpgrade, }; use primitive_types::{H160, U256}; use rlp::Encodable; @@ -89,8 +90,8 @@ pub enum Input { SimpleTransaction(Box), Deposit(Deposit), Upgrade(KernelUpgrade), + SequencerUpgrade(SequencerUpgrade), RemoveSequencer, - NewSequencer(PublicKey), NewChunkedTransaction { tx_hash: TransactionHash, num_chunks: u16, @@ -204,9 +205,9 @@ impl InputResult { if bytes.is_empty() { Self::Input(Input::RemoveSequencer) } else { - let pk_b58 = parsable!(String::from_utf8(bytes.to_vec()).ok()); - let pk = parsable!(PublicKey::from_b58check(&pk_b58).ok()); - Self::Input(Input::NewSequencer(pk)) + let sequencer_upgrade = + parsable!(SequencerUpgrade::from_rlp_bytes(bytes).ok()); + Self::Input(Input::SequencerUpgrade(sequencer_upgrade)) } } diff --git a/etherlink/kernel_evm/kernel/src/storage.rs b/etherlink/kernel_evm/kernel/src/storage.rs index 0fccd1730656fad53cd35b8af3a837e500cc020f..e5e737e197f08cc40ee2f99aa84108d93d4ac894 100644 --- a/etherlink/kernel_evm/kernel/src/storage.rs +++ b/etherlink/kernel_evm/kernel/src/storage.rs @@ -899,9 +899,9 @@ pub fn remove_sequencer(host: &mut Host) -> anyhow::Result<()> { pub fn store_sequencer( host: &mut Host, - public_key: PublicKey, + public_key: &PublicKey, ) -> anyhow::Result<()> { - let pk_b58 = PublicKey::to_b58check(&public_key); + let pk_b58 = PublicKey::to_b58check(public_key); let bytes = String::as_bytes(&pk_b58); host.store_write_all(&SEQUENCER, bytes).map_err(Into::into) } diff --git a/etherlink/kernel_evm/kernel/src/upgrade.rs b/etherlink/kernel_evm/kernel/src/upgrade.rs index 5c58368833390ea77162b77c1df872bb7a1b0727..81b236770b3f81056fd52da536a5b196dc831c12 100644 --- a/etherlink/kernel_evm/kernel/src/upgrade.rs +++ b/etherlink/kernel_evm/kernel/src/upgrade.rs @@ -4,37 +4,51 @@ // // SPDX-License-Identifier: MIT +use crate::blueprint_storage; use crate::error::UpgradeProcessError; use crate::event::Event; +use crate::safe_storage::KernelRuntime; +use crate::storage; use crate::storage::read_optional_rlp; +use crate::storage::store_sequencer; use anyhow::Context; use rlp::Decodable; use rlp::DecoderError; use rlp::Encodable; +use tezos_ethereum::rlp_helpers::append_public_key; use tezos_ethereum::rlp_helpers::append_timestamp; use tezos_ethereum::rlp_helpers::decode_field; +use tezos_ethereum::rlp_helpers::decode_public_key; use tezos_ethereum::rlp_helpers::decode_timestamp; use tezos_ethereum::rlp_helpers::next; use tezos_evm_logging::{log, Level::*}; use tezos_smart_rollup_core::PREIMAGE_HASH_SIZE; +use tezos_smart_rollup_encoding::public_key::PublicKey; use tezos_smart_rollup_encoding::timestamp::Timestamp; use tezos_smart_rollup_host::path::OwnedPath; use tezos_smart_rollup_host::path::RefPath; use tezos_smart_rollup_host::runtime::Runtime; use tezos_smart_rollup_installer_config::binary::promote::upgrade_reveal_flow; +const KERNEL_UPGRADE: RefPath = RefPath::assert_from(b"/kernel_upgrade"); +const SEQUENCER_UPGRADE: RefPath = RefPath::assert_from(b"/sequencer_upgrade"); + #[derive(Debug, PartialEq, Clone)] pub struct KernelUpgrade { pub preimage_hash: [u8; PREIMAGE_HASH_SIZE], pub activation_timestamp: Timestamp, } +impl KernelUpgrade { + const RLP_LIST_SIZE: usize = 2; +} + impl Decodable for KernelUpgrade { fn decode(decoder: &rlp::Rlp) -> Result { if !decoder.is_list() { return Err(DecoderError::RlpExpectedToBeList); } - if decoder.item_count()? != 2 { + if decoder.item_count()? != KernelUpgrade::RLP_LIST_SIZE { return Err(DecoderError::RlpIncorrectListLen); } @@ -54,18 +68,16 @@ impl Decodable for KernelUpgrade { impl Encodable for KernelUpgrade { fn rlp_append(&self, stream: &mut rlp::RlpStream) { - stream.begin_list(2); + stream.begin_list(KernelUpgrade::RLP_LIST_SIZE); stream.append_iter(self.preimage_hash); append_timestamp(stream, self.activation_timestamp); } } -const KERNEL_UPGRADE: RefPath = RefPath::assert_from(b"/kernel_upgrade"); - pub fn store_kernel_upgrade( host: &mut Host, kernel_upgrade: &KernelUpgrade, -) -> Result<(), anyhow::Error> { +) -> anyhow::Result<()> { log!( host, Info, @@ -82,7 +94,7 @@ pub fn store_kernel_upgrade( pub fn read_kernel_upgrade( host: &Host, -) -> Result, anyhow::Error> { +) -> anyhow::Result> { let path = OwnedPath::from(KERNEL_UPGRADE); read_optional_rlp(host, &path).context("Failed to decode kernel upgrade") } @@ -114,6 +126,99 @@ pub fn upgrade( Ok(()) } +#[derive(Debug, PartialEq, Clone)] +pub struct SequencerUpgrade { + pub sequencer: PublicKey, + pub activation_timestamp: Timestamp, +} + +impl SequencerUpgrade { + const RLP_LIST_SIZE: usize = 2; +} + +impl Decodable for SequencerUpgrade { + fn decode(decoder: &rlp::Rlp) -> Result { + if !decoder.is_list() { + return Err(DecoderError::RlpExpectedToBeList); + } + if decoder.item_count()? != SequencerUpgrade::RLP_LIST_SIZE { + return Err(DecoderError::RlpIncorrectListLen); + } + + let mut it = decoder.iter(); + let sequencer = decode_public_key(&next(&mut it)?)?; + let activation_timestamp = decode_timestamp(&next(&mut it)?)?; + + Ok(Self { + sequencer, + activation_timestamp, + }) + } +} + +impl Encodable for SequencerUpgrade { + fn rlp_append(&self, stream: &mut rlp::RlpStream) { + stream.begin_list(SequencerUpgrade::RLP_LIST_SIZE); + append_public_key(stream, &self.sequencer); + append_timestamp(stream, self.activation_timestamp); + } +} + +pub fn store_sequencer_upgrade( + host: &mut Host, + sequencer_upgrade: &SequencerUpgrade, +) -> anyhow::Result<()> { + log!( + host, + Info, + "A sequencer upgrade to {} is planned for {}", + sequencer_upgrade.sequencer.to_b58check(), + sequencer_upgrade.activation_timestamp + ); + let path = OwnedPath::from(SEQUENCER_UPGRADE); + let bytes = &sequencer_upgrade.rlp_bytes(); + host.store_write_all(&path, bytes) + .context("Failed to store sequencer upgrade") +} + +pub fn read_sequencer_upgrade( + host: &Host, +) -> anyhow::Result> { + let path = OwnedPath::from(SEQUENCER_UPGRADE); + read_optional_rlp(host, &path).context("Failed to decode sequencer upgrade") +} + +fn delete_sequencer_upgrade(host: &mut Host) -> anyhow::Result<()> { + host.store_delete(&SEQUENCER_UPGRADE) + .context("Failed to delete sequencer upgrade") +} + +fn sequencer_upgrade( + host: &mut Host, + sequencer: &PublicKey, +) -> anyhow::Result<()> { + log!(host, Info, "sequencer upgrade initialisation."); + + store_sequencer(host, sequencer)?; + delete_sequencer_upgrade(host)?; + log!(host, Info, "Sequencer has been updated."); + Ok(()) +} + +pub fn possible_sequencer_upgrade( + host: &mut Host, +) -> anyhow::Result<()> { + let upgrade = read_sequencer_upgrade(host)?; + if let Some(upgrade) = upgrade { + let ipl_timestamp = storage::read_last_info_per_level_timestamp(host)?; + if ipl_timestamp >= upgrade.activation_timestamp { + sequencer_upgrade(host, &upgrade.sequencer)?; + blueprint_storage::clear_all_blueprint(host)?; + } + } + Ok(()) +} + #[cfg(test)] mod tests { use super::*; diff --git a/etherlink/tezt/lib/evm_node.ml b/etherlink/tezt/lib/evm_node.ml index a2de05d3b5fcf3dac62b028a01f2de85ff9461fb..b7d8d81d4b08b528fc1411dc1dc8081ac58a2d4d 100644 --- a/etherlink/tezt/lib/evm_node.ml +++ b/etherlink/tezt/lib/evm_node.ml @@ -486,3 +486,29 @@ let transform_dump ~dump_json ~dump_rlp = let args = ["transform"; "dump"; dump_json; "to"; "rlp"; dump_rlp] in let process = Process.spawn (Uses.path Constant.octez_evm_node) @@ args in Process.check process + +let sequencer_upgrade_payload ?client ~public_key ~activation_timestamp () = + let args = + [ + "make"; + "sequencer"; + "upgrade"; + "payload"; + "at"; + "activation"; + "timestamp"; + activation_timestamp; + "for"; + public_key; + ] + in + let process = + Process.spawn (Uses.path Constant.octez_evm_node) + @@ args + @ Cli_arg.optional_arg + "wallet-dir" + Fun.id + (Option.map Client.base_dir client) + in + let* payload = Process.check_and_read_stdout process in + return (String.trim payload) diff --git a/etherlink/tezt/lib/evm_node.mli b/etherlink/tezt/lib/evm_node.mli index babab544dee3fd575d51707b6320360e5445f0d7..b18b47a57a019b7bcaf6f394103bb028fd35fdc1 100644 --- a/etherlink/tezt/lib/evm_node.mli +++ b/etherlink/tezt/lib/evm_node.mli @@ -173,6 +173,18 @@ val txpool_content : t -> (txpool_slot list * txpool_slot list) Lwt.t val upgrade_payload : root_hash:string -> activation_timestamp:string -> string Lwt.t +(** [sequencer_upgrade_payload ?client ~public_key + ~activation_timestamp ()] gives the sequencer upgrade payload to + put in a upgrade message, it will upgrade the sequencer to + [public_key] at the first l1 block after [activation_timestamp] + (in RFC3399 format). *) +val sequencer_upgrade_payload : + ?client:Client.t -> + public_key:string -> + activation_timestamp:string -> + unit -> + string Lwt.t + (** [init_from_rollup_node_data_dir ?devmode evm_node rollup_node] initialises the data dir of the evm node by importing the evm state from a rollup node data dir. [devmode] is false by default. *) diff --git a/etherlink/tezt/lib/helpers.ml b/etherlink/tezt/lib/helpers.ml index ff54658517952f9eeb4beedbf48e228c7c2b57d6..5e6816b53e0d384774e83f8c8f150934dd3c10b8 100644 --- a/etherlink/tezt/lib/helpers.ml +++ b/etherlink/tezt/lib/helpers.ml @@ -119,3 +119,20 @@ let upgrade ~sc_rollup_node ~sc_rollup_address ~admin ~admin_contract ~client in let* () = Client.bake_for_and_wait ~keys:[] client in unit + +let check_head_consistency ~left ~right ?error_msg () = + let open Rpc.Syntax in + let error_msg = + Option.value + ~default: + Format.( + sprintf + "Nodes do not have the same head (%s is %%L while %s is %%R" + (Evm_node.name left) + (Evm_node.name right)) + error_msg + in + let*@ left_head = Rpc.get_block_by_number ~block:"latest" left in + let*@ right_head = Rpc.get_block_by_number ~block:"latest" right in + Check.((left_head.hash = right_head.hash) string) ~error_msg ; + unit diff --git a/etherlink/tezt/lib/helpers.mli b/etherlink/tezt/lib/helpers.mli index 69ebb6f042dfc2246ba4628850a08112f52bf23e..f9eb9b5c1edba297528c2e90c674f6e36ebe17c3 100644 --- a/etherlink/tezt/lib/helpers.mli +++ b/etherlink/tezt/lib/helpers.mli @@ -95,3 +95,9 @@ val upgrade : upgrade_to:Uses.t -> activation_timestamp:string -> unit Lwt.t + +(** [check_head_consistency ~left ~right ?error_msg ()] check that the + latest block hash of [left] and [right] are equal. Fails if they + are not with [error_msg] *) +val check_head_consistency : + left:Evm_node.t -> right:Evm_node.t -> ?error_msg:string -> unit -> unit Lwt.t diff --git a/etherlink/tezt/tests/evm_rollup.ml b/etherlink/tezt/tests/evm_rollup.ml index 1e2d83a84372e198624b1523a47f6dc984812965..8747aa5f29fd158d8574a4ce9611cfa456f4aec5 100644 --- a/etherlink/tezt/tests/evm_rollup.ml +++ b/etherlink/tezt/tests/evm_rollup.ml @@ -4307,7 +4307,7 @@ let test_l2_timestamp_opcode = ~title:"Check L2 opcode timestamp" test -let test_migrate_proxy_to_sequencer = +let test_migrate_proxy_to_sequencer_future = Protocol.register_test ~__FILE__ ~tags:["evm"; "rollup_node"; "init"; "migration"; "sequencer"] @@ -4318,22 +4318,165 @@ let test_migrate_proxy_to_sequencer = Constant.smart_rollup_installer; Constant.WASM.evm_kernel; ]) - ~title:"migrate from proxy to sequencer using a sequencer admin contract" + ~title: + "migrate from proxy to sequencer using a sequencer admin contract with a \ + future timestamp" @@ fun protocol -> - let check_block_hash ~sequencer_node ~proxy_node = - let*@ expected_level = Rpc.block_number sequencer_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" sequencer_node + let genesis_timestamp = + Client.(At (Time.of_notation_exn "2020-01-01T00:00:00Z")) + in + (* 10s per block, 6 block. *) + let activation_timestamp = "2020-01-01T00:01:00Z" in + let sequencer_admin = Constant.bootstrap5 in + let sequencer_key = Constant.bootstrap4 in + let* ({ + evm_node = proxy_node; + sc_rollup_node; + client; + kernel; + sc_rollup_address; + l1_contracts; + node; + _; + } as full_evm_setup) = + setup_evm_kernel + ~timestamp:genesis_timestamp + ~sequencer_admin + ~admin:(Some Constant.bootstrap3) + protocol + in + (* Send a transaction in proxy mode. *) + let* () = + let sender = Eth_account.bootstrap_accounts.(0) in + let receiver = Eth_account.bootstrap_accounts.(1) in + let* tx = send ~sender ~receiver ~value:Wei.one_eth full_evm_setup in + check_tx_succeeded ~endpoint:(Evm_node.endpoint proxy_node) ~tx + in + (* Send the internal message to add a sequencer on the rollup. *) + let sequencer_admin_contract = + match + Option.bind l1_contracts (fun {sequencer_admin; _} -> sequencer_admin) + with + | Some contract -> contract + | None -> Test.fail "missing sequencer admin contract" + in + let* payload = + Evm_node.sequencer_upgrade_payload + ~client + ~public_key:sequencer_key.alias + ~activation_timestamp + () + in + let* () = + Client.transfer + ~amount:Tez.zero + ~giver:sequencer_admin.public_key_hash + ~receiver:sequencer_admin_contract + ~arg:(sf "Pair %S 0x%s" sc_rollup_address payload) + ~burn_cap:Tez.one + client + in + let* _ = next_evm_level ~evm_node:proxy_node ~sc_rollup_node ~node ~client in + let sequencer_node = + let mode = + Evm_node.Sequencer + { + initial_kernel = kernel; + preimage_dir = Sc_rollup_node.data_dir sc_rollup_node // "wasm_2_0_0"; + private_rpc_port = Port.fresh (); + time_between_blocks = Some Nothing; + sequencer = sequencer_key.alias; + genesis_timestamp = None; + max_blueprints_lag = None; + max_blueprints_catchup = None; + catchup_cooldown = None; + devmode = true; + wallet_dir = Some (Client.base_dir client); + } in - Check.((sequencer_head.hash = rollup_node_head.hash) string) - ~error_msg:"block hash is not equal (sequencer: %L; rollup: %R)" ; - Check.((sequencer_head.number = expected_level) int32) - ~error_msg:"block level is not equal (sequencer: %L; expected: %R)" ; - unit + Evm_node.create ~mode (Sc_rollup_node.endpoint sc_rollup_node) + in + let* () = + repeat 6 (fun () -> + let* _ = Client.bake_for_and_wait client in + unit) + in + (* Run the sequencer from the rollup node state. *) + let* () = + Evm_node.init_from_rollup_node_data_dir + ~devmode:true + sequencer_node + sc_rollup_node + in + let* () = Evm_node.run sequencer_node in + (* Same head after initialisation. *) + let* () = + check_head_consistency + ~left:sequencer_node + ~right:proxy_node + ~error_msg:"block hash is not equal (sequencer: %L; rollup: %R)" + () + in + (* Produce a block in sequencer. *) + let* _ = Rpc.produce_block sequencer_node in + (* Conservative way to make sure the produced block is published to L1. *) + let* _ = + repeat 5 (fun () -> + let* _ = + next_evm_level ~evm_node:proxy_node ~sc_rollup_node ~node ~client + in + unit) + in + (* Same head after first sequencer produced block. *) + let* () = + check_head_consistency + ~left:sequencer_node + ~right:proxy_node + ~error_msg:"block hash is not equal (sequencer: %L; rollup: %R)" + () + in + + (* Send a transaction to sequencer. *) + let* () = + let sender = Eth_account.bootstrap_accounts.(0) in + let receiver = Eth_account.bootstrap_accounts.(1) in + let full_evm_setup = {full_evm_setup with evm_node = sequencer_node} in + let* tx = send ~sender ~receiver ~value:Wei.one_eth full_evm_setup in + check_tx_succeeded ~endpoint:(Evm_node.endpoint sequencer_node) ~tx + in + (* Conservative way to make sure the produced block is published to L1. *) + let* _ = + repeat 5 (fun () -> + let* _ = + next_evm_level ~evm_node:proxy_node ~sc_rollup_node ~node ~client + in + unit) in + (* Same head after sequencer transaction. *) + let* () = + check_head_consistency + ~left:sequencer_node + ~right:proxy_node + ~error_msg:"block hash is not equal (sequencer: %L; rollup: %R)" + () + in + unit + +let test_migrate_proxy_to_sequencer_past = + Protocol.register_test + ~__FILE__ + ~tags:["evm"; "rollup_node"; "init"; "migration"; "sequencer"] + ~uses:(fun _protocol -> + [ + Constant.octez_smart_rollup_node; + Constant.octez_evm_node; + Constant.smart_rollup_installer; + Constant.WASM.evm_kernel; + ]) + ~title: + "migrate from proxy to sequencer using a sequencer admin contract with a \ + past timestamp" + @@ fun protocol -> let sequencer_admin = Constant.bootstrap5 in let sequencer_key = Constant.bootstrap4 in let* ({ @@ -4363,16 +4506,19 @@ let test_migrate_proxy_to_sequencer = | Some contract -> contract | None -> Test.fail "missing sequencer admin contract" in + let* payload = + Evm_node.sequencer_upgrade_payload + ~client + ~public_key:sequencer_key.alias + ~activation_timestamp:"0" + () + in let* () = Client.transfer ~amount:Tez.zero ~giver:sequencer_admin.public_key_hash ~receiver:sequencer_admin_contract - ~arg: - (sf - "Pair %S 0x%s" - sc_rollup_address - (Hex.of_string sequencer_key.public_key |> Hex.show)) + ~arg:(sf "Pair %S 0x%s" sc_rollup_address payload) ~burn_cap:Tez.one client in @@ -4396,7 +4542,6 @@ let test_migrate_proxy_to_sequencer = in Evm_node.create ~mode (Sc_rollup_node.endpoint sc_rollup_node) in - (* Run the sequencer from the rollup node state. *) let* () = Evm_node.init_from_rollup_node_data_dir @@ -4406,7 +4551,13 @@ let test_migrate_proxy_to_sequencer = in let* () = Evm_node.run sequencer_node in (* Same head after initialisation. *) - let* () = check_block_hash ~sequencer_node ~proxy_node in + let* () = + check_head_consistency + ~left:sequencer_node + ~right:proxy_node + ~error_msg:"block hash is not equal (sequencer: %L; rollup: %R)" + () + in (* Produce a block in sequencer. *) let* _ = Rpc.produce_block sequencer_node in @@ -4419,7 +4570,13 @@ let test_migrate_proxy_to_sequencer = unit) in (* Same head after first sequencer produced block. *) - let* () = check_block_hash ~sequencer_node ~proxy_node in + let* () = + check_head_consistency + ~left:sequencer_node + ~right:proxy_node + ~error_msg:"block hash is not equal (sequencer: %L; rollup: %R)" + () + in (* Send a transaction to sequencer. *) let* () = @@ -4438,7 +4595,13 @@ let test_migrate_proxy_to_sequencer = unit) in (* Same head after sequencer transaction. *) - let* () = check_block_hash ~sequencer_node ~proxy_node in + let* () = + check_head_consistency + ~left:sequencer_node + ~right:proxy_node + ~error_msg:"block hash is not equal (sequencer: %L; rollup: %R)" + () + in unit @@ -4826,7 +4989,8 @@ let register_evm_node ~protocols = test_regression_block_hash_gen protocols ; test_reboot_out_of_ticks protocols ; test_l2_timestamp_opcode protocols ; - test_migrate_proxy_to_sequencer protocols ; + test_migrate_proxy_to_sequencer_past protocols ; + test_migrate_proxy_to_sequencer_future protocols ; test_ghostnet_kernel protocols ; test_estimate_gas_out_of_ticks protocols ; test_l2_call_selfdetruct_contract_in_same_transaction protocols ; diff --git a/etherlink/tezt/tests/evm_sequencer.ml b/etherlink/tezt/tests/evm_sequencer.ml index c325e3b64ca29f13a81ccbd7f1ceff909bb88fc4..e7cfc852c1bbdc0bd7a7a3a728f33fced1554681 100644 --- a/etherlink/tezt/tests/evm_sequencer.ml +++ b/etherlink/tezt/tests/evm_sequencer.ml @@ -217,22 +217,6 @@ let setup_sequencer ?(devmode = true) ?config ?genesis_timestamp sc_rollup_node; } -let check_head_consistency ~left ~right ?error_msg () = - let error_msg = - Option.value - ~default: - Format.( - sprintf - "Nodes do not have the same head (%s is %%L while %s is %%R" - (Evm_node.name left) - (Evm_node.name right)) - error_msg - in - let*@ left_head = Rpc.get_block_by_number ~block:"latest" left in - let*@ right_head = Rpc.get_block_by_number ~block:"latest" right in - Check.((left_head.hash = right_head.hash) string) ~error_msg ; - unit - let send_raw_transaction_to_delayed_inbox ?(amount = Tez.one) ?expect_failure ~sc_rollup_node ~node ~client ~l1_contracts ~sc_rollup_address raw_tx = let expected_hash =