前言Context用于提供本次交易执行过程中的全局上下文.包括本次TX信息、当前区块配置、状态变更日志、外部DB交互.Context直接跳到结构体定义部分.derive_where宏之前说过了.就是为Context分别派生Debug和Clone.block:区块环境,就是
Context 用于提供本次交易执行过程中的全局上下文.
包括本次TX信息、当前区块配置、状态变更日志、外部DB交互.
直接跳到结构体定义部分.
derive_where 宏之前说过了.就是为 Context 分别派生 Debug 和Clone.
// crates/context/src/context.rs
#[derive_where(Clone, Debug; BLOCK, CFG, CHAIN, TX, DB, JOURNAL, <DB as Database>::Error, LOCAL)]
pub struct Context<
BLOCK = BlockEnv,
TX = TxEnv,
CFG = CfgEnv,
DB: Database = EmptyDB,
JOURNAL: JournalTr<Database = DB> = Journal<DB>,
CHAIN = (),
LOCAL: LocalContextTr = LocalContext,
> {
pub block: BLOCK,
pub tx: TX,
pub cfg: CFG,
pub journaled_state: JOURNAL,
pub chain: CHAIN,
pub local: LOCAL,
pub error: Result<(), ContextError<DB::Error>>,
}
继续看实现部分.基本上都很简单.就不贴函数具体内容了.
Context 就是起一个管理作用,配置和操作都是由他的成员来提供.
all 和 all_mut 的区别是 all_mut 后面三个返回值是 可变引用 &mut
// crates/context/src/context.rs
#[inline]
fn all(&self,) -> (&Self::Block,&Self::Tx,&Self::Cfg,&Self::Db,&Self::Journal,&Self::Chain,&Self::Local,
) {}
#[inline]
fn all_mut(&self,) -> (&Self::Block,&Self::Tx,&Self::Cfg,&Self::Db,&mut Self::Journal,&mut Self::Chain,&mut Self::Local,
) {}
#[inline]
fn error(&mut self) -> &mut Result<(), ContextError<<Self::Db as Database>::Error>> {
&mut self.error
}
fn set_tx(&mut self, tx: Self::Tx) {
self.tx = tx;
}
fn set_block(&mut self, block: Self::Block) {
self.block = block;
}
后面的函数也很简单.都是 New 实例化部分,或者对成员字段内部函数的包装.
就不贴了.感兴趣的直接去看下.
讲一下里面的 selfdestruct 里面的 cold_path.
点进起来看实现,是一个空函数.不过加了 #[cold] 宏.
这是用于编译器的.它是告诉 编译器/CPU ,这段代码极少会被执行到.将它放到生成的二进制文件相对偏僻的位置.
这样CPU在加载执行的时候可以只加载执行正常流程,提高运行速度.
pub fn new(db: DB, spec: SPEC) -> Self {
let mut journaled_state = JOURNAL::new(db);
journaled_state.set_spec_id(spec.into());
Self {
tx: TX::default(),
block: BLOCK::default(),
cfg: CfgEnv {
spec,
..Default::default()
},
local: LOCAL::default(),
journaled_state,
chain: Default::default(),
error: Ok(()),
}
}
#[inline]
fn selfdestruct(
&mut self,
address: Address,
target: Address,
skip_cold_load: bool,
) -> Result<StateLoad<SelfDestructResult>, LoadError> {
self.journal_mut()
.selfdestruct(address, target, skip_cold_load)
.map_err(|e| {
cold_path();
let (ret, err) = e.into_parts();
if let Some(err) = err {
*self.error() = Err(err.into());
}
ret
})
}
// crates/primitives/src/hints_util.rs
#[cold]
pub fn cold_path() {}
上面讲到 Block 是区块环境,就是打包这次TX的Block配置.
EVM 只负责执行部分,没有保存 Block 信息.
所以 Block 信息都是由外部提供, 例如 ETH.
我们看下结构体
RANDAO mix 值,可用于链上生成随机数.blob gas 和 blob gas 价格,
blob 主要是 L2 在用, 这里就不深入了pub struct BlockEnv {
pub number: U256,
pub beneficiary: Address,
pub timestamp: U256,
pub gas_limit: u64,
pub basefee: u64,
pub difficulty: U256,
pub prevrandao: Option<B256>,
pub blob_excess_gas_and_price: Option<BlobExcessGasAndPrice>,
}
因为只是保存外部提供的数据,并提供给 EVM 使用.
所以并没有什么复杂的操作. 实现的部分都是提供 Setter 供外部调用.
#[inline]
fn number(&self) -> U256 {
self.number
}
#[inline]
fn beneficiary(&self) -> Address {
self.beneficiary
}
#[inline]
fn timestamp(&self) -> U256 {
self.timestamp
}
#[inline]
fn gas_limit(&self) -> u64 {
self.gas_limit
}
#[inline]
fn basefee(&self) -> u64 {
self.basefee
}
#[inline]
fn difficulty(&self) -> U256 {
self.difficulty
}
#[inline]
fn prevrandao(&self) -> Option<B256> {
self.prevrandao
}
#[inline]
fn blob_excess_gas_and_price(&self) -> Option<BlobExcessGasAndPrice> {
self.blob_excess_gas_and_price
}
上面讲到 tx 包含这次交易的所有信息.
跟 Block 一样也是由外部提供信息. EVM 只负责执行.
TX 部分的内容看起来比 Block 多
原因就是 EVM 出现得早, 补丁打得多, 历史包袱重,得分别处理每次升级带来的变化.
简单来说就是 老块老办法, 新块新办法
TX 部分的内容分为两段.
TxEnv, 负责返回tx的数据,相当于 Getter 部分TxEnvBuilder, 负责构建 Tx ,相当于 Setter 部分先看下 TxEnv
tx_type tx类型
Legacy Eip2930Eip1559Eip4844Eip7702Customcaller 本次tx的发起者、tx的签名人gas_limit tx的gas最大使用限制gas_price gas的每单位价格.EIP-1559之后表示max_gas_fee.kind 是 Create 交易(部署合约)还是 Call (调用)交易,如果是 Call 交易, 会包含 tovalue 本次交易发送的valuedata 本次交易发送到to的数据nonce EOA账户的Nonce,等于发送上链的交易数chain_id 链ID,用于防止重放攻击access_list access列表,用于提前预热地址列表,后面访问可以减少Gas消耗gas_priority_fee Gas优先费用,用于贿赂矿工提前打包txblob_hashes blob的版本号哈希列表max_fee_per_blob_gas 本次交易中的每个 blob gas 愿意支付的最大费用authorization_list 授权列表,用于授权EOA账户成智能合约账户#[derive(Clone, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct TxEnv {
pub tx_type: u8,
pub caller: Address,
pub gas_limit: u64,
pub gas_price: u128,
pub kind: TxKind,
pub value: U256,
pub data: Bytes,
pub nonce: u64,
pub chain_id: Option<u64>,
pub access_list: AccessList,
pub gas_priority_fee: Option<u128>,
pub blob_hashes: Vec<B256>,
pub max_fee_per_blob_gas: u128,
pub authorization_list: Vec<Either<SignedAuthorization, RecoveredAuthorization>>,
}
大部分实现都一目了然,只讲个特定的.
pub fn derive_tx_type(&mut self) 根据 Tx 中的字段来设置 tx_type
这里 EIP-2930 和 EIP-1559 的判断会让人有些迷惑.不应该是先 EIP-1159 再到 EIP-2930吗
但现实是 EIP-2930 比 EIP-1559 的提案要早.
Eip2930 的交易类型并不包含gas_priority_fee.
其他没啥讲的, TxEnvBuilder 也是.
pub fn derive_tx_type(&mut self) -> Result<(), DeriveTxTypeError> {
if !self.access_list.0.is_empty() {
self.tx_type = TransactionType::Eip2930 as u8;
}
if self.gas_priority_fee.is_some() {
self.tx_type = TransactionType::Eip1559 as u8;
}
if !self.blob_hashes.is_empty() || self.max_fee_per_blob_gas > 0 {
if let TxKind::Call(_) = self.kind {
self.tx_type = TransactionType::Eip4844 as u8;
return Ok(());
} else {
return Err(DeriveTxTypeError::MissingTargetForEip4844);
}
}
if !self.authorization_list.is_empty() {
if let TxKind::Call(_) = self.kind {
self.tx_type = TransactionType::Eip7702 as u8;
return Ok(());
} else {
return Err(DeriveTxTypeError::MissingTargetForEip7702);
}
}
Ok(())
}
这里用来设置或获取 EVM 配置. 也是由外部传入设置.
内容比较简单,就不详细展开.简单说下各个属性的作用.
带 blob 的主要是 L2 在用.
chain_id 链ID,每个 EVM 链有自己的chain id,用于防止重放攻击.tx_chain_id_check 配置是否检查 chain_id spec 当前硬分叉版本limit_contract_code_size 合约代码大小限制limit_contract_initcode_size 合约初始化代码大小限制disable_nonce_check 禁用 tx 中的 nonce 检查.max_blobs_per_tx tx中blobs最大限制blob_base_fee_update_fraction Blob 基本费用更新分数tx_gas_limit_cap 限制单笔交易的Gas Limitmemory_limit 内存限制,当前 ETH主网 默认无限制.disable_balance_check 禁用余额检查,测试使用.disable_block_gas_limit 禁用区块 gas limit 检查disable_eip3541 (EIP-3541: 禁止以 0xEF 字节开头的新合约部署)disable_eip3607 (EIP-3607:禁止部署了合约的地址作为交易的发起者)disable_eip7623 (EIP-7623: calldata 非零字节成本从 16 gas -> 更高)disable_base_fee 禁用 BaseFeedisable_priority_fee_check 禁用 PriorityFeedisable_fee_charge 禁用费用收取.相当于 免Gas 交易,交易不扣 Gas// crates/context/src/cfg.rs
pub struct CfgEnv<SPEC = SpecId> {
pub chain_id: u64,
pub tx_chain_id_check: bool,
pub spec: SPEC,
pub limit_contract_code_size: Option<usize>,
pub limit_contract_initcode_size: Option<usize>,
pub disable_nonce_check: bool,
pub max_blobs_per_tx: Option<u64>,
pub blob_base_fee_update_fraction: Option<u64>,
pub tx_gas_limit_cap: Option<u64>,
pub memory_limit: u64,
pub disable_balance_check: bool,
pub disable_block_gas_limit: bool,
pub disable_eip3541: bool,
pub disable_eip3607: bool,
pub disable_eip7623: bool,
pub disable_base_fee: bool,
pub disable_priority_fee_check: bool,
pub disable_fee_charge: bool,
}
REVM 只负责执行部分, 自然没有持久化保存数据的部分,也就是没有 DB.
REVM 只提供 Trait 约束, 然后由外部实现并提供给 REVM.
REVM 在交易执行完成后可以选择是否通过接口提交到外部数据库.
这一节就只讲下设计到 DB 相关的 Trait.
后续有时间可以讲下 AlloyDb 和 CacheDb, 它们是用来 Fork 主网数据.
使用主网数据,模拟交易在主网环境下运行的结果.
basic 获取指定地址的基本信息.不区分 EOA 和 合约账户.
balance 余额nonce code_hash 如果是EOA账户,是个固定值.如果是合约账户,是代码的哈希account_id REVM 内部实现,不是 ETH 协议层面的东西.code 账户字节码(为 None 才用 code_hash 去获取)code_by_hash 根据 code_hash 获取对应的合约字节码storage 获取地址在 index 的值storage_by_account_id 通过 account_id 获取storage,account_id 为空则走storage函数block_hash 获取指定 block 的 hash// crates/database/interface/src/lib.rs
pub trait Database {
type Error: DBErrorMarker;
fn basic(&mut self, address: Address) -> Result<Option<AccountInfo>, Self::Error>;
fn code_by_hash(&mut self, code_hash: B256) -> Result<Bytecode, Self::Error>;
fn storage(&mut self, address: Address, index: StorageKey)
-> Result<StorageValue, Self::Error>;
fn storage_by_account_id(
&mut self,
address: Address,
account_id: usize,
storage_key: StorageKey,
) -> Result<StorageValue, Self::Error> {
let _ = account_id;
self.storage(address, storage_key)
}
/// Gets block hash by block number.
fn block_hash(&mut self, number: u64) -> Result<B256, Self::Error>;
}
两者不同的只是传入的参数不通. commit_iter 还是转换参数类型后还是调用的 commit
// crates/database/interface/src/lib.rs
#[auto_impl(&mut, Box)]
pub trait DatabaseCommit {
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);
}
}
DatabaseRef 和 Database Trait 的函数功能是一样的.
DatabaseRef 函数的第一个参数是&self, Database 是可变引用 &mut self.
名字后面加了_ref.
至于为什么这样做.接下来会说.
继续往下看, WrapDatabaseRef.
对实现了 DatabaseRef Trait 的 泛型T 添加一层外包装.
一直没懂这个包装 WrapDatabaseRef 的使用场景在哪.
唯一的调用是 context 中的 with_ref_db.
这里加了外包装算是对 Database 做降级处理.
本来调用函数的都是 可变引用 &mut , 现在降级成引用 &.
可能是为了兼容, Rust Trait 系统不允许 &mut self方法直接调用 &self 方法
加个中间层,你想用哪个用哪个
#[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct WrapDatabaseRef<T: DatabaseRef>(pub T);
impl<F: DatabaseRef> From<F> for WrapDatabaseRef<F> {
#[inline]
fn from(f: F) -> Self {
WrapDatabaseRef(f)
}
}
impl<T: DatabaseRef> Database for WrapDatabaseRef<T> {
type Error = T::Error;
#[inline]
fn basic(&mut self, address: Address) -> Result<Option<AccountInfo>, Self::Error> {
self.0.basic_ref(address)
}
#[inline]
fn code_by_hash(&mut self, code_hash: B256) -> Result<Bytecode, Self::Error> {
self.0.code_by_hash_ref(code_hash)
}
#[inline]
fn storage(
&mut self,
address: Address,
index: StorageKey,
) -> Result<StorageValue, Self::Error> {
self.0.storage_ref(address, index)
}
#[inline]
fn block_hash(&mut self, number: u64) -> Result<B256, Self::Error> {
self.0.block_hash_ref(number)
}
}
其他的都是差不多的 Trait ,跳过了.
直接看 DatabaseCommitExt 部分.
两个函数逻辑差不多,简单讲下 drain_balances
addresses_iter.size_hint 返回原组,左边是最小长度.
core::mem::take 取出原有的值,并替换为类型的默认值(相当于置零).
pub trait DatabaseCommitExt: Database + DatabaseCommit {
fn increment_balances(
&mut self,
balances: impl IntoIterator<Item = (Address, u128)>,
) -> Result<(), Self::Error> {
// Make transition and update cache state
let transitions = balances
.into_iter()
.map(|(address, balance)| {
let mut original_account = match self.basic(address)? {
Some(acc_info) => Account::from(acc_info),
None => Account::new_not_existing(0),
};
original_account.info.balance = original_account
.info
.balance
.saturating_add(U256::from(balance));
original_account.mark_touch();
Ok((address, original_account))
})
// Unfortunately must collect here to short circuit on error
.collect::<Result<Vec<_>, _>>()?;
self.commit_iter(transitions);
Ok(())
}
fn drain_balances(
&mut self,
addresses: impl IntoIterator<Item = Address>,
) -> Result<Vec<u128>, Self::Error> {
// Make transition and update cache state
let addresses_iter = addresses.into_iter();
let (lower, _) = addresses_iter.size_hint();
let mut transitions = Vec::with_capacity(lower);
let balances = addresses_iter
.map(|address| {
let mut original_account = match self.basic(address)? {
Some(acc_info) => Account::from(acc_info),
None => Account::new_not_existing(0),
};
let balance = core::mem::take(&mut original_account.info.balance);
original_account.mark_touch();
transitions.push((address, original_account));
Ok(balance.try_into().unwrap())
})
.collect::<Result<Vec<_>, _>>()?;
self.commit_iter(transitions);
Ok(balances)
}
}
LocalContext 负责的内容蛮简单的.就两个功能:
提供一大块内存作为共享供 Frame 使用.只负责提供,不负责管理.
提供接口让 OpCode 在执行过程中保存 调用预编译合约 时的错误.
至于为什么不和普通 OpCode 执行一样,放到内存中进行返回?
是因为 预编译合约 和 OpCode 属于两个不同层面的东西. OpCode 由EVM 中 解释器 运行执行的,有完整的Stack、Memory、Return Data.
而 Precompile 相当于直接调用 Rust 函数. 这些函数并不是访问当前 Frame 的状态.只能根据传入的值进行计算.自然也无法访问内存,将错误信息写入到内存中的返回数据中.
看下 LocalContext 的属性和实现.
非常简单,就不多解释了.
Rc<RefCell<Vec<u8>>> 前面说过,就是让 Vec<u8> 可以被多个对象持有并修改.
pub struct LocalContext {
pub shared_memory_buffer: Rc<RefCell<Vec<u8>>>,
pub precompile_error_message: Option<String>,
}
impl Default for LocalContext {
fn default() -> Self {
Self {
shared_memory_buffer: Rc::new(RefCell::new(Vec::with_capacity(1024 * 4))),
precompile_error_message: None,
}
}
}
impl LocalContextTr for LocalContext {
fn clear(&mut self) {
unsafe { self.shared_memory_buffer.borrow_mut().set_len(0) };
self.precompile_error_message = None;
}
fn shared_memory_buffer(&self) -> &Rc<RefCell<Vec<u8>>> {
&self.shared_memory_buffer
}
fn set_precompile_error_context(&mut self, output: String) {
self.precompile_error_message = Some(output);
}
fn take_precompile_error_context(&mut self) -> Option<String> {
self.precompile_error_message.take()
}
}
impl LocalContext {
pub fn new() -> Self {
Self::default()
}
}
在 EVM 执行过程中,会产生很多临时状态变更.例如 NonceChange、BalanceTransfer、BalanceChange、CodeChange等待.
这些临时状态不会马上存储到外部永久数据库中,而是结束后再统一 Commit.
在上节中, 我们讲了 Database 的部分,但是Context并不直接持有 Database 的实例.而是由 Journal 持有并管理.
Journal 相当于 REVM 和 ETH 之间的缓存层,缓存从外部 DB 读来的数据.
缓存将来要写入 DB 的数据.
看下 Journal 的结构体.
很简单,就两个成员变量,一个 database, 一个 inner.
database 是持有的外部数据库引用,用来获取和保存链上数据.
inner 是 Journal 逻辑的真正部分, Journal 的大部分操作都是对 inner 的一层封装.
// crates/context/interface/src/journaled_state/entry.rs
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Journal<DB, ENTRY = JournalEntry>
where
ENTRY: JournalEntryTr,
{
pub database: DB,
pub inner: JournalInner<ENTRY>,
}
直接跳转到 JournalInner 的部分.
解释下各个变量的作用.
state 缓存当前交易已 Touched 过 或者 修改过 的账户transient_storage 瞬态存储.和内存的作用很像,都是临时存储.不同的是内存 不能跨Frame 访问,Frame 结束就删除. 而transient storage是所有 Frame 可访问,生命周期在整个交易内.logs 交易执行过程,产生的所有事件Log.包含所有 Frame 的,也就是所有的合约调用.depth 当前调用 Frame 的深度,和 Frame 中的 depth 是一样的.配合 journal 使用.journal 交易执行过程中,产生的所有临时状态变更栈.transaction_id 当前 Tx 在 当前 Block 中的 位置索引 Index.// crates/context/src/journal/inner.rs
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct JournalInner<ENTRY> {
pub state: EvmState,
pub transient_storage: TransientStorage,
pub logs: Vec<Log>,
pub depth: usize,
pub journal: Vec<ENTRY>,
pub transaction_id: usize,
pub spec: SpecId,
pub warm_addresses: WarmAddresses,
}
看下 JournalInner 的部分实现.
commit_tx 每个 TX 执行成功调用
transient_storage 清空 depth 置零journal 清空warm_addresses 清空transaction_id 加1logs 清空discard_tx 丢弃当前 TX, Revert 时调用.和 commit_tx 不同的是
journal 不是清空,而是从后向前回滚所有的条目.流程(5) 已经解释过,但是 entry.revert没说,在后面再具体解释.finalize 整个 Block 的所有交易结束时调用.和 commit_tx 不同的是
transaction_id 置零,而不是加1state,也就是所有Touched和修改过的账户,由外部决定是否提交到数据库.// 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;
// Spec, precompiles, BAL and state are not changed. It is always set again execution.
let _ = spec;
let _ = state;
transient_storage.clear();
*depth = 0;
// Do nothing with journal history so we can skip cloning present journal.
journal.clear();
// Clear coinbase address warming for next tx
warm_addresses.clear_coinbase_and_access_list();
// increment transaction id.
*transaction_id += 1;
logs.clear();
}
pub fn discard_tx(&mut self) {
~~~
journal.drain(..).rev().for_each(|entry| {
entry.revert(state, None, is_spurious_dragon_enabled);
});
~~~
}
pub fn finalize(&mut self) -> EvmState {}
没有继续讲 JournalInner 而是跳到这里的原因是
后面 JournalInner 的内容基本都是跟 Account 和 ENTRY 相关.
后面的内容都是对它们的封装,直接讲只知道怎么用而不知道为什么这样写.
上面讲到的 state 的类型是 EvmState. pub type EvmState = HashMap<Address, Account>.
这就是为什么要讲 Account 的原因.
先看下结构体
info 账户的所有信息,具体内容在上一章的 Database 有介绍过original_info 账户在交易开始时,首次加载后的初始值.transaction_id Tx 在 Block 中的 Indexstorage 地址对应的存储,合约的属性字段就是保存在 storage 中.
value 的类型是 EvmStorageSlot,这是它的字段
original_value storage 的原始值present_value storage 的现值transaction_id tx 在 block 中的 indexis_cold 是否冷加载(第一次加载,Gas 扣费不一样)status 账户状态(数值按位表示,方便叠加和移除)
Created:0b00000001账户在 本次交易 中被创建.(标记后不会去查数据库)CreatedLocal:0b10000000 账户在 当前Frame 被创建SelfDestructed:0b00000010 账户 本次交易被标记为自毁.(交易结束后会被从数据库删除)SelfDestructedLocal:0b01000000 账户在 当前Frame 中被标记为自毁.Touched:0b00000100 账户被读或者写过.(被标记过的账户才会考虑写入数据库)LoadedAsNotExisting:0b00001000 账户被加载为不存在(去DB查,没找到)Cold:0b00010000 冷账户,和上面的 is_cold 功能一样#[derive(Debug, Clone, PartialEq, Eq, Default)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct Account {
pub info: AccountInfo,
pub original_info: Box<AccountInfo>,
pub transaction_id: usize,
pub storage: EvmStorage,
pub status: AccountStatus,
}
看下实现
new_not_existing 创建一个新的 Account 并标记为不存在.
这里使用了 OnceLock. OneLock 不是读写锁,而是单例锁.
用于保证这个对象只会被初始化一次.
EVM 执行过程中可能会频繁创建新 Account
新建一个static 模板在全局内存中用 Clone 的方式,可以减少内存分配开销.
caller_initial_modification 为 caller 设置新的金额并标记为 Touched
state_clear_aware_is_empty 判断一个账户是否为空.
changed_storage_slots 判断 storage 是否被修改过
storage 中 original_value 和 present_value 是否相等.is_cold_transaction_id 判断
Journal 实例是会被多个交易共用的.Coldmark_warm_with_transaction_id 标志该地址对于该交易是 Warm
剩下的都是 Setter 和 Getter,就不细讲了.
pub fn new_not_existing(transaction_id: usize) -> Self {
static DEFAULT: OnceLock<Account> = OnceLock::new();
DEFAULT
.get_or_init(|| Self {
info: AccountInfo::default(),
storage: HashMap::default(),
transaction_id,
status: AccountStatus::LoadedAsNotExisting,
original_info: Box::new(AccountInfo::default()),
})
.clone()
}
pub fn caller_initial_modification(&mut self, new_balance: U256, is_call: bool) -> U256 {
self.mark_touch();
if is_call {
self.info.nonce = self.info.nonce.saturating_add(1);
}
core::mem::replace(&mut self.info.balance, new_balance)
}
pub fn state_clear_aware_is_empty(&self, spec: SpecId) -> bool {
if SpecId::is_enabled_in(spec, SpecId::SPURIOUS_DRAGON) {
self.is_empty()
} else {
self.is_loaded_as_not_existing_not_touched()
}
}
pub fn is_cold_transaction_id(&self, transaction_id: usize) -> bool {
self.transaction_id != transaction_id || self.status.contains(AccountStatus::Cold)
}
#[inline]
pub fn mark_warm_with_transaction_id(&mut self, transaction_id: usize) -> bool {
let is_cold = self.is_cold_transaction_id(transaction_id);
self.status -= AccountStatus::Cold;
self.transaction_id = transaction_id;
is_cold
}
之前说 Journal 会保存 EVM 运行过程中的临时状态变更列表.
这里的 JournalEntry 就是变更条目,每次的变更记录都用 JournalEntry 进行保存.
和前面的类型不同, JournalEntry 不是结构体 Struct , 是枚举 Enum
AccountWarmed 标记地址预热
AccountDestroyed 标记地址销毁并将 Journal 中的余额回滚
AccountTouched 标记地址被Touched
BalanceChange 地址Balance被修改
BalanceTransfer 转账操作
NonceChange Nonce 变化(主要用于调试)
NonceBump Nonce递增1(正常EVM流程)
AccountCreated 账户创建
StorageChanged Storage改变
StorageWarmed Storage预热
TransientStorageChange 瞬态Storage改变
CodeChange Code改变
pub enum JournalEntry {
AccountWarmed {
address: Address,
},
AccountDestroyed {
had_balance: U256,
address: Address,
target: Address,
destroyed_status: SelfdestructionRevertStatus,
},
AccountTouched {
address: Address,
},
BalanceChange {
old_balance: U256,
address: Address,
},
BalanceTransfer {
balance: U256,
from: Address,
to: Address,
},
NonceChange {
address: Address,
previous_nonce: u64,
},
NonceBump {
address: Address,
},
AccountCreated {
address: Address,
is_created_globally: bool,
},
StorageChanged {
key: StorageKey,
had_value: StorageValue,
address: Address,
},
StorageWarmed {
key: StorageKey,
address: Address,
},
TransientStorageChange {
key: StorageKey,
had_value: StorageValue,
address: Address,
},
CodeChange {
address: Address,
},
}
看下 Revert 的部分.
针对每种改变日志进行处理并回滚之前保存的值或者状态.
都挺简单的,就是繁琐了点,就不解释了
fn revert(
self,
state: &mut EvmState,
transient_storage: Option<&mut TransientStorage>,
is_spurious_dragon_enabled: bool,
) {
match self {
JournalEntry::AccountWarmed { address } => {
state.get_mut(&address).unwrap().mark_cold();
}
JournalEntry::AccountTouched { address } => {
if is_spurious_dragon_enabled && address == PRECOMPILE3 {
return;
}
// remove touched status
state.get_mut(&address).unwrap().unmark_touch();
}
JournalEntry::AccountDestroyed {
address,
target,
destroyed_status,
had_balance,
} => {
let account = state.get_mut(&address).unwrap();
// set previous state of selfdestructed flag, as there could be multiple
// selfdestructs in one transaction.
match destroyed_status {
SelfdestructionRevertStatus::GloballySelfdestroyed => {
account.unmark_selfdestruct();
account.unmark_selfdestructed_locally();
}
SelfdestructionRevertStatus::LocallySelfdestroyed => {
account.unmark_selfdestructed_locally();
}
// do nothing on repeated selfdestruction
SelfdestructionRevertStatus::RepeatedSelfdestruction => (),
}
account.info.balance += had_balance;
if address != target {
let target = state.get_mut(&target).unwrap();
target.info.balance -= had_balance;
}
}
JournalEntry::BalanceChange {
address,
old_balance,
} => {
let account = state.get_mut(&address).unwrap();
account.info.balance = old_balance;
}
JournalEntry::BalanceTransfer { from, to, balance } => {
// we don't need to check overflow and underflow when adding and subtracting the balance.
let from = state.get_mut(&from).unwrap();
from.info.balance += balance;
let to = state.get_mut(&to).unwrap();
to.info.balance -= balance;
}
JournalEntry::NonceChange {
address,
previous_nonce,
} => {
state.get_mut(&address).unwrap().info.nonce = previous_nonce;
}
JournalEntry::NonceBump { address } => {
let nonce = &mut state.get_mut(&address).unwrap().info.nonce;
*nonce = nonce.saturating_sub(1);
}
JournalEntry::AccountCreated {
address,
is_created_globally,
} => {
let account = &mut state.get_mut(&address).unwrap();
account.unmark_created_locally();
if is_created_globally {
account.unmark_created();
}
// only account that have nonce == 0 can be created so it is safe to set it to 0.
account.info.nonce = 0;
}
JournalEntry::StorageWarmed { address, key } => {
state
.get_mut(&address)
.unwrap()
.storage
.get_mut(&key)
.unwrap()
.mark_cold();
}
JournalEntry::StorageChanged {
address,
key,
had_value,
} => {
state
.get_mut(&address)
.unwrap()
.storage
.get_mut(&key)
.unwrap()
.present_value = had_value;
}
JournalEntry::TransientStorageChange {
address,
key,
had_value,
} => {
let Some(transient_storage) = transient_storage else {
return;
};
let tkey = (address, key);
if had_value.is_zero() {
// if previous value is zero, remove it
transient_storage.remove(&tkey);
} else {
// if not zero, reinsert old value to transient storage.
transient_storage.insert(tkey, had_value);
}
}
JournalEntry::CodeChange { address } => {
let acc = state.get_mut(&address).unwrap();
acc.info.code_hash = KECCAK_EMPTY;
acc.info.code = None;
}
}
}
继续回到 Journal 剩下的部分,只挑几个函数来讲讲.
checkpoint 创建检查点.
JournalCheckpoint 对象.checkpoint_commit 提交检查点Frame 运行成功.checkpoint_revert 回滚到指定检查点checkpoint 日志开始的位置进行截断.drain 截断checkpoint.journal_i之后的元素并返回.rev 反转截断返回的元素列表Revert 进行回滚操作.pub fn checkpoint(&mut self) -> JournalCheckpoint {
let checkpoint = JournalCheckpoint {
log_i: self.logs.len(),
journal_i: self.journal.len(),
};
self.depth += 1;
checkpoint
}
/// Commits the checkpoint.
#[inline]
pub fn checkpoint_commit(&mut self) {
self.depth = self.depth.saturating_sub(1);
}
/// Reverts all changes to state until given checkpoint.
#[inline]
pub fn checkpoint_revert(&mut self, checkpoint: JournalCheckpoint) {
let is_spurious_dragon_enabled = self.spec.is_enabled_in(SPURIOUS_DRAGON);
let state = &mut self.state;
let transient_storage = &mut self.transient_storage;
self.depth = self.depth.saturating_sub(1);
self.logs.truncate(checkpoint.log_i);
// iterate over last N journals sets and revert our global state
if checkpoint.journal_i < self.journal.len() {
self.journal
.drain(checkpoint.journal_i..)
.rev()
.for_each(|entry| {
entry.revert(state, Some(transient_storage), is_spurious_dragon_enabled);
});
}
}
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();
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)?;
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));
}
(account, is_cold)
}
Entry::Vacant(vac) => {
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)
};
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,
))
}
pub fn load_account_delegated<DB: Database>(
&mut self,
db: &mut DB,
address: Address,
) -> Result<StateLoad<AccountLoad>, DB::Error> {
let spec = self.spec;
let is_eip7702_enabled = spec.is_enabled_in(SpecId::PRAGUE);
let account = self
.load_account_optional(db, address, is_eip7702_enabled, false)
.map_err(JournalLoadError::unwrap_db_error)?;
let is_empty = account.state_clear_aware_is_empty(spec);
let mut account_load = StateLoad::new(
AccountLoad {
is_delegate_account_cold: None,
is_empty,
},
account.is_cold,
);
if let Some(Bytecode::Eip7702(code)) = &account.info.code {
let address = code.address();
let delegate_account = self
.load_account_optional(db, address, true, false)
.map_err(JournalLoadError::unwrap_db_error)?;
account_load.data.is_delegate_account_cold = Some(delegate_account.is_cold);
}
Ok(account_load)
}
剩下的函数逻辑都蛮简单的.感兴趣的可以自己去看一下.
touch() 将账户标记为已触碰touch_account() 内部函数,标记账户已触碰并添加日志条目account() 返回已加载账户的只读引用set_code_with_hash() 为账户设置代码和哈希set_code() 为账户设置代码(自动计算哈希)caller_accounting_journal_entry() 为调用者添加余额/nonce变更的日志条目balance_incr() 增加账户余额nonce_bump_journal_entry() 添加 nonce 递增的日志条目transfer_loaded() 在两个已加载账户之间转账transfer() 加载账户并执行转账create_account_checkpoint() 创建账户时的检查点处理(检测碰撞、转移余额等)selfdestruct() 执行 SELFDESTRUCT 操作load_account() 加载账户到内存,返回冷/热状态load_code() 加载账户及其代码load_account_optional() 可选加载账户(可跳过冷加载)load_account_mut() 加载账户并返回可变引用load_account_mut_optional_code() 可选加载账户代码的可变引用版本get_account_mut() 获取账户可变引用(不标记冷/热状态)sload() 加载存储槽值sload_assume_account_present() 加载存储槽值(假设账户已存在)sstore() 写入存储槽值sstore_assume_account_present() 写入存储槽值(假设账户已存在)tload() 读取瞬态存储值tstore() 写入瞬态存储值log() 将日志推入日志列表如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!