PreExecute根据代码里的注释,PreExecute负责的功能:准备EVM状态以供执行加载受益人账户(EIP-3651:预热COINBASE)以及访问列表中的所有账户/存储(EIP-2929)从调用者的余额中扣除最大可能费用对于EIP-7702交易,应用授权列表并委托
根据代码里的注释, PreExecute 负责的功能:
说的比较虚,我们直接跳到源码部分:
// crates/handler/src/handler.rs
#[inline]
fn pre_execution(&self, evm: &mut Self::Evm) -> Result<u64, Self::Error> {
self.validate_against_state_and_deduct_caller(evm)?;
self.load_accounts(evm)?;
let gas = self.apply_eip7702_auth_list(evm)?;
Ok(gas)
}
总共分成3个步骤,我们先看步骤1,validate_against_state_and_deduct_caller\ 跳转到源码实现:
// crates/handler/src/handler.rs
#[inline]
fn validate_against_state_and_deduct_caller(
&self,
evm: &mut Self::Evm,
) -> Result<(), Self::Error> {
pre_execution::validate_against_state_and_deduct_caller(evm.ctx())
}
继续跳转
// crates/handler/src/pre_execution.rs
#[inline]
pub fn validate_against_state_and_deduct_caller<
CTX: ContextTr,
ERROR: From<InvalidTransaction> + From<<CTX::Db as Database>::Error>,
>(
context: &mut CTX,
) -> Result<(), ERROR> {
let (block, tx, cfg, journal, _, _) = context.all_mut();
// Load caller's account.
let mut caller = journal.load_account_with_code_mut(tx.caller())?.data;
validate_account_nonce_and_code_with_components(&caller.account().info, tx, cfg)?;
let new_balance = calculate_caller_fee(*caller.balance(), tx, block, cfg)?;
caller.set_balance(new_balance);
if tx.kind().is_call() {
caller.bump_nonce();
}
Ok(())
}
看下 context.all_mut() 的定义:
// crates/context/interface/src/context.rs
fn all_mut(
&mut self,
) -> (
&Self::Block,
&Self::Tx,
&Self::Cfg,
&mut Self::Journal,
&mut Self::Chain,
&mut Self::Local,
);
返回的这几个变量的功能,我们在前面已经介绍过,这里不再复述了.
接着继续往下看
let mut caller = journal.load_account_with_code_mut(tx.caller())?.data;
这里实际调用的是下面的函数,略过查找跳转的部分:
// crates/context/src/journal/inner.rs
/// Loads account into memory. If account is already loaded it will be marked as warm.
#[inline]
pub fn load_account_mut_optional_code<'a, 'db, DB: Database>(
&'a mut self,
db: &'db mut DB,
address: Address,
load_code: bool,
skip_cold_load: bool,
) -> Result<StateLoad<JournaledAccount<'a, DB, ENTRY>>, JournalLoadError<DB::Error>>
where
'db: 'a,
{
let mut load = self.load_account_mut_optional(db, address, skip_cold_load)?;
if load_code {
load.data.load_code_preserve_error()?;
}
Ok(load)
}
Database 在前面没有出现过,他负责的是数据持久化的部分.提供 EVM 执行所需的所有底层状态数据.在这里负责加载账户信息、账户字节码.
先跳转到 load_account_mut_optional 查看实现部分.
// crates/context/src/journal/inner.rs
#[inline(never)]
pub fn load_account_mut_optional<'a, 'db, DB: Database>(
&'a mut self,
db: &'db mut DB,
address: Address,
skip_cold_load: bool,
) -> Result<StateLoad<JournaledAccount<'a, DB, ENTRY>>, JournalLoadError<DB::Error>>
where
'db: 'a,
{
let (account, is_cold) = match self.state.entry(address) {
Entry::Occupied(entry) => {
let account = entry.into_mut();
// skip load if account is cold.
let mut is_cold = account.is_cold_transaction_id(self.transaction_id);
if unlikely(is_cold) {
is_cold = self
.warm_addresses
.check_is_cold(&address, skip_cold_load)?;
// mark it warm.
account.mark_warm_with_transaction_id(self.transaction_id);
// if it is cold loaded and we have selfdestructed locally it means that
// account was selfdestructed in previous transaction and we need to clear its information and storage.
if account.is_selfdestructed_locally() {
account.selfdestruct();
account.unmark_selfdestructed_locally();
}
// set original info to current info.
*account.original_info = account.info.clone();
// unmark locally created
account.unmark_created_locally();
// journal loading of cold account.
self.journal.push(ENTRY::account_warmed(address));
}
(account, is_cold)
}
Entry::Vacant(vac) => {
// Precompiles, among some other account(access list and coinbase included)
// are warm loaded so we need to take that into account
let is_cold = self
.warm_addresses
.check_is_cold(&address, skip_cold_load)?;
let account = if let Some(account) = db.basic(address)? {
let mut account: Account = account.into();
account.transaction_id = self.transaction_id;
account
} else {
Account::new_not_existing(self.transaction_id)
};
// journal loading of cold account.
if is_cold {
self.journal.push(ENTRY::account_warmed(address));
}
(vac.insert(account), is_cold)
}
};
Ok(StateLoad::new(
JournaledAccount::new(
address,
account,
&mut self.journal,
db,
self.warm_addresses.access_list(),
self.transaction_id,
),
is_cold,
))
}
self.state 是一个 HashMap, Key 是 Address, Value 是 Account\ 这里是查找 Account 之前是否已经加载过,并处理各自的逻辑.\ 虽然在代码中,先判断的找到的部分,但是这里我们先看未找到的部分
Entry::Vacant(vac)
// 检测warm_address 中是否存在当前地址
// warm_addresses 包含预编译、AccessList 和 Coinbase
let is_cold = self
.warm_addresses
.check_is_cold(&address, skip_cold_load)?;
下面是 warm_addresses 的定义,
// crates/context/src/journal/warm_addresses.rs
pub struct WarmAddresses {
/// Set of warm loaded precompile addresses.
precompile_set: HashSet<Address>,
/// Bit vector of precompile short addresses. If address is shorter than [`SHORT_ADDRESS_CAP`] it
/// will be stored in this bit vector for faster access.
precompile_short_addresses: BitVec,
/// `true` if all precompiles are short addresses.
precompile_all_short_addresses: bool,
/// Coinbase address.
coinbase: Option<Address>,
/// Access list
access_list: HashMap<Address, HashSet<StorageKey>>,
}
check_is_cold 是检查所有的属性中是否有当前地址.\ 扩展太多,这里就不代码了.\ 继续往下看 Entry::Vacant(vac) 后面的部分
let account = if let Some(account) = db.basic(address)? {
let mut account: Account = account.into();
account.transaction_id = self.transaction_id;
account
} else {
Account::new_not_existing(self.transaction_id)
};
从数据库中查找该地址.如果找到则设置account 的 transaction_id 并返回.\ 这里的 transaction_id 不是 transaction_hash\ 而是当前 tx 在 block 中的 index, 就是tx在当前block排的位置.\ 记住这里,为什么先看没找到的部分就是因为 transaction_id 的设置.
接着往下比较容易理解,如果 is_cold ,则插入 journal 和 self.state 中
if is_cold {
self.journal.push(ENTRY::account_warmed(address));
}
(vac.insert(account), is_cold)
接着看找到的部分\ Entry::Occupied(entry)
let account = entry.into_mut();
let mut is_cold = account.is_cold_transaction_id(self.transaction_id);
我们跳转到 is_cold_transaction_id 实现进行查看
#[inline]
pub fn is_cold_transaction_id(&self, transaction_id: usize) -> bool {
self.transaction_id != transaction_id || self.status.contains(AccountStatus::Cold)
}
这里为什么能用 self.transaction_id != transaction_id 能判断 Account 是否 cold\ 就是因为在前面没找到的时候设置了 self.transaction_id\ 如果相等的话说明是这次 tx 执行的时候获取的 Account\ 如果不相等的话说明是之前的 tx 执行的时候就获取了 Account, Account 已经 Warm
继续往下.
// 这里跟Rust的分支判断有关,感兴趣的自己去了解下,这里就不扩展了.
if unlikely(is_cold){
// 前面说过了
is_cold = self
.warm_addresses
.check_is_cold(&address, skip_cold_load)?;
// 标记成热地址
account.mark_warm_with_transaction_id(self.transaction_id);
// 合约销毁相关代码,暂时略过
if account.is_selfdestructed_locally() {
account.selfdestruct();
account.unmark_selfdestructed_locally();
}
// 都比较明显,就不细讲了
*account.original_info = account.info.clone();
account.unmark_created_locally();
self.journal.push(ENTRY::account_warmed(address));
}
讲完 load_account_mut_optional\ 跳回 crates/context/src/journal/inner.rs -> load_account_mut_optional_code\ 接着往下
if load_code {
load.data.load_code_preserve_error()?;
}
跳转进去\ 逻辑还是比较清晰,还没需要特别讲的.\ 这里 code_hash 是 Account 代码 的哈希值\ EOA账户没有代码,code_hash 为空\ 合约账户和抽象账户都会有代码,code_hash不为空
// crates/context/interface/src/journaled_state/account.rs
pub fn load_code_preserve_error(&mut self) -> Result<&Bytecode, JournalLoadError<DB::Error>> {
if self.account.info.code.is_none() {
let hash = *self.code_hash();
let code = if hash == KECCAK_EMPTY {
Bytecode::default()
} else {
self.db.code_by_hash(hash)?
};
self.account.info.code = Some(code);
}
Ok(self.account.info.code.as_ref().unwrap())
}
讲完了 load_account_mut_optional_code\ 我们跳转回到crates/handler/src/pre_execution.rs -> validate_against_state_and_deduct_caller\ 接着往下来到
validate_account_nonce_and_code_with_components(&caller.account().info, tx, cfg)?;
实际调用的 validate_account_nonce_and_code,我们直接跳转查看\ 逻辑很简单,两个判断
eip3607
nonce
// crates/handler/src/pre_execution.rs
#[inline]
pub fn validate_account_nonce_and_code(
caller_info: &AccountInfo,
tx_nonce: u64,
is_eip3607_disabled: bool,
is_nonce_check_disabled: bool,
) -> Result<(), InvalidTransaction> {
if !is_eip3607_disabled {
let bytecode = match caller_info.code.as_ref() {
Some(code) => code,
None => &Bytecode::default(),
};
if !bytecode.is_empty() && !bytecode.is_eip7702() {
return Err(InvalidTransaction::RejectCallerWithCode);
}
}
// Check that the transaction's nonce is correct
if !is_nonce_check_disabled {
let tx = tx_nonce;
let state = caller_info.nonce;
match tx.cmp(&state) {
Ordering::Greater => {
return Err(InvalidTransaction::NonceTooHigh { tx, state });
}
Ordering::Less => {
return Err(InvalidTransaction::NonceTooLow { tx, state });
}
_ => {}
}
}
Ok(())
}
继续回到crates/handler/src/pre_execution.rs -> validate_against_state_and_deduct_caller\ 接着来到
let new_balance = calculate_caller_fee(*caller.balance(), tx, block, cfg)?;
caller.set_balance(new_balance);
跳转到具体实现
// crates/handler/src/pre_execution.rs
#[inline]
pub fn calculate_caller_fee(
balance: U256,
tx: impl Transaction,
block: impl Block,
cfg: impl Cfg,
) -> Result<U256, InvalidTransaction> {
let basefee = block.basefee() as u128;
let blob_price = block.blob_gasprice().unwrap_or_default();
let is_balance_check_disabled = cfg.is_balance_check_disabled();
if !is_balance_check_disabled {
tx.ensure_enough_balance(balance)?;
}
let effective_balance_spending = tx
.effective_balance_spending(basefee, blob_price)
.expect("effective balance is always smaller than max balance so it can't overflow");
let gas_balance_spending = effective_balance_spending - tx.value();
let mut new_balance = balance.saturating_sub(gas_balance_spending);
if is_balance_check_disabled {
new_balance = new_balance.max(tx.value());
}
Ok(new_balance)
}
前面都是获取参数,我们直接跳到 effective_balance_spending,并点进去看他实现\ 主要关注第一段,这里是估算 tx 会花费的最大费用\ gas_limit * max_fee + value + additional_gas_cost
// crates/context/interface/src/transaction.rs
#[inline]
fn effective_balance_spending(
&self,
base_fee: u128,
blob_price: u128,
) -> Result<U256, InvalidTransaction> {
let mut effective_balance_spending = (self.gas_limit() as u128)
.checked_mul(self.effective_gas_price(base_fee))
.and_then(|gas_cost| U256::from(gas_cost).checked_add(self.value()))
.ok_or(InvalidTransaction::OverflowPaymentInTransaction)?;
// add blob fee
if self.tx_type() == TransactionType::Eip4844 {
let blob_gas = self.total_blob_gas() as u128;
effective_balance_spending = effective_balance_spending
.checked_add(U256::from(blob_price.saturating_mul(blob_gas)))
.ok_or(InvalidTransaction::OverflowPaymentInTransaction)?;
}
Ok(effective_balance_spending)
}
跳转到 effective_gas_price\ Gas Price跟上一章的 validate_tx_env 差不多,这里就不细讲了.\ 可以对照着看下
// crates/context/interface/src/transaction.rs
fn effective_gas_price(&self, base_fee: u128) -> u128 {
if self.tx_type() == TransactionType::Legacy as u8
|| self.tx_type() == TransactionType::Eip2930 as u8
{
return self.gas_price();
}
// for EIP-1559 tx and onwards gas_price represents maximum price.
let max_price = self.gas_price();
let Some(max_priority_fee) = self.max_priority_fee_per_gas() else {
return max_price;
};
min(max_price, base_fee.saturating_add(max_priority_fee))
}
继续回到 calculate_caller_fee\ 逻辑都比较简单,就是将 caller 的 Balance 减去 Gas 费用并确保剩下的余额大于** tx.value**
let gas_balance_spending = effective_balance_spending - tx.value();
let mut new_balance = balance.saturating_sub(gas_balance_spending);
if is_balance_check_disabled {
new_balance = new_balance.max(tx.value());
}
Ok(new_balance)
继续回到 crates/handler/src/pre_execution.rs -> validate_against_state_and_deduct_caller
if tx.kind().is_call() {
caller.bump_nonce();
}
tx.kind 分为两种
跳转回 crates/handler/src/handler.rs -> pre_execution
self.load_accounts(evm)?;
直接跳到具体实现
pub fn load_accounts<
EVM: EvmTr<Precompiles: PrecompileProvider<EVM::Context>>,
ERROR: From<<<EVM::Context as ContextTr>::Db as Database>::Error>,
>(
evm: &mut EVM,
) -> Result<(), ERROR> {
let (context, precompiles) = evm.ctx_precompiles();
let gen_spec = context.cfg().spec();
let spec = gen_spec.clone().into();
// sets eth spec id in journal
context.journal_mut().set_spec_id(spec);
let precompiles_changed = precompiles.set_spec(gen_spec);
let empty_warmed_precompiles = context.journal_mut().precompile_addresses().is_empty();
if precompiles_changed || empty_warmed_precompiles {
// load new precompile addresses into journal.
// When precompiles addresses are changed we reset the warmed hashmap to those new addresses.
context
.journal_mut()
.warm_precompiles(precompiles.warm_addresses().collect());
}
// Load coinbase
// EIP-3651: Warm COINBASE. Starts the `COINBASE` address warm
if spec.is_enabled_in(SpecId::SHANGHAI) {
let coinbase = context.block().beneficiary();
context.journal_mut().warm_coinbase_account(coinbase);
}
// Load access list
let (tx, journal) = context.tx_journal_mut();
// legacy is only tx type that does not have access list.
if tx.tx_type() != TransactionType::Legacy {
if let Some(access_list) = tx.access_list() {
let mut map: HashMap<Address, HashSet<StorageKey>> = HashMap::default();
for item in access_list {
map.entry(*item.address())
.or_default()
.extend(item.storage_slots().map(|key| U256::from_be_bytes(key.0)));
}
journal.warm_access_list(map);
}
}
Ok(())
}
看着长, 但是功能都差不多.\ 加载 Precompiles 地址、CoinBase(矿工) 地址、 上一章提到的 AccessList 地址\ 直接看加载的源码\ 除了 Precompiles ,其他两个直接设置而已\ Precompiles 的初始地址是下面这样的形式
/// crates/context/src/journal/warm_addresses.rs
#[inline]
pub fn set_precompile_addresses(&mut self, addresses: HashSet<Address>) {
self.precompile_short_addresses.fill(false);
let mut all_short_addresses = true;
for address in addresses.iter() {
if let Some(short_address) = short_address(address) {
self.precompile_short_addresses.set(short_address, true);
} else {
all_short_addresses = false;
}
}
self.precompile_all_short_addresses = all_short_addresses;
self.precompile_set = addresses;
}
/// Set the coinbase address.
#[inline]
pub fn set_coinbase(&mut self, address: Address) {
self.coinbase = Some(address);
}
/// Set the access list.
#[inline]
pub fn set_access_list(&mut self, access_list: HashMap<Address, HashSet<StorageKey>>) {
self.access_list = access_list;
}
跳转回 crates/handler/src/handler.rs -> pre_execution
let gas = self.apply_eip7702_auth_list(evm)?;
这是对交易类型为 EIP7702 的处理.\ 不是 EIP7702 类型则直接跳过.
开始前介绍下 EIP7702\ 核心功能是让 ** EOA 账户** 能像智能合约一样,执行一些复杂逻辑.\ 原理是通过私钥签名,将 code 字段设置为一串特殊的字节码.指向一个委托的地址.\ 后续 EOA账户 可以选择通过这个委托地址进行其他的合约调用.\ 委托后的账户称为 智能合约账户, 委托的这个过程称为 账户抽象
直接跳转到函数实现.\ 这里是设置 EOA账户 为 智能合约账户,不是 Sender 通过委托地址调用合约.\ 再说明一点, 账户抽象 的账户不一定是 tx.caller(这里不区分tx.origin) 发出来的\ 可以由 想 账户抽象 的账户 签名,再由其他人广播出去.
// crates/handler/src/pre_execution.rs
#[inline]
pub fn apply_eip7702_auth_list<
CTX: ContextTr,
ERROR: From<InvalidTransaction> + From<<CTX::Db as Database>::Error>,
>(
context: &mut CTX,
) -> Result<u64, ERROR> {
let chain_id = context.cfg().chain_id();
let (tx, journal) = context.tx_journal_mut();
// Return if not EIP-7702 transaction.
if tx.tx_type() != TransactionType::Eip7702 {
return Ok(0);
}
apply_auth_list(chain_id, tx.authorization_list(), journal)
}
继续跳,下面的逻辑注释比较清晰,就不逐一解释,直接在代码里注释说明
// crates/handler/src/pre_execution.rs
#[inline]
pub fn apply_auth_list<
JOURNAL: JournalTr,
ERROR: From<InvalidTransaction> + From<<JOURNAL::Database as Database>::Error>,
>(
chain_id: u64,
auth_list: impl Iterator<Item = impl AuthorizationTr>,
journal: &mut JOURNAL,
) -> Result<u64, ERROR> {
let mut refunded_accounts = 0;
for authorization in auth_list {
// 1. 确认链 ID 为 0 或链的当前 ID。
let auth_chain_id = authorization.chain_id();
if !auth_chain_id.is_zero() && auth_chain_id != U256::from(chain_id) {
continue;
}
// 2. 验证 `nonce` 是否小于 `2**64 - 1`。
if authorization.nonce() == u64::MAX {
continue;
}
// 恢复权限和授权地址。
// 这里的authority指的就是要账户抽象的地址
// 因为不一定是tx的caller,所这里必须从签名中恢复
// 3. `authority = ecrecover(keccak(MAGIC || rlp([chain_id, address, nonce])), y_parity, r, s]`
let Some(authority) = authorization.authority() else {
continue;
};
// 4. 预热authority并检查nonce
// load_account_with_code_mut前面已经深入说过,加载account的信息
let mut authority_acc = journal.load_account_with_code_mut(authority)?;
let authority_acc_info = &authority_acc.account().info;
// 5. 验证 `authority` 的代码是否为空或已被委托。
if let Some(bytecode) = &authority_acc_info.code {
// if it is not empty and it is not eip7702
if !bytecode.is_empty() && !bytecode.is_eip7702() {
continue;
}
}
// 6. 验证 `authority` 的 nonce 是否等于 `nonce`。如果 `authority` 不存在于 trie 树中,则验证 `nonce` 是否等于 `0`。
// 这里authorization.nonce就是authority这个原本eoa账户的nonce
// 如果sender和authority相同,sender的nonce就是authority的nonce
if authorization.nonce() != authority_acc_info.nonce {
continue;
}
// 7. 如果 trie 中存在 `authority`,则将 `PER_EMPTY_ACCOUNT_COST - PER_AUTH_BASE_COST` gas 添加到全局退款计数器。
// 这里是计算退款,EIP7702规定,如果authority不是空账户,则退还部分auth退款
// auth的费用计算在crates/interpreter/src/gas/calc.rs
// ->calculate_initial_tx_gas 中,在上一章节讲过了
if !(authority_acc_info.is_empty()
&& authority_acc
.account()
.is_loaded_as_not_existing_not_touched())
{
refunded_accounts += 1;
}
// 8. 将 `authority` 的代码设置为 `0xef0100 || address`。这是一个委托指定。
// * 特殊情况下,如果 `address` 为`0x000000000000000000000000000000000000000000`,则不写入指定。
// 清除帐户代码并将帐户代码哈希重置为空哈希 `0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470`。
// 9. 将 `authority` 的 nonce 值加 1。
authority_acc.delegate(authorization.address());
}
let refunded_gas =
refunded_accounts * (eip7702::PER_EMPTY_ACCOUNT_COST - eip7702::PER_AUTH_BASE_COST);
Ok(refunded_gas)
} 如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!