diff --git a/src/kernel_evm/Cargo.toml b/src/kernel_evm/Cargo.toml index fa4deeadd123474b884cbd9bdcd06a5ac8d770a6..6a71ec8f88a0ff354c67d98607fdaa7f6bfc3e03 100644 --- a/src/kernel_evm/Cargo.toml +++ b/src/kernel_evm/Cargo.toml @@ -9,7 +9,7 @@ members = [ "ethereum", "kernel", "evm_execution", - "logging" + "logging", ] [workspace.dependencies] @@ -44,6 +44,7 @@ libsecp256k1 = { version = "0.7", default-features = false, features = ["static- tezos_ethereum = { path = "./ethereum" } evm-execution = { path = "./evm_execution" } tezos-evm-logging = { path = "./logging" } +evm_kernel = { path = "./kernel" } # SDK tezos-smart-rollup-core = { path = "../kernel_sdk/core", features = ["proto-nairobi"] } diff --git a/src/kernel_evm/evm_execution/src/account_storage.rs b/src/kernel_evm/evm_execution/src/account_storage.rs index 05ca0df9935e03921e03407ba1b6978381ac2819..d2c12bb0b07094500fadab24236b6c550af3fe27 100644 --- a/src/kernel_evm/evm_execution/src/account_storage.rs +++ b/src/kernel_evm/evm_execution/src/account_storage.rs @@ -14,6 +14,7 @@ use tezos_smart_rollup_storage::storage::Storage; use thiserror::Error; use crate::DurableStorageError; +use crate::precompile_state::PrecompileState; /// The size of one 256 bit word. Size in bytes pub const WORD_SIZE: usize = 32_usize; @@ -133,6 +134,11 @@ const STORAGE_ROOT_PATH: RefPath = RefPath::assert_from(b"/storage"); /// Flag indicating an account has already been indexed. const INDEXED_PATH: RefPath = RefPath::assert_from(b"/indexed"); +/// Stateful precompiled contracts are allowed to have raw storage access. The account +/// location prefixed to this path gives the root path to a durable storage subtree +/// where particular precompile keeps its state in arbitrary format. +const PRECOMPILE_STATE_ROOT_PATH: RefPath = RefPath::assert_from(b"/precompile_state"); + /// If a contract tries to read a value from storage and it has previously not written /// anything to this location or if it wrote the default value, then it gets this /// value back. @@ -484,6 +490,16 @@ impl EthereumAccount { host.store_write(&path, &[0_u8; 0], 0) .map_err(DurableStorageError::from) } + + /// Returns an instance of PrecompiledState which provides extended storage access + /// to a specific account subfolder `/precompile_state`: + /// - no restrictions on value size + /// - atomicity is preserved (if outer transaction is rolled back, changes won't apply) + /// Intended for stateful precompiles only. + pub fn precompile_state(&self) -> Result { + let path = concat(&self.path, &PRECOMPILE_STATE_ROOT_PATH)?; + Ok(PrecompileState::from_path(path)) + } } /// The type of the storage API for accessing the Ethereum World State. diff --git a/src/kernel_evm/evm_execution/src/handler.rs b/src/kernel_evm/evm_execution/src/handler.rs index 4df10d67e234ff13ff505c98f2de24e91263442c..537a70c13a7e959b1c65e0ff797eb2b6ea1171b6 100644 --- a/src/kernel_evm/evm_execution/src/handler.rs +++ b/src/kernel_evm/evm_execution/src/handler.rs @@ -585,7 +585,7 @@ impl<'a, Host: Runtime> EvmHandler<'a, Host> { self.end_initial_transaction(Ok((result, None, vec![]))) } - fn get_or_create_account( + pub fn get_or_create_account( &self, address: H160, ) -> Result { diff --git a/src/kernel_evm/evm_execution/src/lib.rs b/src/kernel_evm/evm_execution/src/lib.rs index 182b5fa5a0e37415d7811c9728923be16763cdfa..1ce0df57d1661724e4d98306f84cd16111684624 100644 --- a/src/kernel_evm/evm_execution/src/lib.rs +++ b/src/kernel_evm/evm_execution/src/lib.rs @@ -21,6 +21,7 @@ use thiserror::Error; pub mod account_storage; pub mod handler; pub mod precompiles; +pub mod precompile_state; pub mod storage; pub mod transaction; diff --git a/src/kernel_evm/evm_execution/src/precompile_state.rs b/src/kernel_evm/evm_execution/src/precompile_state.rs new file mode 100644 index 0000000000000000000000000000000000000000..a8d58de8f66e9baac9dd1812ee879d4ee1542034 --- /dev/null +++ b/src/kernel_evm/evm_execution/src/precompile_state.rs @@ -0,0 +1,49 @@ +// SPDX-FileCopyrightText: 2023 Baking Bad +// +// SPDX-License-Identifier: MIT + +use host::path::{OwnedPath, concat}; +use host::runtime::{Runtime, ValueType}; + +use crate::DurableStorageError; + +pub struct PrecompileState { + path: OwnedPath +} + +impl PrecompileState { + pub fn from_path(path: OwnedPath) -> Self { + Self { path } + } + + pub fn get( + &self, + host: &impl Runtime, + key: String, + ) -> Result>, DurableStorageError> { + let key_path = OwnedPath::try_from(key)?; + let path = concat(&self.path, &key_path)?; + match host.store_has(&path)? { + Some(ValueType::Value | ValueType::ValueWithSubtree) => { + let value = host.store_read_all(&path)?; + Ok(Some(value)) + } + _ => Ok(None), + } + } + + pub fn set( + &mut self, + host: &mut impl Runtime, + key: String, + value: Option<&[u8]>, + ) -> Result<(), DurableStorageError> { + let key_path = OwnedPath::try_from(key)?; + let path = concat(&self.path, &key_path)?; + match value { + Some(value) => host.store_write_all(&path, value)?, + None => host.store_delete_value(&path)? + } + Ok(()) + } +} diff --git a/src/kernel_evm/kernel/Cargo.toml b/src/kernel_evm/kernel/Cargo.toml index 93084e174b7ac39d447d8b4e6c2491e757753237..f2f306f0ce28cc7f4549839b0147a12c823c8462 100644 --- a/src/kernel_evm/kernel/Cargo.toml +++ b/src/kernel_evm/kernel/Cargo.toml @@ -12,7 +12,7 @@ edition = '2021' build = "build.rs" [lib] -crate-type = ["cdylib"] +crate-type = ["cdylib", "rlib"] [dependencies] thiserror.workspace = true diff --git a/src/kernel_evm/kernel/src/block.rs b/src/kernel_evm/kernel/src/block.rs index 41ad57619c4d1024f965710764323838e10ec13c..24c6fe8f0aecf090babe6fab8ae3a51da0dd5734 100644 --- a/src/kernel_evm/kernel/src/block.rs +++ b/src/kernel_evm/kernel/src/block.rs @@ -10,13 +10,13 @@ use crate::blueprint::Queue; use crate::current_timestamp; use crate::error::Error; use crate::indexable_storage::IndexableStorage; +use crate::settings::KernelSettings; use crate::storage; use crate::storage::init_account_index; use crate::tick_model; use anyhow::Context; use block_in_progress::BlockInProgress; use evm_execution::account_storage::{init_account_storage, EthereumAccountStorage}; -use evm_execution::precompiles; use evm_execution::precompiles::PrecompileBTreeMap; use primitive_types::U256; use tezos_evm_logging::{log, Level::*}; @@ -86,7 +86,7 @@ fn compute( Ok(ComputationResult::Finished) } -pub fn produce( +pub fn produce( host: &mut Host, queue: Queue, ) -> Result<(), anyhow::Error> { @@ -102,7 +102,7 @@ pub fn produce( let mut evm_account_storage = init_account_storage().context("Failed to initialize EVM account storage")?; let mut accounts_index = init_account_index()?; - let precompiles = precompiles::precompile_set::(); + let precompiles = Settings::precompile_set::(); let mut tick_counter = TickCounter::new(tick_model::top_level_overhead_ticks()); for proposal in queue.proposals { @@ -176,6 +176,7 @@ mod tests { use crate::inbox::TransactionContent; use crate::inbox::TransactionContent::Ethereum; use crate::indexable_storage::internal_for_tests::{get_value, length}; + use crate::settings::DefaultSettings; use crate::storage::internal_for_tests::{ read_transaction_receipt, read_transaction_receipt_status, }; @@ -361,7 +362,7 @@ mod tests { U256::from(10000000000000000000u64), ); - produce(host, queue).expect("The block production failed.") + produce::<_, DefaultSettings>(host, queue).expect("The block production failed.") } fn assert_current_block_reading_validity(host: &mut MockHost) { @@ -391,7 +392,7 @@ mod tests { kernel_upgrade: None, }; - produce(&mut host, queue).expect("The block production failed."); + produce::<_, DefaultSettings>(&mut host, queue).expect("The block production failed."); let status = read_transaction_receipt_status(&mut host, &tx_hash) .expect("Should have found receipt"); @@ -425,7 +426,7 @@ mod tests { U256::from(5000000000000000u64), ); - produce(&mut host, queue).expect("The block production failed."); + produce::<_, DefaultSettings>(&mut host, queue).expect("The block production failed."); let status = read_transaction_receipt_status(&mut host, &tx_hash) .expect("Should have found receipt"); @@ -463,7 +464,7 @@ mod tests { U256::from(5000000000000000u64), ); - produce(&mut host, queue).expect("The block production failed."); + produce::<_, DefaultSettings>(&mut host, queue).expect("The block production failed."); let receipt = read_transaction_receipt(&mut host, &tx_hash) .expect("should have found receipt"); @@ -526,7 +527,7 @@ mod tests { U256::from(10000000000000000000u64), ); - produce(&mut host, queue).expect("The block production failed."); + produce::<_, DefaultSettings>(&mut host, queue).expect("The block production failed."); let dest_address = H160::from_str("423163e58aabec5daa3dd1130b759d24bef0f6ea").unwrap(); @@ -570,7 +571,7 @@ mod tests { U256::from(10000000000000000000u64), ); - produce(&mut host, queue).expect("The block production failed."); + produce::<_, DefaultSettings>(&mut host, queue).expect("The block production failed."); let receipt0 = read_transaction_receipt(&mut host, &tx_hash_0) .expect("should have found receipt"); let receipt1 = read_transaction_receipt(&mut host, &tx_hash_1) @@ -621,7 +622,7 @@ mod tests { U256::from(10000000000000000000u64), ); - produce(&mut host, queue).expect("The block production failed."); + produce::<_, DefaultSettings>(&mut host, queue).expect("The block production failed."); let dest_address = H160::from_str("423163e58aabec5daa3dd1130b759d24bef0f6ea").unwrap(); @@ -653,7 +654,7 @@ mod tests { let indexed_accounts = length(&host, &accounts_index).unwrap(); - produce(&mut host, queue).expect("The block production failed."); + produce::<_, DefaultSettings>(&mut host, queue).expect("The block production failed."); let indexed_accounts_after_produce = length(&host, &accounts_index).unwrap(); @@ -684,7 +685,7 @@ mod tests { kernel_upgrade: None, }; - produce(&mut host, queue).expect("The block production failed."); + produce::<_, DefaultSettings>(&mut host, queue).expect("The block production failed."); let indexed_accounts = length(&host, &accounts_index).unwrap(); @@ -693,7 +694,7 @@ mod tests { kernel_upgrade: None, }; - produce(&mut host, next_queue).expect("The block production failed."); + produce::<_, DefaultSettings>(&mut host, next_queue).expect("The block production failed."); let indexed_accounts_after_second_produce = length(&host, &accounts_index).unwrap(); @@ -737,7 +738,7 @@ mod tests { &sender, U256::from(10000000000000000000u64), ); - produce(&mut host, queue).expect("The block production failed."); + produce::<_, DefaultSettings>(&mut host, queue).expect("The block production failed."); let new_number_of_blocks_indexed = length(&host, &blocks_index).unwrap(); let new_number_of_transactions_indexed = @@ -781,7 +782,7 @@ mod tests { mut evm_account_storage: EthereumAccountStorage, ) -> BlockInProgress { let block_constants = first_block(host); - let precompiles = precompiles::precompile_set::(); + let precompiles = DefaultSettings::precompile_set::(); let mut accounts_index = init_account_index().unwrap(); // init block in progress @@ -888,7 +889,7 @@ mod tests { // init host let mut host = MockHost::default(); let block_constants = first_block(&mut host); - let precompiles = precompiles::precompile_set::(); + let precompiles = DefaultSettings::precompile_set::(); let mut accounts_index = init_account_index().unwrap(); //provision sender account @@ -976,7 +977,7 @@ mod tests { }; // Apply the transaction - produce(&mut host, queue).expect("The block production failed."); + produce::<_, DefaultSettings>(&mut host, queue).expect("The block production failed."); let receipt = read_transaction_receipt(&mut host, &tx_hash) .expect("should have found receipt"); assert_eq!( @@ -1019,17 +1020,17 @@ mod tests { let mut host = MockHost::default(); // first block should be 0 - produce(&mut host, almost_empty_queue()) + produce::<_, DefaultSettings>(&mut host, almost_empty_queue()) .expect("Empty block should have been produced"); check_current_block_number(&mut host, 0); // second block - produce(&mut host, almost_empty_queue()) + produce::<_, DefaultSettings>(&mut host, almost_empty_queue()) .expect("Empty block should have been produced"); check_current_block_number(&mut host, 1); // third block - produce(&mut host, almost_empty_queue()) + produce::<_, DefaultSettings>(&mut host, almost_empty_queue()) .expect("Empty block should have been produced"); check_current_block_number(&mut host, 2); } @@ -1108,7 +1109,7 @@ mod tests { kernel_upgrade: None, }; - produce(&mut host, queue).expect("Should have produced"); + produce::<_, DefaultSettings>(&mut host, queue).expect("Should have produced"); // test no new block assert!( @@ -1159,7 +1160,7 @@ mod tests { kernel_upgrade: None, }; - produce(&mut host, queue).expect("Should have produced"); + produce::<_, DefaultSettings>(&mut host, queue).expect("Should have produced"); // test no new block assert!( diff --git a/src/kernel_evm/kernel/src/lib.rs b/src/kernel_evm/kernel/src/lib.rs index c1f49c8172ef46e552eb7550f3b99692d13e131a..f2a9211848b2b2fa78ae27adf7a2a5e4eeba462e 100644 --- a/src/kernel_evm/kernel/src/lib.rs +++ b/src/kernel_evm/kernel/src/lib.rs @@ -15,7 +15,6 @@ use storage::{ }; use tezos_crypto_rs::hash::ContractKt1Hash; use tezos_smart_rollup_encoding::timestamp::Timestamp; -use tezos_smart_rollup_entrypoint::kernel_entry; use tezos_smart_rollup_host::path::{concat, OwnedPath, RefPath}; use tezos_smart_rollup_host::runtime::Runtime; @@ -28,6 +27,7 @@ use crate::safe_storage::{SafeStorage, TMP_PATH}; use crate::blueprint::{fetch, Queue}; use crate::error::Error; use crate::error::UpgradeProcessError::Fallback; +use crate::settings::KernelSettings; use crate::storage::{read_smart_rollup_address, store_smart_rollup_address}; use crate::upgrade::upgrade_kernel; use crate::Error::UpgradeError; @@ -47,6 +47,8 @@ mod storage; mod tick_model; mod upgrade; +pub mod settings; + /// The chain id will need to be unique when the EVM rollup is deployed in /// production. pub const CHAIN_ID: u32 = 1337; @@ -117,7 +119,7 @@ pub fn stage_one( Ok(queue) } -fn produce_and_upgrade( +fn produce_and_upgrade( host: &mut Host, queue: Queue, kernel_upgrade: KernelUpgrade, @@ -125,7 +127,7 @@ fn produce_and_upgrade( // Since a kernel upgrade was detected, in case an error is thrown // by the block production, we exceptionally "recover" from it and // still process the kernel upgrade. - if let Err(e) = block::produce(host, queue) { + if let Err(e) = block::produce::<_, Settings>(host, queue) { log!( host, Error, @@ -143,16 +145,16 @@ fn produce_and_upgrade( upgrade_status } -pub fn stage_two( +pub fn stage_two( host: &mut Host, queue: Queue, ) -> Result<(), anyhow::Error> { log!(host, Info, "Entering stage two."); let kernel_upgrade = queue.kernel_upgrade.clone(); if let Some(kernel_upgrade) = kernel_upgrade { - produce_and_upgrade(host, queue, kernel_upgrade) + produce_and_upgrade::<_, Settings>(host, queue, kernel_upgrade) } else { - block::produce(host, queue) + block::produce::<_, Settings>(host, queue) } } @@ -212,7 +214,7 @@ fn fetch_queue_left(host: &mut Host) -> Result(host: &mut Host) -> Result<(), anyhow::Error> { +pub fn main(host: &mut Host) -> Result<(), anyhow::Error> { let queue = if storage::was_rebooted(host)? { // kernel was rebooted log!( @@ -236,7 +238,7 @@ pub fn main(host: &mut Host) -> Result<(), anyhow::Error> { .context("Failed during stage 1")? }; - stage_two(host, queue).context("Failed during stage 2") + stage_two::<_, Settings>(host, queue).context("Failed during stage 2") } const EVM_PATH: RefPath = RefPath::assert_from(b"/evm"); @@ -259,7 +261,7 @@ fn log_error( Ok(()) } -pub fn kernel_loop(host: &mut Host) { +pub fn kernel_loop(host: &mut Host) { // In order to setup the temporary directory, we need to move something // from /evm to /tmp, so /evm must be non empty, this only happen // at the first run. @@ -275,7 +277,7 @@ pub fn kernel_loop(host: &mut Host) { .expect("The kernel failed to create the temporary directory"); let mut host = SafeStorage(host); - match main(&mut host) { + match main::<_, Settings>(&mut host) { Ok(()) => { host.promote_upgrade() .expect("Potential kernel upgrade promotion failed"); @@ -309,4 +311,5 @@ pub fn kernel_loop(host: &mut Host) { } } -kernel_entry!(kernel_loop); +#[cfg(crate_type="cdylib")] +tezos_smart_rollup_entrypoint::kernel_entry!(kernel_loop); diff --git a/src/kernel_evm/kernel/src/settings.rs b/src/kernel_evm/kernel/src/settings.rs new file mode 100644 index 0000000000000000000000000000000000000000..8b6cf3dbfebe46e083df25c30a557203e70df8bf --- /dev/null +++ b/src/kernel_evm/kernel/src/settings.rs @@ -0,0 +1,25 @@ +// SPDX-FileCopyrightText: 2023 Baking Bad +// +// SPDX-License-Identifier: MIT + +//! Execution settings for the kernel +//! +//! This module defines a trait used to configure: +//! - EVM precompiles +//! and other settings at compile time via static methods. +//! It also provides a default implementation. + +use evm_execution::precompiles::{PrecompileBTreeMap, precompile_set}; +use tezos_smart_rollup_host::runtime::Runtime; + +pub trait KernelSettings { + fn precompile_set() -> PrecompileBTreeMap; +} + +pub struct DefaultSettings {} + +impl KernelSettings for DefaultSettings { + fn precompile_set() -> PrecompileBTreeMap { + precompile_set() + } +}