From dea5d6ab805969df27e9f0b6664c1a217ccfd7eb Mon Sep 17 00:00:00 2001 From: Luciano Freitas Date: Wed, 16 Jul 2025 16:28:58 +0200 Subject: [PATCH 01/11] Tezlink/Kernel: create Skipped and Backtracked results --- .../tezos/src/operation_result.rs | 44 ++++++++++++++++++- 1 file changed, 43 insertions(+), 1 deletion(-) diff --git a/etherlink/kernel_latest/tezos/src/operation_result.rs b/etherlink/kernel_latest/tezos/src/operation_result.rs index 646c874e4433..4b36050cbd53 100644 --- a/etherlink/kernel_latest/tezos/src/operation_result.rs +++ b/etherlink/kernel_latest/tezos/src/operation_result.rs @@ -20,7 +20,7 @@ use tezos_smart_rollup::types::{PublicKey, PublicKeyHash}; use tezos_smart_rollup_host::runtime::RuntimeError; use thiserror::Error; -use crate::operation::ManagerOperationContent; +use crate::operation::{ManagerOperation, ManagerOperationContent, OperationContent}; #[derive(Debug, PartialEq, Eq, NomReader, BinWriter, Clone)] pub struct CounterError { @@ -320,6 +320,8 @@ impl From> for ApplyOperationErrors { pub enum ContentResult { Applied(M::Success), Failed(ApplyOperationErrors), + Skipped, + BackTracked(M::Success), } /// A [Balance] updates can be triggered on different target @@ -379,6 +381,25 @@ pub fn is_applied(res: &OperationResultSum) -> bool { } } +pub fn transform_result_backtrack(op: OperationResultSum) -> OperationResultSum { + match op { + OperationResultSum::Reveal(mut op_result) => { + op_result.result = match op_result.result { + ContentResult::Applied(success) => ContentResult::BackTracked(success), + other => other, + }; + OperationResultSum::Reveal(op_result) + } + OperationResultSum::Transfer(mut op_result) => { + op_result.result = match op_result.result { + ContentResult::Applied(success) => ContentResult::BackTracked(success), + other => other, + }; + OperationResultSum::Transfer(op_result) + } + } +} + pub fn produce_operation_result( balance_updates: Vec, result: Result, @@ -397,6 +418,27 @@ pub fn produce_operation_result( } } +pub fn produce_skipped_receipt( + op: &ManagerOperation, +) -> OperationResultSum { + match op.operation { + OperationContent::Reveal(_) => { + OperationResultSum::Reveal(produce_skipped_result()) + } + OperationContent::Transfer(_) => { + OperationResultSum::Transfer(produce_skipped_result()) + } + } +} + +fn produce_skipped_result() -> OperationResult { + OperationResult { + balance_updates: vec![], + result: ContentResult::Skipped, + internal_operation_results: vec![], + } +} + #[derive(PartialEq, Debug, NomReader, BinWriter)] pub enum OperationDataAndMetadata { OperationWithMetadata(OperationBatchWithMetadata), -- GitLab From a41157e793b7892a97420e82d0727e26538f804b Mon Sep 17 00:00:00 2001 From: Luciano Freitas Date: Mon, 4 Aug 2025 14:35:57 +0200 Subject: [PATCH 02/11] Tezlink/Kernel/Transfer: rename manager_operation to content --- .../kernel_latest/tezos_execution/src/lib.rs | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/etherlink/kernel_latest/tezos_execution/src/lib.rs b/etherlink/kernel_latest/tezos_execution/src/lib.rs index 3ce49e442795..0ae3106736e5 100644 --- a/etherlink/kernel_latest/tezos_execution/src/lib.rs +++ b/etherlink/kernel_latest/tezos_execution/src/lib.rs @@ -508,11 +508,10 @@ pub fn validate_and_apply_operation( operation: Operation, ) -> Result { let branch = operation.branch; - let manager_operation: ManagerOperation = - operation.content.clone().into(); + let content: ManagerOperation = operation.content.clone().into(); let signature = operation.signature; - let source = &manager_operation.source; + let source = &content.source; let mut safe_host = SafeStorage { host, @@ -534,7 +533,7 @@ pub fn validate_and_apply_operation( &mut safe_host, context, &branch, - vec![manager_operation.clone()], + vec![content.clone()], signature, ) { Ok(validation_info) => validation_info, @@ -548,8 +547,7 @@ pub fn validate_and_apply_operation( safe_host.promote_trace()?; safe_host.start()?; - let receipt = - apply_operation(&mut safe_host, context, &manager_operation, validation_info); + let receipt = apply_operation(&mut safe_host, context, &content, validation_info); if is_applied(&receipt) { safe_host.promote()?; @@ -564,7 +562,7 @@ pub fn validate_and_apply_operation( fn apply_operation( host: &mut Host, context: &Context, - operation: &ManagerOperation, + content: &ManagerOperation, validation_info: ValidationInfo, ) -> OperationResultSum { let ValidationInfo { @@ -572,9 +570,9 @@ fn apply_operation( mut source_account, balance_updates: validation_balance_updates, } = validation_info; - match operation.operation { + match content.operation { OperationContent::Reveal(RevealContent { ref pk, proof: _ }) => { - let reveal_result = reveal(host, &operation.source, &mut source_account, pk); + let reveal_result = reveal(host, &content.source, &mut source_account, pk); let manager_result = produce_operation_result( validation_balance_updates, reveal_result.map_err(|e| e.into()), @@ -589,7 +587,7 @@ fn apply_operation( let transfer_result = transfer_external( host, context, - &operation.source, + &content.source, &mut source_account, &new_source_balance, amount, -- GitLab From 43e842bc56d406257e98b71bd903ec99cc6ff05b Mon Sep 17 00:00:00 2001 From: Luciano Freitas Date: Mon, 4 Aug 2025 14:42:17 +0200 Subject: [PATCH 03/11] Tezlink/Kernel/Transfer: include source in ValidationInfo --- .../kernel_latest/tezos_execution/src/lib.rs | 15 ++++----------- .../kernel_latest/tezos_execution/src/validate.rs | 1 + 2 files changed, 5 insertions(+), 11 deletions(-) diff --git a/etherlink/kernel_latest/tezos_execution/src/lib.rs b/etherlink/kernel_latest/tezos_execution/src/lib.rs index 0ae3106736e5..9c047bfd6a3e 100644 --- a/etherlink/kernel_latest/tezos_execution/src/lib.rs +++ b/etherlink/kernel_latest/tezos_execution/src/lib.rs @@ -496,6 +496,7 @@ fn execute_validation( } Ok(ValidationInfo { + source, new_source_balance, source_account: account, balance_updates, @@ -511,20 +512,11 @@ pub fn validate_and_apply_operation( let content: ManagerOperation = operation.content.clone().into(); let signature = operation.signature; - let source = &content.source; - let mut safe_host = SafeStorage { host, world_state: context::contracts::root(context).unwrap(), }; - log!( - safe_host, - Debug, - "Going to run a Tezos Manager Operation from {}", - source - ); - safe_host.start()?; log!(safe_host, Debug, "Verifying that the operation is valid"); @@ -566,13 +558,14 @@ fn apply_operation( validation_info: ValidationInfo, ) -> OperationResultSum { let ValidationInfo { + source, new_source_balance, mut source_account, balance_updates: validation_balance_updates, } = validation_info; match content.operation { OperationContent::Reveal(RevealContent { ref pk, proof: _ }) => { - let reveal_result = reveal(host, &content.source, &mut source_account, pk); + let reveal_result = reveal(host, &source, &mut source_account, pk); let manager_result = produce_operation_result( validation_balance_updates, reveal_result.map_err(|e| e.into()), @@ -587,7 +580,7 @@ fn apply_operation( let transfer_result = transfer_external( host, context, - &content.source, + &source, &mut source_account, &new_source_balance, amount, diff --git a/etherlink/kernel_latest/tezos_execution/src/validate.rs b/etherlink/kernel_latest/tezos_execution/src/validate.rs index dcfe76cdb9af..ef3227812b50 100644 --- a/etherlink/kernel_latest/tezos_execution/src/validate.rs +++ b/etherlink/kernel_latest/tezos_execution/src/validate.rs @@ -120,6 +120,7 @@ fn check_storage_limit( } pub struct ValidationInfo { + pub source: PublicKeyHash, pub new_source_balance: Narith, pub source_account: TezlinkImplicitAccount, pub balance_updates: Vec, -- GitLab From 8a6e2debe1035174bc00901d5d4b3027928c65a1 Mon Sep 17 00:00:00 2001 From: Luciano Freitas Date: Mon, 4 Aug 2025 17:01:18 +0200 Subject: [PATCH 04/11] Tezlink/Kernel/Transfer: introduce apply_batch --- .../kernel_latest/tezos_execution/src/lib.rs | 57 ++++++++++++------- 1 file changed, 38 insertions(+), 19 deletions(-) diff --git a/etherlink/kernel_latest/tezos_execution/src/lib.rs b/etherlink/kernel_latest/tezos_execution/src/lib.rs index 9c047bfd6a3e..0f3cbf176679 100644 --- a/etherlink/kernel_latest/tezos_execution/src/lib.rs +++ b/etherlink/kernel_latest/tezos_execution/src/lib.rs @@ -539,7 +539,7 @@ pub fn validate_and_apply_operation( safe_host.promote_trace()?; safe_host.start()?; - let receipt = apply_operation(&mut safe_host, context, &content, validation_info); + let receipt = apply_batch(&mut safe_host, context, &content, validation_info); if is_applied(&receipt) { safe_host.promote()?; @@ -551,7 +551,7 @@ pub fn validate_and_apply_operation( Ok(receipt) } -fn apply_operation( +fn apply_batch( host: &mut Host, context: &Context, content: &ManagerOperation, @@ -561,37 +561,56 @@ fn apply_operation( source, new_source_balance, mut source_account, - balance_updates: validation_balance_updates, + balance_updates, } = validation_info; - match content.operation { - OperationContent::Reveal(RevealContent { ref pk, proof: _ }) => { - let reveal_result = reveal(host, &source, &mut source_account, pk); + apply_operation( + host, + context, + content, + &source, + &new_source_balance, + &mut source_account, + &balance_updates, + ) +} + +fn apply_operation( + host: &mut Host, + context: &Context, + content: &ManagerOperation, + source: &PublicKeyHash, + new_source_balance: &Narith, + source_account: &mut TezlinkImplicitAccount, + balance_updates: &[BalanceUpdate], +) -> OperationResultSum { + match &content.operation { + OperationContent::Reveal(RevealContent { pk, .. }) => { + let reveal_result = reveal(host, source, source_account, pk); let manager_result = produce_operation_result( - validation_balance_updates, - reveal_result.map_err(|e| e.into()), + balance_updates.to_vec(), + reveal_result.map_err(Into::into), ); OperationResultSum::Reveal(manager_result) } OperationContent::Transfer(TransferContent { - ref amount, - ref destination, - ref parameters, + amount, + destination, + parameters, }) => { let transfer_result = transfer_external( host, context, - &source, - &mut source_account, - &new_source_balance, + source, + source_account, + new_source_balance, amount, destination, parameters.clone(), - ); + ) + .map(TransferTarget::ToContrat); let manager_result = produce_operation_result( - validation_balance_updates, - transfer_result - .map(TransferTarget::ToContrat) - .map_err(|e| e.into()), + balance_updates.to_vec(), + transfer_result.map_err(Into::into), ); OperationResultSum::Transfer(manager_result) } -- GitLab From 8e6a7b7275c1a6b3b3d5b69d04e6edfe974be22b Mon Sep 17 00:00:00 2001 From: Luciano Freitas Date: Mon, 4 Aug 2025 15:31:35 +0200 Subject: [PATCH 05/11] Tezlink/Kernel/Transfer: apply batch and return first receipt --- .../kernel_latest/tezos_execution/src/lib.rs | 61 +++++++++++++------ 1 file changed, 43 insertions(+), 18 deletions(-) diff --git a/etherlink/kernel_latest/tezos_execution/src/lib.rs b/etherlink/kernel_latest/tezos_execution/src/lib.rs index 0f3cbf176679..df7c5e9673d0 100644 --- a/etherlink/kernel_latest/tezos_execution/src/lib.rs +++ b/etherlink/kernel_latest/tezos_execution/src/lib.rs @@ -21,16 +21,16 @@ use tezos_evm_runtime::{runtime::Runtime, safe_storage::SafeStorage}; use tezos_smart_rollup::types::{Contract, PublicKey, PublicKeyHash}; use tezos_tezlink::enc_wrappers::BlockHash; use tezos_tezlink::operation::Operation; -use tezos_tezlink::operation_result::TransferTarget; +use tezos_tezlink::operation_result::{produce_skipped_receipt, TransferTarget}; use tezos_tezlink::{ operation::{ verify_signature, ManagerOperation, ManagerOperationContent, OperationContent, Parameter, RevealContent, TransferContent, }, operation_result::{ - is_applied, produce_operation_result, Balance, BalanceTooLow, BalanceUpdate, - OperationError, OperationResultSum, RevealError, RevealSuccess, TransferError, - TransferSuccess, UpdateOrigin, ValidityError, + is_applied, produce_operation_result, transform_result_backtrack, Balance, + BalanceTooLow, BalanceUpdate, OperationError, OperationResultSum, RevealError, + RevealSuccess, TransferError, TransferSuccess, UpdateOrigin, ValidityError, }, }; use validate::{validate_individual_operation, ValidationInfo}; @@ -539,39 +539,64 @@ pub fn validate_and_apply_operation( safe_host.promote_trace()?; safe_host.start()?; - let receipt = apply_batch(&mut safe_host, context, &content, validation_info); + let (receipts, applied) = + apply_batch(&mut safe_host, context, vec![content], validation_info); - if is_applied(&receipt) { + if applied { safe_host.promote()?; safe_host.promote_trace()?; } else { safe_host.revert()?; } - Ok(receipt) + Ok(receipts[0].clone()) } fn apply_batch( host: &mut Host, context: &Context, - content: &ManagerOperation, + operations: Vec>, validation_info: ValidationInfo, -) -> OperationResultSum { +) -> (Vec, bool) { let ValidationInfo { source, new_source_balance, mut source_account, balance_updates, } = validation_info; - apply_operation( - host, - context, - content, - &source, - &new_source_balance, - &mut source_account, - &balance_updates, - ) + let mut first_failure: Option = None; + let mut receipts = Vec::with_capacity(operations.len()); + + for (index, content) in operations.into_iter().enumerate() { + let receipt = if first_failure.is_some() { + produce_skipped_receipt(&content) + } else { + apply_operation( + host, + context, + &content, + &source, + &new_source_balance, + &mut source_account, + &balance_updates, + ) + }; + + if first_failure.is_none() && !is_applied(&receipt) { + first_failure = Some(index); + } + + receipts.push(receipt); + } + + if let Some(failure_idx) = first_failure { + receipts[..failure_idx] + .iter_mut() + .for_each(|r| *r = transform_result_backtrack(r.clone())); + return (receipts, false); + } + + (receipts, true) } fn apply_operation( -- GitLab From 536340e1cdab30e68e8f2505814a5438627f70a0 Mon Sep 17 00:00:00 2001 From: Luciano Freitas Date: Thu, 7 Aug 2025 11:03:06 +0200 Subject: [PATCH 06/11] Tezlink/Kernel: improve logging --- .../kernel_latest/tezos_execution/src/lib.rs | 44 ++++++++++++++++++- .../tezos_execution/src/validate.rs | 35 +++++++++++++-- 2 files changed, 74 insertions(+), 5 deletions(-) diff --git a/etherlink/kernel_latest/tezos_execution/src/lib.rs b/etherlink/kernel_latest/tezos_execution/src/lib.rs index df7c5e9673d0..70fef3614a4e 100644 --- a/etherlink/kernel_latest/tezos_execution/src/lib.rs +++ b/etherlink/kernel_latest/tezos_execution/src/lib.rs @@ -267,6 +267,7 @@ pub fn transfer<'a, Host: Runtime>( parser, ctx, ); + log!(host, Debug, "Transfer operation succeeded"); Ok(TransferSuccess { storage: Some(new_storage), ..receipt @@ -414,6 +415,13 @@ fn apply_balance_changes( dest_account .set_balance(host, &new_dest_balance) .map_err(|_| TransferError::FailedToUpdateDestinationBalance)?; + + log!( + host, + Debug, + "Transfer: OK - the new balance of the source is {:?} and the new balance of the destination is {:?}", + new_src_balance, new_dest_balance); + Ok(AppliedBalanceChanges { new_src_balance, new_dest_balance, @@ -519,7 +527,7 @@ pub fn validate_and_apply_operation( safe_host.start()?; - log!(safe_host, Debug, "Verifying that the operation is valid"); + log!(safe_host, Debug, "Verifying that the batch is valid"); let validation_info = match execute_validation( &mut safe_host, @@ -530,11 +538,18 @@ pub fn validate_and_apply_operation( ) { Ok(validation_info) => validation_info, Err(validity_err) => { + log!( + safe_host, + Debug, + "Reverting the changes because the batch is invalid." + ); safe_host.revert()?; return Err(OperationError::Validation(validity_err)); } }; + log!(safe_host, Debug, "Batch is valid!"); + safe_host.promote()?; safe_host.promote_trace()?; safe_host.start()?; @@ -542,10 +557,22 @@ pub fn validate_and_apply_operation( let (receipts, applied) = apply_batch(&mut safe_host, context, vec![content], validation_info); + log!(safe_host, Debug, "Receipts: {:#?}", receipts); + if applied { + log!( + safe_host, + Debug, + "Committing the changes because the batch was successfully applied." + ); safe_host.promote()?; safe_host.promote_trace()?; } else { + log!( + safe_host, + Debug, + "Reverting the changes because some operation failed." + ); safe_host.revert()?; } @@ -568,7 +595,20 @@ fn apply_batch( let mut receipts = Vec::with_capacity(operations.len()); for (index, content) in operations.into_iter().enumerate() { + log!( + host, + Debug, + "Applying operation #{} in the batch with counter {:?}.", + index, + content.counter + ); let receipt = if first_failure.is_some() { + log!( + host, + Debug, + "Skipping this operation because we already failed on {:?}.", + first_failure + ); produce_skipped_receipt(&content) } else { apply_operation( @@ -1185,7 +1225,7 @@ mod tests { ); } - // Test an invalid transfer operation, source has not enough balance to fullfil the Transfer + // Test an invalid transfer operation, source has not enough balance to fulfill the Transfer #[test] fn apply_transfer_with_not_enough_balance() { let mut host = MockKernelHost::default(); diff --git a/etherlink/kernel_latest/tezos_execution/src/validate.rs b/etherlink/kernel_latest/tezos_execution/src/validate.rs index ef3227812b50..012c85bf6339 100644 --- a/etherlink/kernel_latest/tezos_execution/src/validate.rs +++ b/etherlink/kernel_latest/tezos_execution/src/validate.rs @@ -3,7 +3,7 @@ // SPDX-License-Identifier: MIT use tezos_data_encoding::types::Narith; -use tezos_evm_logging::log; +use tezos_evm_logging::{log, Level::*}; use tezos_evm_runtime::runtime::Runtime; use tezos_smart_rollup::types::{PublicKey, PublicKeyHash}; use tezos_tezlink::{ @@ -34,6 +34,12 @@ impl TezlinkImplicitAccount { let expected_counter = Narith(&contract_counter.0 + 1_u64); if &expected_counter == counter { + log!( + host, + Debug, + "Validation: OK - Operation has the expected counter {:?}.", + expected_counter + ); Ok(()) } else if expected_counter.0 > counter.0 { let error = CounterError { @@ -168,10 +174,26 @@ pub fn validate_individual_operation( 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 - check_gas_limit(&1040000_u64.into(), &content.gas_limit)?; + let hard_gas_limit = 1040000_u64; + check_gas_limit(&hard_gas_limit.into(), &content.gas_limit)?; + log!( + host, + Debug, + "Validation: OK - the gas_limit {:?} does not exceed the {:?} threshold.", + &content.gas_limit, + hard_gas_limit + ); // TODO: hard storage limit per operation is a Tezos constant, for now we took the one from ghostnet - check_storage_limit(&60000_u64.into(), &content.storage_limit)?; + let hard_storage_limit = 60000_u64; + check_storage_limit(&hard_storage_limit.into(), &content.storage_limit)?; + log!( + host, + Debug, + "Validation: OK - the storage_limit {:?} does not exceed the {:?} threshold.", + &content.storage_limit, + hard_storage_limit + ); // The manager account must be solvent to pay the announced fees. let new_balance = match account.simulate_spending(host, &content.fee) { @@ -181,6 +203,13 @@ pub fn validate_individual_operation( } Err(_) => return Err(ValidityError::FailedToFetchBalance), }; + log!( + host, + Debug, + "Validation: OK - the source can pay {:?} in fees, being left with a new balance of {:?}.", + &content.fee, + new_balance + ); let (src_delta, block_fees) = crate::compute_fees_balance_updates(source, &content.fee) -- GitLab From 7eb1815a3facfa388d2b3b156de1129d9f7b8842 Mon Sep 17 00:00:00 2001 From: Luciano Freitas Date: Fri, 1 Aug 2025 15:04:17 +0200 Subject: [PATCH 07/11] Tezlink/Kernel/Transfer: apply batches --- etherlink/kernel_latest/kernel/src/block.rs | 50 +++++----- etherlink/kernel_latest/kernel/src/chains.rs | 12 +-- etherlink/kernel_latest/tezos/src/block.rs | 14 ++- .../kernel_latest/tezos/src/operation.rs | 34 +++++-- .../kernel_latest/tezos_execution/src/lib.rs | 93 ++++++++++--------- 5 files changed, 113 insertions(+), 90 deletions(-) diff --git a/etherlink/kernel_latest/kernel/src/block.rs b/etherlink/kernel_latest/kernel/src/block.rs index 3b83d28e260c..e789a4424d82 100644 --- a/etherlink/kernel_latest/kernel/src/block.rs +++ b/etherlink/kernel_latest/kernel/src/block.rs @@ -578,12 +578,6 @@ pub fn produce( #[cfg(test)] mod tests { use super::*; - use tezos_crypto_rs::hash::SecretKeyEd25519; - use tezos_ethereum::tx_common::AuthorizationList; - use tezos_execution::account_storage::TezlinkAccount; - use tezos_tezlink::operation::sign_operation; - use tezos_tezlink::operation::Parameter; - use crate::block_storage; use crate::blueprint::Blueprint; use crate::blueprint_storage::store_inbox_blueprint; @@ -609,16 +603,19 @@ mod tests { use evm_execution::configuration::EVMVersion; use primitive_types::{H160, U256}; use std::str::FromStr; + use tezos_crypto_rs::hash::SecretKeyEd25519; use tezos_data_encoding::types::Narith; use tezos_ethereum::block::BlockFees; use tezos_ethereum::transaction::{ TransactionHash, TransactionStatus, TransactionType, TRANSACTION_HASH_SIZE, }; + use tezos_ethereum::tx_common::AuthorizationList; use tezos_ethereum::tx_common::EthereumTransactionCommon; use tezos_evm_runtime::extensions::WithGas; use tezos_evm_runtime::runtime::MockKernelHost; use tezos_evm_runtime::runtime::Runtime; use tezos_execution::account_storage::Manager; + use tezos_execution::account_storage::TezlinkAccount; use tezos_execution::account_storage::TezlinkImplicitAccount; use tezos_execution::context; use tezos_smart_rollup::types::Contract; @@ -627,6 +624,8 @@ mod tests { use tezos_smart_rollup_encoding::timestamp::Timestamp; use tezos_smart_rollup_host::path::concat; use tezos_smart_rollup_host::path::RefPath; + use tezos_tezlink::operation::sign_operation; + use tezos_tezlink::operation::Parameter; fn read_current_number(host: &impl Runtime) -> anyhow::Result { Ok(crate::blueprint_storage::read_current_blueprint_header(host)?.number) @@ -684,29 +683,32 @@ mod tests { gas_limit: u64, storage_limit: u64, source: Bootstrap, - content: OperationContent, + content: Vec, ) -> Operation { let branch = TezBlock::genesis_block_hash().into(); - let manager_op: ManagerOperationContent = ManagerOperation { - source: source.pkh, - fee: fee.into(), - counter: counter.into(), - operation: content, - gas_limit: gas_limit.into(), - storage_limit: storage_limit.into(), - } - .into(); + let content = content + .into_iter() + .map(|c| -> ManagerOperationContent { + ManagerOperation { + source: source.pkh.clone(), + fee: fee.into(), + counter: counter.into(), + operation: c, + gas_limit: gas_limit.into(), + storage_limit: storage_limit.into(), + } + .into() + }) + .collect::>(); - let signature = - sign_operation(&source.sk, &branch, vec![manager_op.clone()]).unwrap(); + let signature = sign_operation(&source.sk, &branch, content.clone()).unwrap(); Operation { branch, - content: manager_op, + content, signature, } } - fn make_reveal_operation( fee: u64, counter: u64, @@ -720,10 +722,10 @@ mod tests { gas_limit, storage_limit, source.clone(), - OperationContent::Reveal(RevealContent { + vec![OperationContent::Reveal(RevealContent { pk: source.pk, proof: None, - }), + })], ) } @@ -744,11 +746,11 @@ mod tests { gas_limit, storage_limit, source, - OperationContent::Transfer(TransferContent { + vec![OperationContent::Transfer(TransferContent { amount, destination, parameters, - }), + })], ) } diff --git a/etherlink/kernel_latest/kernel/src/chains.rs b/etherlink/kernel_latest/kernel/src/chains.rs index c47940273eaf..6dba2d0191c5 100644 --- a/etherlink/kernel_latest/kernel/src/chains.rs +++ b/etherlink/kernel_latest/kernel/src/chains.rs @@ -35,10 +35,9 @@ use tezos_smart_rollup_host::path::{Path, RefPath}; use tezos_tezlink::{ block::{AppliedOperation, TezBlock}, enc_wrappers::BlockNumber, - operation::Operation, + operation::{zip_operations, Operation}, operation_result::{ OperationBatchWithMetadata, OperationDataAndMetadata, OperationError, - OperationWithMetadata, }, }; @@ -507,16 +506,15 @@ impl ChainConfigTrait for MichelsonChainConfig { // Compute the hash of the operation let hash = operation.hash()?; + let operations = zip_operations(operation.clone(), receipt); + // Add the applied operation in the block in progress let applied_operation = AppliedOperation { hash, branch: operation.branch, op_and_receipt: OperationDataAndMetadata::OperationWithMetadata( OperationBatchWithMetadata { - operations: vec![OperationWithMetadata { - content: operation.content, - receipt, - }], + operations, signature: operation.signature, }, ), @@ -524,7 +522,7 @@ impl ChainConfigTrait for MichelsonChainConfig { applied.push(applied_operation); } - // Create a Tezos block from the block in progess + // Create a Tezos block from the block in progress let tezblock = TezBlock::new(number, timestamp, previous_hash, applied)?; let new_block = L2Block::Tezlink(tezblock); let root = self.storage_root_path(); diff --git a/etherlink/kernel_latest/tezos/src/block.rs b/etherlink/kernel_latest/tezos/src/block.rs index 55276fdc6c3f..a8728a20b62c 100644 --- a/etherlink/kernel_latest/tezos/src/block.rs +++ b/etherlink/kernel_latest/tezos/src/block.rs @@ -74,6 +74,7 @@ impl TezBlock { #[cfg(test)] mod tests { + use crate::operation::zip_operations; use primitive_types::H256; use tezos_data_encoding::enc::BinWriter; use tezos_data_encoding::nom::NomReader; @@ -81,7 +82,7 @@ mod tests { use crate::operation_result::{ OperationBatchWithMetadata, OperationDataAndMetadata, OperationResult, - OperationResultSum, OperationWithMetadata, RevealSuccess, + OperationResultSum, RevealSuccess, }; use super::{AppliedOperation, TezBlock}; @@ -98,22 +99,19 @@ mod tests { fn dummy_applied_operation() -> AppliedOperation { let hash = H256::random().into(); let data = crate::operation::make_dummy_reveal_operation(); - let receipt = OperationResultSum::Reveal(OperationResult { + let receipt = vec![OperationResultSum::Reveal(OperationResult { balance_updates: vec![], result: crate::operation_result::ContentResult::Applied(RevealSuccess { consumed_gas: 0u64.into(), }), internal_operation_results: vec![], - }); + })]; AppliedOperation { hash, - branch: data.branch, + branch: data.branch.clone(), op_and_receipt: OperationDataAndMetadata::OperationWithMetadata( OperationBatchWithMetadata { - operations: vec![OperationWithMetadata { - content: data.content, - receipt, - }], + operations: zip_operations(data.clone(), receipt), signature: data.signature, }, ), diff --git a/etherlink/kernel_latest/tezos/src/operation.rs b/etherlink/kernel_latest/tezos/src/operation.rs index ad034b48a49d..7cb95d80743b 100644 --- a/etherlink/kernel_latest/tezos/src/operation.rs +++ b/etherlink/kernel_latest/tezos/src/operation.rs @@ -5,6 +5,7 @@ //! Tezos operations: this module defines the fragment of Tezos operations supported by Tezlink and how to serialize them. /// The whole module is inspired of `src/proto_alpha/lib_protocol/operation_repr.ml` to represent the operation use crate::enc_wrappers::{BlockHash, OperationHash}; +use crate::operation_result::{OperationResultSum, OperationWithMetadata}; use mir::ast::michelson_address::entrypoint; use primitive_types::H256; use rlp::Decodable; @@ -64,7 +65,7 @@ pub struct ManagerOperation { #[derive(PartialEq, Debug, Clone, NomReader, BinWriter)] pub struct Operation { pub branch: BlockHash, - pub content: ManagerOperationContent, + pub content: Vec, pub signature: UnknownSignature, } @@ -254,6 +255,21 @@ pub fn verify_signature( Ok(check) } +pub fn zip_operations( + operation: Operation, + receipt: Vec, +) -> Vec { + operation + .content + .into_iter() + .zip(receipt) + .map(|(c, r)| OperationWithMetadata { + content: c, + receipt: r, + }) + .collect::>() +} + #[cfg(test)] fn make_dummy_operation( operation: OperationContent, @@ -269,7 +285,7 @@ fn make_dummy_operation( Operation { branch, - content: ManagerOperation { + content: vec![ManagerOperation { source, fee: 1_u64.into(), counter: 10_u64.into(), @@ -277,7 +293,7 @@ fn make_dummy_operation( storage_limit: 45_u64.into(), operation, } - .into(), + .into()], signature, } } @@ -355,7 +371,7 @@ mod tests { let signature = UnknownSignature::from_base58_check("sigbuPiC4jLnc17xWVXMRgKiWsTsckZ2jBeHYVamUWi2m8MBXGsL6SPf9SadUEzXM7D5NfpJiYnr5BhcVfm2zwrLGuvNSGVM").unwrap(); let expected_operation = Operation { branch, - content: ManagerOperationContent::Reveal(ManagerOperation { + content: vec![ManagerOperationContent::Reveal(ManagerOperation { source: PublicKeyHash::from_b58check( "tz1cckAZtxYwxAfwQuHnabTWfbp2ScWobxHH", ) @@ -371,7 +387,7 @@ mod tests { }, gas_limit: 169_u64.into(), storage_limit: 0_u64.into(), - }), + })], signature, }; @@ -401,7 +417,7 @@ mod tests { let signature = UnknownSignature::from_base58_check("sigT4yGRRhiMZCjGigdhopaXkshKrwDbYrPw3jGFZGkjpvpT57a6KmLa4mFVKBTNHR8NrmyMEt9Pgusac5HLqUoJie2MB5Pd").unwrap(); let expected_operation = Operation { branch, - content: ManagerOperationContent::Transfer(ManagerOperation { + content: vec![ManagerOperationContent::Transfer(ManagerOperation { source: PublicKeyHash::from_b58check( "tz1gjaF81ZRRvdzjobyfVNsAeSC6PScjfQwN", ) @@ -418,7 +434,7 @@ mod tests { }, gas_limit: 169_u64.into(), storage_limit: 0_u64.into(), - }), + })], signature, }; @@ -479,7 +495,7 @@ mod tests { let expected_operation = Operation { branch, - content: ManagerOperationContent::Transfer(ManagerOperation { + content: vec![ManagerOperationContent::Transfer(ManagerOperation { source: PublicKeyHash::from_b58check( "tz1KqTpEZ7Yob7QbPE4Hy4Wo8fHG8LhKxZSx", ) @@ -501,7 +517,7 @@ mod tests { }, gas_limit: 1380_u64.into(), storage_limit: 0_u64.into(), - }), + })], signature, }; diff --git a/etherlink/kernel_latest/tezos_execution/src/lib.rs b/etherlink/kernel_latest/tezos_execution/src/lib.rs index 70fef3614a4e..206ec3dc7ce9 100644 --- a/etherlink/kernel_latest/tezos_execution/src/lib.rs +++ b/etherlink/kernel_latest/tezos_execution/src/lib.rs @@ -515,9 +515,14 @@ pub fn validate_and_apply_operation( host: &mut Host, context: &context::Context, operation: Operation, -) -> Result { +) -> Result, OperationError> { let branch = operation.branch; - let content: ManagerOperation = operation.content.clone().into(); + let content: Vec> = operation + .content + .into_iter() + .map(|op| op.into()) + .collect::>>(); + let signature = operation.signature; let mut safe_host = SafeStorage { @@ -533,7 +538,7 @@ pub fn validate_and_apply_operation( &mut safe_host, context, &branch, - vec![content.clone()], + content.clone(), signature, ) { Ok(validation_info) => validation_info, @@ -555,7 +560,7 @@ pub fn validate_and_apply_operation( safe_host.start()?; let (receipts, applied) = - apply_batch(&mut safe_host, context, vec![content], validation_info); + apply_batch(&mut safe_host, context, content, validation_info); log!(safe_host, Debug, "Receipts: {:#?}", receipts); @@ -576,7 +581,7 @@ pub fn validate_and_apply_operation( safe_host.revert()?; } - Ok(receipts[0].clone()) + Ok(receipts) } fn apply_batch( @@ -773,25 +778,29 @@ mod tests { gas_limit: u64, storage_limit: u64, source: Bootstrap, - content: OperationContent, + content: Vec, ) -> Operation { let branch = TezBlock::genesis_block_hash().into(); - let manager_op: ManagerOperationContent = ManagerOperation { - source: source.pkh, - fee: fee.into(), - counter: counter.into(), - operation: content, - gas_limit: gas_limit.into(), - storage_limit: storage_limit.into(), - } - .into(); + let content = content + .into_iter() + .map(|c| -> ManagerOperationContent { + ManagerOperation { + source: source.pkh.clone(), + fee: fee.into(), + counter: counter.into(), + operation: c, + gas_limit: gas_limit.into(), + storage_limit: storage_limit.into(), + } + .into() + }) + .collect::>(); - let signature = - sign_operation(&source.sk, &branch, vec![manager_op.clone()]).unwrap(); + let signature = sign_operation(&source.sk, &branch, content.clone()).unwrap(); Operation { branch, - content: manager_op, + content, signature, } } @@ -809,10 +818,10 @@ mod tests { gas_limit, storage_limit, source.clone(), - OperationContent::Reveal(RevealContent { + vec![OperationContent::Reveal(RevealContent { pk: source.pk, proof: None, - }), + })], ) } @@ -833,11 +842,11 @@ mod tests { gas_limit, storage_limit, source, - OperationContent::Transfer(TransferContent { + vec![OperationContent::Transfer(TransferContent { amount, destination, parameters, - }), + })], ) } @@ -1040,7 +1049,7 @@ mod tests { ); // Reveal operation should fail - let expected_receipt = OperationResultSum::Reveal(OperationResult { + let expected_receipt = vec![OperationResultSum::Reveal(OperationResult { balance_updates: vec![ BalanceUpdate { balance: Balance::Account(Contract::Implicit(source.pkh)), @@ -1057,7 +1066,7 @@ mod tests { vec![RevealError::PreviouslyRevealedKey(pk).into()].into(), ), internal_operation_results: vec![], - }); + })]; assert_eq!( account.counter(&host).unwrap(), @@ -1098,7 +1107,7 @@ mod tests { "validate_and_apply_operation should not have failed with a kernel error", ); - let expected_receipt = OperationResultSum::Reveal(OperationResult { + let expected_receipt = vec![OperationResultSum::Reveal(OperationResult { balance_updates: vec![ BalanceUpdate { balance: Balance::Account(Contract::Implicit(source.pkh)), @@ -1115,7 +1124,7 @@ mod tests { vec![RevealError::InconsistentHash(inconsistent_pkh).into()].into(), ), internal_operation_results: vec![], - }); + })]; assert_eq!(receipt, expected_receipt); assert_eq!( @@ -1191,7 +1200,7 @@ mod tests { "validate_and_apply_operation should not have failed with a kernel error", ); - let expected_receipt = OperationResultSum::Reveal(OperationResult { + let expected_receipt = vec![OperationResultSum::Reveal(OperationResult { balance_updates: vec![ BalanceUpdate { balance: Balance::Account(Contract::Implicit(source.pkh)), @@ -1208,7 +1217,7 @@ mod tests { consumed_gas: 0_u64.into(), }), internal_operation_results: vec![], - }); + })]; assert_eq!(receipt, expected_receipt); @@ -1260,7 +1269,7 @@ mod tests { "validate_and_apply_operation should not have failed with a kernel error", ); - let expected_receipt = OperationResultSum::Transfer(OperationResult { + let expected_receipt = vec![OperationResultSum::Transfer(OperationResult { balance_updates: vec![ BalanceUpdate { balance: Balance::Account(Contract::Implicit(source.pkh.clone())), @@ -1283,7 +1292,7 @@ mod tests { .into(), ), internal_operation_results: vec![], - }); + })]; assert_eq!(receipt, expected_receipt); @@ -1333,7 +1342,7 @@ mod tests { "validate_and_apply_operation should not have failed with a kernel error", ); - let expected_receipt = OperationResultSum::Transfer(OperationResult { + let expected_receipt = vec![OperationResultSum::Transfer(OperationResult { balance_updates: vec![ BalanceUpdate { balance: Balance::Account(Contract::Implicit(src.pkh.clone())), @@ -1369,7 +1378,7 @@ mod tests { allocated_destination_contract: false, })), internal_operation_results: vec![], - }); + })]; assert_eq!(receipt, expected_receipt); // Verify that source and destination balances changed @@ -1417,7 +1426,7 @@ mod tests { "validate_and_apply_operation should not have failed with a kernel error", ); - let expected_receipt = OperationResultSum::Transfer(OperationResult { + let expected_receipt = vec![OperationResultSum::Transfer(OperationResult { balance_updates: vec![ BalanceUpdate { balance: Balance::Account(Contract::Implicit(src.pkh.clone())), @@ -1453,7 +1462,7 @@ mod tests { allocated_destination_contract: false, })), internal_operation_results: vec![], - }); + })]; // Verify that balance was only debited for fees assert_eq!(source.balance(&host).unwrap(), 35_u64.into()); @@ -1580,7 +1589,7 @@ mod tests { let storage = Some(storage_value.clone()); - let expected_receipt = OperationResultSum::Transfer(OperationResult { + let expected_receipt = vec![OperationResultSum::Transfer(OperationResult { balance_updates: vec![ BalanceUpdate { balance: Balance::Account(Contract::Implicit(src.pkh.clone())), @@ -1618,7 +1627,7 @@ mod tests { allocated_destination_contract: false, })), internal_operation_results: vec![], - }); + })]; // Verify that source and destination balances changed // 30 for transfer + 15 for fees, 5 should be left @@ -1686,7 +1695,7 @@ mod tests { "validate_and_apply_operation should not have failed with a kernel error", ); - let expected_receipt = OperationResultSum::Transfer(OperationResult { + let expected_receipt = vec![OperationResultSum::Transfer(OperationResult { balance_updates: vec![ BalanceUpdate { balance: Balance::Account(Contract::Implicit(src.pkh)), @@ -1706,7 +1715,7 @@ mod tests { ) )].into()), internal_operation_results: vec![], - }); + })]; // Verify that source and destination balances changed // Transfer should be free as it got reverted + 15 for fees, 5 should be left @@ -1763,7 +1772,7 @@ mod tests { "validate_and_apply_operation should not have failed with a kernel error", ); - let expected_receipt = OperationResultSum::Transfer(OperationResult { + let expected_receipt = vec![OperationResultSum::Transfer(OperationResult { balance_updates: vec![ BalanceUpdate { balance: Balance::Account(Contract::Implicit(src.pkh)), @@ -1783,7 +1792,7 @@ mod tests { .into(), ), internal_operation_results: vec![], - }); + })]; assert_eq!(receipt, expected_receipt); } @@ -1824,7 +1833,7 @@ mod tests { "validate_and_apply_operation should not have failed with a kernel error", ); - let expected_receipt = OperationResultSum::Transfer(OperationResult { + let expected_receipt = vec![OperationResultSum::Transfer(OperationResult { balance_updates: vec![ BalanceUpdate { balance: Balance::Account(Contract::Implicit(src.pkh)), @@ -1844,7 +1853,7 @@ mod tests { .into(), ), internal_operation_results: vec![], - }); + })]; assert_eq!(receipt, expected_receipt); } -- GitLab From 026e58dfd38dd76063cd593765b962693d172ea8 Mon Sep 17 00:00:00 2001 From: Luciano Freitas Date: Thu, 7 Aug 2025 15:27:43 +0200 Subject: [PATCH 08/11] Tezlink/Kernel/Transfer: split fees for each receipt --- etherlink/kernel_latest/tezos_execution/src/lib.rs | 4 ++-- etherlink/kernel_latest/tezos_execution/src/validate.rs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/etherlink/kernel_latest/tezos_execution/src/lib.rs b/etherlink/kernel_latest/tezos_execution/src/lib.rs index 206ec3dc7ce9..5b43527f32c6 100644 --- a/etherlink/kernel_latest/tezos_execution/src/lib.rs +++ b/etherlink/kernel_latest/tezos_execution/src/lib.rs @@ -482,7 +482,7 @@ fn execute_validation( .increment_counter(host) .map_err(|_| ValidityError::FailedToIncrementCounter)?; - balance_updates.extend(op_balance_updates); + balance_updates.push(op_balance_updates); } let new_source_balance = account @@ -623,7 +623,7 @@ fn apply_batch( &source, &new_source_balance, &mut source_account, - &balance_updates, + &balance_updates[index], ) }; diff --git a/etherlink/kernel_latest/tezos_execution/src/validate.rs b/etherlink/kernel_latest/tezos_execution/src/validate.rs index 012c85bf6339..5aecb2ea589e 100644 --- a/etherlink/kernel_latest/tezos_execution/src/validate.rs +++ b/etherlink/kernel_latest/tezos_execution/src/validate.rs @@ -129,7 +129,7 @@ pub struct ValidationInfo { pub source: PublicKeyHash, pub new_source_balance: Narith, pub source_account: TezlinkImplicitAccount, - pub balance_updates: Vec, + pub balance_updates: Vec>, } pub fn validate_source( -- GitLab From bf165cfe01bb2dead182bc1e06863c7af7e84a5b Mon Sep 17 00:00:00 2001 From: Luciano Freitas Date: Thu, 7 Aug 2025 15:46:33 +0200 Subject: [PATCH 09/11] Tezlink/Kernel/Transfer: update source balance upon application --- .../kernel_latest/tezos_execution/src/lib.rs | 84 ++++++++++++------- .../tezos_execution/src/validate.rs | 2 +- 2 files changed, 55 insertions(+), 31 deletions(-) diff --git a/etherlink/kernel_latest/tezos_execution/src/lib.rs b/etherlink/kernel_latest/tezos_execution/src/lib.rs index 5b43527f32c6..6d220e34fb05 100644 --- a/etherlink/kernel_latest/tezos_execution/src/lib.rs +++ b/etherlink/kernel_latest/tezos_execution/src/lib.rs @@ -201,7 +201,7 @@ pub fn transfer<'a, Host: Runtime>( param: Micheline<'a>, parser: &'a Parser<'a>, ctx: &mut Ctx<'a>, -) -> Result { +) -> Result<(TransferSuccess, Narith), TransferError> { match dest_contract { Contract::Implicit(pkh) => { if param != Micheline::from(()) || !entrypoint.is_default() { @@ -223,13 +223,18 @@ pub fn transfer<'a, Host: Runtime>( dest_contract, &mut dest_account, ) - .map(|(success, _applied_balance_changes)| TransferSuccess { - // This boolean is kept at false on purpose to maintain compatibility with TZKT. - // When transferring to a non-existent account, we need to allocate it (I/O to durable storage). - // This incurs a cost, and TZKT expects balance updates in the operation receipt representing this cost. - // So, as long as we don't have balance updates to represent this cost, we keep this boolean false. - allocated_destination_contract: false, - ..success + .map(|(success, applied_balance_changes)| { + ( + TransferSuccess { + // This boolean is kept at false on purpose to maintain compatibility with TZKT. + // When transferring to a non-existent account, we need to allocate it (I/O to durable storage). + // This incurs a cost, and TZKT expects balance updates in the operation receipt representing this cost. + // So, as long as we don't have balance updates to represent this cost, we keep this boolean false. + allocated_destination_contract: false, + ..success + }, + applied_balance_changes.new_src_balance, + ) }) } Contract::Originated(_) => { @@ -268,10 +273,13 @@ pub fn transfer<'a, Host: Runtime>( ctx, ); log!(host, Debug, "Transfer operation succeeded"); - Ok(TransferSuccess { - storage: Some(new_storage), - ..receipt - }) + Ok(( + TransferSuccess { + storage: Some(new_storage), + ..receipt + }, + applied_balance_changes.new_src_balance, + )) } } } @@ -287,7 +295,7 @@ pub fn transfer_external( amount: &Narith, dest: &Contract, parameter: Option, -) -> Result { +) -> Result<(TransferSuccess, Narith), TransferError> { log!( host, Debug, @@ -505,7 +513,7 @@ fn execute_validation( Ok(ValidationInfo { source, - new_source_balance, + source_balance: new_source_balance, source_account: account, balance_updates, }) @@ -592,7 +600,7 @@ fn apply_batch( ) -> (Vec, bool) { let ValidationInfo { source, - new_source_balance, + mut source_balance, mut source_account, balance_updates, } = validation_info; @@ -616,15 +624,17 @@ fn apply_batch( ); produce_skipped_receipt(&content) } else { - apply_operation( + let (r, b) = apply_operation( host, context, &content, &source, - &new_source_balance, + &source_balance, &mut source_account, &balance_updates[index], - ) + ); + source_balance = b; + r }; if first_failure.is_none() && !is_applied(&receipt) { @@ -649,10 +659,10 @@ fn apply_operation( context: &Context, content: &ManagerOperation, source: &PublicKeyHash, - new_source_balance: &Narith, + source_balance: &Narith, source_account: &mut TezlinkImplicitAccount, balance_updates: &[BalanceUpdate], -) -> OperationResultSum { +) -> (OperationResultSum, Narith) { match &content.operation { OperationContent::Reveal(RevealContent { pk, .. }) => { let reveal_result = reveal(host, source, source_account, pk); @@ -660,29 +670,43 @@ fn apply_operation( balance_updates.to_vec(), reveal_result.map_err(Into::into), ); - OperationResultSum::Reveal(manager_result) + ( + OperationResultSum::Reveal(manager_result), + source_balance.clone(), + ) } OperationContent::Transfer(TransferContent { amount, destination, parameters, }) => { - let transfer_result = transfer_external( + match transfer_external( host, context, source, source_account, - new_source_balance, + source_balance, amount, destination, parameters.clone(), - ) - .map(TransferTarget::ToContrat); - let manager_result = produce_operation_result( - balance_updates.to_vec(), - transfer_result.map_err(Into::into), - ); - OperationResultSum::Transfer(manager_result) + ) { + Ok((res, bal)) => { + let transfer_result = TransferTarget::ToContrat(res); + let manager_result = produce_operation_result( + balance_updates.to_vec(), + Ok(transfer_result), + ); + (OperationResultSum::Transfer(manager_result), bal) + } + Err(e) => { + let manager_result = + produce_operation_result(balance_updates.to_vec(), Err(e.into())); + ( + OperationResultSum::Transfer(manager_result), + source_balance.clone(), + ) + } + } } } } diff --git a/etherlink/kernel_latest/tezos_execution/src/validate.rs b/etherlink/kernel_latest/tezos_execution/src/validate.rs index 5aecb2ea589e..e347aaec7bb2 100644 --- a/etherlink/kernel_latest/tezos_execution/src/validate.rs +++ b/etherlink/kernel_latest/tezos_execution/src/validate.rs @@ -127,7 +127,7 @@ fn check_storage_limit( pub struct ValidationInfo { pub source: PublicKeyHash, - pub new_source_balance: Narith, + pub source_balance: Narith, pub source_account: TezlinkImplicitAccount, pub balance_updates: Vec>, } -- GitLab From d7455334ba161f9cd2d54e506ecdb0c5bf005d59 Mon Sep 17 00:00:00 2001 From: Luciano Freitas Date: Wed, 23 Jul 2025 16:22:20 +0200 Subject: [PATCH 10/11] Tezlink/Kernel: test compatibility of batch encoding --- .../kernel_latest/tezos/src/operation.rs | 76 +++++++++++++++++++ 1 file changed, 76 insertions(+) diff --git a/etherlink/kernel_latest/tezos/src/operation.rs b/etherlink/kernel_latest/tezos/src/operation.rs index 7cb95d80743b..10f163895988 100644 --- a/etherlink/kernel_latest/tezos/src/operation.rs +++ b/etherlink/kernel_latest/tezos/src/operation.rs @@ -536,4 +536,80 @@ mod tests { assert_eq!(operation_bytes, kernel_bytes) } + + // The operation below is the batch of a reveal and a transfer using the mockup mode of octez-client as follows: + // $ alias mockup-client='octez-client --mode mockup --base-dir /tmp/mockup --protocol + // PtSeouLo' + // $ mockup-client create mockup + // $ mockup-client import secret key alice unencrypted:edsk44ifgGvYJW7zEUasv156yPgVSUbNocwzXy4eMXjV2BSPBvQv3A + // $ mockup-client transfer 2 from bootstrap1 to alice --burn-cap 1 + // $ BATCH_HEX=$(mockup-client transfer 1 from alice to bootstrap1 --burn-cap 1 --dry-run | grep Operation: | cut -d x -f 2) + // $ octez-codec decode 023-PtSeouLo.operation from "$BATCH_HEX" + #[test] + fn tezos_compatibility_for_reveal_transfer_batch() { + // The goal of this test is to try to decode an encoding generated by 'octez-codec encode' command + let branch_vec = HashType::b58check_to_hash( + &HashType::BlockHash, + "BLockGenesisGenesisGenesisGenesisGenesisCCCCCeZiLHU", + ) + .unwrap(); + let branch = BlockHash::from(H256::from_slice(&branch_vec)); + let signature = UnknownSignature::from_base58_check("sigcBAU3AtdyxrMsZ4ScgFRWfTh66dD7mMNXQ2KmGP9y125hiJFgKtgcjmi3jVmUB5ytLjrU3xY6EVTveyfb4XcBvxNvCUDi").unwrap(); + let expected_operation = Operation { + branch, + content: vec![ + ManagerOperationContent::Reveal(ManagerOperation { + source: PublicKeyHash::from_b58check( + "tz1cckAZtxYwxAfwQuHnabTWfbp2ScWobxHH", + ) + .unwrap(), + fee: 276_u64.into(), + counter: 2_u64.into(), + operation: RevealContent { + pk: PublicKey::from_b58check( + "edpkuqNrmPPcy2S3G1uKYnxmg7Gov3c8q7AABKRs9EtTVtfDg5Fu7R", + ) + .unwrap(), + proof: None, + }, + gas_limit: 171_u64.into(), + storage_limit: 0_u64.into(), + }), + ManagerOperationContent::Transfer(ManagerOperation { + source: PublicKeyHash::from_b58check( + "tz1cckAZtxYwxAfwQuHnabTWfbp2ScWobxHH", + ) + .unwrap(), + fee: 365_u64.into(), + counter: 3_u64.into(), + operation: TransferContent { + amount: 1000000_u64.into(), + destination: Contract::from_b58check( + "tz1KqTpEZ7Yob7QbPE4Hy4Wo8fHG8LhKxZSx", + ) + .unwrap(), + parameters: None, + }, + gas_limit: 2101_u64.into(), + storage_limit: 0_u64.into(), + }), + ], + signature, + }; + + // This bytes sequence comes from the command just above the test + let operation_bytes = hex::decode("8fcf233671b6a04fcf679d2a381c2544ea6c1ea29ba6157776ed842426e5cab86b00ba3bed311a5d7b06dc4daf3c94c5c406927e4bcf940202ab0100009d05b06ea36a6ad464d94dc07a38b77b80b577c1ae51bbd8d20105cd5aed496c006c00ba3bed311a5d7b06dc4daf3c94c5c406927e4bcfed0203b51000c0843d000002298c03ed7d454a101eb7022bc95f7e5f41ac78006c7157e12e95a182e2d6caa7c7d275faf1dbaf97ec974b89c1ca1e27b43d063b975a4425b0c88d4cc00d88df6c6ca0328bfbd329a53eef6b59506e5cbc7dc90b").unwrap(); + + let operation = Operation::nom_read_exact(&operation_bytes) + .expect("Decoding operation should have succeeded"); + + assert_eq!(operation, expected_operation); + + // Also test the encoding + let kernel_bytes = expected_operation + .to_bytes() + .expect("Operation encoding should have succeed"); + + assert_eq!(operation_bytes, kernel_bytes) + } } -- GitLab From 881b6ad8bc77f216396fe3ad0d3b4cdb2c03d2aa Mon Sep 17 00:00:00 2001 From: Luciano Freitas Date: Fri, 18 Jul 2025 12:08:00 +0200 Subject: [PATCH 11/11] Tezlink/Kernel/Tests: test atomicity --- .../kernel_latest/tezos_execution/src/lib.rs | 344 +++++++++++++++++- 1 file changed, 341 insertions(+), 3 deletions(-) diff --git a/etherlink/kernel_latest/tezos_execution/src/lib.rs b/etherlink/kernel_latest/tezos_execution/src/lib.rs index 6d220e34fb05..e533628c6b86 100644 --- a/etherlink/kernel_latest/tezos_execution/src/lib.rs +++ b/etherlink/kernel_latest/tezos_execution/src/lib.rs @@ -778,6 +778,8 @@ mod tests { const CONTRACT_1: &str = "KT1EFxv88KpjxzGNu1ozh9Vta4BaV3psNknp"; + const CONTRACT_2: &str = "KT1RJ6PbjHpwc3M5rw5s2Nbmefwbuwbdxton"; + static SCRIPT: &str = r#" parameter string; storage string; @@ -798,7 +800,7 @@ mod tests { fn make_operation( fee: u64, - counter: u64, + first_counter: u64, gas_limit: u64, storage_limit: u64, source: Bootstrap, @@ -807,11 +809,12 @@ mod tests { let branch = TezBlock::genesis_block_hash().into(); let content = content .into_iter() - .map(|c| -> ManagerOperationContent { + .enumerate() + .map(|(i, c)| -> ManagerOperationContent { ManagerOperation { source: source.pkh.clone(), fee: fee.into(), - counter: counter.into(), + counter: (first_counter + i as u64).into(), operation: c, gas_limit: gas_limit.into(), storage_limit: storage_limit.into(), @@ -1881,4 +1884,339 @@ mod tests { assert_eq!(receipt, expected_receipt); } + + #[test] + fn apply_three_valid_operations() { + let mut host = MockKernelHost::default(); + let ctx = context::Context::init_context(); + + let src = bootstrap1(); + let dest = bootstrap2(); + + // src & dest each credited with 50ꜩ + let src_acc = init_account(&mut host, &src.pkh); + let dest_acc = init_account(&mut host, &dest.pkh); + + // op‑1: reveal + let reveal_content = OperationContent::Reveal(RevealContent { + pk: src.pk.clone(), + proof: None, + }); + + println!("Balance: {:?}", src_acc.balance(&host).unwrap()); + + // op‑2: transfer 10ꜩ to dest + let transfer_content_1 = OperationContent::Transfer(TransferContent { + amount: 10.into(), + destination: Contract::Implicit(dest.pkh.clone()), + parameters: None, + }); + + // op‑3: transfer 20ꜩ to dest + let transfer_content_2 = OperationContent::Transfer(TransferContent { + amount: 20.into(), + destination: Contract::Implicit(dest.pkh.clone()), + parameters: None, + }); + + let batch = make_operation( + 5, + 1, + 0, + 0, + src.clone(), + vec![reveal_content, transfer_content_1, transfer_content_2], + ); + + let receipts = validate_and_apply_operation(&mut host, &ctx, batch).unwrap(); + + let expected_receipts = vec![ + OperationResultSum::Reveal(OperationResult { + balance_updates: vec![ + BalanceUpdate { + balance: Balance::Account(Contract::Implicit(src.pkh.clone())), + changes: -5, + update_origin: UpdateOrigin::BlockApplication, + }, + BalanceUpdate { + balance: Balance::BlockFees, + changes: 5, + update_origin: UpdateOrigin::BlockApplication, + }, + ], + result: ContentResult::Applied(RevealSuccess { + consumed_gas: 0_u64.into(), + }), + internal_operation_results: vec![], + }), + OperationResultSum::Transfer(OperationResult { + balance_updates: vec![ + BalanceUpdate { + balance: Balance::Account(Contract::Implicit(src.pkh.clone())), + changes: -5, + update_origin: UpdateOrigin::BlockApplication, + }, + BalanceUpdate { + balance: Balance::BlockFees, + changes: 5, + update_origin: UpdateOrigin::BlockApplication, + }, + ], + result: ContentResult::Applied(TransferTarget::ToContrat( + TransferSuccess { + storage: None, + lazy_storage_diff: None, + balance_updates: vec![ + BalanceUpdate { + balance: Balance::Account(Contract::Implicit( + src.pkh.clone(), + )), + changes: -10, + update_origin: UpdateOrigin::BlockApplication, + }, + BalanceUpdate { + balance: Balance::Account(Contract::Implicit( + dest.pkh.clone(), + )), + changes: 10, + update_origin: UpdateOrigin::BlockApplication, + }, + ], + ticket_receipt: vec![], + originated_contracts: vec![], + consumed_gas: 0_u64.into(), + storage_size: 0_u64.into(), + paid_storage_size_diff: 0_u64.into(), + allocated_destination_contract: false, + }, + )), + internal_operation_results: vec![], + }), + OperationResultSum::Transfer(OperationResult { + balance_updates: vec![ + BalanceUpdate { + balance: Balance::Account(Contract::Implicit(src.pkh.clone())), + changes: -5, + update_origin: UpdateOrigin::BlockApplication, + }, + BalanceUpdate { + balance: Balance::BlockFees, + changes: 5, + update_origin: UpdateOrigin::BlockApplication, + }, + ], + result: ContentResult::Applied(TransferTarget::ToContrat( + TransferSuccess { + storage: None, + lazy_storage_diff: None, + balance_updates: vec![ + BalanceUpdate { + balance: Balance::Account(Contract::Implicit(src.pkh)), + changes: -20, + update_origin: UpdateOrigin::BlockApplication, + }, + BalanceUpdate { + balance: Balance::Account(Contract::Implicit(dest.pkh)), + changes: 20, + update_origin: UpdateOrigin::BlockApplication, + }, + ], + ticket_receipt: vec![], + originated_contracts: vec![], + consumed_gas: 0_u64.into(), + storage_size: 0_u64.into(), + paid_storage_size_diff: 0_u64.into(), + allocated_destination_contract: false, + }, + )), + internal_operation_results: vec![], + }), + ]; + + assert_eq!(receipts, expected_receipts); + + // counter updated, balances moved + // initial_balance: 50 tez, fee amount: (3*5)tez, transfer amount: (10 + 20)tez + assert_eq!(src_acc.balance(&host).unwrap(), 5u64.into()); + assert_eq!(dest_acc.balance(&host).unwrap(), 80u64.into()); + + assert_eq!( + src_acc.counter(&host).unwrap(), + 3.into(), + "Counter should have been incremented three times." + ); + } + + #[test] + fn apply_valid_then_invalid_operation_is_atomic() { + let mut host = MockKernelHost::default(); + let ctx = context::Context::init_context(); + + let src = bootstrap1(); + let dest = bootstrap2(); + + // src & dest each credited with 50ꜩ + let src_acc = init_account(&mut host, &src.pkh); + let _dst_acc = init_account(&mut host, &dest.pkh); + + // op‑1: reveal + let reveal_content = OperationContent::Reveal(RevealContent { + pk: src.pk.clone(), + proof: None, + }); + + // op‑2: transfer 10ꜩ to dest + let transfer_content = OperationContent::Transfer(TransferContent { + amount: 10.into(), + destination: Contract::Implicit(dest.pkh.clone()), + parameters: None, + }); + + let batch = make_operation( + 100, + 1, + 0, + 0, + src.clone(), + vec![reveal_content, transfer_content], + ); + + let receipts = validate_and_apply_operation(&mut host, &ctx, batch); + + let expected_error = + OperationError::Validation(ValidityError::CantPayFees(100_u64.into())); + + assert_eq!(receipts, Err(expected_error)); + + assert_eq!( + TezlinkImplicitAccount::from_public_key_hash(&ctx, &src.pkh) + .unwrap() + .balance(&host) + .unwrap(), + 50u64.into() + ); + + assert_eq!( + TezlinkImplicitAccount::from_public_key_hash(&ctx, &src.pkh) + .unwrap() + .manager(&host) + .unwrap(), + Manager::NotRevealed(src.pkh.clone()) + ); + + assert_eq!( + src_acc.counter(&host).unwrap(), + 0.into(), + "Counter should not have been incremented." + ); + } + + #[test] + fn apply_smart_contract_failure_reverts_batch() { + let mut host = MockKernelHost::default(); + let parser = mir::parser::Parser::new(); + + let src = bootstrap1(); + let src_acc = init_account(&mut host, &src.pkh); + + let fail_dest = ContractKt1Hash::from_base58_check(CONTRACT_1).unwrap(); + let succ_dest = ContractKt1Hash::from_base58_check(CONTRACT_2).unwrap(); + + init_contract(&mut host, &fail_dest, FAILING_SCRIPT, "Unit", &0_u64.into()); + let succ_account = + init_contract(&mut host, &succ_dest, SCRIPT, "\"initial\"", &0_u64.into()); + + let reveal_content = OperationContent::Reveal(RevealContent { + pk: src.pk.clone(), + proof: None, + }); + + let succ_transfer = OperationContent::Transfer(TransferContent { + amount: 1.into(), + destination: Contract::Originated(succ_dest.clone()), + parameters: Some(Parameter { + entrypoint: mir::ast::entrypoint::Entrypoint::default(), + value: parser.parse("\"Hello world\"").unwrap().encode(), + }), + }); + + let fail_transfer = OperationContent::Transfer(TransferContent { + amount: 1.into(), + destination: Contract::Originated(fail_dest.clone()), + parameters: Some(Parameter { + entrypoint: mir::ast::entrypoint::Entrypoint::default(), + value: parser.parse("Unit").unwrap().encode(), + }), + }); + + let batch = make_operation( + 10_u64, + 1, + 0, + 0, + src.clone(), + vec![reveal_content, succ_transfer, fail_transfer], + ); + + let receipts = validate_and_apply_operation( + &mut host, + &context::Context::init_context(), + batch, + ) + .unwrap(); + + println!("{:?}", receipts); + + assert!( + matches!( + &receipts[0], + OperationResultSum::Reveal(OperationResult { + result: ContentResult::BackTracked(_), + .. + }) + ), + "First receipt should be BackTracked Reveal" + ); + + assert!( + matches!( + &receipts[1], + OperationResultSum::Transfer(OperationResult { + result: ContentResult::BackTracked(_), + .. + }) + ), + "Second receipt should be BackTracked Transfer" + ); + + assert!( + matches!( + &receipts[2], + OperationResultSum::Transfer(OperationResult { + result: ContentResult::Failed(_), + .. + }) + ), + "Third receipt should be Failed Transfer" + ); + + // Storage must have reverted + assert!( + succ_account.storage(&host).unwrap() + == parser.parse("\"initial\"").unwrap().encode() + ); + + assert_eq!( + src_acc.counter(&host).unwrap(), + 3.into(), + "Counter should have been incremented three times." + ); + + // Initial balance: 50 tez, paid in fees: (3*10)tez, transfer reverted + assert_eq!( + src_acc.balance(&host).unwrap(), + 20.into(), + "Fees should have been paid for failed operation" + ) + } } -- GitLab