diff --git a/CHANGELOG.md b/CHANGELOG.md index 7c690d5f2..0983e3da8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ Unreleased changes, in reverse chronological order. New entries are added at the top of this list. +- [#1443](https://github.com/Zilliqa/zq2/pull/1451): Include failed transaction in blocks - [#1472](https://github.com/Zilliqa/zq2/pull/1472): Add `GetTransactionsForTxBlockEx` API. - [#1476](https://github.com/Zilliqa/zq2/pull/1476): Fix topic in EVM-encoded Scilla events. - [#1474](https://github.com/Zilliqa/zq2/pull/1474): Fix reading `ByStr20` maps in Scilla via EVM. diff --git a/zilliqa/src/exec.rs b/zilliqa/src/exec.rs index 85f36419b..d4c684ac3 100644 --- a/zilliqa/src/exec.rs +++ b/zilliqa/src/exec.rs @@ -9,17 +9,16 @@ use std::{ sync::{Arc, MutexGuard}, }; -use alloy::primitives::{hex, Address, U256}; -use anyhow::{anyhow, Result}; -use bytes::Bytes; +use alloy::primitives::{hex, Address, Bytes, U256}; +use anyhow::{anyhow, bail, Result}; use eth_trie::{EthTrie, Trie}; use ethabi::Token; use libp2p::PeerId; use revm::{ inspector_handle_register, primitives::{ - AccountInfo, BlockEnv, Bytecode, Env, ExecutionResult, HaltReason, HandlerCfg, Output, - ResultAndState, SpecId, TxEnv, B256, KECCAK_EMPTY, + AccountInfo, AccountStatus, BlockEnv, Bytecode, EVMError, Env, ExecutionResult, HaltReason, + HandlerCfg, Output, ResultAndState, SpecId, TxEnv, B256, KECCAK_EMPTY, }, Database, DatabaseRef, Evm, Inspector, }; @@ -37,7 +36,7 @@ use crate::{ message::{Block, BlockHeader}, precompiles::{get_custom_precompiles, scilla_call_handle_register}, scilla::{self, split_storage_key, storage_key, Scilla}, - state::{contract_addr, Account, Code, State}, + state::{contract_addr, Account, Code, Code::Evm as EvmCode, State}, time::SystemTime, transaction::{ total_scilla_gas_price, EvmGas, EvmLog, Log, ScillaGas, ScillaLog, ScillaParam, @@ -497,9 +496,66 @@ impl State { }) .build(); - let e = evm.transact()?; + let transact_result = evm.transact(); + let (mut state, cfg) = evm.into_db_and_env_with_handler_cfg(); - Ok((e, state.finalize(), cfg.env)) + + let result_and_state = match transact_result { + Err( + EVMError::Transaction(_) + | EVMError::Header(_) + | EVMError::Custom(_) + | EVMError::Precompile(_), + ) => { + let account = state.load_account(from_addr)?; + let Account { + code, + nonce, + balance, + .. + } = &mut account.account; + + let code_hash = match &code { + EvmCode(vec) if vec.is_empty() => KECCAK_EMPTY, + // Just signal that the code exists by returning non-KECCAK_EMPTY hash (we don't have to be precise here) + EvmCode(_) => B256::ZERO, + _ => KECCAK_EMPTY, + }; + let code = match code { + EvmCode(code) => mem::take(code), + _ => bail!("Evm account contains non-evm code type!"), + }; + + // The only updated fields are nonce and balance, rest can be set to default (means: no update) + let affected_acc = revm::primitives::Account { + status: AccountStatus::default(), + storage: HashMap::new(), + info: AccountInfo { + balance: balance + .saturating_sub(gas_price * u128::from(gas_limit.0) + amount) + .try_into()?, + nonce: *nonce + 1, + code_hash, + code: Some(Bytecode::new_raw(code.into())), + }, + }; + let mut state = HashMap::new(); + state.insert(from_addr, affected_acc); + ResultAndState { + result: ExecutionResult::Revert { + gas_used: 0u64, + output: Bytes::default(), + }, + state, + } + } + Err(err) => { + bail!(err); + } + Ok(result) => result, + }; + + Ok((result_and_state, state.finalize(), cfg.env)) } fn apply_transaction_scilla( @@ -958,7 +1014,7 @@ impl State { None, current_block, inspector::noop(), - BaseFeeCheck::Validate, + BaseFeeCheck::Ignore, )?; match result { diff --git a/zilliqa/tests/it/eth.rs b/zilliqa/tests/it/eth.rs index da68820c0..d188202c0 100644 --- a/zilliqa/tests/it/eth.rs +++ b/zilliqa/tests/it/eth.rs @@ -1306,3 +1306,72 @@ async fn deploy_deterministic_deployment_proxy(mut network: Network) { .unwrap() ); } + +#[zilliqa_macros::test] +async fn include_failed_tx_in_block(mut network: Network) { + let wallet_1 = network.genesis_wallet().await; + let wallet_2 = network.random_wallet().await; + + let provider = wallet_1.provider(); + + let funds = wallet_1 + .get_balance(wallet_1.address(), None) + .await + .unwrap(); + let block_gas_limit = network.nodes[0] + .inner + .lock() + .unwrap() + .config + .consensus + .eth_block_gas_limit + .0 + - 1; + + let gas_price = funds / block_gas_limit; + //let gas_price = (gas_price / 2) + 10; + + // Send a transaction from wallet 1 to wallet 2. + let _hash_1 = wallet_1 + .send_transaction( + TransactionRequest::pay(wallet_2.address(), 10) + .nonce(0) + .gas_price(gas_price) + .gas(block_gas_limit), + None, + ) + .await + .unwrap() + .tx_hash(); + + // Send second transaction from wallet 1 to wallet 2. + let hash_2 = wallet_1 + .send_transaction( + TransactionRequest::pay(wallet_2.address(), 10) + .nonce(1) + .gas_price(gas_price) + .gas(block_gas_limit), + None, + ) + .await + .unwrap() + .tx_hash(); + + // Process pending transaction + network + .run_until_async( + || async { + provider + .get_transaction_receipt(hash_2) + .await + .unwrap() + .is_some() + }, + 50, + ) + .await + .unwrap(); + + let receipt_2 = provider.get_transaction_receipt(hash_2).await.unwrap(); + assert_eq!(receipt_2.unwrap().status.unwrap(), U64::zero()); +}