diff --git a/src/kernel_evm/ethereum/src/block.rs b/src/kernel_evm/ethereum/src/block.rs index 4b69f548838cce659d0f0f22a40722f3a4d75070..181a8fa57ff24a21913d1011081a636166c8a617 100644 --- a/src/kernel_evm/ethereum/src/block.rs +++ b/src/kernel_evm/ethereum/src/block.rs @@ -96,23 +96,9 @@ impl L2Block { L2Block { number, hash: H256(number.into()), - parent_hash: L2Block::dummy_block_hash(), - nonce: U256::zero(), - sha3_uncles: L2Block::dummy_hash(), - logs_bloom: None, - transactions_root: L2Block::dummy_hash(), - state_root: L2Block::dummy_hash(), - receipts_root: L2Block::dummy_hash(), - miner: L2Block::dummy_hash(), - difficulty: U256::zero(), - total_difficulty: U256::zero(), - extra_data: L2Block::dummy_hash(), - size: U256::zero(), - gas_limit: 1u64, - gas_used: U256::zero(), timestamp, transactions, - uncles: Vec::new(), + ..Self::default() } } @@ -130,3 +116,29 @@ impl L2Block { } } } + +impl Default for L2Block { + fn default() -> Self { + Self { + number: U256::default(), + hash: H256::default(), + parent_hash: L2Block::dummy_block_hash(), + nonce: U256::zero(), + sha3_uncles: L2Block::dummy_hash(), + logs_bloom: None, + transactions_root: L2Block::dummy_hash(), + state_root: L2Block::dummy_hash(), + receipts_root: L2Block::dummy_hash(), + miner: L2Block::dummy_hash(), + difficulty: U256::zero(), + total_difficulty: U256::zero(), + extra_data: L2Block::dummy_hash(), + size: U256::zero(), + gas_limit: 1u64, + gas_used: U256::zero(), + timestamp: Timestamp::from(0), + transactions: Vec::new(), + uncles: Vec::new(), + } + } +} diff --git a/src/kernel_evm/kernel/src/block.rs b/src/kernel_evm/kernel/src/block.rs index 3181bd26ddedee6a1dc97fd544cfa781b6fac73e..e73068337de7fe8d46a20f347702879a0594bf44 100644 --- a/src/kernel_evm/kernel/src/block.rs +++ b/src/kernel_evm/kernel/src/block.rs @@ -5,7 +5,7 @@ use crate::apply::apply_transaction; use crate::block_in_progress; -use crate::blueprint::{Blueprint, Queue}; +use crate::blueprint::Queue; use crate::current_timestamp; use crate::error::Error; use crate::error::StorageError::AccountInitialisation; @@ -22,16 +22,20 @@ use tezos_ethereum::block::BlockConstants; fn compute( host: &mut Host, - proposal: Blueprint, block_in_progress: &mut BlockInProgress, block_constants: &BlockConstants, precompiles: &PrecompileBTreeMap, evm_account_storage: &mut EthereumAccountStorage, accounts_index: &mut IndexableStorage, ) -> Result<(), Error> { - let transactions = proposal.transactions; - - for transaction in transactions.into_iter() { + while block_in_progress.has_tx() { + if block_in_progress.would_overflow() { + // TODO: https://gitlab.com/tezos/tezos/-/issues/5873 + // store the block in progress + return Ok(()); + } + let transaction = block_in_progress.pop_tx().ok_or(Error::Reboot)?; + block_in_progress.account_for_transaction(&transaction); let tx_hash = transaction.tx_hash; // If `apply_transaction` returns `None`, the transaction should be @@ -70,20 +74,32 @@ pub fn produce(host: &mut Host, queue: Queue) -> Result<(), Error let mut accounts_index = init_account_index()?; let precompiles = precompiles::precompile_set::(); + // TODO: https://gitlab.com/tezos/tezos/-/issues/5873 + // if there is no more gas for the transaction, + // reload current container in progress and rest of queue + for proposal in queue.proposals { + // proposal is turn into a ring to allow poping from the front + let ring = proposal.transactions.into(); // TODO: https://gitlab.com/tezos/tezos/-/issues/5873 // add proposal to the container - let mut block_in_progress = - BlockInProgress::new(current_block_number, current_constants.gas_price)?; + let mut block_in_progress = BlockInProgress::new( + current_block_number, + current_constants.gas_price, + ring, + )?; compute( host, - proposal, &mut block_in_progress, ¤t_constants, &precompiles, &mut evm_account_storage, &mut accounts_index, )?; + + // TODO: https://gitlab.com/tezos/tezos/-/issues/5873 + // if reboot initiated, store container in progress and exit + // else store block let new_block = block_in_progress.finalize_and_store(host)?; current_block_number = new_block.number + 1; current_constants = new_block.constants(); @@ -95,6 +111,7 @@ pub fn produce(host: &mut Host, queue: Queue) -> Result<(), Error mod tests { use super::*; use crate::blueprint::Blueprint; + use crate::inbox::TransactionContent; use crate::inbox::{Transaction, TransactionContent::Ethereum}; use crate::indexable_storage::internal_for_tests::{get_value, length}; use crate::storage::internal_for_tests::{ @@ -105,6 +122,7 @@ mod tests { account_path, init_account_storage, EthereumAccountStorage, }; use primitive_types::{H160, H256, U256}; + use std::collections::VecDeque; use std::str::FromStr; use tezos_ethereum::signatures::EthereumTransactionCommon; use tezos_ethereum::transaction::{TransactionStatus, TRANSACTION_HASH_SIZE}; @@ -656,7 +674,6 @@ mod tests { &sender, U256::from(10000000000000000000u64), ); - produce(&mut host, queue).expect("The block production failed."); let new_number_of_blocks_indexed = length(&host, &blocks_index).unwrap(); @@ -688,4 +705,145 @@ mod tests { get_value(&host, &transaction_hashes_index, last_indexed_transaction) ); } + + fn first_block(host: &mut MockHost) -> BlockConstants { + let timestamp = current_timestamp(host); + let timestamp = U256::from(timestamp.as_u64()); + BlockConstants::first_block(timestamp) + } + + fn compute_block( + transactions: VecDeque, + host: &mut MockHost, + mut evm_account_storage: EthereumAccountStorage, + ) -> BlockInProgress { + let block_constants = first_block(host); + let precompiles = precompiles::precompile_set::(); + let mut accounts_index = init_account_index().unwrap(); + + // init block in progress + let mut block_in_progress = + BlockInProgress::new(U256::from(1), U256::from(1), transactions) + .expect("Should have initialised block in progress"); + + compute::( + host, + &mut block_in_progress, + &block_constants, + &precompiles, + &mut evm_account_storage, + &mut accounts_index, + ) + .expect("Should have computed block"); + block_in_progress + } + + #[test] + fn test_ticks_transaction() { + // init host + let mut host = MockHost::default(); + + //provision sender account + let sender = H160::from_str("af1276cbb260bb13deddb4209ae99ae6e497f446").unwrap(); + let mut evm_account_storage = init_account_storage().unwrap(); + set_balance( + &mut host, + &mut evm_account_storage, + &sender, + U256::from(5000000000000000u64), + ); + + // tx is valid because correct nonce and account provisionned + let valid_tx = Transaction { + tx_hash: [0; TRANSACTION_HASH_SIZE], + content: TransactionContent::Ethereum(dummy_eth_transaction_deploy()), + }; + + let transactions = vec![valid_tx.clone()].into(); + + // act + let block_in_progress = + compute_block(transactions, &mut host, evm_account_storage); + + // assert + let ticks = block_in_progress.estimated_ticks; + let block = block_in_progress + .finalize_and_store(&mut host) + .expect("should have succeeded in processing block"); + + assert_eq!( + block.gas_used, + U256::from(123), + "Gas used for contract creation" + ); + + assert_eq!(ticks, valid_tx.estimate_ticks()); + } + + #[test] + fn test_stop_computation() { + // init host + let mut host = MockHost::default(); + let block_constants = first_block(&mut host); + let precompiles = precompiles::precompile_set::(); + let mut accounts_index = init_account_index().unwrap(); + + //provision sender account + let sender = H160::from_str("af1276cbb260bb13deddb4209ae99ae6e497f446").unwrap(); + let mut evm_account_storage = init_account_storage().unwrap(); + set_balance( + &mut host, + &mut evm_account_storage, + &sender, + U256::from(10000000000000000000u64), + ); + + // tx is valid because correct nonce and account provisionned + let valid_tx = Transaction { + tx_hash: [0; TRANSACTION_HASH_SIZE], + content: TransactionContent::Ethereum(dummy_eth_transaction_zero()), + }; + let transactions = vec![valid_tx].into(); + + // init block in progress + let mut block_in_progress = + BlockInProgress::new(U256::from(1), U256::from(1), transactions) + .expect("Should have initialised block in progress"); + // block is almost full wrt ticks + block_in_progress.estimated_ticks = block_in_progress::MAX_TICKS - 1000; + + // act + compute::( + &mut host, + &mut block_in_progress, + &block_constants, + &precompiles, + &mut evm_account_storage, + &mut accounts_index, + ) + .expect("Should have computed block"); + + // assert + + // block in progress should not have registered any gas or ticks + assert_eq!( + block_in_progress.cumulative_gas, + U256::from(0), + "should not have consumed any gas" + ); + assert_eq!( + block_in_progress.estimated_ticks, + block_in_progress::MAX_TICKS - 1000, + "should not have consumed any tick" + ); + + // the transaction should not have been processed + let dest_address = + H160::from_str("423163e58aabec5daa3dd1130b759d24bef0f6ea").unwrap(); + let sender_balance = get_balance(&mut host, &mut evm_account_storage, &sender); + let dest_balance = + get_balance(&mut host, &mut evm_account_storage, &dest_address); + assert_eq!(sender_balance, U256::from(10000000000000000000u64)); + assert_eq!(dest_balance, U256::from(0u64)) + } } diff --git a/src/kernel_evm/kernel/src/block_in_progress.rs b/src/kernel_evm/kernel/src/block_in_progress.rs index 1040e639de4fe214416a678f33546fed9848c2b4..bb01ea31aaa44faf2bcfc988912a524aed3e5bfe 100644 --- a/src/kernel_evm/kernel/src/block_in_progress.rs +++ b/src/kernel_evm/kernel/src/block_in_progress.rs @@ -8,16 +8,28 @@ use crate::apply::{TransactionObjectInfo, TransactionReceiptInfo}; use crate::current_timestamp; use crate::error::Error; use crate::error::TransferError::CumulativeGasUsedOverflow; +use crate::inbox::{Transaction, TransactionContent}; use crate::storage; use primitive_types::{H256, U256}; +use std::collections::VecDeque; use tezos_ethereum::block::L2Block; use tezos_ethereum::transaction::*; -use tezos_smart_rollup_debug::Runtime; +use tezos_smart_rollup_host::runtime::Runtime; + +/// DO NOT TOUCH ////////////////////////////////////////////////////////////// +/// Those constants where derived from benchmarking +pub const TICK_PER_GAS: u64 = 2000; +pub const TICK_FOR_CRYPTO: u64 = 25_000_000; +pub const MAX_TICKS: u64 = 10_000_000_000; +pub const TICK_FOR_DEPOSIT: u64 = TICK_FOR_CRYPTO; +/// DO NOT TOUCH ////////////////////////////////////////////////////////////// /// Container for all data needed during block computation pub struct BlockInProgress { /// block number pub number: U256, + /// queue containing the transactions to execute + tx_queue: VecDeque, /// list of transactions executed without issue valid_txs: Vec<[u8; 32]>, /// gas accumulator @@ -29,12 +41,19 @@ pub struct BlockInProgress { /// hash to use for receipt /// (computed from number, not the correct way to do it) pub hash: H256, + /// Cumulative number of ticks used + pub estimated_ticks: u64, } impl BlockInProgress { - pub fn new(number: U256, gas_price: U256) -> Result { + pub fn new( + number: U256, + gas_price: U256, + transactions: VecDeque, + ) -> Result { let block_in_progress = BlockInProgress { number, + tx_queue: transactions, valid_txs: Vec::new(), cumulative_gas: U256::zero(), index: 0, @@ -43,6 +62,7 @@ impl BlockInProgress { // it should be computed at the end, and not included in the receipt // the block is referenced in the storage by the block number anyway hash: H256(number.into()), + estimated_ticks: 0, }; Ok(block_in_progress) @@ -67,11 +87,45 @@ impl BlockInProgress { host: &mut Host, ) -> Result { let timestamp = current_timestamp(host); - let new_block = L2Block::new(self.number, self.valid_txs, timestamp); + let new_block = L2Block { + timestamp, + gas_used: self.cumulative_gas, + ..L2Block::new(self.number, self.valid_txs, timestamp) + }; storage::store_current_block(host, &new_block)?; Ok(new_block) } + pub fn pop_tx(&mut self) -> Option { + self.tx_queue.pop_front() + } + + pub fn has_tx(&self) -> bool { + !self.tx_queue.is_empty() + } + + pub fn estimate_ticks_for_transaction(transaction: &Transaction) -> u64 { + match &transaction.content { + TransactionContent::Ethereum(eth) => { + eth.gas_limit * TICK_PER_GAS + TICK_FOR_CRYPTO + } + TransactionContent::Deposit(_) => TICK_FOR_DEPOSIT, + } + } + + pub fn would_overflow(&self) -> bool { + match self.tx_queue.front() { + Some(transaction) => { + self.estimated_ticks + transaction.estimate_ticks() > MAX_TICKS + } + None => false, // should not happen, but false is a safe value anyway + } + } + + pub fn account_for_transaction(&mut self, transaction: &Transaction) { + self.estimated_ticks += transaction.estimate_ticks(); + } + pub fn make_receipt( &self, receipt_info: TransactionReceiptInfo, diff --git a/src/kernel_evm/kernel/src/error.rs b/src/kernel_evm/kernel/src/error.rs index 20a47a6d37e12a20fd8566880ef1dbbe2ed7f996..7b158cee499ac909819f60718aa0852bb81829d3 100644 --- a/src/kernel_evm/kernel/src/error.rs +++ b/src/kernel_evm/kernel/src/error.rs @@ -50,6 +50,7 @@ pub enum Error { UpgradeError(UpgradeProcessError), InvalidSignature(SigError), InvalidSignatureCheck, + Reboot, } impl From for StorageError { diff --git a/src/kernel_evm/kernel/src/inbox.rs b/src/kernel_evm/kernel/src/inbox.rs index ef60068a1aead2c8000b7d3567ef6652409c3b7f..35b107f2cbc47639a03bb08b105cdce96b1a992c 100644 --- a/src/kernel_evm/kernel/src/inbox.rs +++ b/src/kernel_evm/kernel/src/inbox.rs @@ -11,6 +11,7 @@ use tezos_smart_rollup_core::PREIMAGE_HASH_SIZE; use tezos_smart_rollup_debug::debug_msg; use tezos_smart_rollup_host::runtime::Runtime; +use crate::block_in_progress::BlockInProgress; use crate::parsing::{ Input, InputResult, MAX_SIZE_PER_CHUNK, SIGNATURE_HASH_SIZE, UPGRADE_NONCE_SIZE, }; @@ -45,6 +46,13 @@ pub struct Transaction { pub content: TransactionContent, } +impl Transaction { + pub fn estimate_ticks(&self) -> u64 { + // all details of tick model stay in the same module + BlockInProgress::estimate_ticks_for_transaction(self) + } +} + #[derive(Debug, PartialEq, Clone)] pub struct KernelUpgrade { pub nonce: [u8; UPGRADE_NONCE_SIZE], diff --git a/src/kernel_evm/kernel/src/lib.rs b/src/kernel_evm/kernel/src/lib.rs index c5306b85f5039f78d7a577906c5d5d3a39e3db4f..30bb86b390d6313d533d4bedd2545ea0185c02f4 100644 --- a/src/kernel_evm/kernel/src/lib.rs +++ b/src/kernel_evm/kernel/src/lib.rs @@ -73,7 +73,8 @@ pub fn stage_one( "Ticketer not specified, the kernel ignores internal transfers" ), } - + // TODO: https://gitlab.com/tezos/tezos/-/issues/5873 + // if rebooted, don't fetch inbox let queue = fetch(host, smart_rollup_address, chain_id, ticketer)?; for (i, blueprint) in queue.proposals.iter().enumerate() {