diff --git a/etherlink/kernel_evm/kernel/src/inbox.rs b/etherlink/kernel_evm/kernel/src/inbox.rs index 78d95c1e57d0cefa36f00fe78992b47760c0c224..3dde01097e044b6504a287d05868d136299894dd 100644 --- a/etherlink/kernel_evm/kernel/src/inbox.rs +++ b/etherlink/kernel_evm/kernel/src/inbox.rs @@ -14,7 +14,7 @@ use crate::storage::{ chunked_hash_transaction_path, chunked_transaction_num_chunks, chunked_transaction_path, create_chunked_transaction, get_and_increment_deposit_nonce, remove_chunked_transaction, - store_last_info_per_level_timestamp, store_transaction_chunk, + store_last_info_per_level_timestamp, store_sequencer, store_transaction_chunk, }; use crate::upgrade::*; use crate::Error; @@ -33,18 +33,33 @@ use tezos_smart_rollup_host::runtime::Runtime; pub struct TezosContracts { pub ticketer: Option, pub admin: Option, + pub sequencer_admin: Option, } impl Display for TezosContracts { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!( f, - "Ticketer is {:?}. Administrator is {:?}", - self.ticketer, self.admin, + "Ticketer is {:?}. Administrator is {:?}. Sequencer administrator is {:?}.", + self.ticketer, self.admin, self.sequencer_admin, ) } } +fn contains(contract: &Option, expected: &ContractKt1Hash) -> bool { + contract.as_ref().map_or(false, |kt1| kt1 == expected) +} +impl TezosContracts { + pub fn is_admin(&self, contract: &ContractKt1Hash) -> bool { + contains(&self.admin, contract) + } + pub fn is_sequencer_admin(&self, contract: &ContractKt1Hash) -> bool { + contains(&self.sequencer_admin, contract) + } + pub fn is_ticketer(&self, contract: &ContractKt1Hash) -> bool { + contains(&self.ticketer, contract) + } +} #[derive(Debug, PartialEq, Clone)] pub struct Deposit { pub amount: U256, @@ -339,6 +354,9 @@ pub fn read_inbox( InputResult::Input(Input::Upgrade(kernel_upgrade)) => { res.kernel_upgrade = Some(kernel_upgrade) } + InputResult::Input(Input::NewSequencer(sequencer)) => { + store_sequencer(host, sequencer)? + } InputResult::Input(Input::Simulation) => { // kernel enters in simulation mode, reading will be done by the // simulation and all the previous and next transactions are @@ -574,6 +592,7 @@ mod tests { TezosContracts { ticketer: None, admin: Some(sender), + sequencer_admin: None, }, None, None, diff --git a/etherlink/kernel_evm/kernel/src/lib.rs b/etherlink/kernel_evm/kernel/src/lib.rs index f3d145c11e69882ab484ce167199dda3611458fc..bfd98c5fafb839d3be9f388ff0bac590f0ec0145 100644 --- a/etherlink/kernel_evm/kernel/src/lib.rs +++ b/etherlink/kernel_evm/kernel/src/lib.rs @@ -20,9 +20,9 @@ use primitive_types::U256; use storage::{ read_admin, read_base_fee_per_gas, read_chain_id, read_delayed_transaction_bridge, read_kernel_version, read_last_info_per_level_timestamp, - read_last_info_per_level_timestamp_stats, read_ticketer, sequencer, - store_base_fee_per_gas, store_chain_id, store_kernel_version, store_storage_version, - STORAGE_VERSION, STORAGE_VERSION_PATH, + read_last_info_per_level_timestamp_stats, read_sequencer_admin, read_ticketer, + sequencer, store_base_fee_per_gas, store_chain_id, store_kernel_version, + store_storage_version, STORAGE_VERSION, STORAGE_VERSION_PATH, }; use tezos_crypto_rs::hash::ContractKt1Hash; use tezos_ethereum::block::BlockFees; @@ -210,10 +210,15 @@ pub fn main(host: &mut Host) -> Result<(), anyhow::Error> { .context("Failed to retrieve smart rollup address")?; let ticketer = read_ticketer(host); let admin = read_admin(host); + let sequencer_admin = read_sequencer_admin(host); let mut configuration = fetch_configuration(host)?; let block_fees = retrieve_block_fees(host)?; - let tezos_contracts = TezosContracts { ticketer, admin }; + let tezos_contracts = TezosContracts { + ticketer, + admin, + sequencer_admin, + }; // Run the stage one, this is a no-op if the inbox was already consumed // by another kernel run. This ensures that if the migration does not // consume all reboots. At least one reboot will be used to consume the diff --git a/etherlink/kernel_evm/kernel/src/migration.rs b/etherlink/kernel_evm/kernel/src/migration.rs index 0ec4ce7a536c1179f33d553274e23bac14f0a506..3588329a7514906efbe673bc55a37dbf92dc1020 100644 --- a/etherlink/kernel_evm/kernel/src/migration.rs +++ b/etherlink/kernel_evm/kernel/src/migration.rs @@ -4,7 +4,9 @@ // SPDX-License-Identifier: MIT use crate::error::Error; use crate::error::UpgradeProcessError::Fallback; -use crate::storage::{read_storage_version, store_storage_version, STORAGE_VERSION}; +use crate::storage::{ + read_storage_version, store_storage_version, SEQUENCER_ADMIN, STORAGE_VERSION, +}; use tezos_smart_rollup_host::runtime::Runtime; pub enum MigrationStatus { @@ -13,6 +15,15 @@ pub enum MigrationStatus { Done, } +fn store_sequencer_admin(host: &mut Host) -> Result<(), Error> { + // contract deployed in ghostnet with same pk as the admin + // contract + let contract_b58 = "KT1UwK4znfwrsqheq9EoBd4KFYtmbsf85eb2"; + let bytes = contract_b58.as_bytes(); + host.store_write_all(&SEQUENCER_ADMIN, bytes) + .map_err(Into::into) +} + // The workflow for migration is the following: // // - bump `storage::STORAGE_VERSION` by one @@ -35,6 +46,7 @@ fn migration(host: &mut Host) -> Result { let current_version = read_storage_version(host)?; if STORAGE_VERSION == current_version + 1 { // MIGRATION CODE - START + store_sequencer_admin(host)?; // MIGRATION CODE - END store_storage_version(host, STORAGE_VERSION)?; return Ok(MigrationStatus::Done); diff --git a/etherlink/kernel_evm/kernel/src/parsing.rs b/etherlink/kernel_evm/kernel/src/parsing.rs index 9504c30c8ef976d1d8b5a8fb367d0cd9768e49c6..be25967aae9d0f5b1e09dfb69fdd53a174473ee7 100644 --- a/etherlink/kernel_evm/kernel/src/parsing.rs +++ b/etherlink/kernel_evm/kernel/src/parsing.rs @@ -80,6 +80,7 @@ pub enum Input { SimpleTransaction(Box), Deposit(Deposit), Upgrade(KernelUpgrade), + NewSequencer(PublicKey), NewChunkedTransaction { tx_hash: TransactionHash, num_chunks: u16, @@ -182,20 +183,17 @@ impl InputResult { }) } - fn parse_kernel_upgrade( - source: ContractKt1Hash, - admin: &Option, - bytes: &[u8], - ) -> Self { - // Consider only upgrades from the bridge contract. - if admin.is_none() || &source != admin.as_ref().unwrap() { - return Self::Unparsable; - } - + fn parse_kernel_upgrade(bytes: &[u8]) -> Self { let kernel_upgrade = parsable!(KernelUpgrade::from_rlp_bytes(bytes).ok()); Self::Input(Input::Upgrade(kernel_upgrade)) } + fn parse_sequencer_update(bytes: &[u8]) -> Self { + 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)) + } + fn parse_sequencer_blueprint_input(sequencer: &PublicKey, bytes: &[u8]) -> Self { // Parse the sequencer blueprint let seq_blueprint: SequencerBlueprint = @@ -352,8 +350,14 @@ impl InputResult { ) } }, - MichelsonOr::Right(MichelsonBytes(upgrade)) => { - Self::parse_kernel_upgrade(source, &tezos_contracts.admin, &upgrade) + MichelsonOr::Right(MichelsonBytes(bytes)) => { + if tezos_contracts.is_admin(&source) { + Self::parse_kernel_upgrade(&bytes) + } else if tezos_contracts.is_sequencer_admin(&source) { + Self::parse_sequencer_update(&bytes) + } else { + Self::Unparsable + } } } } @@ -433,6 +437,7 @@ mod tests { &TezosContracts { ticketer: None, admin: None, + sequencer_admin: None }, &None, &None diff --git a/etherlink/kernel_evm/kernel/src/storage.rs b/etherlink/kernel_evm/kernel/src/storage.rs index 1f8445958d69508b9f0094bb96bb31424b84b4c5..34d8ab3673ea5bc05abf8e67d16f37f1642e17b4 100644 --- a/etherlink/kernel_evm/kernel/src/storage.rs +++ b/etherlink/kernel_evm/kernel/src/storage.rs @@ -27,13 +27,14 @@ use tezos_ethereum::wei::Wei; use primitive_types::{H160, H256, U256}; -pub const STORAGE_VERSION: u64 = 3; +pub const STORAGE_VERSION: u64 = 4; pub const STORAGE_VERSION_PATH: RefPath = RefPath::assert_from(b"/storage_version"); const KERNEL_VERSION_PATH: RefPath = RefPath::assert_from(b"/kernel_version"); const TICKETER: RefPath = RefPath::assert_from(b"/ticketer"); const ADMIN: RefPath = RefPath::assert_from(b"/admin"); +pub const SEQUENCER_ADMIN: RefPath = RefPath::assert_from(b"/sequencer_admin"); const DELAYED_BRIDGE: RefPath = RefPath::assert_from(b"/delayed_bridge"); // Path to the block in progress, used between reboots @@ -670,6 +671,10 @@ pub fn read_admin(host: &mut Host) -> Option { read_b58_kt1(host, &ADMIN.into()) } +pub fn read_sequencer_admin(host: &mut Host) -> Option { + read_b58_kt1(host, &SEQUENCER_ADMIN.into()) +} + pub fn get_and_increment_deposit_nonce( host: &mut Host, ) -> Result { @@ -801,6 +806,14 @@ pub fn sequencer(host: &Host) -> anyhow::Result Ok(None) } } +pub fn store_sequencer( + host: &mut Host, + public_key: PublicKey, +) -> anyhow::Result<()> { + 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) +} #[cfg(test)] mod internal_for_tests { diff --git a/etherlink/kernel_evm/kernel/tests/resources/failed_migration.wasm b/etherlink/kernel_evm/kernel/tests/resources/failed_migration.wasm index 1d2860545fc67f3a462038827284988e8770875e..15ade5059aa42d256f7d1da6b1e1c17c89509a3f 100755 Binary files a/etherlink/kernel_evm/kernel/tests/resources/failed_migration.wasm and b/etherlink/kernel_evm/kernel/tests/resources/failed_migration.wasm differ diff --git a/etherlink/tezt/lib/configuration.ml b/etherlink/tezt/lib/configuration.ml index ef9b1b5bd4801d44e1898332d8a9ee8529a1bfc5..152ac22a7aa8506d0668ba43de8bb300de508a74 100644 --- a/etherlink/tezt/lib/configuration.ml +++ b/etherlink/tezt/lib/configuration.ml @@ -8,8 +8,9 @@ let default_bootstrap_account_balance = Wei.of_eth_int 9999 -let make_config ?bootstrap_accounts ?ticketer ?administrator ?sequencer - ?delayed_bridge ?(flat_fee = Wei.zero) () = +let make_config ?bootstrap_accounts ?ticketer ?administrator + ?sequencer_administrator ?sequencer ?delayed_bridge ?(flat_fee = Wei.zero) + () = let open Sc_rollup_helpers.Installer_kernel_config in let ticketer = Option.fold @@ -44,6 +45,15 @@ let make_config ?bootstrap_accounts ?ticketer ?administrator ?sequencer ~none:[] administrator in + let sequencer_administrator = + Option.fold + ~some:(fun sequencer_administrator -> + let to_ = Durable_storage_path.sequencer_admin in + let value = Hex.(of_string sequencer_administrator |> show) in + [Set {value; to_}]) + ~none:[] + sequencer_administrator + in let sequencer = match sequencer with | Some secret_key -> @@ -67,8 +77,8 @@ let make_config ?bootstrap_accounts ?ticketer ?administrator ?sequencer [Set {value; to_}] in match - ticketer @ bootstrap_accounts @ administrator @ sequencer @ delayed_bridge - @ flat_fee + ticketer @ bootstrap_accounts @ administrator @ sequencer_administrator + @ sequencer @ delayed_bridge @ flat_fee with | [] -> None | res -> Some (`Config res) diff --git a/etherlink/tezt/lib/configuration.mli b/etherlink/tezt/lib/configuration.mli index 534b6ef355aa4002c013bc7f14e8a9be12653a95..9c209c46ca33e57f3cc3a5c2ac3d6b5be78c5593 100644 --- a/etherlink/tezt/lib/configuration.mli +++ b/etherlink/tezt/lib/configuration.mli @@ -16,6 +16,7 @@ val make_config : ?bootstrap_accounts:Eth_account.t array -> ?ticketer:string -> ?administrator:string -> + ?sequencer_administrator:string -> ?sequencer:string -> ?delayed_bridge:string -> ?flat_fee:Wei.t -> diff --git a/etherlink/tezt/lib/durable_storage_path.ml b/etherlink/tezt/lib/durable_storage_path.ml index 4110e53627d112f5405dbc009389f7f86599074c..27e02c3c4f4ec5abeafeb90693415e5d7a70076c 100644 --- a/etherlink/tezt/lib/durable_storage_path.ml +++ b/etherlink/tezt/lib/durable_storage_path.ml @@ -47,6 +47,8 @@ let storage addr ?key () = let admin = evm "/admin" +let sequencer_admin = evm "/sequencer_admin" + let ticketer = evm "/ticketer" let sequencer = evm "/sequencer" diff --git a/etherlink/tezt/lib/durable_storage_path.mli b/etherlink/tezt/lib/durable_storage_path.mli index cfc22ecd52f57aec390fda8b38d5ec361af44b2e..0ec2f615294f5f930cbd1c2f4783b230ff7ac3ff 100644 --- a/etherlink/tezt/lib/durable_storage_path.mli +++ b/etherlink/tezt/lib/durable_storage_path.mli @@ -48,6 +48,10 @@ val storage : string -> ?key:string -> unit -> path (** [admin] is the path to the administrator contract. *) val admin : path +(** [sequencer_admin] is the path to the contract administrating the + sequencer. *) +val sequencer_admin : path + (** [ticketer] is the path to the ticketer contract. *) val ticketer : path diff --git a/etherlink/tezt/tests/evm_rollup.ml b/etherlink/tezt/tests/evm_rollup.ml index e262d01904a44f1ffc35f61cf49c042087a91e4c..979594092f7add3c40f793d269c239706cc9d3d5 100644 --- a/etherlink/tezt/tests/evm_rollup.ml +++ b/etherlink/tezt/tests/evm_rollup.ml @@ -24,7 +24,12 @@ let pvm_kind = "wasm_2_0_0" let kernel_inputs_path = "etherlink/tezt/tests/evm_kernel_inputs" -type l1_contracts = {exchanger : string; bridge : string; admin : string} +type l1_contracts = { + exchanger : string; + bridge : string; + admin : string; + sequencer_admin : string option; +} type full_evm_setup = { node : Node.t; @@ -41,6 +46,7 @@ type full_evm_setup = { | `Path of string | `Both of Installer_kernel_config.instr list * string ] option; + kernel : string; } let hex_256_of n = Printf.sprintf "%064x" n @@ -230,7 +236,7 @@ let send_n_transactions ~sc_rollup_node ~node ~client ~evm_node ?wait_for_blocks in return (requests, receipt, hashes) -let setup_l1_contracts ~admin client = +let setup_l1_contracts ~admin ?sequencer_admin client = (* Originates the exchanger. *) let* exchanger = Client.originate_contract @@ -257,6 +263,25 @@ let setup_l1_contracts ~admin client = in let* () = Client.bake_for_and_wait ~keys:[] client in + (* Originates the sequencer administrator contract. *) + let* sequencer_admin = + match sequencer_admin with + | Some sequencer_admin -> + let* sequencer_admin = + Client.originate_contract + ~alias:"evm-sequencer-admin" + ~amount:Tez.zero + ~src:Constant.bootstrap1.public_key_hash + ~init:(sf "%S" sequencer_admin.Account.public_key_hash) + ~prg:(admin_path ()) + ~burn_cap:Tez.one + client + in + let* () = Client.bake_for_and_wait ~keys:[] client in + return (Some sequencer_admin) + | None -> return None + in + (* Originates the administrator contract. *) let* admin = Client.originate_contract @@ -270,7 +295,7 @@ let setup_l1_contracts ~admin client = in let* () = Client.bake_for_and_wait ~keys:[] client in - return {exchanger; bridge; admin} + return {exchanger; bridge; admin; sequencer_admin} type setup_mode = | Setup_sequencer of { @@ -283,16 +308,16 @@ let setup_evm_kernel ?config ?(kernel_installee = Constant.WASM.evm_kernel) ?(originator_key = Constant.bootstrap1.public_key_hash) ?(rollup_operator_key = Constant.bootstrap1.public_key_hash) ?(bootstrap_accounts = Eth_account.bootstrap_accounts) - ?(with_administrator = true) ?flat_fee ~admin ?commitment_period - ?challenge_window ?timestamp ?(setup_mode = Setup_proxy {devmode = true}) - protocol = + ?(with_administrator = true) ?flat_fee ~admin ?sequencer_admin + ?commitment_period ?challenge_window ?timestamp + ?(setup_mode = Setup_proxy {devmode = true}) protocol = let* node, client = setup_l1 ?commitment_period ?challenge_window ?timestamp protocol in let* l1_contracts = match admin with | Some admin -> - let* res = setup_l1_contracts ~admin client in + let* res = setup_l1_contracts ~admin ?sequencer_admin client in return (Some res) | None -> return None in @@ -315,6 +340,8 @@ let setup_evm_kernel ?config ?(kernel_installee = Constant.WASM.evm_kernel) ?ticketer ?administrator ?sequencer + ?sequencer_administrator: + (Option.bind l1_contracts (fun {sequencer_admin; _} -> sequencer_admin)) () in let config = @@ -394,6 +421,7 @@ let setup_evm_kernel ?config ?(kernel_installee = Constant.WASM.evm_kernel) endpoint; l1_contracts; config; + kernel = output; } let register_test ?config ~title ~tags ?(admin = None) ?uses ?commitment_period @@ -2073,7 +2101,7 @@ let test_deposit_and_withdraw = evm_node; _; } -> - let {bridge; admin = _; exchanger = _} = + let {bridge; admin = _; exchanger = _; sequencer_admin = _} = match l1_contracts with | Some x -> x | None -> Test.fail ~__LOC__ "The test needs the L1 bridge" @@ -4227,6 +4255,139 @@ let test_l2_timestamp_opcode = ~title:"Check L2 opcode timestamp" test +let test_migrate_proxy_to_sequencer = + 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" + @@ 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 + in + Check.((sequencer_head.hash = rollup_node_head.hash) (option 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 + 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 ~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* () = + 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)) + ~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 = + let sequencer = + match sequencer_key.secret_key with + | Unencrypted sk -> sk + | Encrypted _ -> + Test.fail "Provide an unencrypted key for the sequencer" + in + 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; + genesis_timestamp = None; + } + 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 sequencer_node sc_rollup_node + in + let* () = Evm_node.run sequencer_node in + (* Same head after initialisation. *) + let* () = check_block_hash ~sequencer_node ~proxy_node 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_block_hash ~sequencer_node ~proxy_node 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_block_hash ~sequencer_node ~proxy_node in + + unit + let register_evm_node ~protocols = test_originate_evm_kernel protocols ; test_evm_node_connection protocols ; @@ -4300,7 +4461,8 @@ let register_evm_node ~protocols = test_keep_alive protocols ; test_regression_block_hash_gen protocols ; test_reboot_out_of_ticks protocols ; - test_l2_timestamp_opcode protocols + test_l2_timestamp_opcode protocols ; + test_migrate_proxy_to_sequencer protocols let () = register_evm_node ~protocols:[Alpha] ;