From b47bc76926e0a94517bc669032679badf185e697 Mon Sep 17 00:00:00 2001 From: arnaud Date: Tue, 15 Apr 2025 11:16:01 +0200 Subject: [PATCH 1/3] Tezlink/Kernel/Storage: Introduce a new kind of Error when reading binary data in the durable storage --- etherlink/kernel_latest/Cargo.lock | 1 + .../evm_execution/src/account_storage.rs | 8 ++++++++ .../indexable_storage/src/lib.rs | 10 ++++++++++ etherlink/kernel_latest/kernel/src/error.rs | 15 ++++++++++++++ etherlink/kernel_latest/storage/Cargo.toml | 1 + etherlink/kernel_latest/storage/src/error.rs | 20 +++++++++++++++++++ 6 files changed, 55 insertions(+) diff --git a/etherlink/kernel_latest/Cargo.lock b/etherlink/kernel_latest/Cargo.lock index 64f1d45f26e6..69bb08588040 100644 --- a/etherlink/kernel_latest/Cargo.lock +++ b/etherlink/kernel_latest/Cargo.lock @@ -2579,6 +2579,7 @@ dependencies = [ "tezos-smart-rollup-host", "tezos-smart-rollup-storage", "tezos_crypto_rs", + "tezos_data_encoding", "tezos_ethereum_latest", "thiserror 1.0.69", ] diff --git a/etherlink/kernel_latest/evm_execution/src/account_storage.rs b/etherlink/kernel_latest/evm_execution/src/account_storage.rs index 7ec9c263655e..32a2e99ccc8f 100644 --- a/etherlink/kernel_latest/evm_execution/src/account_storage.rs +++ b/etherlink/kernel_latest/evm_execution/src/account_storage.rs @@ -36,6 +36,10 @@ pub enum AccountStorageError { 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: Failed to encode a value with BinWriter: {0}")] + BinWriteError(String), + #[error("Storage error: Failed to decode a value with NomReader: {0}")] + NomReadError(String), /// Some account balance became greater than what can be /// stored in an unsigned 256 bit integer. #[error("Account balance overflow")] @@ -75,6 +79,10 @@ impl From for AccountStorageError { GenStorageError::Runtime(e) => { AccountStorageError::DurableStorageError(DurableStorageError::from(e)) } + GenStorageError::BinWriteError(msg) => { + AccountStorageError::BinWriteError(msg) + } + GenStorageError::NomReadError(msg) => AccountStorageError::NomReadError(msg), GenStorageError::Storage(e) => AccountStorageError::StorageError(e), GenStorageError::RlpDecoderError(e) => { AccountStorageError::RlpDecoderError(e) diff --git a/etherlink/kernel_latest/indexable_storage/src/lib.rs b/etherlink/kernel_latest/indexable_storage/src/lib.rs index 383d87337623..c1f71eecc72f 100644 --- a/etherlink/kernel_latest/indexable_storage/src/lib.rs +++ b/etherlink/kernel_latest/indexable_storage/src/lib.rs @@ -39,6 +39,10 @@ pub enum IndexableStorageError { InvalidLoadValue { expected: usize, actual: usize }, #[error("Storage error: index out of bound")] IndexOutOfBounds, + #[error("Storage error: Failed to encode a value with BinWriter: {0}")] + BinWriteError(String), + #[error("Storage error: Failed to decode a value with NomReader: {0}")] + NomReadError(String), } impl From for IndexableStorageError { @@ -53,6 +57,12 @@ impl From for IndexableStorageError { GenStorageError::InvalidLoadValue { expected, actual } => { IndexableStorageError::InvalidLoadValue { expected, actual } } + GenStorageError::NomReadError(msg) => { + IndexableStorageError::NomReadError(msg) + } + GenStorageError::BinWriteError(msg) => { + IndexableStorageError::BinWriteError(msg) + } } } } diff --git a/etherlink/kernel_latest/kernel/src/error.rs b/etherlink/kernel_latest/kernel/src/error.rs index c05335aae09e..3684320a3ced 100644 --- a/etherlink/kernel_latest/kernel/src/error.rs +++ b/etherlink/kernel_latest/kernel/src/error.rs @@ -77,6 +77,11 @@ pub enum EncodingError { Entrypoint(EntrypointError), #[error("Invalid serialization")] Bin(BinError), + // BinWriter error comes from the storage crate wich need to + // implement PartialEq + Display which is why we're keeping a + // String instead of a BinError + #[error("Storage error: Failed to encode a value with BinWriter: {0}")] + BinWriterError(String), } #[derive(Error, Debug)] @@ -90,6 +95,8 @@ pub enum Error { InvalidConversion, #[error("Failed to decode: {0}")] RlpDecoderError(DecoderError), + #[error("Storage error: Failed to decode a value with NomReader: {0}")] + NomReadError(String), #[error("Invalid parsing")] InvalidParsing, #[error(transparent)] @@ -208,6 +215,10 @@ impl From for Error { IndexableStorageError::InvalidLoadValue { expected, actual } => { Error::Storage(StorageError::InvalidLoadValue { expected, actual }) } + IndexableStorageError::BinWriteError(msg) => { + Error::Encoding(EncodingError::BinWriterError(msg)) + } + IndexableStorageError::NomReadError(msg) => Error::NomReadError(msg), } } } @@ -222,6 +233,10 @@ impl From for Error { GenStorageError::InvalidLoadValue { expected, actual } => { Error::Storage(StorageError::InvalidLoadValue { expected, actual }) } + GenStorageError::BinWriteError(msg) => { + Error::Encoding(EncodingError::BinWriterError(msg)) + } + GenStorageError::NomReadError(msg) => Error::NomReadError(msg), } } } diff --git a/etherlink/kernel_latest/storage/Cargo.toml b/etherlink/kernel_latest/storage/Cargo.toml index 65dcc3b75cf5..dfddbdcb7556 100644 --- a/etherlink/kernel_latest/storage/Cargo.toml +++ b/etherlink/kernel_latest/storage/Cargo.toml @@ -24,3 +24,4 @@ tezos_ethereum.workspace = true tezos-evm-runtime.workspace = true tezos-smart-rollup-host.workspace = true tezos-smart-rollup-storage.workspace = true +tezos_data_encoding.workspace = true diff --git a/etherlink/kernel_latest/storage/src/error.rs b/etherlink/kernel_latest/storage/src/error.rs index 55ac5bca2151..e5cd61cb78a4 100644 --- a/etherlink/kernel_latest/storage/src/error.rs +++ b/etherlink/kernel_latest/storage/src/error.rs @@ -3,6 +3,8 @@ // SPDX-License-Identifier: MIT use rlp::DecoderError; +use tezos_data_encoding::enc::BinError; +use tezos_data_encoding::nom::error::DecodeError; use tezos_smart_rollup_host::path::PathError; use tezos_smart_rollup_host::runtime::RuntimeError; use thiserror::Error; @@ -19,6 +21,10 @@ pub enum Error { 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: Failed to encode a value with BinWriter: {0}")] + BinWriteError(String), + #[error("Storage error: Failed to decode a value with NomReader: {0}")] + NomReadError(String), } impl From for Error { @@ -37,3 +43,17 @@ impl From for Error { Self::RlpDecoderError(e) } } + +impl From> for Error { + fn from(value: DecodeError<&[u8]>) -> Self { + let msg = format!("{:?}", value); + Self::NomReadError(msg) + } +} + +impl From for Error { + fn from(value: BinError) -> Self { + let msg = format!("{}", value); + Self::BinWriteError(msg) + } +} -- GitLab From 32353cdd0cbb2db48d0cbb962b9bbf282e906d0b Mon Sep 17 00:00:00 2001 From: arnaud Date: Tue, 15 Apr 2025 17:44:20 +0200 Subject: [PATCH 2/3] Tezlink/Kernel/Storage: Store and read binary data in the durable storage --- etherlink/kernel_latest/Cargo.lock | 1 + etherlink/kernel_latest/storage/Cargo.toml | 1 + .../kernel_latest/storage/src/helpers.rs | 10 +++ etherlink/kernel_latest/storage/src/lib.rs | 62 +++++++++++++++++++ 4 files changed, 74 insertions(+) diff --git a/etherlink/kernel_latest/Cargo.lock b/etherlink/kernel_latest/Cargo.lock index 69bb08588040..fdc351299a13 100644 --- a/etherlink/kernel_latest/Cargo.lock +++ b/etherlink/kernel_latest/Cargo.lock @@ -2572,6 +2572,7 @@ version = "0.1.0" dependencies = [ "anyhow", "hex", + "nom", "primitive-types", "rlp", "sha3", diff --git a/etherlink/kernel_latest/storage/Cargo.toml b/etherlink/kernel_latest/storage/Cargo.toml index dfddbdcb7556..e4d1bd96dee7 100644 --- a/etherlink/kernel_latest/storage/Cargo.toml +++ b/etherlink/kernel_latest/storage/Cargo.toml @@ -25,3 +25,4 @@ tezos-evm-runtime.workspace = true tezos-smart-rollup-host.workspace = true tezos-smart-rollup-storage.workspace = true tezos_data_encoding.workspace = true +nom.workspace = true diff --git a/etherlink/kernel_latest/storage/src/helpers.rs b/etherlink/kernel_latest/storage/src/helpers.rs index 62f7ecd39244..e755ef9ef677 100644 --- a/etherlink/kernel_latest/storage/src/helpers.rs +++ b/etherlink/kernel_latest/storage/src/helpers.rs @@ -4,8 +4,18 @@ use primitive_types::H256; use sha3::{Digest, Keccak256}; +use tezos_data_encoding::nom::NomResult; /// Compute the Keccak256 hash of `bytes`. pub fn bytes_hash(bytes: &[u8]) -> H256 { H256(Keccak256::digest(bytes).into()) } + +/// Verify if the result of a NomReader is incomplete +pub fn is_incomplete(result: &NomResult) -> bool { + if let Err(nom_error) = result { + nom_error.is_incomplete() + } else { + false + } +} diff --git a/etherlink/kernel_latest/storage/src/lib.rs b/etherlink/kernel_latest/storage/src/lib.rs index a4834e10f972..af6f0e21fd5e 100644 --- a/etherlink/kernel_latest/storage/src/lib.rs +++ b/etherlink/kernel_latest/storage/src/lib.rs @@ -10,12 +10,19 @@ pub mod helpers; use crate::error::Error; +use helpers::is_incomplete; +use nom::error::ParseError; +use nom::Finish; use primitive_types::{H256, U256}; use rlp::{Decodable, Encodable}; use tezos_crypto_rs::hash::{ContractKt1Hash, HashTrait}; +use tezos_data_encoding::enc::BinWriter; +use tezos_data_encoding::nom as tezos_nom; +use tezos_data_encoding::nom::NomReader; use tezos_ethereum::rlp_helpers::FromRlpBytes; use tezos_evm_runtime::runtime::Runtime; +use tezos_nom::error::DecodeError; use tezos_smart_rollup_host::path::*; use tezos_smart_rollup_host::runtime::{RuntimeError, ValueType}; @@ -240,3 +247,58 @@ pub fn read_b58_kt1(host: &impl Runtime, path: &impl Path) -> Option Result<(), Error> { + let mut buffer = vec![]; + value.bin_write(&mut buffer)?; + host.store_write_all(path, &buffer)?; + Ok(()) +} + +/// Return a potential decoded value using NomReader +/// at the given `path` +pub fn read_nom_value NomReader<'a>>( + host: &impl Runtime, + path: &impl Path, +) -> Result { + let bytes = host.store_read_all(path)?; + let result = T::nom_read(&bytes); + // The finish function may panic if the parser is a *streaming parser* that has not enough data. + // To be sure we don't panic, verify if the result is complete + if is_incomplete(&result) { + let incomplete_error = + DecodeError::from_error_kind(bytes.as_ref(), nom::error::ErrorKind::Eof); + return Err(incomplete_error.into()); + } + // Finish the parsing because we can't panic now + let (remaining, value) = result.finish()?; + // Verify that all data were consumed + if !remaining.is_empty() { + return Err(Error::NomReadError(format!( + "decoding didn't consume all data, remaining data: {:?}", + remaining + ))); + } + Ok(value) +} + +/// Return an optional decoded value using NomReader +/// at the given `path` +pub fn read_optional_nom_value NomReader<'a>>( + host: &impl Runtime, + path: &impl Path, +) -> Result, Error> { + if let Some(ValueType::Value) = host.store_has(path)? { + let value = read_nom_value(host, path)?; + Ok(Some(value)) + } else { + Ok(None) + } +} -- GitLab From ff9150f6e9eb302d83b25984a5e7b1084c503a20 Mon Sep 17 00:00:00 2001 From: arnaud Date: Mon, 28 Apr 2025 18:05:56 +0200 Subject: [PATCH 3/3] Tezlink/Kernel: Use Narith for Tezlink account's balance and counter It also helps to test the feature as there's an existing test for encoding and decoding Tezlink balances and counters --- etherlink/kernel_latest/Cargo.lock | 2 + .../kernel_latest/tezos_execution/Cargo.toml | 2 + .../tezos_execution/src/account_storage.rs | 48 ++++++++----------- 3 files changed, 24 insertions(+), 28 deletions(-) diff --git a/etherlink/kernel_latest/Cargo.lock b/etherlink/kernel_latest/Cargo.lock index fdc351299a13..7cb739459a0c 100644 --- a/etherlink/kernel_latest/Cargo.lock +++ b/etherlink/kernel_latest/Cargo.lock @@ -2394,6 +2394,7 @@ name = "tezos-execution-latest" version = "0.1.0" dependencies = [ "hex", + "num-bigint", "primitive-types", "tezos-evm-runtime-latest", "tezos-smart-rollup", @@ -2401,6 +2402,7 @@ dependencies = [ "tezos-storage-latest", "tezos_crypto_rs", "tezos_data_encoding", + "tezos_tezlink_latest", ] [[package]] diff --git a/etherlink/kernel_latest/tezos_execution/Cargo.toml b/etherlink/kernel_latest/tezos_execution/Cargo.toml index 47975e8633a3..d355990a3dca 100644 --- a/etherlink/kernel_latest/tezos_execution/Cargo.toml +++ b/etherlink/kernel_latest/tezos_execution/Cargo.toml @@ -12,6 +12,7 @@ license = "MIT" tezos_crypto_rs.workspace = true hex.workspace = true +num-bigint.workspace = true tezos-evm-runtime.workspace = true @@ -22,3 +23,4 @@ tezos_data_encoding.workspace = true primitive-types.workspace = true tezos-smart-rollup.workspace = true +tezos_tezlink.workspace = true diff --git a/etherlink/kernel_latest/tezos_execution/src/account_storage.rs b/etherlink/kernel_latest/tezos_execution/src/account_storage.rs index 6353d91af5a5..bf8233a53343 100644 --- a/etherlink/kernel_latest/tezos_execution/src/account_storage.rs +++ b/etherlink/kernel_latest/tezos_execution/src/account_storage.rs @@ -5,16 +5,12 @@ //! Tezos account state and storage -use primitive_types::U256; -use tezos_data_encoding::enc::BinWriter; +use crate::context; +use tezos_data_encoding::{enc::BinWriter, types::Narith}; use tezos_evm_runtime::runtime::Runtime; use tezos_smart_rollup::types::Contract; use tezos_smart_rollup_host::path::{concat, OwnedPath, RefPath}; -use tezos_storage::{ - read_u256_le_default, read_u64_le_default, write_u256_le, write_u64_le, -}; - -use crate::context; +use tezos_storage::{read_optional_nom_value, store_bin}; #[derive(Debug, PartialEq)] pub struct TezlinkImplicitAccount { @@ -60,9 +56,9 @@ impl TezlinkImplicitAccount { pub fn counter( &self, host: &impl Runtime, - ) -> Result { + ) -> Result { let path = concat(&self.path, &COUNTER_PATH)?; - read_u64_le_default(host, &path, 0u64) + Ok(read_optional_nom_value(host, &path)?.unwrap_or(0_u64.into())) } /// Set the **counter** for the Tezlink account. @@ -70,10 +66,10 @@ impl TezlinkImplicitAccount { pub fn set_counter( &mut self, host: &mut impl Runtime, - counter: u64, + counter: &Narith, ) -> Result<(), tezos_storage::error::Error> { let path = concat(&self.path, &COUNTER_PATH)?; - write_u64_le(host, &path, counter) + store_bin(counter, host, &path) } /// Get the **balance** of an account in Mutez held by the account. @@ -81,9 +77,9 @@ impl TezlinkImplicitAccount { pub fn balance( &self, host: &impl Runtime, - ) -> Result { + ) -> Result { let path = concat(&self.path, &BALANCE_PATH)?; - read_u256_le_default(host, &path, U256::zero()) + Ok(read_optional_nom_value(host, &path)?.unwrap_or(0_u64.into())) } /// Set the **balance** of an account in Mutez held by the account. @@ -91,19 +87,17 @@ impl TezlinkImplicitAccount { pub fn set_balance( &mut self, host: &mut impl Runtime, - balance: U256, + balance: &Narith, ) -> Result<(), tezos_storage::error::Error> { let path = concat(&self.path, &BALANCE_PATH)?; - write_u256_le(host, &path, balance) + store_bin(balance, host, &path) } } #[cfg(test)] mod test { use super::*; - use std::str::FromStr; use tezos_evm_runtime::runtime::MockKernelHost; - use tezos_smart_rollup::host::Runtime; // Read test use hard coded path on purpose to verify the Tezos compatibility. // These paths comes from the context.json generated by the create mockup command @@ -112,13 +106,11 @@ mod test { fn test_read_balance() { let mut host = MockKernelHost::default(); - let balance = U256::from_str("2944").unwrap(); + let balance: Narith = 2944_u64.into(); // octez-codec decode alpha.contract from '000002298c03ed7d454a101eb7022bc95f7e5f41ac78' // Result: "tz1KqTpEZ7Yob7QbPE4Hy4Wo8fHG8LhKxZSx" let path = RefPath::assert_from(b"/tezlink/context/contracts/index/000002298c03ed7d454a101eb7022bc95f7e5f41ac78/balance"); - let mut balance_array: [u8; 32] = [0; 32]; - balance.to_little_endian(&mut balance_array); - host.store_write_all(&path, &balance_array).unwrap(); + store_bin(&balance, &mut host, &path).expect("Store balance should have succeed"); // Initalize path for Tezlink context at /tezlink/context let context = context::Context::init_context(); @@ -141,12 +133,12 @@ mod test { fn test_read_counter() { let mut host = MockKernelHost::default(); - let counter = 3u64; + let counter: Narith = 3u64.into(); // octez-codec decode alpha.contract from '000002298c03ed7d454a101eb7022bc95f7e5f41ac78' // Result: "tz1KqTpEZ7Yob7QbPE4Hy4Wo8fHG8LhKxZSx" let path = RefPath::assert_from(b"/tezlink/context/contracts/index/000002298c03ed7d454a101eb7022bc95f7e5f41ac78/counter"); - let counter_array: [u8; 8] = counter.to_le_bytes(); - host.store_write_all(&path, &counter_array).unwrap(); + store_bin(&counter, &mut host, &path) + .expect("Store counter should have succeeded"); // Initalize path for Tezlink context at /tezlink/context let context = context::Context::init_context(); @@ -169,7 +161,7 @@ mod test { fn test_set_and_read_balance() { let mut host = MockKernelHost::default(); - let balance = U256::from_str("4579").unwrap(); + let balance = 4579_u64.into(); // Initalize path for Tezlink context at /tezlink/context let context = context::Context::init_context(); @@ -182,7 +174,7 @@ mod test { .expect("Account creation should have succeed"); let () = account - .set_balance(&mut host, balance) + .set_balance(&mut host, &balance) .expect("set_balance should succeed"); let read_balance = account @@ -196,7 +188,7 @@ mod test { fn test_set_and_read_counter() { let mut host = MockKernelHost::default(); - let counter = 6u64; + let counter: Narith = 6u64.into(); // Initalize path for Tezlink context at /tezlink/context let context = context::Context::init_context(); @@ -209,7 +201,7 @@ mod test { .expect("Account creation should have succeed"); let () = account - .set_counter(&mut host, counter) + .set_counter(&mut host, &counter) .expect("set_counter should have succeed"); let read_counter = account -- GitLab