From 3f54d56a6abf75f840eed84edd3221e0a04a3769 Mon Sep 17 00:00:00 2001 From: Luciano Freitas Date: Mon, 4 Aug 2025 10:54:40 +0200 Subject: [PATCH 1/8] Tezlink/Kernel/Transfer: validate first operation in batch --- etherlink/kernel_latest/tezos/src/operation_result.rs | 2 ++ etherlink/kernel_latest/tezos_execution/src/lib.rs | 9 +++++++-- etherlink/kernel_latest/tezos_execution/src/validate.rs | 8 +++++++- 3 files changed, 16 insertions(+), 3 deletions(-) diff --git a/etherlink/kernel_latest/tezos/src/operation_result.rs b/etherlink/kernel_latest/tezos/src/operation_result.rs index 5d090baaf7da..44d432b36d73 100644 --- a/etherlink/kernel_latest/tezos/src/operation_result.rs +++ b/etherlink/kernel_latest/tezos/src/operation_result.rs @@ -62,6 +62,8 @@ pub enum ValidityError { FailedToUpdateBalance, #[error("Failed to increment counter.")] FailedToIncrementCounter, + #[error("Batch is empty.")] + EmptyBatch, } #[derive(Error, Debug, PartialEq, Eq, NomReader, BinWriter, Clone)] diff --git a/etherlink/kernel_latest/tezos_execution/src/lib.rs b/etherlink/kernel_latest/tezos_execution/src/lib.rs index 5ad0d5dcb258..a9e595b61a32 100644 --- a/etherlink/kernel_latest/tezos_execution/src/lib.rs +++ b/etherlink/kernel_latest/tezos_execution/src/lib.rs @@ -457,8 +457,13 @@ fn execute_validation( content: &ManagerOperation, signature: UnknownSignature, ) -> Result { - let mut validation_info = - validate::validate_operation(host, context, branch, content, signature)?; + let mut validation_info = validate::validate_operation( + host, + context, + branch, + vec![content.clone()], + signature, + )?; validation_info .source_account diff --git a/etherlink/kernel_latest/tezos_execution/src/validate.rs b/etherlink/kernel_latest/tezos_execution/src/validate.rs index 53aa7950f533..9845f9c8ca56 100644 --- a/etherlink/kernel_latest/tezos_execution/src/validate.rs +++ b/etherlink/kernel_latest/tezos_execution/src/validate.rs @@ -130,9 +130,15 @@ pub fn validate_operation( host: &Host, context: &Context, branch: &BlockHash, - content: &ManagerOperation, + content: Vec>, signature: UnknownSignature, ) -> Result { + if content.is_empty() { + return Err(ValidityError::EmptyBatch); + } + + let content = &content[0]; + let account = TezlinkImplicitAccount::from_public_key_hash(context, &content.source) .map_err(|_| ValidityError::FailedToFetchAccount)?; -- GitLab From c2785bc53bf858c3580952151c066a43ef1bccdf Mon Sep 17 00:00:00 2001 From: Luciano Freitas Date: Mon, 4 Aug 2025 10:56:02 +0200 Subject: [PATCH 2/8] Tezlink/Kernel/Transfer: remove redundant log --- etherlink/kernel_latest/tezos_execution/src/validate.rs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/etherlink/kernel_latest/tezos_execution/src/validate.rs b/etherlink/kernel_latest/tezos_execution/src/validate.rs index 9845f9c8ca56..647346e53968 100644 --- a/etherlink/kernel_latest/tezos_execution/src/validate.rs +++ b/etherlink/kernel_latest/tezos_execution/src/validate.rs @@ -164,11 +164,6 @@ pub fn validate_operation( let new_balance = match account.simulate_spending(host, &content.fee) { Ok(Some(new_balance)) => new_balance, Ok(None) => { - log!( - host, - tezos_evm_logging::Level::Debug, - "Invalid operation: Can't pay the fees" - ); return Err(ValidityError::CantPayFees(content.fee.clone())); } Err(_) => return Err(ValidityError::FailedToFetchBalance), -- GitLab From 549ec9d36b0034c6262d184436c28e5dbd71fd38 Mon Sep 17 00:00:00 2001 From: Luciano Freitas Date: Mon, 4 Aug 2025 11:07:27 +0200 Subject: [PATCH 3/8] Tezlink/Kernel/Transfer: verify batch sources --- .../kernel_latest/tezos/src/operation_result.rs | 2 ++ .../kernel_latest/tezos_execution/src/validate.rs | 12 ++++++++++-- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/etherlink/kernel_latest/tezos/src/operation_result.rs b/etherlink/kernel_latest/tezos/src/operation_result.rs index 44d432b36d73..646c874e4433 100644 --- a/etherlink/kernel_latest/tezos/src/operation_result.rs +++ b/etherlink/kernel_latest/tezos/src/operation_result.rs @@ -64,6 +64,8 @@ pub enum ValidityError { FailedToIncrementCounter, #[error("Batch is empty.")] EmptyBatch, + #[error("Batch contains operations from multiple sources.")] + MultipleSources, } #[derive(Error, Debug, PartialEq, Eq, NomReader, BinWriter, Clone)] diff --git a/etherlink/kernel_latest/tezos_execution/src/validate.rs b/etherlink/kernel_latest/tezos_execution/src/validate.rs index 647346e53968..46d1d40de9bb 100644 --- a/etherlink/kernel_latest/tezos_execution/src/validate.rs +++ b/etherlink/kernel_latest/tezos_execution/src/validate.rs @@ -137,9 +137,15 @@ pub fn validate_operation( return Err(ValidityError::EmptyBatch); } - let content = &content[0]; + let source = &content[0].source; + + for c in &content { + if c.source != *source { + return Err(ValidityError::MultipleSources); + } + } - let account = TezlinkImplicitAccount::from_public_key_hash(context, &content.source) + let account = TezlinkImplicitAccount::from_public_key_hash(context, source) .map_err(|_| ValidityError::FailedToFetchAccount)?; // Account must exist in the durable storage @@ -150,6 +156,8 @@ pub fn validate_operation( return Err(ValidityError::EmptyImplicitContract); } + let content = &content[0]; + account.check_counter_increment(host, &content.counter)?; let pk = get_revealed_key(host, &account, &content.operation)?; -- GitLab From 30f9768b056cf637757b653f03a58523dd03e01e Mon Sep 17 00:00:00 2001 From: Luciano Freitas Date: Mon, 4 Aug 2025 11:20:23 +0200 Subject: [PATCH 4/8] Tezlink/Kernel/Transfer: get revealed key from a batch Co-authored-by: Arnaud Bihan --- .../kernel_latest/tezos_execution/src/validate.rs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/etherlink/kernel_latest/tezos_execution/src/validate.rs b/etherlink/kernel_latest/tezos_execution/src/validate.rs index 46d1d40de9bb..6736d03d804c 100644 --- a/etherlink/kernel_latest/tezos_execution/src/validate.rs +++ b/etherlink/kernel_latest/tezos_execution/src/validate.rs @@ -82,13 +82,14 @@ impl TezlinkImplicitAccount { /// In all cases except Reveal, we obtain the public key from the context. /// However, the purpose of Reveal is to register the public key in the context, /// making it a special case. In this case, we obtain the public key -/// from the operation's payload. +/// from the operation's payload. When processing a batch, the reveal operation is the +/// first operation on it. fn get_revealed_key( host: &Host, account: &TezlinkImplicitAccount, - content: &OperationContent, + first_content: &OperationContent, ) -> Result { - match content { + match first_content { OperationContent::Reveal(RevealContent { pk, proof: _ }) => Ok(pk.clone()), _ => account .get_manager_key(host) @@ -156,12 +157,12 @@ pub fn validate_operation( return Err(ValidityError::EmptyImplicitContract); } + let pk = get_revealed_key(host, &account, &content[0].operation)?; + let content = &content[0]; account.check_counter_increment(host, &content.counter)?; - let pk = get_revealed_key(host, &account, &content.operation)?; - // TODO: hard gas limit per operation is a Tezos constant, for now we took the one from ghostnet check_gas_limit(&1040000_u64.into(), &content.gas_limit)?; -- GitLab From bbc76fe0f45a79361cf509ca9025aac1113ee222 Mon Sep 17 00:00:00 2001 From: Luciano Freitas Date: Mon, 4 Aug 2025 13:17:52 +0200 Subject: [PATCH 5/8] Tezlink/Kernel/Transfer: split verification of batches --- .../kernel_latest/tezos_execution/src/lib.rs | 36 ++++++++++-------- .../tezos_execution/src/validate.rs | 38 ++++++++----------- 2 files changed, 37 insertions(+), 37 deletions(-) diff --git a/etherlink/kernel_latest/tezos_execution/src/lib.rs b/etherlink/kernel_latest/tezos_execution/src/lib.rs index a9e595b61a32..5d180bddfa72 100644 --- a/etherlink/kernel_latest/tezos_execution/src/lib.rs +++ b/etherlink/kernel_latest/tezos_execution/src/lib.rs @@ -24,7 +24,8 @@ use tezos_tezlink::operation::Operation; use tezos_tezlink::operation_result::TransferTarget; use tezos_tezlink::{ operation::{ - ManagerOperation, OperationContent, Parameter, RevealContent, TransferContent, + verify_signature, ManagerOperation, OperationContent, Parameter, RevealContent, + TransferContent, }, operation_result::{ is_applied, produce_operation_result, Balance, BalanceTooLow, BalanceUpdate, @@ -32,7 +33,7 @@ use tezos_tezlink::{ TransferSuccess, UpdateOrigin, ValidityError, }, }; -use validate::ValidationInfo; +use validate::{validate_individual_operation, ValidationInfo}; extern crate alloc; pub mod account_storage; @@ -457,25 +458,30 @@ fn execute_validation( content: &ManagerOperation, signature: UnknownSignature, ) -> Result { - let mut validation_info = validate::validate_operation( - host, - context, - branch, - vec![content.clone()], - signature, - )?; + let (source, pk, mut account) = + validate::validate_source(host, context, &[content.clone()])?; + + let (new_source_balance, balance_updates) = + validate_individual_operation(host, &source, &account, content)?; - validation_info - .source_account - .set_balance(host, &validation_info.new_source_balance) + account + .set_balance(host, &new_source_balance) .map_err(|_| ValidityError::FailedToUpdateBalance)?; - validation_info - .source_account + match verify_signature(&pk, branch, vec![content.clone().into()], signature.clone()) { + Ok(true) => (), + _ => return Err(ValidityError::InvalidSignature), + } + + account .increment_counter(host) .map_err(|_| ValidityError::FailedToIncrementCounter)?; - Ok(validation_info) + Ok(ValidationInfo { + new_source_balance, + source_account: account, + balance_updates, + }) } pub fn validate_and_apply_operation( diff --git a/etherlink/kernel_latest/tezos_execution/src/validate.rs b/etherlink/kernel_latest/tezos_execution/src/validate.rs index 6736d03d804c..dcfe76cdb9af 100644 --- a/etherlink/kernel_latest/tezos_execution/src/validate.rs +++ b/etherlink/kernel_latest/tezos_execution/src/validate.rs @@ -2,14 +2,12 @@ // // SPDX-License-Identifier: MIT -use tezos_crypto_rs::hash::UnknownSignature; use tezos_data_encoding::types::Narith; use tezos_evm_logging::log; use tezos_evm_runtime::runtime::Runtime; -use tezos_smart_rollup::types::PublicKey; -use tezos_tezlink::enc_wrappers::BlockHash; +use tezos_smart_rollup::types::{PublicKey, PublicKeyHash}; use tezos_tezlink::{ - operation::{verify_signature, ManagerOperation, OperationContent, RevealContent}, + operation::{ManagerOperation, OperationContent, RevealContent}, operation_result::{CounterError, ValidityError}, }; @@ -127,20 +125,18 @@ pub struct ValidationInfo { pub balance_updates: Vec, } -pub fn validate_operation( +pub fn validate_source( host: &Host, context: &Context, - branch: &BlockHash, - content: Vec>, - signature: UnknownSignature, -) -> Result { + content: &[ManagerOperation], +) -> Result<(PublicKeyHash, PublicKey, TezlinkImplicitAccount), ValidityError> { if content.is_empty() { return Err(ValidityError::EmptyBatch); } let source = &content[0].source; - for c in &content { + for c in content { if c.source != *source { return Err(ValidityError::MultipleSources); } @@ -159,8 +155,15 @@ pub fn validate_operation( let pk = get_revealed_key(host, &account, &content[0].operation)?; - let content = &content[0]; + Ok((source.clone(), pk, account)) +} +pub fn validate_individual_operation( + host: &Host, + source: &PublicKeyHash, + account: &TezlinkImplicitAccount, + content: &ManagerOperation, +) -> Result<(Narith, Vec), ValidityError> { account.check_counter_increment(host, &content.counter)?; // TODO: hard gas limit per operation is a Tezos constant, for now we took the one from ghostnet @@ -178,18 +181,9 @@ pub fn validate_operation( Err(_) => return Err(ValidityError::FailedToFetchBalance), }; - match verify_signature(&pk, branch, vec![content.clone().into()], signature.clone()) { - Ok(true) => (), - _ => return Err(ValidityError::InvalidSignature), - } - let (src_delta, block_fees) = - crate::compute_fees_balance_updates(&content.source, &content.fee) + crate::compute_fees_balance_updates(source, &content.fee) .map_err(|_| ValidityError::FailedToComputeFeeBalanceUpdate)?; - Ok(ValidationInfo { - new_source_balance: new_balance, - source_account: account, - balance_updates: vec![src_delta, block_fees], - }) + Ok((new_balance, vec![src_delta, block_fees])) } -- GitLab From 3c4998f1a6e3d9eb84a83fb0333e58a5298681f1 Mon Sep 17 00:00:00 2001 From: Luciano Freitas Date: Mon, 4 Aug 2025 13:26:27 +0200 Subject: [PATCH 6/8] Tezlink/Kernel/Transfer: extend balance_updates --- etherlink/kernel_latest/tezos_execution/src/lib.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/etherlink/kernel_latest/tezos_execution/src/lib.rs b/etherlink/kernel_latest/tezos_execution/src/lib.rs index 5d180bddfa72..33408e29bd0b 100644 --- a/etherlink/kernel_latest/tezos_execution/src/lib.rs +++ b/etherlink/kernel_latest/tezos_execution/src/lib.rs @@ -461,7 +461,9 @@ fn execute_validation( let (source, pk, mut account) = validate::validate_source(host, context, &[content.clone()])?; - let (new_source_balance, balance_updates) = + let mut balance_updates = vec![]; + + let (new_source_balance, op_balance_updates) = validate_individual_operation(host, &source, &account, content)?; account @@ -477,6 +479,8 @@ fn execute_validation( .increment_counter(host) .map_err(|_| ValidityError::FailedToIncrementCounter)?; + balance_updates.extend(op_balance_updates); + Ok(ValidationInfo { new_source_balance, source_account: account, -- GitLab From 4d1ad13b0c9c8b70670888ed06891727fc3cdbae Mon Sep 17 00:00:00 2001 From: Luciano Freitas Date: Mon, 4 Aug 2025 13:31:41 +0200 Subject: [PATCH 7/8] Tezlink/Kernel/Transfer: read the balance at end of validation --- etherlink/kernel_latest/tezos_execution/src/lib.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/etherlink/kernel_latest/tezos_execution/src/lib.rs b/etherlink/kernel_latest/tezos_execution/src/lib.rs index 33408e29bd0b..b35ebbddf087 100644 --- a/etherlink/kernel_latest/tezos_execution/src/lib.rs +++ b/etherlink/kernel_latest/tezos_execution/src/lib.rs @@ -481,6 +481,10 @@ fn execute_validation( balance_updates.extend(op_balance_updates); + let new_source_balance = account + .balance(host) + .map_err(|_| ValidityError::FailedToFetchBalance)?; + Ok(ValidationInfo { new_source_balance, source_account: account, -- GitLab From 156067ebef49893757d07a2a40ec5ebab733b13a Mon Sep 17 00:00:00 2001 From: Luciano Freitas Date: Mon, 4 Aug 2025 13:34:59 +0200 Subject: [PATCH 8/8] Tezlink/Kernel/Transfer: validate batches --- .../kernel_latest/tezos_execution/src/lib.rs | 50 +++++++++++-------- 1 file changed, 30 insertions(+), 20 deletions(-) diff --git a/etherlink/kernel_latest/tezos_execution/src/lib.rs b/etherlink/kernel_latest/tezos_execution/src/lib.rs index b35ebbddf087..3ce49e442795 100644 --- a/etherlink/kernel_latest/tezos_execution/src/lib.rs +++ b/etherlink/kernel_latest/tezos_execution/src/lib.rs @@ -24,8 +24,8 @@ use tezos_tezlink::operation::Operation; use tezos_tezlink::operation_result::TransferTarget; use tezos_tezlink::{ operation::{ - verify_signature, ManagerOperation, OperationContent, Parameter, RevealContent, - TransferContent, + verify_signature, ManagerOperation, ManagerOperationContent, OperationContent, + Parameter, RevealContent, TransferContent, }, operation_result::{ is_applied, produce_operation_result, Balance, BalanceTooLow, BalanceUpdate, @@ -455,36 +455,46 @@ fn execute_validation( host: &mut Host, context: &Context, branch: &BlockHash, - content: &ManagerOperation, + content: Vec>, signature: UnknownSignature, ) -> Result { - let (source, pk, mut account) = - validate::validate_source(host, context, &[content.clone()])?; + let (source, pk, mut account) = validate::validate_source(host, context, &content)?; let mut balance_updates = vec![]; - let (new_source_balance, op_balance_updates) = - validate_individual_operation(host, &source, &account, content)?; + for c in &content { + let (new_source_balance, op_balance_updates) = + validate_individual_operation(host, &source, &account, c)?; - account - .set_balance(host, &new_source_balance) - .map_err(|_| ValidityError::FailedToUpdateBalance)?; - - match verify_signature(&pk, branch, vec![content.clone().into()], signature.clone()) { - Ok(true) => (), - _ => return Err(ValidityError::InvalidSignature), - } + account + .set_balance(host, &new_source_balance) + .map_err(|_| ValidityError::FailedToUpdateBalance)?; - account - .increment_counter(host) - .map_err(|_| ValidityError::FailedToIncrementCounter)?; + account + .increment_counter(host) + .map_err(|_| ValidityError::FailedToIncrementCounter)?; - balance_updates.extend(op_balance_updates); + balance_updates.extend(op_balance_updates); + } let new_source_balance = account .balance(host) .map_err(|_| ValidityError::FailedToFetchBalance)?; + match verify_signature( + &pk, + branch, + content + .clone() + .into_iter() + .map(|op| op.into()) + .collect::>(), + signature.clone(), + ) { + Ok(true) => (), + _ => return Err(ValidityError::InvalidSignature), + } + Ok(ValidationInfo { new_source_balance, source_account: account, @@ -524,7 +534,7 @@ pub fn validate_and_apply_operation( &mut safe_host, context, &branch, - &manager_operation, + vec![manager_operation.clone()], signature, ) { Ok(validation_info) => validation_info, -- GitLab