REVM源码阅读-流程(5)

PostExecution的逻辑比较简单,主要用于计算gas返还和支付矿工奖励所以这一章合并PostExecution和ExecutionResultPostExecutionPostExecution用前面三个阶段运行的结果做为输入.再回忆下返回值init_andfloor

PostExecution 的逻辑比较简单,主要用于计算gas返还和支付矿工奖励\ 所以这一章合并 PostExecutionExecutionResult

PostExecution

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

    • output 返回数据
    • gas_used 实际消耗gas(不包含 refund)
    • gas_refunded 可退还gas
    • logs 事件日志
    • created_address 如果是CREATE,则是新合约地址
  • Revert

    • output 失败原因
  • Halt

    • reason 异常终止原因
// 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

直接跳到 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

跳到 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

跳到 reimburse_caller 实现\ 前面只是计算已花费和返还的设置,还没实际返还给Caller\ 这里才是将 待返还的Gas 加到Caller账户的地方\ additional_refund 这里在当前EVM版本是** 0**\ effective_gas_price 和之前提到过的公式一样:

  • GasPrice = Priority Fee + min(Base Fee, Max Fee - Base Fee)

后面的逻辑就简单.由内到外一层一层\ 全部返还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(())
}

reward_beneficiary

支付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(())
}

ExecutionResult

core::mem::replace(evm.ctx().error(), Ok(())),检查之前的执行是否有错误

  • DB 错误 → 直接返回 Err
  • 如果是自定义错误 → 转成 Self::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)
    }

post_execution::output

对执行结果返回值进行处理,没有啥需要特别讲的,跳过了

// 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:?}"
            )
        }
    }
}

evm.ctx().journal_mut().commit_tx()

这里名字听起来比较误导人,听起来像是把本次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(); 也只是清空.\ 那真正提交更改,保存到数据库的逻辑在哪呢?

evm.ctx().journal_mut().finalize

我们回到第一篇文章.\ 跳到 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
}

evm.ctx().db_mut().commit

跳回到 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 ,直接跳到 AlloyCacheDBcommit 的实现.\ 没啥说的,感兴趣的自己了解下吧

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())),
            );
        }
    }
}
点赞 0
收藏 0
分享

0 条评论

请先 登录 后评论
唐好酸
唐好酸
江湖只有他的大名,没有他的介绍。