From cb4ae97e43ad8a1553b47f3fc1a60bc7befd12fe Mon Sep 17 00:00:00 2001 From: Rodi-Can Bozman Date: Mon, 2 Sep 2024 13:21:13 +0200 Subject: [PATCH 1/5] Etherlink: generic storage crate In order to keep all the generic functions in one place and avoid code duplication. --- etherlink/kernel_evm/Cargo.lock | 16 ++ etherlink/kernel_evm/Cargo.toml | 2 + etherlink/kernel_evm/storage/Cargo.toml | 25 +++ etherlink/kernel_evm/storage/src/error.rs | 39 ++++ etherlink/kernel_evm/storage/src/helpers.rs | 11 ++ etherlink/kernel_evm/storage/src/lib.rs | 205 ++++++++++++++++++++ 6 files changed, 298 insertions(+) create mode 100644 etherlink/kernel_evm/storage/Cargo.toml create mode 100644 etherlink/kernel_evm/storage/src/error.rs create mode 100644 etherlink/kernel_evm/storage/src/helpers.rs create mode 100644 etherlink/kernel_evm/storage/src/lib.rs diff --git a/etherlink/kernel_evm/Cargo.lock b/etherlink/kernel_evm/Cargo.lock index 9f054c8ddd08..94987e7a230e 100644 --- a/etherlink/kernel_evm/Cargo.lock +++ b/etherlink/kernel_evm/Cargo.lock @@ -2340,6 +2340,22 @@ dependencies = [ "thiserror", ] +[[package]] +name = "tezos-storage" +version = "0.1.0" +dependencies = [ + "anyhow", + "hex", + "primitive-types", + "rlp", + "sha3", + "tezos-smart-rollup-host", + "tezos-smart-rollup-storage", + "tezos_crypto_rs", + "tezos_ethereum", + "thiserror", +] + [[package]] name = "tezos_crypto_rs" version = "0.6.0" diff --git a/etherlink/kernel_evm/Cargo.toml b/etherlink/kernel_evm/Cargo.toml index e0f37d722e59..91885a04702b 100644 --- a/etherlink/kernel_evm/Cargo.toml +++ b/etherlink/kernel_evm/Cargo.toml @@ -16,6 +16,7 @@ members = [ "evm_evaluation", "indexable_storage", "logging", + "storage", ] [workspace.dependencies] @@ -61,6 +62,7 @@ tezos_ethereum = { path = "./ethereum" } evm-execution = { path = "./evm_execution" } tezos-evm-logging = { path = "./logging" } tezos-indexable-storage = { path = "./indexable_storage" } +tezos-storage = { path = "./storage" } # SDK tezos-smart-rollup = { path = "../../src/kernel_sdk/sdk" } diff --git a/etherlink/kernel_evm/storage/Cargo.toml b/etherlink/kernel_evm/storage/Cargo.toml new file mode 100644 index 000000000000..7b07958d35e7 --- /dev/null +++ b/etherlink/kernel_evm/storage/Cargo.toml @@ -0,0 +1,25 @@ +# SPDX-FileCopyrightText: 2024 Functori +# +# SPDX-License-Identifier: MIT + +[package] +name = "tezos-storage" +version = "0.1.0" +edition = "2021" +license = "MIT" + +[dependencies] +thiserror.workspace = true +anyhow.workspace = true + +primitive-types.workspace = true + +rlp.workspace = true +hex.workspace = true + +tezos_crypto_rs.workspace = true +sha3.workspace = true + +tezos_ethereum.workspace = true +tezos-smart-rollup-host.workspace = true +tezos-smart-rollup-storage.workspace = true diff --git a/etherlink/kernel_evm/storage/src/error.rs b/etherlink/kernel_evm/storage/src/error.rs new file mode 100644 index 000000000000..55ac5bca2151 --- /dev/null +++ b/etherlink/kernel_evm/storage/src/error.rs @@ -0,0 +1,39 @@ +// SPDX-FileCopyrightText: 2024 Functori +// +// SPDX-License-Identifier: MIT + +use rlp::DecoderError; +use tezos_smart_rollup_host::path::PathError; +use tezos_smart_rollup_host::runtime::RuntimeError; +use thiserror::Error; + +#[derive(Error, Debug, Eq, PartialEq)] +pub enum Error { + #[error(transparent)] + Path(PathError), + #[error(transparent)] + Runtime(RuntimeError), + #[error(transparent)] + Storage(tezos_smart_rollup_storage::StorageError), + #[error("Failed to decode: {0}")] + RlpDecoderError(DecoderError), + #[error("Storage error: error while reading a value (incorrect size). Expected {expected} but got {actual}")] + InvalidLoadValue { expected: usize, actual: usize }, +} + +impl From for Error { + fn from(e: PathError) -> Self { + Self::Path(e) + } +} +impl From for Error { + fn from(e: RuntimeError) -> Self { + Self::Runtime(e) + } +} + +impl From for Error { + fn from(e: DecoderError) -> Self { + Self::RlpDecoderError(e) + } +} diff --git a/etherlink/kernel_evm/storage/src/helpers.rs b/etherlink/kernel_evm/storage/src/helpers.rs new file mode 100644 index 000000000000..62f7ecd39244 --- /dev/null +++ b/etherlink/kernel_evm/storage/src/helpers.rs @@ -0,0 +1,11 @@ +// SPDX-FileCopyrightText: 2024 Functori +// +// SPDX-License-Identifier: MIT + +use primitive_types::H256; +use sha3::{Digest, Keccak256}; + +/// Compute the Keccak256 hash of `bytes`. +pub fn bytes_hash(bytes: &[u8]) -> H256 { + H256(Keccak256::digest(bytes).into()) +} diff --git a/etherlink/kernel_evm/storage/src/lib.rs b/etherlink/kernel_evm/storage/src/lib.rs new file mode 100644 index 000000000000..140b8f783d81 --- /dev/null +++ b/etherlink/kernel_evm/storage/src/lib.rs @@ -0,0 +1,205 @@ +// SPDX-FileCopyrightText: 2023 Nomadic Labs +// SPDX-FileCopyrightText: 2023-2024 Functori +// SPDX-FileCopyrightText: 2023 Marigold +// SPDX-FileCopyrightText: 2024 Trilitech +// +// SPDX-License-Identifier: MIT + +pub mod error; +pub mod helpers; + +use crate::error::Error; + +use primitive_types::{H256, U256}; +use rlp::{Decodable, Encodable}; + +use tezos_crypto_rs::hash::{ContractKt1Hash, HashTrait}; +use tezos_ethereum::rlp_helpers::FromRlpBytes; +use tezos_smart_rollup_host::path::*; +use tezos_smart_rollup_host::runtime::{Runtime, RuntimeError, ValueType}; + +/// The size of one 256 bit word. Size in bytes. +const WORD_SIZE: usize = 32usize; + +/// Return up to buffer.len() from the given path in storage and +/// store the read slice in `buffer`. +/// +/// NB: Value is read starting 0. +pub fn store_read_slice( + host: &impl Runtime, + path: &impl Path, + buffer: &mut [u8], + expected_size: usize, +) -> Result<(), Error> { + let size = host.store_read_slice(path, 0, buffer)?; + if size == expected_size { + Ok(()) + } else { + Err(Error::InvalidLoadValue { + expected: expected_size, + actual: size, + }) + } +} + +/// Get the path corresponding to an index of H256. +pub fn path_from_h256(index: &H256) -> Result { + let path_string = format!("/{}", hex::encode(index.to_fixed_bytes())); + OwnedPath::try_from(path_string).map_err(Error::from) +} + +/// Return a 32 bytes hash from storage at the given `path`. +/// +/// NB: The given bytes are interpreted in big endian order. +pub fn read_h256_be(host: &impl Runtime, path: &impl Path) -> anyhow::Result { + let mut buffer = [0_u8; WORD_SIZE]; + store_read_slice(host, path, &mut buffer, WORD_SIZE)?; + Ok(H256::from_slice(&buffer)) +} + +/// Return a 32 bytes hash from storage at the given `path`. +/// If the path is not found, `default` is returned. +/// +/// NB: The given bytes are interpreted in big endian order. +pub fn read_h256_be_default( + host: &impl Runtime, + path: &impl Path, + default: H256, +) -> Result { + match host.store_read_all(path) { + Ok(bytes) if bytes.len() == WORD_SIZE => Ok(H256::from_slice(&bytes)), + Ok(_) | Err(RuntimeError::PathNotFound) => Ok(default), + Err(err) => Err(err.into()), + } +} + +/// Write a 32 bytes hash into storage at the given `path`. +/// +/// NB: The hash is stored in big endian order. +pub fn write_h256_be( + host: &mut impl Runtime, + path: &impl Path, + hash: H256, +) -> anyhow::Result<()> { + Ok(host.store_write_all(path, hash.as_bytes())?) +} + +/// Return an unsigned 32 bytes value from storage at the given `path`. +/// +/// NB: The given bytes are interpreted in little endian order. +pub fn read_u256_le(host: &impl Runtime, path: &impl Path) -> Result { + let bytes = host.store_read_all(path)?; + Ok(U256::from_little_endian(&bytes)) +} + +/// Return an unsigned 32 bytes value from storage at the given `path`. +/// If the path is not found, `default` is returned. +/// +/// NB: The given bytes are interpreted in little endian order. +pub fn read_u256_le_default( + host: &impl Runtime, + path: &impl Path, + default: U256, +) -> Result { + match host.store_read_all(path) { + Ok(bytes) if bytes.len() == WORD_SIZE => Ok(U256::from_little_endian(&bytes)), + Ok(_) | Err(RuntimeError::PathNotFound) => Ok(default), + Err(err) => Err(err.into()), + } +} + +/// Write an unsigned 32 bytes value into storage at the given `path`. +/// +/// NB: The value is stored in little endian order. +pub fn write_u256_le( + host: &mut impl Runtime, + path: &impl Path, + value: U256, +) -> Result<(), Error> { + let mut bytes: [u8; WORD_SIZE] = value.into(); + value.to_little_endian(&mut bytes); + host.store_write_all(path, &bytes).map_err(Error::from) +} + +/// Return an unsigned 8 bytes value from storage at the given `path`. +/// +/// NB: The given bytes are interpreted in little endian order. +pub fn read_u64_le(host: &impl Runtime, path: &impl Path) -> Result { + let mut bytes = [0; std::mem::size_of::()]; + host.store_read_slice(path, 0, bytes.as_mut_slice())?; + Ok(u64::from_le_bytes(bytes)) +} + +/// Return an unsigned 8 bytes value from storage at the given `path`. +/// If the path is not found, `default` is returned. +/// +/// NB: The given bytes are interpreted in little endian order. +pub fn read_u64_le_default( + host: &impl Runtime, + path: &impl Path, + default: u64, +) -> Result { + match host.store_read_all(path) { + Ok(bytes) if bytes.len() == std::mem::size_of::() => { + let bytes_array: [u8; std::mem::size_of::()] = bytes + .try_into() + .map_err(|_| Error::Runtime(RuntimeError::DecodingError))?; + Ok(u64::from_le_bytes(bytes_array)) + } + Ok(_) | Err(RuntimeError::PathNotFound) => Ok(default), + Err(err) => Err(err.into()), + } +} + +/// Write an unsigned 8 bytes value into storage at the given `path`. +/// +/// NB: The value is stored in little endian order. +pub fn write_u64_le( + host: &mut impl Runtime, + path: &impl Path, + value: u64, +) -> Result<(), Error> { + host.store_write_all(path, value.to_le_bytes().as_slice()) + .map_err(Error::from) +} + +/// Store `src` (which must be encodable) as rlp bytes into storage +/// at the given `path`. +pub fn store_rlp( + src: &T, + host: &mut impl Runtime, + path: &impl Path, +) -> Result<(), Error> { + host.store_write_all(path, &src.rlp_bytes()) + .map_err(Error::from) +} + +/// Return a decodable value from storage as rlp bytes +/// at the given `path`. +pub fn read_rlp(host: &impl Runtime, path: &impl Path) -> Result { + let bytes = host.store_read_all(path)?; + FromRlpBytes::from_rlp_bytes(&bytes).map_err(Error::from) +} + +/// Return a potential decodable value from storage as rlp bytes +/// at the given `path`. +/// +/// If there is no data, `None` is returned. +pub fn read_optional_rlp( + host: &impl Runtime, + path: &impl Path, +) -> Result, anyhow::Error> { + if let Some(ValueType::Value) = host.store_has(path)? { + let elt = read_rlp(host, path)?; + Ok(Some(elt)) + } else { + Ok(None) + } +} + +/// Return a base58 contract address from storage at the given `path`. +pub fn read_b58_kt1(host: &impl Runtime, path: &impl Path) -> Option { + let bytes = host.store_read_all(path).ok()?; + let kt1_b58 = String::from_utf8(bytes).ok()?; + ContractKt1Hash::from_b58check(&kt1_b58).ok() +} -- GitLab From c0669cdee87038038be06f2fef0b04d547394a48 Mon Sep 17 00:00:00 2001 From: Rodi-Can Bozman Date: Mon, 2 Sep 2024 13:21:37 +0200 Subject: [PATCH 2/5] Etherlink: use generic storage crate in kernel's codebase --- etherlink/kernel_evm/Cargo.lock | 1 + etherlink/kernel_evm/kernel/Cargo.toml | 1 + .../kernel_evm/kernel/src/block_storage.rs | 13 +- .../kernel/src/blueprint_storage.rs | 15 +- etherlink/kernel_evm/kernel/src/error.rs | 15 ++ etherlink/kernel_evm/kernel/src/lib.rs | 5 +- .../kernel_evm/kernel/src/linked_list.rs | 17 +- etherlink/kernel_evm/kernel/src/storage.rs | 155 ++++-------------- etherlink/kernel_evm/kernel/src/upgrade.rs | 5 +- 9 files changed, 71 insertions(+), 156 deletions(-) diff --git a/etherlink/kernel_evm/Cargo.lock b/etherlink/kernel_evm/Cargo.lock index 94987e7a230e..8e97341cf854 100644 --- a/etherlink/kernel_evm/Cargo.lock +++ b/etherlink/kernel_evm/Cargo.lock @@ -795,6 +795,7 @@ dependencies = [ "tezos-smart-rollup-mock", "tezos-smart-rollup-panic-hook", "tezos-smart-rollup-storage", + "tezos-storage", "tezos_crypto_rs", "tezos_data_encoding", "tezos_ethereum", diff --git a/etherlink/kernel_evm/kernel/Cargo.toml b/etherlink/kernel_evm/kernel/Cargo.toml index 12ac36f064b6..3da5ff8ca745 100644 --- a/etherlink/kernel_evm/kernel/Cargo.toml +++ b/etherlink/kernel_evm/kernel/Cargo.toml @@ -41,6 +41,7 @@ evm-execution.workspace = true tezos_ethereum.workspace = true tezos-evm-logging.workspace = true tezos-indexable-storage.workspace = true +tezos-storage.workspace = true tezos-smart-rollup.workspace = true tezos-smart-rollup-core.workspace = true diff --git a/etherlink/kernel_evm/kernel/src/block_storage.rs b/etherlink/kernel_evm/kernel/src/block_storage.rs index 709ffb0c97b7..e0010b7d6815 100644 --- a/etherlink/kernel_evm/kernel/src/block_storage.rs +++ b/etherlink/kernel_evm/kernel/src/block_storage.rs @@ -2,10 +2,6 @@ // // SPDX-License-Identifier: MIT -use crate::storage::read_h256; -use crate::storage::read_u256_le; -use crate::storage::write_h256; -use crate::storage::write_u256_le; use primitive_types::{H256, U256}; use tezos_ethereum::block::L2Block; use tezos_ethereum::rlp_helpers::VersionedEncoding; @@ -14,6 +10,7 @@ use tezos_indexable_storage::IndexableStorage; use tezos_smart_rollup_host::path::OwnedPath; use tezos_smart_rollup_host::path::RefPath; use tezos_smart_rollup_host::{path::concat, runtime::Runtime}; +use tezos_storage::{read_h256_be, read_u256_le, write_h256_be, write_u256_le}; mod path { use super::*; @@ -38,11 +35,11 @@ mod path { } fn store_current_number(host: &mut impl Runtime, number: U256) -> anyhow::Result<()> { - Ok(write_u256_le(host, &path::CURRENT_NUMBER.into(), number)?) + Ok(write_u256_le(host, &path::CURRENT_NUMBER, number)?) } fn store_current_hash(host: &mut impl Runtime, hash: H256) -> anyhow::Result<()> { - write_h256(host, &path::CURRENT_HASH, hash) + write_h256_be(host, &path::CURRENT_HASH, hash) } fn store_block(host: &mut impl Runtime, block: &L2Block) -> anyhow::Result<()> { @@ -72,11 +69,11 @@ pub fn store_current(host: &mut impl Runtime, block: &L2Block) -> anyhow::Result } pub fn read_current_number(host: &impl Runtime) -> anyhow::Result { - Ok(read_u256_le(host, &path::CURRENT_NUMBER.into())?) + Ok(read_u256_le(host, &path::CURRENT_NUMBER)?) } pub fn read_current_hash(host: &impl Runtime) -> anyhow::Result { - read_h256(host, &path::CURRENT_HASH) + read_h256_be(host, &path::CURRENT_HASH) } pub fn read_current(host: &mut impl Runtime) -> anyhow::Result { diff --git a/etherlink/kernel_evm/kernel/src/blueprint_storage.rs b/etherlink/kernel_evm/kernel/src/blueprint_storage.rs index db61b43f1851..bf02bbd0abdb 100644 --- a/etherlink/kernel_evm/kernel/src/blueprint_storage.rs +++ b/etherlink/kernel_evm/kernel/src/blueprint_storage.rs @@ -10,9 +10,7 @@ use crate::configuration::{Configuration, ConfigurationMode}; use crate::error::{Error, StorageError}; use crate::inbox::{Transaction, TransactionContent}; use crate::sequencer_blueprint::{BlueprintWithDelayedHashes, SequencerBlueprint}; -use crate::storage::{ - read_last_info_per_level_timestamp, read_rlp, store_read_slice, store_rlp, -}; +use crate::storage::read_last_info_per_level_timestamp; use crate::{delayed_inbox, DelayedInbox}; use primitive_types::U256; use rlp::{Decodable, DecoderError, Encodable}; @@ -24,6 +22,9 @@ use tezos_smart_rollup::types::Timestamp; use tezos_smart_rollup_core::MAX_INPUT_MESSAGE_SIZE; use tezos_smart_rollup_host::path::*; use tezos_smart_rollup_host::runtime::{Runtime, RuntimeError}; +use tezos_storage::{ + error::Error as GenStorageError, read_rlp, store_read_slice, store_rlp, +}; pub const EVM_BLUEPRINTS: RefPath = RefPath::assert_from(b"/evm/blueprints"); @@ -135,7 +136,7 @@ pub fn store_sequencer_blueprint( let blueprint_chunk_path = blueprint_chunk_path(&blueprint_path, blueprint.blueprint.chunk_index)?; let store_blueprint = StoreBlueprint::SequencerChunk(blueprint.blueprint.chunk); - store_rlp(&store_blueprint, host, &blueprint_chunk_path) + store_rlp(&store_blueprint, host, &blueprint_chunk_path).map_err(Error::from) } pub fn store_inbox_blueprint_by_number( @@ -147,7 +148,7 @@ pub fn store_inbox_blueprint_by_number( store_blueprint_nb_chunks(host, &blueprint_path, 1)?; let chunk_path = blueprint_chunk_path(&blueprint_path, 0)?; let store_blueprint = StoreBlueprint::InboxBlueprint(blueprint); - store_rlp(&store_blueprint, host, &chunk_path) + store_rlp(&store_blueprint, host, &chunk_path).map_err(Error::from) } pub fn store_inbox_blueprint( @@ -162,7 +163,7 @@ pub fn store_inbox_blueprint( pub fn read_next_blueprint_number(host: &Host) -> anyhow::Result { match block_storage::read_current_number(host) { Err(err) => match err.downcast_ref() { - Some(Error::Storage(StorageError::Runtime(RuntimeError::PathNotFound))) => { + Some(GenStorageError::Runtime(RuntimeError::PathNotFound)) => { Ok(U256::zero()) } _ => Err(err), @@ -181,7 +182,7 @@ pub fn store_immediate_blueprint( store_blueprint_nb_chunks(host, &blueprint_path, 1)?; let chunk_path = blueprint_chunk_path(&blueprint_path, 0)?; let store_blueprint = StoreBlueprint::InboxBlueprint(blueprint); - store_rlp(&store_blueprint, host, &chunk_path) + store_rlp(&store_blueprint, host, &chunk_path).map_err(Error::from) } /// For the tick model we only accept blueprints where cumulative size of chunks diff --git a/etherlink/kernel_evm/kernel/src/error.rs b/etherlink/kernel_evm/kernel/src/error.rs index 25c628f50f53..da2ff0ae99dc 100644 --- a/etherlink/kernel_evm/kernel/src/error.rs +++ b/etherlink/kernel_evm/kernel/src/error.rs @@ -16,6 +16,7 @@ use tezos_smart_rollup_encoding::entrypoint::EntrypointError; use tezos_smart_rollup_encoding::michelson::ticket::TicketError; use tezos_smart_rollup_host::path::PathError; use tezos_smart_rollup_host::runtime::RuntimeError; +use tezos_storage::error::Error as GenStorageError; use thiserror::Error; #[derive(Error, Debug)] @@ -206,3 +207,17 @@ impl From for Error { } } } + +impl From for Error { + fn from(e: GenStorageError) -> Self { + match e { + GenStorageError::Path(e) => Error::Storage(StorageError::Path(e)), + GenStorageError::Runtime(e) => Error::Storage(StorageError::Runtime(e)), + GenStorageError::Storage(e) => Error::Storage(StorageError::Storage(e)), + GenStorageError::RlpDecoderError(e) => Error::RlpDecoderError(e), + GenStorageError::InvalidLoadValue { expected, actual } => { + Error::Storage(StorageError::InvalidLoadValue { expected, actual }) + } + } + } +} diff --git a/etherlink/kernel_evm/kernel/src/lib.rs b/etherlink/kernel_evm/kernel/src/lib.rs index 5bc8bd1c5859..2c4c8de0d90c 100644 --- a/etherlink/kernel_evm/kernel/src/lib.rs +++ b/etherlink/kernel_evm/kernel/src/lib.rs @@ -634,8 +634,7 @@ mod tests { #[test] fn load_block_fees_with_minimum() { let min_path = - RefPath::assert_from(b"/evm/world_state/fees/minimum_base_fee_per_gas") - .into(); + RefPath::assert_from(b"/evm/world_state/fees/minimum_base_fee_per_gas"); // Arrange let mut host = MockHost::default(); @@ -643,7 +642,7 @@ mod tests { let min_base_fee = U256::from(17); let curr_base_fee = U256::from(20); storage::store_base_fee_per_gas(&mut host, curr_base_fee).unwrap(); - storage::write_u256_le(&mut host, &min_path, min_base_fee).unwrap(); + tezos_storage::write_u256_le(&mut host, &min_path, min_base_fee).unwrap(); // Act let result = crate::retrieve_block_fees(&mut host); diff --git a/etherlink/kernel_evm/kernel/src/linked_list.rs b/etherlink/kernel_evm/kernel/src/linked_list.rs index d8f8e5ba4219..ded7142c4b24 100644 --- a/etherlink/kernel_evm/kernel/src/linked_list.rs +++ b/etherlink/kernel_evm/kernel/src/linked_list.rs @@ -1,6 +1,5 @@ // SPDX-FileCopyrightText: 2023 Marigold -use crate::storage; use anyhow::{Context, Result}; use rlp::{Decodable, DecoderError, Encodable, Rlp, RlpIterator, RlpStream}; use std::marker::PhantomData; @@ -9,6 +8,7 @@ use tezos_smart_rollup_host::{ path::{concat, OwnedPath, Path}, runtime::Runtime, }; +use tezos_storage::{read_optional_rlp, read_rlp, store_rlp}; /// Doubly linked list using the durable storage. /// @@ -183,22 +183,22 @@ impl, Elt: Encodable + Decodable> data: &Elt, ) -> Result<()> { let path = self.data_path(prefix)?; - storage::store_rlp(data, host, &path).context("cannot save the pointer's data") + store_rlp(data, host, &path).context("cannot save the pointer's data") } fn get_data(&self, host: &impl Runtime, prefix: &impl Path) -> Result { let path = self.data_path(prefix)?; - storage::read_rlp(host, &path).context("cannot read the pointer's data") + read_rlp(host, &path).context("cannot read the pointer's data") } /// Load the pointer from the durable storage fn read(host: &impl Runtime, prefix: &impl Path, id: &Id) -> Result> { - storage::read_optional_rlp(host, &Self::pointer_path(id, prefix)?) + read_optional_rlp(host, &Self::pointer_path(id, prefix)?) } /// Save the pointer in the durable storage fn save(&self, host: &mut impl Runtime, prefix: &impl Path) -> Result<()> { - storage::store_rlp(self, host, &self.path(prefix)?) + store_rlp(self, host, &self.path(prefix)?) .context("cannot save pointer to storage") } @@ -248,14 +248,13 @@ where /// Only save the back and front pointers. fn save(&self, host: &mut impl Runtime) -> Result<()> { let path = Self::metadata_path(&self.path)?; - storage::store_rlp(self, host, &path) - .context("cannot save linked list from the storage") + store_rlp(self, host, &path).context("cannot save linked list from the storage") } /// Load the LinkedList from the durable storage. fn read(host: &impl Runtime, path: &impl Path) -> Result> { let path = Self::metadata_path(path)?; - storage::read_optional_rlp(host, &path) + read_optional_rlp(host, &path) } /// Returns true if the list contains no elements. @@ -330,7 +329,7 @@ where else { return Ok(None); }; - storage::read_optional_rlp(host, &pointer.data_path(&self.path)?) + read_optional_rlp(host, &pointer.data_path(&self.path)?) } /// Removes and returns the element at position index within the vector. diff --git a/etherlink/kernel_evm/kernel/src/storage.rs b/etherlink/kernel_evm/kernel/src/storage.rs index d41482332c04..80d6eba83f09 100644 --- a/etherlink/kernel_evm/kernel/src/storage.rs +++ b/etherlink/kernel_evm/kernel/src/storage.rs @@ -14,7 +14,7 @@ use evm_execution::trace::{ }; use num_derive::{FromPrimitive, ToPrimitive}; use num_traits::{FromPrimitive, ToPrimitive}; -use tezos_crypto_rs::hash::{ContractKt1Hash, HashTrait}; +use tezos_crypto_rs::hash::ContractKt1Hash; use tezos_evm_logging::{log, Level::*}; use tezos_indexable_storage::IndexableStorage; use tezos_smart_rollup_core::MAX_FILE_CHUNK_SIZE; @@ -22,16 +22,19 @@ use tezos_smart_rollup_encoding::public_key::PublicKey; use tezos_smart_rollup_encoding::timestamp::Timestamp; use tezos_smart_rollup_host::path::*; use tezos_smart_rollup_host::runtime::{Runtime, ValueType}; +use tezos_storage::{ + read_b58_kt1, read_u256_le, read_u64_le, store_read_slice, write_u256_le, + write_u64_le, +}; -use crate::error::{Error, StorageError}; +use crate::error::Error; use rlp::{Decodable, Encodable, Rlp}; use tezos_ethereum::rlp_helpers::{FromRlpBytes, VersionedEncoding}; use tezos_ethereum::transaction::{ TransactionHash, TransactionObject, TransactionReceipt, }; -use tezos_ethereum::wei::Wei; -use primitive_types::{H160, H256, U256}; +use primitive_types::{H160, U256}; #[derive( FromPrimitive, ToPrimitive, Copy, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, @@ -139,9 +142,6 @@ const EVM_DELAYED_INBOX_TIMEOUT: RefPath = const EVM_DELAYED_INBOX_MIN_LEVELS: RefPath = RefPath::assert_from(b"/evm/delayed_inbox_min_levels"); -/// The size of one 256 bit word. Size in bytes -pub const WORD_SIZE: usize = 32usize; - // Path to the tz1 administrating the sequencer. If there is nothing // at this path, the kernel is in proxy mode. pub const SEQUENCER: RefPath = RefPath::assert_from(b"/evm/sequencer"); @@ -166,64 +166,6 @@ const EVM_NODE_FLAG: RefPath = RefPath::assert_from(b"/__evm_node"); const MAX_BLUEPRINT_LOOKAHEAD_IN_SECONDS: RefPath = RefPath::assert_from(b"/evm/max_blueprint_lookahead_in_seconds"); -pub fn store_read_slice( - host: &Host, - path: &T, - buffer: &mut [u8], - expected_size: usize, -) -> Result<(), Error> { - let size = Runtime::store_read_slice(host, path, 0, buffer)?; - if size == expected_size { - Ok(()) - } else { - Err(Error::Storage(StorageError::InvalidLoadValue { - expected: expected_size, - actual: size, - })) - } -} - -pub fn read_h256(host: &impl Runtime, path: &impl Path) -> anyhow::Result { - let mut buffer = [0_u8; 32]; - store_read_slice(host, path, &mut buffer, 32)?; - Ok(H256::from_slice(&buffer)) -} - -pub fn write_h256( - host: &mut impl Runtime, - path: &impl Path, - hash: H256, -) -> anyhow::Result<()> { - Ok(host.store_write_all(path, hash.as_bytes())?) -} - -/// Read a single unsigned 256 bit value from storage at the path given. -pub fn read_u256_le(host: &impl Runtime, path: &OwnedPath) -> Result { - let bytes = host.store_read(path, 0, WORD_SIZE)?; - Ok(Wei::from_little_endian(&bytes)) -} - -pub fn write_u256_le( - host: &mut impl Runtime, - path: &OwnedPath, - value: U256, -) -> Result<(), Error> { - let mut bytes: [u8; WORD_SIZE] = value.into(); - value.to_little_endian(&mut bytes); - host.store_write(path, &bytes, 0).map_err(Error::from) -} - -fn read_u64(host: &impl Runtime, path: &impl Path) -> Result { - let mut bytes = [0; std::mem::size_of::()]; - host.store_read_slice(path, 0, bytes.as_mut_slice())?; - Ok(u64::from_le_bytes(bytes)) -} - -fn write_u64(host: &mut impl Runtime, path: &impl Path, value: u64) -> Result<(), Error> { - host.store_write_all(path, value.to_le_bytes().as_slice())?; - Ok(()) -} - pub fn receipt_path(receipt_hash: &TransactionHash) -> Result { let hash = hex::encode(receipt_hash); let raw_receipt_path: Vec = format!("/{}", &hash).into(); @@ -238,38 +180,6 @@ pub fn object_path(object_hash: &TransactionHash) -> Result { concat(&EVM_TRANSACTIONS_OBJECTS, &object_path).map_err(Error::from) } -pub fn store_rlp( - src: &T, - host: &mut Host, - path: &impl Path, -) -> Result<(), Error> { - let bytes = src.rlp_bytes(); - host.store_write_all(path, &bytes).map_err(Error::from) -} - -pub fn read_rlp( - host: &Host, - path: &impl Path, -) -> Result { - let bytes = host.store_read_all(path)?; - FromRlpBytes::from_rlp_bytes(&bytes).map_err(Error::from) -} - -/// Read data from the durable storage under the given path. -/// -/// If there is no data, None is returned. -pub fn read_optional_rlp( - host: &impl Runtime, - path: &impl Path, -) -> Result, anyhow::Error> { - if let Some(ValueType::Value) = host.store_has(path)? { - let elt = read_rlp(host, path)?; - Ok(Some(elt)) - } else { - Ok(None) - } -} - pub fn store_simulation_result( host: &mut Host, result: SimulationResult, @@ -523,45 +433,45 @@ pub fn store_chain_id( host: &mut Host, chain_id: U256, ) -> Result<(), Error> { - write_u256_le(host, &EVM_CHAIN_ID.into(), chain_id) + write_u256_le(host, &EVM_CHAIN_ID, chain_id).map_err(Error::from) } pub fn read_chain_id(host: &Host) -> Result { - read_u256_le(host, &EVM_CHAIN_ID.into()) + read_u256_le(host, &EVM_CHAIN_ID).map_err(Error::from) } pub fn store_base_fee_per_gas( host: &mut Host, base_fee_per_gas: U256, ) -> Result<(), Error> { - write_u256_le(host, &EVM_BASE_FEE_PER_GAS.into(), base_fee_per_gas) + write_u256_le(host, &EVM_BASE_FEE_PER_GAS, base_fee_per_gas).map_err(Error::from) } pub fn read_base_fee_per_gas(host: &mut Host) -> Result { - read_u256_le(host, &EVM_BASE_FEE_PER_GAS.into()) + read_u256_le(host, &EVM_BASE_FEE_PER_GAS).map_err(Error::from) } pub fn read_minimum_base_fee_per_gas(host: &Host) -> Result { - read_u256_le(host, &EVM_MINIMUM_BASE_FEE_PER_GAS.into()) + read_u256_le(host, &EVM_MINIMUM_BASE_FEE_PER_GAS).map_err(Error::from) } pub fn read_tick_backlog(host: &impl Runtime) -> Result { - read_u64(host, &TICK_BACKLOG_PATH) + read_u64_le(host, &TICK_BACKLOG_PATH).map_err(Error::from) } pub fn store_tick_backlog(host: &mut impl Runtime, value: u64) -> Result<(), Error> { - write_u64(host, &TICK_BACKLOG_PATH, value) + write_u64_le(host, &TICK_BACKLOG_PATH, value).map_err(Error::from) } pub fn read_tick_backlog_timestamp(host: &impl Runtime) -> Result { - read_u64(host, &TICK_BACKLOG_TIMESTAMP_PATH) + read_u64_le(host, &TICK_BACKLOG_TIMESTAMP_PATH).map_err(Error::from) } pub fn store_tick_backlog_timestamp( host: &mut impl Runtime, value: u64, ) -> Result<(), Error> { - write_u64(host, &TICK_BACKLOG_TIMESTAMP_PATH, value)?; + write_u64_le(host, &TICK_BACKLOG_TIMESTAMP_PATH, value)?; Ok(()) } @@ -570,33 +480,33 @@ pub fn store_minimum_base_fee_per_gas( host: &mut Host, price: U256, ) -> Result<(), Error> { - write_u256_le(host, &EVM_MINIMUM_BASE_FEE_PER_GAS.into(), price) + write_u256_le(host, &EVM_MINIMUM_BASE_FEE_PER_GAS, price).map_err(Error::from) } pub fn store_da_fee( host: &mut impl Runtime, base_fee_per_gas: U256, ) -> Result<(), Error> { - write_u256_le(host, &EVM_DA_FEE.into(), base_fee_per_gas) + write_u256_le(host, &EVM_DA_FEE, base_fee_per_gas).map_err(Error::from) } pub fn read_da_fee(host: &impl Runtime) -> Result { - read_u256_le(host, &EVM_DA_FEE.into()) + read_u256_le(host, &EVM_DA_FEE).map_err(Error::from) } pub fn update_burned_fees( host: &mut impl Runtime, burned_fee: U256, ) -> Result<(), Error> { - let path = &EVM_BURNED_FEES.into(); + let path = &EVM_BURNED_FEES; let current = read_u256_le(host, path).unwrap_or_else(|_| U256::zero()); let new = current.saturating_add(burned_fee); - write_u256_le(host, path, new) + write_u256_le(host, path, new).map_err(Error::from) } #[cfg(test)] pub fn read_burned_fees(host: &mut impl Runtime) -> U256 { - let path = &EVM_BURNED_FEES.into(); + let path = &EVM_BURNED_FEES; read_u256_le(host, path).unwrap_or_else(|_| U256::zero()) } @@ -697,39 +607,32 @@ pub fn read_last_info_per_level_timestamp( read_timestamp_path(host, &EVM_INFO_PER_LEVEL_TIMESTAMP.into()) } -fn read_b58_kt1(host: &Host, path: &OwnedPath) -> Option { - let mut buffer = [0; 36]; - store_read_slice(host, path, &mut buffer, 36).ok()?; - let kt1_b58 = String::from_utf8(buffer.to_vec()).ok()?; - ContractKt1Hash::from_b58check(&kt1_b58).ok() -} - pub fn read_admin(host: &mut Host) -> Option { - read_b58_kt1(host, &ADMIN.into()) + read_b58_kt1(host, &ADMIN) } pub fn read_sequencer_governance( host: &mut Host, ) -> Option { - read_b58_kt1(host, &SEQUENCER_GOVERNANCE.into()) + read_b58_kt1(host, &SEQUENCER_GOVERNANCE) } pub fn read_kernel_governance(host: &mut Host) -> Option { - read_b58_kt1(host, &KERNEL_GOVERNANCE.into()) + read_b58_kt1(host, &KERNEL_GOVERNANCE) } pub fn read_kernel_security_governance( host: &mut Host, ) -> Option { - read_b58_kt1(host, &KERNEL_SECURITY_GOVERNANCE.into()) + read_b58_kt1(host, &KERNEL_SECURITY_GOVERNANCE) } pub fn read_maximum_allowed_ticks(host: &mut Host) -> Option { - read_u64(host, &MAXIMUM_ALLOWED_TICKS).ok() + read_u64_le(host, &MAXIMUM_ALLOWED_TICKS).ok() } pub fn read_maximum_gas_per_transaction(host: &mut Host) -> Option { - read_u64(host, &MAXIMUM_GAS_PER_TRANSACTION).ok() + read_u64_le(host, &MAXIMUM_GAS_PER_TRANSACTION).ok() } pub fn store_storage_version( @@ -1028,7 +931,7 @@ pub use internal_for_tests::*; pub fn read_delayed_transaction_bridge( host: &Host, ) -> Option { - read_b58_kt1(host, &DELAYED_BRIDGE.into()) + read_b58_kt1(host, &DELAYED_BRIDGE) } #[cfg(test)] diff --git a/etherlink/kernel_evm/kernel/src/upgrade.rs b/etherlink/kernel_evm/kernel/src/upgrade.rs index 3be4733cf6d5..e86ce388824b 100644 --- a/etherlink/kernel_evm/kernel/src/upgrade.rs +++ b/etherlink/kernel_evm/kernel/src/upgrade.rs @@ -11,9 +11,7 @@ use core::fmt; use crate::error::UpgradeProcessError; use crate::event::Event; use crate::storage; -use crate::storage::read_optional_rlp; -use crate::storage::store_sequencer; -use crate::storage::store_sequencer_pool_address; +use crate::storage::{store_sequencer, store_sequencer_pool_address}; use anyhow::Context; use primitive_types::H160; use rlp::Decodable; @@ -34,6 +32,7 @@ use tezos_smart_rollup_host::path::Path; 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; +use tezos_storage::read_optional_rlp; const KERNEL_UPGRADE: RefPath = RefPath::assert_from(b"/evm/kernel_upgrade"); pub const KERNEL_ROOT_HASH: RefPath = RefPath::assert_from(b"/evm/kernel_root_hash"); -- GitLab From 93f301e7144959e26ceb42469d91226506b33e8f Mon Sep 17 00:00:00 2001 From: Rodi-Can Bozman Date: Mon, 2 Sep 2024 13:59:51 +0200 Subject: [PATCH 3/5] Etherlink: use generic storage crate in evm execution's codebase --- etherlink/kernel_evm/Cargo.lock | 1 + etherlink/kernel_evm/evm_execution/Cargo.toml | 7 +- .../evm_execution/src/account_storage.rs | 128 +++++++----------- .../evm_execution/src/fa_bridge/test_utils.rs | 5 +- .../src/fa_bridge/ticket_table.rs | 24 ++-- .../evm_execution/src/withdrawal_counter.rs | 21 ++- 6 files changed, 80 insertions(+), 106 deletions(-) diff --git a/etherlink/kernel_evm/Cargo.lock b/etherlink/kernel_evm/Cargo.lock index 8e97341cf854..9b47ff89b543 100644 --- a/etherlink/kernel_evm/Cargo.lock +++ b/etherlink/kernel_evm/Cargo.lock @@ -735,6 +735,7 @@ dependencies = [ "tezos-smart-rollup-host", "tezos-smart-rollup-mock", "tezos-smart-rollup-storage", + "tezos-storage", "tezos_crypto_rs", "tezos_data_encoding", "tezos_ethereum", diff --git a/etherlink/kernel_evm/evm_execution/Cargo.toml b/etherlink/kernel_evm/evm_execution/Cargo.toml index 5243db920457..d2ba7bb84b56 100644 --- a/etherlink/kernel_evm/evm_execution/Cargo.toml +++ b/etherlink/kernel_evm/evm_execution/Cargo.toml @@ -39,6 +39,7 @@ bn.workspace = true tezos_ethereum.workspace = true tezos-evm-logging.workspace = true tezos-indexable-storage.workspace = true +tezos-storage.workspace = true tezos-smart-rollup-core.workspace = true tezos-smart-rollup-host.workspace = true @@ -67,7 +68,11 @@ alloy-primitives.workspace = true [features] default = ["evm_execution"] testing = ["rand", "proptest", "dep:tezos-smart-rollup-mock"] -fa_bridge_testing = ["dep:tezos-smart-rollup-mock", "dep:alloy-primitives", "dep:alloy-sol-types"] +fa_bridge_testing = [ + "dep:tezos-smart-rollup-mock", + "dep:alloy-primitives", + "dep:alloy-sol-types", +] evm_execution = [] debug = ["tezos-evm-logging/debug"] # the `benchmark` and `benchmark-opcodes` feature flags instrument the kernel for profiling diff --git a/etherlink/kernel_evm/evm_execution/src/account_storage.rs b/etherlink/kernel_evm/evm_execution/src/account_storage.rs index a944b1dd14e7..29064dd3e9ef 100644 --- a/etherlink/kernel_evm/evm_execution/src/account_storage.rs +++ b/etherlink/kernel_evm/evm_execution/src/account_storage.rs @@ -9,8 +9,13 @@ use const_decoder::Decoder; use host::path::{concat, OwnedPath, Path, RefPath}; use host::runtime::{Runtime, RuntimeError, ValueType}; use primitive_types::{H160, H256, U256}; -use sha3::{Digest, Keccak256}; +use rlp::DecoderError; use tezos_smart_rollup_storage::storage::Storage; +use tezos_storage::helpers::bytes_hash; +use tezos_storage::{ + error::Error as GenStorageError, read_u256_le_default, read_u64_le_default, +}; +use tezos_storage::{path_from_h256, read_h256_be_default}; use thiserror::Error; use crate::DurableStorageError; @@ -30,6 +35,10 @@ pub enum AccountStorageError { /// API. #[error("Transaction storage API error: {0:?}")] StorageError(tezos_smart_rollup_storage::StorageError), + #[error("Failed to decode: {0}")] + RlpDecoderError(DecoderError), + #[error("Storage error: error while reading a value (incorrect size). Expected {expected} but got {actual}")] + InvalidLoadValue { expected: usize, actual: usize }, /// Some account balance became greater than what can be /// stored in an unsigned 256 bit integer. #[error("Account balance overflow")] @@ -60,6 +69,26 @@ impl From for AccountStorageError { } } +impl From for AccountStorageError { + fn from(e: GenStorageError) -> Self { + match e { + GenStorageError::Path(e) => { + AccountStorageError::DurableStorageError(DurableStorageError::from(e)) + } + GenStorageError::Runtime(e) => { + AccountStorageError::DurableStorageError(DurableStorageError::from(e)) + } + GenStorageError::Storage(e) => AccountStorageError::StorageError(e), + GenStorageError::RlpDecoderError(e) => { + AccountStorageError::RlpDecoderError(e) + } + GenStorageError::InvalidLoadValue { expected, actual } => { + AccountStorageError::InvalidLoadValue { expected, actual } + } + } + } +} + /// When an Ethereum contract acts on storage, it spends gas. The gas cost of /// operations that affect storage depends both on what was in storage already, /// and the new value written to storage. @@ -155,75 +184,6 @@ const CODE_HASH_BYTES: [u8; WORD_SIZE] = Decoder::Hex /// The default hash for when there is no code - the hash of the empty string. pub const CODE_HASH_DEFAULT: H256 = H256(CODE_HASH_BYTES); -/// Read a single unsigned 256 bit value from storage at the path given. -pub fn read_u256( - host: &impl Runtime, - path: &impl Path, - default: U256, -) -> Result { - match host.store_read_all(path) { - Ok(bytes) if bytes.len() == WORD_SIZE => Ok(U256::from_little_endian(&bytes)), - Ok(_) | Err(RuntimeError::PathNotFound) => Ok(default), - Err(err) => Err(err.into()), - } -} - -/// Write a single unsigned 256 but value to the storage at the given path -pub fn write_u256( - host: &mut impl Runtime, - path: &impl Path, - x: U256, -) -> Result<(), RuntimeError> { - let mut x_bytes = [0u8; 32]; - x.to_little_endian(&mut x_bytes); - host.store_write(path, &x_bytes, 0) -} - -/// Read a single unsigned 64 bit value from storage at the path given. -fn read_u64( - host: &impl Runtime, - path: &impl Path, - default: u64, -) -> Result { - match host.store_read(path, 0, 8) { - Ok(bytes) if bytes.len() == 8 => { - let bytes_array: [u8; 8] = bytes.try_into().map_err(|_| { - AccountStorageError::DurableStorageError( - DurableStorageError::RuntimeError(RuntimeError::DecodingError), - ) - })?; - Ok(u64::from_le_bytes(bytes_array)) - } - Ok(_) | Err(RuntimeError::PathNotFound) => Ok(default), - Err(err) => Err(err.into()), - } -} - -/// Read a single 256 bit hash from storage at the path given. -fn read_h256( - host: &impl Runtime, - path: &impl Path, - default: H256, -) -> Result { - match host.store_read_all(path) { - Ok(bytes) if bytes.len() == WORD_SIZE => Ok(H256::from_slice(&bytes)), - Ok(_) | Err(RuntimeError::PathNotFound) => Ok(default), - Err(err) => Err(err.into()), - } -} - -/// Get the path corresponding to an index of H256. This is used to -/// find the path to a value a contract stores in durable storage. -pub fn path_from_h256(index: &H256) -> Result { - let path_string = alloc::format!("/{}", hex::encode(index.to_fixed_bytes())); - OwnedPath::try_from(path_string).map_err(AccountStorageError::from) -} - -/// Compute Keccak 256 for some bytes -fn bytes_hash(bytes: &[u8]) -> H256 { - H256(Keccak256::digest(bytes).into()) -} - /// Turn an Ethereum address - a H160 - into a valid path pub fn account_path(address: &H160) -> Result { let path_string = alloc::format!("/{}", hex::encode(address.to_fixed_bytes())); @@ -240,7 +200,8 @@ impl EthereumAccount { /// _always_ have this **nonce**. pub fn nonce(&self, host: &impl Runtime) -> Result { let path = concat(&self.path, &NONCE_PATH)?; - read_u64(host, &path, NONCE_DEFAULT_VALUE) + read_u64_le_default(host, &path, NONCE_DEFAULT_VALUE) + .map_err(AccountStorageError::from) } /// Increment the **nonce** by one. It is technically possible for this operation to overflow, @@ -296,7 +257,8 @@ impl EthereumAccount { /// Get the **balance** of an account in Wei held by the account. pub fn balance(&self, host: &impl Runtime) -> Result { let path = concat(&self.path, &BALANCE_PATH)?; - read_u256(host, &path, BALANCE_DEFAULT_VALUE).map_err(AccountStorageError::from) + read_u256_le_default(host, &path, BALANCE_DEFAULT_VALUE) + .map_err(AccountStorageError::from) } /// Add an amount in Wei to the balance of an account. In theory, this can overflow if the @@ -396,7 +358,8 @@ impl EthereumAccount { index: &H256, ) -> Result { let path = self.storage_path(index)?; - read_h256(host, &path, STORAGE_DEFAULT_VALUE).map_err(AccountStorageError::from) + read_h256_be_default(host, &path, STORAGE_DEFAULT_VALUE) + .map_err(AccountStorageError::from) } /// Set the value associated with an index in durable storage. The result depends on the @@ -475,7 +438,8 @@ impl EthereumAccount { /// stored when the code of a contract is set. pub fn code_hash(&self, host: &impl Runtime) -> Result { let path = concat(&self.path, &CODE_HASH_PATH)?; - read_h256(host, &path, CODE_HASH_DEFAULT).map_err(AccountStorageError::from) + read_h256_be_default(host, &path, CODE_HASH_DEFAULT) + .map_err(AccountStorageError::from) } /// Get the size of a contract in number of bytes used for opcodes. This value is @@ -556,6 +520,7 @@ mod test { use host::path::RefPath; use primitive_types::U256; use tezos_smart_rollup_mock::MockHost; + use tezos_storage::write_u256_le; #[test] fn test_account_nonce_update() { @@ -1174,16 +1139,19 @@ mod test { } #[test] - fn test_read_u256_le() { + fn test_read_u256_le_default_le() { let mut host = MockHost::default(); let path = RefPath::assert_from(b"/value"); assert_eq!( - read_u256(&host, &path, U256::from(128)).unwrap(), + read_u256_le_default(&host, &path, U256::from(128)).unwrap(), U256::from(128) ); host.store_write_all(&path, &[1u8; 20]).unwrap(); - assert_eq!(read_u256(&host, &path, U256::zero()).unwrap(), U256::zero()); + assert_eq!( + read_u256_le_default(&host, &path, U256::zero()).unwrap(), + U256::zero() + ); host.store_write_all( &path, @@ -1194,17 +1162,17 @@ mod test { ) .unwrap(); assert_eq!( - read_u256(&host, &path, U256::zero()).unwrap(), + read_u256_le_default(&host, &path, U256::zero()).unwrap(), U256::from(255) ); } #[test] - fn test_write_u256_le() { + fn test_write_u256_le_le() { let mut host = MockHost::default(); let path = RefPath::assert_from(b"/value"); - write_u256(&mut host, &path, U256::from(255)).unwrap(); + write_u256_le(&mut host, &path, U256::from(255)).unwrap(); assert_eq!( hex::encode(host.store_read_all(&path).unwrap()), "ff00000000000000000000000000000000000000000000000000000000000000" diff --git a/etherlink/kernel_evm/evm_execution/src/fa_bridge/test_utils.rs b/etherlink/kernel_evm/evm_execution/src/fa_bridge/test_utils.rs index 174ecafeb3eb..26996ecde22c 100644 --- a/etherlink/kernel_evm/evm_execution/src/fa_bridge/test_utils.rs +++ b/etherlink/kernel_evm/evm_execution/src/fa_bridge/test_utils.rs @@ -19,9 +19,10 @@ use tezos_smart_rollup_encoding::{ michelson::{ticket::FA2_1Ticket, MichelsonOption, MichelsonPair}, }; use tezos_smart_rollup_mock::MockHost; +use tezos_storage::read_u256_le_default; use crate::{ - account_storage::{account_path, read_u256, EthereumAccountStorage}, + account_storage::{account_path, EthereumAccountStorage}, handler::{EvmHandler, ExecutionOutcome}, precompiles::{self, precompile_set, SYSTEM_ACCOUNT_ADDRESS}, run_transaction, @@ -202,7 +203,7 @@ pub fn ticket_balance_get( let path = system .custom_path(&ticket_balance_path(ticket_hash, address).unwrap()) .unwrap(); - read_u256(host, &path, U256::zero()).unwrap() + read_u256_le_default(host, &path, U256::zero()).unwrap() } /// Get next withdrawal counter value diff --git a/etherlink/kernel_evm/evm_execution/src/fa_bridge/ticket_table.rs b/etherlink/kernel_evm/evm_execution/src/fa_bridge/ticket_table.rs index 86b6ff7af1bb..34c2326a1025 100644 --- a/etherlink/kernel_evm/evm_execution/src/fa_bridge/ticket_table.rs +++ b/etherlink/kernel_evm/evm_execution/src/fa_bridge/ticket_table.rs @@ -7,16 +7,13 @@ //! Maintains a ledger that tracks ownership of deposited tickets. //! Any EVM account can be ticket owner, whether it's EOA or smart contract. +use crate::account_storage::{account_path, AccountStorageError, EthereumAccount}; use primitive_types::{H160, H256, U256}; use tezos_smart_rollup_host::{ path::{concat, OwnedPath, RefPath}, runtime::Runtime, }; - -use crate::account_storage::{ - account_path, path_from_h256, read_u256, write_u256, AccountStorageError, - EthereumAccount, -}; +use tezos_storage::{path_from_h256, read_u256_le_default, write_u256_le}; /// Path where global ticket table is stored const TICKET_TABLE_PATH: RefPath = RefPath::assert_from(b"/ticket_table"); @@ -58,10 +55,10 @@ impl TicketTable for EthereumAccount { amount: U256, ) -> Result { let path = self.custom_path(&ticket_balance_path(ticket_hash, owner)?)?; - let balance = read_u256(host, &path, U256::zero())?; + let balance = read_u256_le_default(host, &path, U256::zero())?; if let Some(new_balance) = balance.checked_add(amount) { - write_u256(host, &path, new_balance)?; + write_u256_le(host, &path, new_balance)?; Ok(true) } else { Ok(false) @@ -76,10 +73,10 @@ impl TicketTable for EthereumAccount { amount: U256, ) -> Result { let path = self.custom_path(&ticket_balance_path(ticket_hash, owner)?)?; - let balance = read_u256(host, &path, U256::zero())?; + let balance = read_u256_le_default(host, &path, U256::zero())?; if let Some(new_balance) = balance.checked_sub(amount) { - write_u256(host, &path, new_balance)?; + write_u256_le(host, &path, new_balance)?; Ok(true) } else { Ok(false) @@ -91,8 +88,9 @@ impl TicketTable for EthereumAccount { mod tests { use tezos_smart_rollup_host::path::RefPath; use tezos_smart_rollup_mock::MockHost; + use tezos_storage::read_u256_le_default; - use crate::{account_storage::read_u256, precompiles::SYSTEM_ACCOUNT_ADDRESS}; + use crate::precompiles::SYSTEM_ACCOUNT_ADDRESS; use super::*; @@ -117,7 +115,8 @@ mod tests { /0101010101010101010101010101010101010101010101010101010101010101\ /0202020202020202020202020202020202020202"; let balance = - read_u256(&host, &RefPath::assert_from(path), U256::zero()).unwrap(); + read_u256_le_default(&host, &RefPath::assert_from(path), U256::zero()) + .unwrap(); assert_eq!(U256::from(84), balance); } @@ -145,7 +144,8 @@ mod tests { /0101010101010101010101010101010101010101010101010101010101010101\ /0202020202020202020202020202020202020202"; let balance = - read_u256(&host, &RefPath::assert_from(path), U256::zero()).unwrap(); + read_u256_le_default(&host, &RefPath::assert_from(path), U256::zero()) + .unwrap(); assert_eq!(U256::MAX, balance); } diff --git a/etherlink/kernel_evm/evm_execution/src/withdrawal_counter.rs b/etherlink/kernel_evm/evm_execution/src/withdrawal_counter.rs index bacac2c6dd46..a0cb00dfe164 100644 --- a/etherlink/kernel_evm/evm_execution/src/withdrawal_counter.rs +++ b/etherlink/kernel_evm/evm_execution/src/withdrawal_counter.rs @@ -12,10 +12,9 @@ use primitive_types::U256; use tezos_smart_rollup_host::{path::RefPath, runtime::Runtime}; +use tezos_storage::{read_u256_le_default, write_u256_le}; -use crate::account_storage::{ - read_u256, write_u256, AccountStorageError, EthereumAccount, -}; +use crate::account_storage::{AccountStorageError, EthereumAccount}; /// Path where withdrawal counter is stored (relative to account) pub const WITHDRAWAL_COUNTER_PATH: RefPath = RefPath::assert_from(b"/withdrawal_counter"); @@ -35,12 +34,12 @@ impl WithdrawalCounter for EthereumAccount { host: &mut impl Runtime, ) -> Result { let path = self.custom_path(&WITHDRAWAL_COUNTER_PATH)?; - let old_value = read_u256(host, &path, U256::zero())?; + let old_value = read_u256_le_default(host, &path, U256::zero())?; let new_value = old_value .checked_add(U256::one()) .ok_or(AccountStorageError::NonceOverflow)?; - write_u256(host, &path, new_value)?; + write_u256_le(host, &path, new_value)?; Ok(old_value) } } @@ -50,11 +49,9 @@ mod tests { use primitive_types::U256; use tezos_smart_rollup_host::path::RefPath; use tezos_smart_rollup_mock::MockHost; + use tezos_storage::read_u256_le_default; - use crate::{ - account_storage::{read_u256, EthereumAccount}, - precompiles::SYSTEM_ACCOUNT_ADDRESS, - }; + use crate::{account_storage::EthereumAccount, precompiles::SYSTEM_ACCOUNT_ADDRESS}; use super::WithdrawalCounter; @@ -71,7 +68,8 @@ mod tests { assert_eq!(U256::zero(), id); let next_id = - read_u256(&mock_host, &RefPath::assert_from(path), U256::zero()).unwrap(); + read_u256_le_default(&mock_host, &RefPath::assert_from(path), U256::zero()) + .unwrap(); assert_eq!(U256::one(), next_id); let id = account @@ -80,7 +78,8 @@ mod tests { assert_eq!(U256::one(), id); let next_id = - read_u256(&mock_host, &RefPath::assert_from(path), U256::zero()).unwrap(); + read_u256_le_default(&mock_host, &RefPath::assert_from(path), U256::zero()) + .unwrap(); assert_eq!(U256::from(2), next_id); } } -- GitLab From d159d9b5322062a0317d08c28ee75b7bb8ba39b7 Mon Sep 17 00:00:00 2001 From: Rodi-Can Bozman Date: Mon, 2 Sep 2024 14:16:30 +0200 Subject: [PATCH 4/5] Etherlink: use generic storage crate in indexable storage's codebase --- etherlink/kernel_evm/Cargo.lock | 2 + .../kernel_evm/indexable_storage/Cargo.toml | 2 + .../kernel_evm/indexable_storage/src/lib.rs | 63 +++++++++++-------- etherlink/kernel_evm/kernel/src/error.rs | 4 ++ 4 files changed, 44 insertions(+), 27 deletions(-) diff --git a/etherlink/kernel_evm/Cargo.lock b/etherlink/kernel_evm/Cargo.lock index 9b47ff89b543..521fc99aebe9 100644 --- a/etherlink/kernel_evm/Cargo.lock +++ b/etherlink/kernel_evm/Cargo.lock @@ -2202,10 +2202,12 @@ dependencies = [ name = "tezos-indexable-storage" version = "0.1.0" dependencies = [ + "rlp", "tezos-evm-logging", "tezos-smart-rollup-host", "tezos-smart-rollup-mock", "tezos-smart-rollup-storage", + "tezos-storage", "thiserror", ] diff --git a/etherlink/kernel_evm/indexable_storage/Cargo.toml b/etherlink/kernel_evm/indexable_storage/Cargo.toml index 3d3f27fc81fa..951bf3e44f7f 100644 --- a/etherlink/kernel_evm/indexable_storage/Cargo.toml +++ b/etherlink/kernel_evm/indexable_storage/Cargo.toml @@ -10,7 +10,9 @@ license = "MIT" [dependencies] thiserror.workspace = true +rlp.workspace = true tezos-evm-logging.workspace = true tezos-smart-rollup-host.workspace = true tezos-smart-rollup-mock.workspace = true tezos-smart-rollup-storage.workspace = true +tezos-storage.workspace = true diff --git a/etherlink/kernel_evm/indexable_storage/src/lib.rs b/etherlink/kernel_evm/indexable_storage/src/lib.rs index e49050b78fab..4140190fe31b 100644 --- a/etherlink/kernel_evm/indexable_storage/src/lib.rs +++ b/etherlink/kernel_evm/indexable_storage/src/lib.rs @@ -3,35 +3,17 @@ // // SPDX-License-Identifier: MIT +use rlp::DecoderError; use tezos_evm_logging::log; use tezos_evm_logging::Level::Error; use tezos_smart_rollup_host::path::{concat, OwnedPath, PathError, RefPath}; use tezos_smart_rollup_host::runtime::{Runtime, RuntimeError}; use tezos_smart_rollup_storage::StorageError; +use tezos_storage::{error::Error as GenStorageError, read_u64_le, write_u64_le}; use thiserror::Error; const LENGTH: RefPath = RefPath::assert_from(b"/length"); -/// Utility function to read a little_endian encoded u64 in the storage -fn read_u64(host: &Host, path: &OwnedPath) -> Result { - let mut buffer = [0_u8; std::mem::size_of::()]; - let value = host.store_read_slice(path, 0, &mut buffer)?; - if value != 8 { - Err(RuntimeError::DecodingError) - } else { - Ok(u64::from_le_bytes(buffer)) - } -} - -/// Utility function to write u64 in little endian in the storage -fn store_u64( - host: &mut Host, - path: &OwnedPath, - value: u64, -) -> Result<(), RuntimeError> { - host.store_write_all(path, &value.to_le_bytes()) -} - /// An indexable storage is a push-only mapping between increasing integers to /// bytes. It can serve as a replacement for the combination of the host /// functions `store_get_nth` and `store_list_size` that are unsafe. @@ -50,10 +32,30 @@ pub enum IndexableStorageError { Runtime(#[from] RuntimeError), #[error(transparent)] Storage(#[from] StorageError), + #[error("Failed to decode: {0}")] + RlpDecoderError(DecoderError), + #[error("Storage error: error while reading a value (incorrect size). Expected {expected} but got {actual}")] + InvalidLoadValue { expected: usize, actual: usize }, #[error("Storage error: index out of bound")] IndexOutOfBounds, } +impl From for IndexableStorageError { + fn from(e: GenStorageError) -> Self { + match e { + GenStorageError::Path(e) => IndexableStorageError::Path(e), + GenStorageError::Runtime(e) => IndexableStorageError::Runtime(e), + GenStorageError::Storage(e) => IndexableStorageError::Storage(e), + GenStorageError::RlpDecoderError(e) => { + IndexableStorageError::RlpDecoderError(e) + } + GenStorageError::InvalidLoadValue { expected, actual } => { + IndexableStorageError::InvalidLoadValue { expected, actual } + } + } + } +} + impl IndexableStorage { pub fn new(path: &RefPath<'_>) -> Result { Ok(Self { path: path.into() }) @@ -87,22 +89,29 @@ impl IndexableStorage { host: &mut Host, ) -> Result { let path = concat(&self.path, &LENGTH)?; - let length = read_u64(host, &path).unwrap_or(0); - store_u64(host, &path, length + 1)?; + let length = read_u64_le(host, &path).unwrap_or(0); + write_u64_le(host, &path, length + 1)?; Ok(length) } #[allow(dead_code)] /// `length` returns the number of keys in the storage. If `/length` does /// not exists, the storage is considered as empty and returns '0'. - pub fn length(&self, host: &Host) -> Result { + pub fn length( + &self, + host: &Host, + ) -> Result { let path = concat(&self.path, &LENGTH)?; - match read_u64(host, &path) { + match read_u64_le(host, &path) { Ok(l) => Ok(l), Err( - RuntimeError::PathNotFound - | RuntimeError::HostErr(tezos_smart_rollup_host::Error::StoreNotAValue) - | RuntimeError::HostErr(tezos_smart_rollup_host::Error::StoreInvalidAccess), + GenStorageError::Runtime( + RuntimeError::PathNotFound + | RuntimeError::HostErr(tezos_smart_rollup_host::Error::StoreNotAValue) + | RuntimeError::HostErr( + tezos_smart_rollup_host::Error::StoreInvalidAccess, + ), + ), // An InvalidAccess implies that the path does not exist at all // in the storage: store_read fails because reading is out of // bounds since the value has never been allocated before diff --git a/etherlink/kernel_evm/kernel/src/error.rs b/etherlink/kernel_evm/kernel/src/error.rs index da2ff0ae99dc..c05335aae09e 100644 --- a/etherlink/kernel_evm/kernel/src/error.rs +++ b/etherlink/kernel_evm/kernel/src/error.rs @@ -204,6 +204,10 @@ impl From for Error { IndexableStorageError::IndexOutOfBounds => { Error::Storage(StorageError::IndexOutOfBounds) } + IndexableStorageError::RlpDecoderError(e) => Error::RlpDecoderError(e), + IndexableStorageError::InvalidLoadValue { expected, actual } => { + Error::Storage(StorageError::InvalidLoadValue { expected, actual }) + } } } } -- GitLab From 9953551790f8417f14291a877a2a81d834fb1fbd Mon Sep 17 00:00:00 2001 From: Rodi-Can Bozman Date: Wed, 4 Sep 2024 10:33:24 +0200 Subject: [PATCH 5/5] Etherlink: add an entry in internal changes --- etherlink/CHANGES_KERNEL.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/etherlink/CHANGES_KERNEL.md b/etherlink/CHANGES_KERNEL.md index bb0f2215631d..2d97564b0c5a 100644 --- a/etherlink/CHANGES_KERNEL.md +++ b/etherlink/CHANGES_KERNEL.md @@ -30,6 +30,8 @@ Beta and Testnet, and allows users to deposit and withdraw FA 2.1 tokens. (!14685) - EVM Execution no longer require an additional storage for block number to block hash. Uses the existing indexing table instead. (!14704) +- A generic storage crate was introduced so that every component of the kernel + benefits from the same implementation to read/write primitives. (!14735) ## Version af7909023768bc4aad3120bec7bea4a64a576047 -- GitLab