以太坊交易执行 ( Transaciton Execution ) 流程

以太坊 Yellow Paper 学习笔记(二)

Posted by Nicodechal on 2020-10-21

交易验证

交易执行前,首先需要进行交易验证,这个过程包括:

  1. 交易的 RLP 编码正确且尾部没有多余的字节。
  2. 交易签名验证。
  3. 交易的 nonce 个发送方的当前 nonce 相同。
  4. gaslimit 不小于交易执行的 gas g0g_0
  5. 交易发送方的余额满足预先支付金额要求。

上面的 g0g_0 表示交易执行需要提前支付的 gas 的数量(注意只是提前预支的,最终实际使用的数量要看交易执行情况),其中包括三个部分:

  1. 交易数据 data 需要的 gas,直接根据每个字节消耗 gas,非零字节 ( 68 gas/byte ) 比零字节 ( 4 gas/byte ) 稍贵。
  2. 如果是创建合约的交易,需要消耗一部分创建合约的 gas ( 32000 gas )。
  3. 交易都会消耗一个基础的 gas ( 21000 gas )。

g0iTi,Td{Gtxdatazeroif  i=0Gtxdatanonzerootherwise+{Gtxcreateif  T=0otherwise+Gtransaction\begin{aligned} g_0\equiv &\sum_{i\in T_\mathbf{i},T_\mathbf{d}} \left\{ \begin{array}{l} G_{txdatazero} & if\ \ i= 0 \\ G_{txdatanonzero} & otherwise \end{array} \right.\\ \\ &+ \left\{ \begin{array}{l} G_{txcreate} & if\ \ T= \emptyset \\ 0 & otherwise \end{array} \right.\\ \\ &+ G_{transaction} \end{aligned}

需要预先支付的金额记为 v0v_0,在满足上面约束以后,发送方需要预先支付:

v0TgTp+Tvv_0\equiv T_gT_p+T_v

即:

  1. 执行交易需要的金额上限 gaslimit * gasprice
  2. 如果有转账,还要加上转账金额 TvT_v

验证过程需要满足下面条件:

S(T) σ[S(T)] Tn=σ[S(T)]n g0Tg v0σ[S(T)]b TgBHl(BR)u\begin{aligned} S(T) &\neq \emptyset\ \wedge \\\\ \mathbf{\sigma}[S(T)] &\neq \emptyset\ \wedge \\\\ T_n &= \mathbf{\sigma}[S(T)]_n\ \wedge \\\\ g_0 &\leq T_g\ \wedge \\\\ v_0 &\leq \mathbf{\sigma}[S(T)]_b\ \wedge \\\\ T_g &\leq B_{H_l}-\ell(B_\mathbf{R})_u \end{aligned}

即,发送方不为空、发送方的状态存在、交易的 nonce 和发送方的 nonce 相同、可能用到的 gas 数量不大于交易中声明的 gas 使用上限、发送方有足够的金额预付交易执行费用等。这里特别说明一下最后一条 TgBHl(BR)uT_g \leq B_{H_l}-\ell(B_\mathbf{R})_u

(list)\ell(list) 表示取 listlist 中的最后一个元素。

(x)x[x1]\ell(\mathbf{x})\equiv\mathbf{x}[\parallel\mathbf{x}\parallel - 1]

一个区块中包含多个交易,交易在打包进一个区块后,会按顺序执行每一条交易,每一条交易完成以后,都会生成一个收据 (Receipt, 用 RR 表示),收据中包含了其对应的交易执行后的一些信息,包括:

  1. 交易完成后,交易所在区块使用的 gas 的累积量 RuR_u
  2. 交易日志的集合 RlR_\mathbf{l}
  3. 交易日志对应的布隆过滤器 RbR_b
  4. 一个非负整数表示交易执行的状态 RzR_z

(BR)u\ell(B_\mathbf{R})_uBRB_\mathbf{R} 即是目前区块中所有已经执行的交易产生的收据的集合,(BR)u\ell(B_\mathbf{R})_u 就是目前区块中最后一张收据中的 RuR_u 即执行当前交易前已经执行的交易使用的 gas 的累积量。

例如一个区块有 10 条交易,当前即将执行第 6 条,那么 (BR)u\ell(B_\mathbf{R})_u 可以得到前 5 条交易执行后实际消耗的 gas 数量和。

所以这里面最后一条表示的是当前交易的 gaslimit 和该条交易之前的交易使用的 gas 的和不超过交易所在区块的 gaslimit ( 代表了对区块使用 gas 数量的限制 )。

交易执行

假设交易执行前的状态为 σ\mathbf{\sigma},交易执行前首先进行下面操作:

  1. 使发送者的 nonce 加一。
  2. 使发送者的的余额减少交易声明的 gaslimit * gasprice ( TgTpT_gT_p )。

这部分操作是不可逆的。假设这部分操作后的状态为 σ0\mathbf{\sigma}_0,则有:

σ0σ exceptσ0[S(T)]bσ[S(T)]bTgTpσ0[S(T)]nσ[S(T)]n+1\begin{aligned} \mathbf{\sigma}_0 &\equiv \mathbf{\sigma} \ \mathrm{except} \\\\ \mathbf{\sigma}_0[S(T)]_b &\equiv \mathbf{\sigma}[S(T)]_b-T_gT_p \\\\ \mathbf{\sigma}_0[S(T)]_n &\equiv \mathbf{\sigma}[S(T)]_n + 1 \end{aligned}

接着就进行交易的执行,以太坊有两种交易类型:

  1. 合约创建 ( contract creation ),使用 Λ\Lambda 函数表示。
  2. 消息调用 ( message call ),使用 Θ\Theta 函数表示。

交易执行会得到下面返回值:

  1. 执行完交易后的状态 σP\mathbf{\sigma}_P
  2. 执行完交易后的剩余 gas gg'
  3. 交易执行后的子状态 AA
  4. 返回码 zz

这里介绍一个交易执行的子状态 AA,它表示一个交易执行后的一些信息。包括下面几个部分:

  1. 一个集合,表示交易结束后会丢弃的账户们。AsA_\mathbf{s}
  2. 执行日志集合。AlA_\mathbf{l}
  3. 创建账户集合,其中的空账户在交易后会被删除。AtA_\mathbf{t}
  4. 执行交易后可退还的 gas,ArA_r,这部分通过 SSTORE 指令将一个非零的存储值 ( storage value ) 置为零产生,每次这样的操作返还 Rsclear=15000R_{sclear} = 15000 gas。

执行之后

执行后,首先会对每个丢弃的账户向 ArA_r 中增加丢弃一个账户的 gas 收益:

ArAr+iAsRselfdestructA'_r\equiv A_r + \sum_{i\in A_s}R_{selfdestruct}

其中 Rselfdestruct=24000R_{selfdestruct} = 24000

此时,需要对剩余的 gas 进行退还,剩余的 gas 包括两部分,一部分是执行交易以后剩余的 gas gg' 另一部分是存储值归零返还的 gas ArA_r'。退还的部分记为 gg^\star,则有:

gg+min{Tgg2,Ar}g^\star\equiv g' + min\left\{\left\lfloor \frac{T_g-g'}2 \right\rfloor,A_r'\right\}

gg^\star 根据 gasprice 返还给交易发起方,交易的 gaslimit 中的剩余部分都作为奖励给予挖出当前区块的矿工,这一步完成后的临时状态记为 σ\mathbf{\sigma}^\star,则有:

σσPexceptσ[S(T)]bσ[S(T)]b+gTpσ[m]bσP[m]b+(Tgg)TpmBHc\begin{aligned} \mathbf{\sigma}^\star &\equiv\mathbf{\sigma}_P \mathrm{except} \\\\ \mathbf{\sigma}^\star[S(T)]_b &\equiv \mathbf{\sigma}[S(T)]_b+g^\star T_p \\\\ \mathbf{\sigma}^\star[m]_b &\equiv \mathbf{\sigma}_P[m]_b+(T_g-g^\star)T_p \\\\ m &\equiv B_{H_c} \end{aligned}

最后删除无用的账户到达交易完成后的最终状态 σ\mathbf{\sigma}'。无用的账户包括所有的丢弃的账户和创建的空账户:

σσ  exceptiAs:σ=iAt:σ=   if DEAD(σ,i)\begin{aligned} \mathbf{\sigma}' & \equiv \mathbf{\sigma}^\star\ \ \mathrm{except} \\\\ \forall i \in A_\mathbf{s}: \mathbf{\sigma}' &= \emptyset\\\\ \forall i \in A_\mathbf{t}: \mathbf{\sigma}' &= \emptyset\ \ \ if\ \mathtt{DEAD}(\mathbf{\sigma}^\star, i) \end{aligned}