This repository has been archived by the owner on Nov 5, 2023. It is now read-only.
forked from paradigmxyz/reth
-
Notifications
You must be signed in to change notification settings - Fork 3
[wip] feat: state function changes #6
Closed
merklefruit
wants to merge
15
commits into
op-rs:clabby/goerli-genesis
from
merklefruit:merklefruit/state-function
Closed
Changes from all commits
Commits
Show all changes
15 commits
Select commit
Hold shift + click to select a range
36202a4
feat: executor deposits
merklefruit ec75e0f
feat: receipts, l1 cost wip
merklefruit 56f2a1d
chore: small fixes
merklefruit e9714d0
wip: transact() optimism changes
merklefruit 65083e2
chore: minor changes related to optimism execution
merklefruit 593d3c1
wip: removed receipts changes + improved gas handling
merklefruit 66a1e94
chore: cleanup
merklefruit 25da21d
chore: system_tx and is_success method
merklefruit 8ba4c46
feat: refactoring: separated optimism feature + added routing fees to…
merklefruit 47f8824
fix: fee vault calc
merklefruit 738e9f5
fix: increment balance via executor function
merklefruit d00bad2
chore: minor fixes
merklefruit 0a82357
feat: parse l1 block info from l2 block
merklefruit 0a003c3
chore: removed default trait
merklefruit c7d1c2d
chore: set deposit gas to 0 always
merklefruit File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -55,3 +55,4 @@ parking_lot = "0.12" | |
|
||
[features] | ||
test-utils = ["parking_lot"] | ||
optimism = [] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
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
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)) | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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() | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
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_used
here)l1_cost
for 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