-
Notifications
You must be signed in to change notification settings - Fork 10
[wip] feat: state function changes #6
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
36202a4
ec75e0f
56f2a1d
e9714d0
65083e2
593d3c1
66a1e94
25da21d
8ba4c46
47f8824
738e9f5
d00bad2
0a82357
0a003c3
c7d1c2d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -55,3 +55,4 @@ parking_lot = "0.12" | |
|
|
||
| [features] | ||
| test-utils = ["parking_lot"] | ||
| optimism = [] | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -25,6 +25,9 @@ use std::{ | |
| sync::Arc, | ||
| }; | ||
|
|
||
| #[cfg(feature = "optimism")] | ||
| use crate::optimism; | ||
|
|
||
| /// Main block executor | ||
| pub struct Executor<DB> | ||
| where | ||
|
|
@@ -385,6 +388,9 @@ where | |
|
|
||
| self.init_env(&block.header, total_difficulty); | ||
|
|
||
| #[cfg(feature = "optimism")] | ||
| let mut l1_block_info = optimism::L1BlockInfo::new(block)?; | ||
|
|
||
| let mut cumulative_gas_used = 0; | ||
| let mut post_state = PostState::with_tx_capacity(block.body.len()); | ||
| for (transaction, sender) in block.body.iter().zip(senders.into_iter()) { | ||
|
|
@@ -397,33 +403,147 @@ where | |
| block_available_gas, | ||
| }) | ||
| } | ||
| // Execute transaction. | ||
| let ResultAndState { result, state } = self.transact(transaction, sender)?; | ||
|
|
||
| // commit changes | ||
| self.commit_changes( | ||
| state, | ||
| self.chain_spec.fork(Hardfork::SpuriousDragon).active_at_block(block.number), | ||
| &mut post_state, | ||
| ); | ||
|
|
||
| // append gas used | ||
| cumulative_gas_used += result.gas_used(); | ||
|
|
||
| // cast revm logs to reth logs | ||
| let logs: Vec<Log> = result.logs().into_iter().map(into_reth_log).collect(); | ||
|
|
||
| // Push transaction changeset and calculate header bloom filter for receipt. | ||
| post_state.add_receipt(Receipt { | ||
| tx_type: transaction.tx_type(), | ||
| // Success flag was added in `EIP-658: Embedding transaction status code in | ||
| // receipts`. | ||
| success: result.is_success(), | ||
| cumulative_gas_used, | ||
| bloom: logs_bloom(logs.iter()), | ||
| logs, | ||
| }); | ||
| post_state.finish_transition(); | ||
| #[cfg(feature = "optimism")] | ||
| { | ||
| let db = self.db(); | ||
| let l1_cost = l1_block_info.calculate_tx_l1_cost(transaction); | ||
|
|
||
| let sender_account = db.load_account(sender).map_err(|_| Error::ProviderError)?; | ||
| let old_sender_info = to_reth_acc(&sender_account.info); | ||
| if let Some(m) = transaction.mint() { | ||
| // Add balance to the caller account equal to the minted amount. | ||
| // Note: this is unconditional, and will not be reverted if the tx fails | ||
| // (unless the block can't be built at all due to gas limit constraints) | ||
| sender_account.info.balance += U256::from(m); | ||
| } | ||
|
|
||
| // Check if the sender balance can cover the L1 cost. | ||
| // Deposits pay for their gas directly on L1 so they are exempt from this | ||
| if !transaction.is_deposit() { | ||
| if sender_account.info.balance.cmp(&l1_cost) == std::cmp::Ordering::Less { | ||
| return Err(Error::InsufficientFundsForL1Cost { | ||
| have: sender_account.info.balance.to::<u64>(), | ||
| want: l1_cost.to::<u64>(), | ||
| }) | ||
| } | ||
|
|
||
| // Safely take l1_cost from sender (the rest will be deducted by the | ||
| // internal EVM execution and included in result.gas_used()) | ||
| // TODO: need to handle calls with `disable_balance_check` flag set? | ||
| sender_account.info.balance -= l1_cost; | ||
| } | ||
|
|
||
| let new_sender_info = to_reth_acc(&sender_account.info); | ||
| post_state.change_account(sender, old_sender_info, new_sender_info); | ||
|
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Potential problem: when changing the post_state before the tx execution like we do here, does this create a clash with the |
||
|
|
||
| // Execute transaction. | ||
| let ResultAndState { result, state } = self.transact(transaction, sender)?; | ||
|
|
||
| if transaction.is_deposit() && !result.is_success() { | ||
| // If the Deposited transaction failed, the deposit must still be included. | ||
| // In this case, we need to increment the sender nonce and disregard the | ||
| // state changes. The transaction is also recorded as using all gas. | ||
| let db = self.db(); | ||
| let sender_account = | ||
| db.load_account(sender).map_err(|_| Error::ProviderError)?; | ||
| let old_sender_info = to_reth_acc(&sender_account.info); | ||
| sender_account.info.nonce += 1; | ||
| let new_sender_info = to_reth_acc(&sender_account.info); | ||
|
|
||
| post_state.change_account(sender, old_sender_info, new_sender_info); | ||
| if !transaction.is_system_transaction() { | ||
| cumulative_gas_used += transaction.gas_limit(); | ||
| } | ||
|
|
||
| post_state.add_receipt(Receipt { | ||
| tx_type: transaction.tx_type(), | ||
| success: false, | ||
| cumulative_gas_used, | ||
| bloom: Bloom::zero(), | ||
| logs: vec![], | ||
| deposit_nonce: Some(transaction.nonce()), | ||
| }); | ||
| post_state.finish_transition(); | ||
| continue | ||
| } | ||
|
|
||
| // commit changes | ||
| self.commit_changes( | ||
| state, | ||
| self.chain_spec.fork(Hardfork::SpuriousDragon).active_at_block(block.number), | ||
| &mut post_state, | ||
| ); | ||
|
|
||
| if !transaction.is_system_transaction() { | ||
| // After Regolith, deposits are reported as using the actual gas used instead of | ||
| // all the gas. System transactions are not reported as using any gas. | ||
| cumulative_gas_used += result.gas_used() | ||
| } | ||
|
|
||
| // Route the l1 cost and base fee to the appropriate optimism vaults | ||
| self.increment_account_balance( | ||
| optimism::l1_cost_recipient(), | ||
| l1_cost, | ||
| &mut post_state, | ||
| )?; | ||
| self.increment_account_balance( | ||
| optimism::base_fee_recipient(), | ||
| U256::from( | ||
| block | ||
| .base_fee_per_gas | ||
| .unwrap_or_default() | ||
| .saturating_mul(result.gas_used()), | ||
| ), | ||
| &mut post_state, | ||
| )?; | ||
|
Comment on lines
+484
to
+499
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. can probably be refactored to be prettier |
||
|
|
||
| // cast revm logs to reth logs | ||
| let logs: Vec<Log> = result.logs().into_iter().map(into_reth_log).collect(); | ||
|
|
||
| // Push transaction changeset and calculate header bloom filter for receipt. | ||
| post_state.add_receipt(Receipt { | ||
| tx_type: transaction.tx_type(), | ||
| // Success flag was added in `EIP-658: Embedding transaction status code in | ||
| // receipts`. | ||
| success: result.is_success(), | ||
| cumulative_gas_used, | ||
| bloom: logs_bloom(logs.iter()), | ||
| logs, | ||
| deposit_nonce: Some(transaction.nonce()), | ||
| }); | ||
| post_state.finish_transition(); | ||
| } | ||
|
|
||
| #[cfg(not(feature = "optimism"))] | ||
| { | ||
| // Execute transaction. | ||
| let ResultAndState { result, state } = self.transact(transaction, sender)?; | ||
|
|
||
| // commit changes | ||
| self.commit_changes( | ||
| state, | ||
| self.chain_spec.fork(Hardfork::SpuriousDragon).active_at_block(block.number), | ||
| &mut post_state, | ||
| ); | ||
|
|
||
| cumulative_gas_used += result.gas_used(); | ||
|
|
||
| // cast revm logs to reth logs | ||
| let logs: Vec<Log> = result.logs().into_iter().map(into_reth_log).collect(); | ||
|
|
||
| // Push transaction changeset and calculate header bloom filter for receipt. | ||
| post_state.add_receipt(Receipt { | ||
| tx_type: transaction.tx_type(), | ||
| // Success flag was added in `EIP-658: Embedding transaction status code in | ||
| // receipts`. | ||
| success: result.is_success(), | ||
| cumulative_gas_used, | ||
| bloom: logs_bloom(logs.iter()), | ||
| logs, | ||
| }); | ||
| post_state.finish_transition(); | ||
| } | ||
| } | ||
|
|
||
| Ok((post_state, cumulative_gas_used)) | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,110 @@ | ||
| use std::str::FromStr; | ||
|
|
||
| use reth_interfaces::executor; | ||
| use reth_primitives::{Address, Block, TransactionKind, TransactionSigned, U256}; | ||
|
|
||
| const L1_FEE_RECIPIENT: &str = "0x420000000000000000000000000000000000001A"; | ||
| const BASE_FEE_RECIPIENT: &str = "0x4200000000000000000000000000000000000019"; | ||
| const L1_BLOCK_CONTRACT: &str = "0x4200000000000000000000000000000000000015"; | ||
|
|
||
| const ZERO_BYTE_COST: u64 = 4; | ||
| const NON_ZERO_BYTE_COST: u64 = 16; | ||
|
|
||
| /// L1 block info | ||
| /// | ||
| /// We can extract L1 epoch data from each L2 block, by looking at the `setL1BlockValues` | ||
| /// transaction data. This data is then used to calculate the L1 cost of a transaction. | ||
| /// | ||
| /// Here is the format of the `setL1BlockValues` transaction data: | ||
| /// | ||
| /// setL1BlockValues(uint64 _number, uint64 _timestamp, uint256 _basefee, bytes32 _hash, | ||
| /// uint64 _sequenceNumber, bytes32 _batcherHash, uint256 _l1FeeOverhead, uint256 _l1FeeScalar) | ||
| /// | ||
| /// For now, we only care about the fields necessary for L1 cost calculation. | ||
| pub struct L1BlockInfo { | ||
| l1_base_fee: U256, | ||
| l1_fee_overhead: U256, | ||
| l1_fee_scalar: U256, | ||
| } | ||
|
|
||
| impl L1BlockInfo { | ||
| /// Create a new L1 block info struct from a L2 block | ||
| pub fn new(block: &Block) -> Result<Self, executor::Error> { | ||
| let l1_block_contract = Address::from_str(L1_BLOCK_CONTRACT).unwrap(); | ||
|
|
||
| let l1_info_tx_data = block | ||
| .body | ||
| .iter() | ||
| .find(|tx| matches!(tx.kind(), TransactionKind::Call(to) if to == &l1_block_contract)) | ||
| .ok_or(executor::Error::L1BlockInfoError { | ||
| message: "could not find l1 block info tx in the L2 block".to_string(), | ||
| }) | ||
| .and_then(|tx| { | ||
| tx.input().get(4..).ok_or(executor::Error::L1BlockInfoError { | ||
| message: "could not get l1 block info tx calldata bytes".to_string(), | ||
| }) | ||
| })?; | ||
|
|
||
| // The setL1BlockValues tx calldata must be exactly 184 bytes long, considering that | ||
| // we already removed the first 4 bytes (the function selector). Detailed breakdown: | ||
| // 8 bytes for the block number | ||
| // + 8 bytes for the block timestamp | ||
| // + 32 bytes for the base fee | ||
| // + 32 bytes for the block hash | ||
| // + 8 bytes for the block sequence number | ||
| // + 32 bytes for the batcher hash | ||
| // + 32 bytes for the fee overhead | ||
| // + 32 bytes for the fee scalar | ||
| if l1_info_tx_data.len() != 184 { | ||
| return Err(executor::Error::L1BlockInfoError { | ||
| message: "unexpected l1 block info tx calldata length found".to_string(), | ||
| }) | ||
| } | ||
|
|
||
| let l1_base_fee = U256::try_from_le_slice(&l1_info_tx_data[16..48]).ok_or( | ||
| executor::Error::L1BlockInfoError { | ||
| message: "could not convert l1 base fee".to_string(), | ||
| }, | ||
| )?; | ||
| let l1_fee_overhead = U256::try_from_le_slice(&l1_info_tx_data[120..152]).ok_or( | ||
| executor::Error::L1BlockInfoError { | ||
| message: "could not convert l1 fee overhead".to_string(), | ||
| }, | ||
| )?; | ||
| let l1_fee_scalar = U256::try_from_le_slice(&l1_info_tx_data[152..184]).ok_or( | ||
| executor::Error::L1BlockInfoError { | ||
| message: "could not convert l1 fee scalar".to_string(), | ||
| }, | ||
| )?; | ||
|
|
||
| Ok(Self { l1_base_fee, l1_fee_overhead, l1_fee_scalar }) | ||
| } | ||
|
|
||
| /// Calculate the gas cost of a transaction based on L1 block data posted on L2 | ||
| pub fn calculate_tx_l1_cost(&mut self, tx: &TransactionSigned) -> U256 { | ||
| let rollup_data_gas_cost = U256::from(tx.input().iter().fold(0, |acc, byte| { | ||
| acc + if byte == &0x00 { ZERO_BYTE_COST } else { NON_ZERO_BYTE_COST } | ||
| })); | ||
|
|
||
| if tx.is_deposit() || rollup_data_gas_cost == U256::ZERO { | ||
| return U256::ZERO | ||
| } | ||
|
|
||
| rollup_data_gas_cost | ||
| .saturating_add(self.l1_fee_overhead) | ||
| .saturating_mul(self.l1_base_fee) | ||
| .saturating_mul(self.l1_fee_scalar) | ||
| .checked_div(U256::from(1_000_000)) | ||
| .unwrap_or_default() | ||
| } | ||
| } | ||
|
|
||
| /// Get the base fee recipient address | ||
| pub fn base_fee_recipient() -> Address { | ||
| Address::from_str(BASE_FEE_RECIPIENT).unwrap() | ||
| } | ||
|
|
||
| /// Get the L1 cost recipient address | ||
| pub fn l1_cost_recipient() -> Address { | ||
| Address::from_str(L1_FEE_RECIPIENT).unwrap() | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This could have been addressed in Revm's transact() function in a more clean way. In fact I have a local branch of what this change would look like here: https://github.com/merklefruit/revm/pull/1/files#diff-1d478ba44ccc56e3b1142bd3723bf97f3e254c25dd18323481aedadce0803e91R130
However that solution has some cons:
cumulative_gas_usedhere)l1_costfor the entire block, and would have to access the DB for each transaction in the block instead. This is the main reason why I wanted to keep all the diffs on Op-reth if possible