在以太坊黄皮书学习笔记的前两篇中,我们探讨了以太坊作为“世界计算机”的宏伟愿景,并初步接触了其核心的状态树(State Tree)、交易树(Transactions Tree)和收据树(Receipts Tree)这三大默克尔帕特里夏树(Merkle Patricia Trie, MPT)结构,这些数据结构共同构成了以太坊区块链的“骨架”,记录了系统的历史状态和所有发生过的行为。
本篇笔记,我们将深入到这个“骨架”的“灵魂”——状态转换函数(
什么是状态转换函数(Δ)?
以太坊的状态可以被看作是一个巨大的、分布式的数据库,这个数据库存储了所有账户的余额、代码、存储等信息,而状态转换函数Δ,就是这个数据库的“操作规则”。
Δ函数描述了一个“状态”如何根据一笔“交易”,转变为一个新的“状态”,这个过程可以抽象为一个数学表达式:
S(t) = Δ(S(t-1), T(t))
这个公式的含义是:
S(t):在区块t被执行之后,以太坊的最终状态。S(t-1):在区块t被执行之前,以太坊的初始状态。T(t):区块t中包含的所有交易集合。- 状态转换函数,它接收旧状态和一笔交易,输出一个中间状态,它会将这个中间状态与下一笔交易再次作为输入,直到区块中的所有交易都被处理完毕。
Δ函数是以太坊确定性保证的核心,只要输入(初始状态和交易)相同,无论在哪个节点上运行,输出(最终状态)都必然是相同的,这正是所有节点能够达成共识、维护同一个账本的基础。
Δ函数的内部:一笔交易的完整生命周期
黄皮书用极其严谨和形式化的语言定义了Δ函数,我们可以将其分解为处理单笔交易 T 的几个关键步骤,这正是以太坊虚拟机执行交易的底层逻辑。
假设我们要处理一笔交易 T = (nonce, gasprice, startgas, to, value, data, v, r, s),其处理流程如下:
步骤 1:交易验证
在执行交易之前,必须先验证其有效性,这一步是确保交易发起者拥有足够的权限和资源。
- 检查签名:根据交易中的
v, r, s值,使用椭圆曲线算法恢复出发送者的地址,如果恢复出的地址与交易from字段不匹配,则交易无效。 - 检查 nonce:查询发送者账户
A的当前 nonce 值,如果交易中的nonce不等于账户A的当前 nonce,则交易无效。 - 检查余额与 Gas:查询发送者账户
A的当前余额,如果账户余额不足以支付value(转账金额) +gasprice * startgas(预估的总费用),则交易无效。 - 检查 Gas Limit:如果交易的
startgas大于区块的gaslimit,则交易无效。
如果以上任何一项检查失败,交易将被标记为无效,状态不会发生改变,但交易发起者仍需支付一部分 Gas(用于支付验证成本)。
步骤 2:初始化执行环境
交易验证通过后,EVM 将为这笔交易创建一个独立的执行环境,也称为“沙箱”。
- 创建接收者账户:如果交易
to字段为空(即创建合约交易),则创建一个新的合约账户,新账户的nonce为 1,code为空,storage为空。to不为空,则目标账户B必须已经存在。 - 转移 Ether:从发送者账户
A的余额中扣除value,并将其增加到接收者账户B的余额中。 - 扣除预付 Gas 费:从发送者账户
A的余额中扣除gasprice * startgas这笔总费用,并将其暂时存入一个“矿工奖励”账户中。 - 设置执行上下文:初始化 EVM 的所有寄存器和内存,包括
gas(剩余 Gas,初始为startgas)、value(交易传递的 Ether 值)、sender(发送者地址)、callvalue等。
步骤 3:执行交易代码
这是整个过程中最核心的一步,即运行 EVM 字节码。
- 对于普通转账交易:
to是一个普通账户,则没有代码需要执行,此步骤直接跳过。 - 对于合约创建/调用交易:EVM 会开始执行接收者账户的代码。
- 合约创建:执行
data字段中的字节码,如果代码执行成功(即没有耗尽 Gas 或导致其他错误),则执行结果(返回的字节串)将被设置为合约账户的code,如果执行失败,则状态回滚到交易执行之前。 - 合约调用:执行
to地址合约的代码,EVM 会按照指令集逐条执行字节码,这个过程会修改内存、栈,并根据指令消耗 Gas。
- 合约创建:执行
步骤 4:处理 Gas 消耗与退款
在代码执行过程中,每一条指令都会消耗一定量的 Gas,EVM 会实时跟踪剩余的 Gas。
- Gas 耗尽:如果在执行过程中
gas耗尽,交易会立即失败,状态会完全回滚到执行之前,但矿工仍能获得已消耗的 Gas 作为报酬。 - 执行成功:如果代码正常执行完毕,会进入 Gas 结算阶段。
- Gas 退款:某些操作(如
SSTORE将一个值从非零变为零)会返还一部分 Gas,返还的 Gas 不会超过初始 Gas 的一半。 - 支付矿工:剩余的 Gas(
startgas - 消耗的 Gas)会被返还给发送者,而消耗掉的 Gas(gasprice * 消耗的 Gas)则作为矿工的手续费,存入其账户。
- Gas 退款:某些操作(如
步骤 5:更新状态
这是交易处理的最后一步,将执行结果“写入”状态。
- 更新 Nonce:发送者账户
A的nonce值加 1。 - 清除临时状态:在执行过程中,EVM 可能修改了合约的临时存储,这些修改只有在交易成功时才会被永久写入到状态树的
storage中。 - 生成收据:为这笔交易生成一个收据,并将其添加到当前区块的收据树中,收据包含了交易哈希、状态(成功/失败)、Gas 使用量、日志 bloom 过滤器以及日志等信息,这对于轻客户端和事件监听至关重要。
从抽象到具体
通过学习黄皮书中对状态转换函数 Δ 的定义,我们不再是简单地“发送一笔交易”,而是能够清晰地描绘出其背后精确的、可执行的步骤:
- 验证:确保交易格式正确、发送者有足够权限和资金。
- 初始化:创建或准备账户,转移价值和预付 Gas 费用。
- 执行:在 EVM 中运行代码,改变内存和临时状态,并消耗 Gas。
- 结算:处理 Gas 的消耗、退款和矿工奖励。
- 提交:将最终的变更(Nonce、Storage 等)永久写入世界状态,并生成收据。
这个严谨的流程,是以太坊作为“确定性状态机”的基石,它确保了全球成千上万的节点,无论出于何种动机,只要遵循相同的规则(Δ函数),就能对世界的状态变化达成一致,下一篇文章,我们将继续深入,探讨如何通过区块头将这些状态转换高效地链接起来,形成最终的区块链结构。