PostExecution的逻辑比较简单,主要用于计算gas返还和支付矿工奖励所以这一章合并PostExecution和ExecutionResultPostExecutionPostExecution用前面三个阶段运行的结果做为输入.再回忆下返回值init_andfloor
PostExecution 的逻辑比较简单,主要用于计算gas返还和支付矿工奖励\ 所以这一章合并 PostExecution 和 ExecutionResult
PostExecution 用前面三个阶段运行的结果做为输入.\ 再回忆下返回值\ init_and_floor_gas: Validate的返回值
init_gas 交易固有的、最基本的 gas 消耗,无论交易执行什么内容,都必须扣除
floor_gas 交易必须预留的最低 gas 量,防止 gas_limit 太低导致交易根本无法执行\ eip7702_gas_refund: PreExecute的返回值, 是 EIP7702 的gas退还,如果被委托的地址是之前在 数据库中就存在的地址而不是新地址,则返还部分gas.\ exec_result: execution阶段的返回值,是整个交易执行的核心输出.
Success
Revert
Halt
// crates/handler/src/handler.rs
#[inline]
fn post_execution(
&self,
evm: &mut Self::Evm,
exec_result: &mut FrameResult,
init_and_floor_gas: InitialAndFloorGas,
eip7702_gas_refund: i64,
) -> Result<(), Self::Error> {
// Calculate final refund and add EIP-7702 refund to gas.
self.refund(evm, exec_result, eip7702_gas_refund);
// Ensure gas floor is met and minimum floor gas is spent.
// if `cfg.is_eip7623_disabled` is true, floor gas will be set to zero
self.eip7623_check_gas_floor(evm, exec_result, init_and_floor_gas);
// Return unused gas to caller
self.reimburse_caller(evm, exec_result)?;
// Pay transaction fees to beneficiary
self.reward_beneficiary(evm, exec_result)?;
Ok(())
}
直接跳到 refund 实现\ 函数是计算 Gas 返还值,并确保返还值不能超过指定比例
pub fn refund(spec: SpecId, gas: &mut Gas, eip7702_refund: i64) {
// 内部实现是self.refunded += refund,不展开了
gas.record_refund(eip7702_refund);
gas.set_final_refund(spec.is_enabled_in(SpecId::LONDON));
}
跳到 set_final_refund 的实现\ 这里是设置最大允许退款不能超过指定值\ london升级前最大是 已花费(spent) 的50%,之后不能超过20%
#[inline]
pub fn set_final_refund(&mut self, is_london: bool) {
let max_refund_quotient = if is_london { 5 } else { 2 };
//
self.refunded = (self.refunded() as u64).min(self.spent() / max_refund_quotient) as i64;
}
跳到 eip7623_check_gas_floor 实现
spent_sub_refunded的名字比较明显, spent_sub_refunded = spent - refunded\ 已花费减去退还的结果就是实际花费的Gas\ 这里是确保最低花费要大于floor_gas(地板值),相当于饭店的最低消费\ 是为了防止节点被DOS攻击,被发送大量低消费Gas的交易
// crates/handler/src/post_execution.rs
pub fn eip7623_check_gas_floor(gas: &mut Gas, init_and_floor_gas: InitialAndFloorGas) {
// spent_sub_refunded = spent - refunded
// 也就是实际花费的gas,确保实际花费的gas要与
if gas.spent_sub_refunded() < init_and_floor_gas.floor_gas {
gas.set_spent(init_and_floor_gas.floor_gas);
gas.set_refund(0);
}
}
跳到 reimburse_caller 实现\ 前面只是计算已花费和返还的设置,还没实际返还给Caller\ 这里才是将 待返还的Gas 加到Caller账户的地方\ additional_refund 这里在当前EVM版本是** 0**\ effective_gas_price 和之前提到过的公式一样:
后面的逻辑就简单.由内到外一层一层\ 全部返还GasLimit = (gas.remaining() + gas.refunded() as u64)\ saturating_mul 是相乘\ context.journal_mut().load_account_mut(caller)?.incr_balance 是找到 caller 并增加余额
// crates/handler/src/post_execution.rs
#[inline]
pub fn reimburse_caller<CTX: ContextTr>(
context: &mut CTX,
gas: &Gas,
additional_refund: U256,
) -> Result<(), <CTX::Db as Database>::Error> {
let basefee = context.block().basefee() as u128;
let caller = context.tx().caller();
let effective_gas_price = context.tx().effective_gas_price(basefee);
context
.journal_mut()
.load_account_mut(caller)?
.incr_balance(
U256::from(
effective_gas_price
.saturating_mul((gas.remaining() + gas.refunded() as u64) as u128),
) + additional_refund,
);
Ok(())
}
支付tx的 Gas费用 给矿工\ 这里是tx Gas费用 的Priority奖励,不是挖矿奖励\ EIP-1559 之前,矿工可以吃用户设置的GasPrice的全部.\ 在EIP-1559之后,矿工只能吃 PriorityFee的奖励,BaseFee的部分要销毁\ 这里的难点主要是 EIP-1559,其他的逻辑都比较简单
// crates/handler/src/post_execution.rs
#[inline]
pub fn reward_beneficiary<CTX: ContextTr>(
context: &mut CTX,
gas: &Gas,
) -> Result<(), <CTX::Db as Database>::Error> {
let (block, tx, cfg, journal, _, _) = context.all_mut();
let basefee = block.basefee() as u128;
let effective_gas_price = tx.effective_gas_price(basefee);
// Transfer fee to coinbase/beneficiary.
// EIP-1559 discard basefee for coinbase transfer. Basefee amount of gas is discarded.
let coinbase_gas_price = if cfg.spec().into().is_enabled_in(SpecId::LONDON) {
effective_gas_price.saturating_sub(basefee)
} else {
effective_gas_price
};
// reward beneficiary
journal
.load_account_mut(block.beneficiary())?
.incr_balance(U256::from(coinbase_gas_price * gas.used() as u128));
Ok(())
}
core::mem::replace(evm.ctx().error(), Ok(())),检查之前的执行是否有错误
// crates/handler/src/handler.rs
fn execution_result(
&mut self,
evm: &mut Self::Evm,
result: <<Self::Evm as EvmTr>::Frame as FrameTr>::FrameResult,
) -> Result<ExecutionResult<Self::HaltReason>, Self::Error> {
match core::mem::replace(evm.ctx().error(), Ok(())) {
Err(ContextError::Db(e)) => return Err(e.into()),
Err(ContextError::Custom(e)) => return Err(Self::Error::from_string(e)),
Ok(()) => (),
}
let exec_result = post_execution::output(evm.ctx(), result);
// commit transaction
evm.ctx().journal_mut().commit_tx();
evm.ctx().local_mut().clear();
evm.frame_stack().clear();
Ok(exec_result)
}
对执行结果返回值进行处理,没有啥需要特别讲的,跳过了
// crates/handler/src/post_execution.rs
pub fn output<CTX: ContextTr<Journal: JournalTr>, HALTREASON: HaltReasonTr>(
context: &mut CTX,
// TODO, make this more generic and nice.
// FrameResult should be a generic that returns gas and interpreter result.
result: FrameResult,
) -> ExecutionResult<HALTREASON> {
// Used gas with refund calculated.
let gas_refunded = result.gas().refunded() as u64;
let gas_used = result.gas().used();
let output = result.output();
let instruction_result = result.into_interpreter_result();
// take logs from journal.
let logs = context.journal_mut().take_logs();
match SuccessOrHalt::<HALTREASON>::from(instruction_result.result) {
SuccessOrHalt::Success(reason) => ExecutionResult::Success {
reason,
gas_used,
gas_refunded,
logs,
output,
},
SuccessOrHalt::Revert => ExecutionResult::Revert {
gas_used,
output: output.into_data(),
},
SuccessOrHalt::Halt(reason) => {
// Bubble up precompile errors from context when available
if matches!(
instruction_result.result,
interpreter::InstructionResult::PrecompileError
) {
if let Some(message) = context.local_mut().take_precompile_error_context() {
return ExecutionResult::Halt {
reason: HALTREASON::from(HaltReason::PrecompileErrorWithContext(message)),
gas_used,
};
}
}
ExecutionResult::Halt { reason, gas_used }
}
// Only two internal return flags.
flag @ (SuccessOrHalt::FatalExternalError | SuccessOrHalt::Internal(_)) => {
panic!(
"Encountered unexpected internal return flag: {flag:?} with instruction result: {instruction_result:?}"
)
}
}
}
这里名字听起来比较误导人,听起来像是把本次tx的运行结果提交保存到数据库\ 但其实这里主要是为了下一次tx的做准备\ 整个函数并没有提交写入的部分,只有清空和重置.
// crates/context/src/journal/inner.rs
pub fn commit_tx(&mut self) {
// Clears all field from JournalInner. Doing it this way to avoid
// missing any field.
let Self {
state,
transient_storage,
logs,
depth,
journal,
transaction_id,
spec,
warm_addresses,
} = self;
let _ = spec;
let _ = state;
transient_storage.clear();
*depth = 0;
journal.clear();
warm_addresses.clear_coinbase_and_access_list();
*transaction_id += 1;
logs.clear();
}
后面的 evm.ctx().local_mut().clear(); 和 evm.frame_stack().clear(); 也只是清空.\ 那真正提交更改,保存到数据库的逻辑在哪呢?
我们回到第一篇文章.\ 跳到 examples/erc20_gas/src/exec.rs ->transact_erc20evm.\ 在这里会对 Erc20MainnetHandler::new().run 的结果进行处理
pub fn transact_erc20evm<EVM>(
evm: &mut EVM,
) -> Result<(ExecutionResult<HaltReason>, EvmState), Erc20Error<EVM::Context>>
where
EVM: EvmTr<
Context: ContextTr<Journal: JournalTr<State = EvmState>>,
Precompiles: PrecompileProvider<EVM::Context, Output = InterpreterResult>,
Instructions: InstructionProvider<
Context = EVM::Context,
InterpreterTypes = EthInterpreter,
>,
Frame = EthFrame<EthInterpreter>,
>,
{
Erc20MainnetHandler::new().run(evm).map(|r| {
let state = evm.ctx().journal_mut().finalize();
(r, state)
})
}
直接跳到 finalize 的实现\ 还是再进行了一遍清空重置操作,不同的是返回了 state, transaction_id 也为0\ commit_tx 是每个tx结束都要执行一遍,并为下一个tx做准备.此时数据还保存在内存中\ finalize 是当前块执行完了所有的tx后调用的
//
#[inline]
pub fn finalize(&mut self) -> EvmState {
let Self {
state,
transient_storage,
logs,
depth,
journal,
transaction_id,
spec,
warm_addresses,
} = self;
let _ = spec;
warm_addresses.clear_coinbase_and_access_list();
let state = mem::take(state);
logs.clear();
transient_storage.clear();
journal.clear();
*depth = 0;
*transaction_id = 0;
state
}
跳回到 examples/erc20_gas/src/exec.rs -> transact_erc20evm_commit\ 这里会对 transact_erc20evm 的返回结果进行处理.
// examples/erc20_gas/src/exec.rs
pub fn transact_erc20evm_commit<EVM>(
evm: &mut EVM,
) -> Result<ExecutionResult<HaltReason>, Erc20Error<EVM::Context>>
where
EVM: EvmTr<
Context: ContextTr<Journal: JournalTr<State = EvmState>, Db: DatabaseCommit>,
Precompiles: PrecompileProvider<EVM::Context, Output = InterpreterResult>,
Instructions: InstructionProvider<
Context = EVM::Context,
InterpreterTypes = EthInterpreter,
>,
Frame = EthFrame<EthInterpreter>,
>,
{
transact_erc20evm(evm).map(|(result, state)| {
evm.ctx().db_mut().commit(state);
result
})
}
我们点进去看下 commit\ 跳到的是 DatabaseCommit\ 这里先说明下, 这里可以是任何数据库,只要它实现了 EVM 所需的各种 Trait\ 因为 EVM 只负责TX的执行,不负责数据库的持久化部分.\ 数据库由调用 EVM 进行TX执行的一方提供\ 例如 RETH 调用 REVM ,数据库就由 RETH 提供\ 我们现在看的 Example 例子中的 erc20_gas 调用 EVM,就由 erc20_gas 提供
// crates/database/interface/src/lib.rs
#[auto_impl(&mut, Box)]
pub trait DatabaseCommit {
/// Commit changes to the database.
fn commit(&mut self, changes: HashMap<Address, Account>);
fn commit_iter(&mut self, changes: impl IntoIterator<Item = (Address, Account)>) {
let changes: HashMap<Address, Account> = changes.into_iter().collect();
self.commit(changes);
}
}
回到 examples/erc20_gas/src/main.rs -> transfer\ with_db(cache_db) 这里就是传入设置 db 的地方.
// examples/erc20_gas/src/main.rs
// type AlloyCacheDB = CacheDB<WrapDatabaseAsync<AlloyDB<Ethereum, DynProvider>>>;
fn transfer(from: Address, to: Address, amount: U256, cache_db: &mut AlloyCacheDB) -> Result<()> {
let mut ctx = Context::mainnet()
.with_db(cache_db)
.modify_cfg_chained(|cfg| {
cfg.spec = SpecId::CANCUN;
})
.with_tx(
TxEnv::builder()
.caller(from)
.kind(TxKind::Call(to))
.value(amount)
.gas_price(2)
.build()
.unwrap(),
)
.modify_block_chained(|b| {
b.basefee = 1;
})
.build_mainnet();
transact_erc20evm_commit(&mut ctx).unwrap();
Ok(())
}
它使用的 AlloyCacheDB ,直接跳到 AlloyCacheDB 中 commit 的实现.\ 没啥说的,感兴趣的自己了解下吧
impl<ExtDB> DatabaseCommit for CacheDB<ExtDB> {
fn commit(&mut self, changes: HashMap<Address, Account>) {
for (address, mut account) in changes {
if !account.is_touched() {
continue;
}
if account.is_selfdestructed() {
let db_account = self.cache.accounts.entry(address).or_default();
db_account.storage.clear();
db_account.account_state = AccountState::NotExisting;
db_account.info = AccountInfo::default();
continue;
}
let is_newly_created = account.is_created();
self.insert_contract(&mut account.info);
let db_account = self.cache.accounts.entry(address).or_default();
db_account.info = account.info;
db_account.account_state = if is_newly_created {
db_account.storage.clear();
AccountState::StorageCleared
} else if db_account.account_state.is_storage_cleared() {
// Preserve old account state if it already exists
AccountState::StorageCleared
} else {
AccountState::Touched
};
db_account.storage.extend(
account
.storage
.into_iter()
.map(|(key, value)| (key, value.present_value())),
);
}
}
} 如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!