以太坊黄皮书(1~6章)

以太坊黄皮书(一)

引言

  简单来说,在以太坊模型中,交易和智能合约的执行会改变节点的状态;可以把以太坊想象成一台计算机。因此,如果你将这个逻辑复制到分布于点对点网络中的其他节点上,并找到一种方法来让这些节点就操作的执行顺序和正确状态达成共识,最后就能得到一个去中心化的计算机,其中节点使用交易(输入)来执行计算(处理),存储其结果(输出),以便之后查询(输出)。
  公式 1 是从状态转换顺序的角度对“以太坊计算机”下的数学定义 。我们来看一下

σ t + 1 ≡ γ ( σ t , T ) (1) \sigma _{t+1 }\equiv \gamma ( \sigma _t,T) \tag{1} σt+1γ(σt,T)(1)
  这个公式内包含以下几个参数:

  • σ t + 1 \sigma _{t+1 } σt+1代表下一个世界状态(后面会详细介绍世界状态);
  • γ t + 1 \gamma _{t+1 } γt+1 代表以太坊状态转换函数;
  • σ t \sigma _t σt 代表当前世界状态;
  • T T T代表一个交易。
      这个公式表明,交易(输入)会影响(处理)当前的世界状态(存储),最后得到一个新的世界状态(存储/输出)。
      另一种思路就是将以太坊看作是状态转换机。在这个模型中,交易 T T T就是当前状态 σ t 和 下 一 个 状 态 σ t + 1 \sigma_t和下一个状态\sigma_{t+1} σtσt+1 之间的弧线。
    在这里插入图片描述

拓展模型

  在以太坊上,交易都是打包成区块(数据块)的。这些区块相互链接起来,形成了一条区块链。将新的区块添加到链上的过程被称为挖矿 。其中一个很重要的知识点是,节点必需提供计算机和电力才能生成区块。因此,我们需要通过某个机制来激励人们参与进来。每当有一个节点创建了一个新的区块,相关参与者就能得到奖励。
因此,我们可以将上文的以太坊范式扩展到区块和奖励上。这就是公式 2、3、4 的由来。我们来看一下:
B ≡ ( . . . , ( T 0 , T 1 , . . . ) ) (3) B \equiv(...,(T_0,T_1,...)) \tag{3} B(...,(T0,T1,...))(3)
  这个公式将区块视作一个交易列表(为便于理解,其他部分在此忽略不计)。
  由上文可知,我们是按块来处理交易(而非单笔单笔处理),然后更新世界状态的。因此,我们也可以将这个公式写成:
σ t + 1 ≡ ∏ ( σ t , B ) (2) \sigma _{t+1 }\equiv \prod ( \sigma _t,B) \tag{2} σt+1(σt,B)(2)

  • σ t + 1 σt+1 σt+1代表下一个世界状态
  • ∏ \prod 代表区块层面上的状态转换函数
  • σ t \sigma _t σt代表当前世界状态
  • B B B是一个区块(即一个交易列表)

   简单来说,这个公式表明,将区块中的交易应用到当前状态中,产生一个新的状态,就可以更新世界状态。
  最后还有一个公式 4 。这个公式将上文提到的所有参数都融合进了同一个模型里:
∏ ( σ , B ) ≡ Ω ( B , γ ( γ ( σ , T 0 ) , T 1 ) . . . ) (4) \prod(\sigma,B) \equiv \Omega(B,\gamma(\gamma(\sigma,T_0),T_1)...)\tag{4} (σ,B)Ω(B,γ(γ(σ,T0),T1)...)(4)
  上式子表达的意思是:

  1. 挖出区块 ( B ) (B) (B)之后,按顺序执行交易 ( T 0 , T 1 , . . . ) (T0,T1,...) (T0,T1,...),每个函数 ( σ ) (σ) (σ)的输出都被用作下一个函数的输入。
  2. Ω Ω Ω被称为区块确定性状态转换函数,会奖励参与者。

   综上所述,这 4 个公式阐明了区块链的运作方式。节点挖出一个区块之后,该区块内的交易会(按顺序)执行,改变当前的世界状态。挖出该区块的节点会得到奖励。
  这一节的最后,作者还着重介绍了一下区块奖励。以太坊有自己的原生货币(以太币),以太币会作为出块奖励发放给挖矿节点。以太币可以拆分成很小的单位,如下表所示:


倍数单位
1 0 0 10^0 100Wei
1 0 12 10^{12} 1012Szabo
1 0 15 10^{15} 1015Finney
1 0 18 10^{18} 1018Ether

以太坊黄皮书(二)

  通过本文,将学到以太坊中一些主要组成部分以及它们在整个系统中的作用,同时也将简要地讨论以太坊中默克尔树的工作原理。

默克尔树

  在讨论以太坊的主要数据对象之前,我想先向各位简要介绍一下默尔克树到底是什么,以使得它得以发挥作用的属性特征。
  黄皮书中假设由定制的默克尔-帕特里夏树维护世界状态和交易。附录 D 描述了这个数据结构。
  默克尔-帕特里夏树有许多有意思的属性,如果你想更深入地了解其在以太坊中的应用,我推荐你阅读这篇文章。
  在默克尔树中,由叶子节点保存区块数据的哈希,而由非叶子节点保存其子节点的哈希。
-默克尔树示意图(包括节点以及他们之间的关系)-

-默克尔树示意图(包括节点以及他们之间的关系)-

  默克尔树所指向数据的任何改动都会引起节点哈希的变化。由于每一个父节点中所保存的哈希值都取决于子节点所包含的数据,所以子节点中数据的变更都会引起父节点哈希的变化。并且这样的影响是连锁反应,从叶子节点直达根节点的。因此对叶子节点所指向数据的改动会引起根节点所保存哈希的变化。由上述结构特征,我们可以引申出两条重要的属性:

  • 在判断两棵默克尔树所指向数据是否完全相同时,我们不需要比较每个叶子节点,而只需比较根节点所保存的哈希。
  • 在判断特定数据是否被树所指向时,我们可以使用 默克尔证明 技术。此处不对该技术作过多介绍,只需知道这是证明数据存在于默克尔树中的一种简单、高效的方法。
      第一种属性的重要之处在于,我们能够仅利用根节点的哈希值,就标示某一时刻整棵树所指向的数据。这意味着仅通过保存根节点的哈希值就能标示区块(无需储存区块链中所有的数据),且维护数据的不可篡改。
      至此我们理清了默克尔树中根节点哈希的作用,下面来介绍以太坊中的主要对象。

世界状态

  世界状态是地址(账户)到账户状态的映射。虽然世界状态不保存在区块链上,但在黄皮书的描述中,世界状态也由树来保存数据(此树也被称为状态数据库或者状态树)。世界状态可以被视作为随着交易的执行而持续更新的全局状态。以太坊就像一个去中心化的计算机,世界状态则是这台电脑的硬盘。
  以太坊中所有的账户信息都体现在世界状态之中,并由世界状态树保存。如果你想知道某一账户的余额,或者某智能合约当前的状态,就需要通过查询世界状态树来获取该账户的具体状态信息。下文中我也会简要介绍这些信息是如何存储的。
在这里插入图片描述

-世界状态树与账户存储-

账户状态

  以太坊中有两种账户类型:外部所有账户(Externally Owned Accounts 简称 EOA)以及合约账户。我们用来互相收发以太币、部署智能合约的账户就是 EOA 账户,而部署智能合约时自动生成的账户则是合约账户。每一个智能合约都有其独一无二的以太坊账户。
  账户状态反映了一个以太坊账户的各项信息。例如,它存储了当前账户以太币的余额信息、当前账户发送过的交易数量…每一个账户都有账户状态。
  下面就来看看账户状态中都包括什么:
nonce
  从此地址发送出去的交易数量(如果当前为 EOA 账户)或者此账号产生的合约创建操作(现在先别管合约创建操作是什么)。
balance
  此账号所拥有的以太币数量(以 Wei 计量)。
storageRoot
  账户存储树的根节点哈希值(稍后介绍账户存储是什么)。
codeHash
  对于合约账户,就是此账户存储 EVM 代码的哈希值。对于 EOA 账户,此处留空。
  账户状态中不容忽视的一个细节是,上述对象在内的所有对象都可变(除了 codeHash)。举例来说,当一个账户向其他账户发送以太币时,除了 nonce 会增加,账户的余额也会相应改变。
  而 codeHash 的不可变性使得,如果部署了有漏洞的智能合约,也无法修复更新此合约。对应的,只能部署一个新合约(而有漏洞的版本会一直存在于区块链上)。这也是为什么使用 T r u f f l e Truffle Truffle 进行智能合约的开发和部署十分必要,并且用 Solidity 编程时要遵循 最佳实践 的要求。
  账户存储树是保存与账户相关联数据的结构。该项只有合约账户才有,而在 EOA 中, storageRoot 留空、 codeHash 则是一串空字符串的哈希值。所有智能合约的数据都以 32 字节映射的形式保存在账户存储树中。此处不再赘述账户状态树如何维持合约数据。如果读者对其内部实现感兴趣,强烈建议阅读这篇文章。账户状态中的 storageRoot 区域负责维持账户存储树根节点哈希值。
在这里插入图片描述

-账户状态与账户存储树-

交易

交易推动当前状态到下一状态的转变。在以太坊中有三种交易:

  1. EOA(Externally Owned Accounts)之间传输值的交易(例如,改变发送方和接收方余额大小)。
  2.发送消息来调用合约的交易(例如,通过发送消息调用来触发 setter 方法,以设置合约中的值)。
  3. 用于部署合约的交易(由此创建了合约账户)。
(从技术角度来讲,前两种交易是一样的…它们都是通过消息调用来改变账户状态的交易,只不过一个是 EOA 账户,一个是合约账户。此处将交易分为三种是为了方便读者的理解。)

交易由以下部分组成:

nonce
  此账户发出的交易序号数(校对注:可以粗略理解为“这是该账户的第几笔交易”)。
gasPrice
  执行此交易、进行计算时为每单位 gas 所支付的费用(以 Wei 计量)。
gasLimit
  执行此交易时可以使用的最大 gas 数量。
to

  • 如果此交易用于传送以太币,此处为接收以太币的 EOA 地址。
  • 如果此交易用于向合约发送消息(例如,调用智能合约中的方法),此处为合约的地址。
  • 如果此交易用于创建合约,此处值为空。

value

  • 如果此交易用于收发以太币,此处为发往接收账户以 Wei 计量的代币数量。
  • 如果此交易用于发送对合约的消息调用,此处为向接收此消息智能合约所给付的 Wei 数量。
  • 如果此交易用于创建合约,此处为合约初始化时账户存放的以 Wei 计量的以太币数量。

v, r, s
  在交易的密码学签名中用到的值,可以用于确定交易的发送方。
data(只用于价值传输以及向智能合约发送消息调用)
  发送消息调用时附带的输入数据(例如,假设你想要执行智能合约中的 setter 方法,数据区就应该包括 setter 方法的标识符,以及你想要设定的参数值)。
  init(只用于合约创建)
  用于初始化合约的 EVM 代码。

  别想着一下子就把这些概念消化完… 必须对以太坊的内部机理有更深的认识才真正理解、使用像 data 区、init 区这样的概念。
  相信不出你的意料,区块中所有的交易也是存储在默克尔树中的。并且这棵树的根节点哈希值由区块头保存!下面我们就来剖析一下以太坊区块结构。

区块

  区块分为两部分,即区块头和区块体。
  区块头就是以太坊中的区块链部分。它保存了前一个区块(也可称为父区块)的哈希值,通过区块头的连接形成了一条由密码学背书的链。
  区块体包含了此区块中记录的一系列交易,以及叔块(ommer)区块头列表。如果想要进一步了解叔块,我推荐阅读这篇文章

-以太坊区块的抽象示意图--以太坊区块的抽象示意图-

下面就来介绍区块头包括哪些部分。

parentHash
  前一个区块的区块头哈希值。每个区块都包含前序区块的哈希值,一路可回溯至链上的创世块。这也就是维护数据不会被篡改的结构设计(任何对前序区块的篡改都会影响后续所有区块的哈希值)。
ommersHash
  叔块头以及部分区块体的哈希值。
beneficiary
  因为挖到此区块而获得收益的以太坊账户。
stateRoot
  世界状态树的根节点哈希值(在所有交易被执行后)。
transactionsRoot
  交易树根节点的哈希值。这棵树包含了区块体的所有交易。
receiptsRoot
  每当交易执行时,以太坊都会生成对应结果的交易收据。此处就是这个交易收据树的根节点哈希。
logsBloom
  布隆过滤器,用于判断某区块的交易是否产生了某日志(如果对这方面感兴趣,可以查阅 Stack Overflow 的这个答案)。这避免了在区块中存储日志信息(节省了大量空间)。
difficulty
  此区块的难度值。这是当前区块挖矿难度的度量值(此处不对此概念的细节和计算作介绍)。
number
  前序区块的总数。这标示了区块链的高度(即区块链上有多少区块)。创世区块的 number 为 0 。
gasLimit
  每一个交易都需要消耗 gas 。gas limit 标示了该区块所记录的所有交易可以使用的 gas 总量。这是限制区块内交易数量的一种手段。
gasUsed
  区块中各条交易所实际消耗的 gas 总量。
timestamp
  区块创建时的 Unix 时间戳。谨记由于以太坊网络去中心化的特性,我们不能信任这个值,特别是撰写智能合约、涉及到时间相关的商业逻辑时不能依靠这个值。
extraData
  能输入任何东西的不定长字节数组。当矿工创建区块时,可以在这个区域添加任何东西。
mixHash
  用于验证一个区块是否被真正记录到链上的哈希值(如果想要真正理解这个概念,建议阅读这篇文章 Ethash proof-of-work function )。
nonce
  和 mixHash 一样,用于验证区块是否被真正记录到链上的值。

结论

让我们简要回顾一下学到了什么!总体而言,以太坊有四种前缀树:
  1. 世界状态树包括了从地址到账户状态之间的映射。 世界状态树的根节点哈希值由区块保存(在 stateRoot 字段),它标示了区块创建时的当前状态。整个网络中只有一个世界状态树。
  2. 账户存储树保存了与某一智能合约相关的数据信息。由账户状态保存账户存储树的根节点哈希值(在 storageRoot 字段)。每个账户都有一个账户存储树。
  3. 交易树包含了一个区块中的所有交易信息。由区块头(在 transactionsRoot 区域)保存交易树的根节点哈希值。每个区块都有一棵交易树。
  4. 交易收据树包含了一个区块中所有交易的收据信息。同样由区块头(在 receiptsRoot 区域)保存交易收据树的根节点哈希值;每个区块都有对应的交易收据树。
我们今天讨论的对象有:
  1. 世界状态: 以太坊这台分布式计算机的硬盘。它是从地址到账户状态的映射。

  2. 账户状态: 保存着每个以太坊账户的状态信息。账户状态同样保存着账户状态树的 storageRoot,后者包含了该账户的存储数据。

  3. 交易: 标示了系统中的状态转移。它可以是资金的转移、消息调用或是合约的部署。

  4. 区块: 包括对前序区块(parentHash)的链接,并且保存了当执行时会在系统中产生新状态的交易。区块同时保存了 stateRoot 、transactionRoot 、 receiptsRoot 、 世界状态树的根节点哈希、交易树以及对应的交易收据树。

我想用一张图来表示文中提及的各种概念信息。

-区块、交易、账户状态对象以及以太坊的默克尔树-

-区块、交易、账户状态对象以及以太坊的默克尔树-

原文链接:

  1. https://www.lucassaldanha.com/ethereum-yellow-paper-walkthrough-2/
  2. https://ethfans.org/posts/ethereum-yellow-paper-walkthrough-2-merkle-tree-world-state-transaction-block

以太坊黄皮书(三)

  在这篇文章中,我们会学到更多关于Gas 和支付的以太坊相关知识,了解以太坊背后的经济学理论,以及解释为什么手续费在以太坊生态系中如此重要。
  读完这篇博文,你就会知道为什么交易成本被称为 Gas,明白 gasPrice 与 gasLimit 之间的区别,也能了解矿工节点选择待打包交易的策略。(此篇博文参考以太坊黄皮书第 5 章)

介绍

  我刚开始学习以太坊时,最先碰到的困惑就和交易手续费有关。我的疑惑是,“如果我可以免费获得与 Dapp 相同的服务,为什么还要花钱用 Dapp?”——很快地,我就意识到自己很傻很天真。
  计算是有成本的——我不是指花在购买笔记本和平板电脑上的开销,而是指运行虚拟机、数据存储、信息处理等等服务的成本。今时今日,我们都非常习惯免费的服务,却常常忘了一个关键点:有人在为这些服务买单! 你正在免费使用的 Gmail 或 Yahoo 邮箱,如果要使整个服务保持正常运行,邮件提供商就要为服务器、数据存储,及基础设施买单。如果你有 Facebook 或 Instagram 账号,那你肯定知道,在云端处理、安全存储你所上传的照片都需要成本。所以现在的免费服务和 Dapp 的付费服务,唯一的区别只是买单的人的不同(免费服务不由你直接买单罢了)。
  现在,我们将以太坊网络试想成一部巨大的计算机,能够实现运算及读写数据。这与放在你自己家里的计算机不同,这部巨型计算机(以太坊)由所有人所共有,只要有以太坊账户,任何人都能与整个平台进行交互,像是转移以太币、部署智能合约等等。但囿于分布式系统的原生限制,“谁来为这些服务买单”的问题就比较难回答了。

Gas

  Gas 是以太坊中所有计算量的计价单位,以太坊正是使用 Gas 来解决 “谁为服务买单” 的问题。你想要转账给另一个以太坊账户吗?你想要部署智能合约来存储手机通讯录吗?没问题,请先支付 Gas 。
  这很容易让人联想到,汽车和燃油的关系:如果你想从 A 地开车到 B 地,一定要耗费部分燃油;同理,如果你想要在以太坊 EVM 上执行某些运算,也要支付 Gas。车想要开得越远,则需要越多燃油;想要在以太坊上进行越多计算,则要支付越多 Gas。
  我们可以在以太坊黄皮书的附录 G,找到每一种 EVM 运算对应所需要消耗的 Gas 数量;这些数值看起来很随意,但其实背后是有道理的。一般来讲,这些数值反映了执行运算的成本(按时间维度度量),和占用的永久存储器资源(当写入数据的时候)。如果想要了解更详细的 Gas 成本计算公式,可以查阅 “以太坊 1.0 Gas 成本表”。我不太确定它与最新采用的 Gas 消耗量是否一致,但至少能让你对操作码的 Gas 耗用量制定原则更有概念。
  (要求编程人员对于代码的高效性和面对攻击,攻击者必须承担的费用:安全性)从另一个角度来说,以太坊采取使用者付费的模式,能够避免资源的滥用。一旦你必须为每种运算支付费用,你就会尽可能的将代码写得简洁高效;Gas 的存在还能阻止攻击者通过无效运算,对以太坊网路进行泛洪(Flooding)攻击。(除非攻击者愿意支付一大笔钱来执行无效运算)

gasPrice 和 gasLimit

  现在我们明白了 Gas,是时候谈谈 Gas 究竟要如何定价;先让我们回到汽车和燃油的例子。
  如果你的车子油箱容量为 50 升,装满这个油箱你要支付多少钱?这取决于加油站每升的油价对吧?以太坊中 Gas 的定价也是一样的!如果你要执行的交易需要耗费 10 Gas,则你要支付的费用取决于每单位 Gas 的价格。
  那我们怎么知道每单位 Gas 的价格是多少呢?有种误导性的说法是:自己爱定多少就定多少。虽然从技术角度来讲没有错,但要具体知道 Gas 的定价,我们需要更多知识作为铺垫。
  如果你读过以太坊解析系列的上一篇文章,应该记得以太坊交易结构中,除了其他部分,还包含了 gasPrice 和 gasLimit
  gasPrice 表示交易发送方对每单位 Gas 愿意支付的价格(以 Wei 计量),这意味着交易发送方可以自定义愿意支付的每单位 Gas 价格。假设一笔交易需要耗费 10 Gas,而我们愿意支付 3 Wei/Gas ,则发送这笔交易的成本总价就是 30 Wei(非实际数值,只是便于大家理解怎么计算的)。
  gasLimit表示交易发送方最多能接受多少 Gas 被用于执行此交易。因为有时候,你无法确切知道执行一笔交易要耗费多少 Gas;又或是你的智能合约中,有永远跳不出的死循环 bug,假如没有 gasLimit,这会导致发送方的账户余额被误消耗殆尽 gasLimit 就是一种安全机制,防止有人因为错误估算或 bug 而把账户中所有以太币消耗掉。
  另一个有趣的点是,gasLimit 可以被视为预付的 Gas。当节点在验证交易时,先将 gasPrice 乘 gasLimit 算出交易的固定成本。如果交易发送方的账户余额小于交易固定成本,则该交易视为无效。交易执行完之后,剩余的 Gas 会退回至发送方账户;当然,如果交易执行中 Gas 耗尽,则不会退回任何东西。这也能解释为什么交易发送方总是将 gasLimit 设得高于预估的 Gas 量。

Gas是有价格的:根据交易需要的Gas多少,谁给的单价高,矿工就会优先考虑谁!(gasprice:3 Wei/Gas)

  搞清楚这两个参数的意思之后,你可能会想问:“为什么是交易发送方自行决定每单位 Gas 的价格“。如果你跑去最近的加油站告诉收银员,“每升油我就愿意支付 5 分钱”;好一点的收银员可能就一笑而过,而理智的收银员可能会报警。所以想要了解设计机制,你需要知道矿工节点的工作以及手续费是什么。

矿工

  区块是包含一组交易集合的数据结构,而以太坊中的矿工节点负责创建链上的区块。创建区块的时候,矿工会从交易缓存池(等待打包的交易堆)中选择交易并开始出块。
  我现在不展开以太坊挖矿算法的细节(也许后续文章会讨论),大家只需要记住挖矿是个昂贵的过程,所以如果挖矿没有回报,那肯定没有矿工愿意干!
  在以太坊中,每当矿工成功创建一个区块,就能获得定额的出块奖励及引用叔块的奖励(不在此展开),同时还能获得包含在这个区块中的所有交易的手续费;所以交易中的 gasPrice 设置得越高,矿工就能得到越多交易手续费。如果你想要知道关于矿工奖励的更多信息,请参考此处
  例: 我们假设一个简单的场景。Bob 的账户里有 200 wei,John 的账户里有 100 wei,他俩都想要发送一笔需要耗用 90 Gas 的交易。
  Bob 设置 gasLimit = 100,gasPrice = 2;John想将 gasLimit 设为 200,但不幸的是他只有 100 wei,这样设置会使得交易固定成本高于账户余额;所以John 最终设 gasLimit =100, gasPrice =1。
  当进入选择交易打包进块的环节时,矿工倾向选择手续费更高的交易。在我们的例子中,Bob 的 gasPrice 比 John 的高两倍;因为两笔交易都需要 90 Gas,所以矿工选择 Bob 的交易能获得两倍的手续费奖励。
-矿工会选择 gasPrice 最高的交易-

-矿工会选择 gasPrice 最高的交易-

  由交易发送方付费来奖励矿工的机制,在以太坊中形成一种能自我调节的经济体系。交易发送方千方百计想要降低交易成本,而矿工总是希望收益最大化,两者形成一种平衡。作为交易发送方,如果你把 gasPrice 设得越高,意味着矿工越有动力打包你的交易,则你的交易能越早被装进区块。
  有的矿工甚至会设置自己的 gasPrice 下限,直接忽略那些 gasPrice 小于下限的交易。
  当发送交易时,我们很难知道当前有效的最小 gasPrice 是多少。这些工具能够扫描整个以太坊网络,算出当前其他交易的 gasPrice 均值,帮助发送方选择能被矿工接受的合理 gasPrice。

结论

  从本文中,我们学到了就像汽车消耗燃油一样,执行以太坊交易需要消耗 Gas。希望本文能让大家了解计算需要成本,以及为什么我们需要付费才能获得以太坊的服务。
  我们还探讨了 gasPrice 和 gasLimit 的重要性;如果智能合约出现 bug 或估算错误,gasPrice 能保护使用者避免平白损失以太币。
  再者,我们还研究了交易手续费背后的经济机制,以及矿工如何选择交易以达到收益最大化。现在我们知道如何调整 gasPrice ,让自己发出的交易更吸引矿工,从而使得交易被更早打包。

原文链接:

  1. https://www.lucassaldanha.com/ethereum-yellow-paper-walkthrough-3-7-gas-and-payment/
  2. https://ethfans.org/posts/ethereum-yellow-paper-walkthrough-3-7-gas-and-payment

以太坊黄皮书(四)

  这次我们要聊的话题是——以太坊上的交易是如何执行的。在本文中,我们会学到交易验证规则,以及它们存在的原因;接着深入理解交易的执行过程,和节点在验证交易时采取的每个步骤。

介绍

  在第一篇博文里,我们了解了以太坊的状态转换函数,还有以太坊如何通过连续的状态转换实现计算机的功能。
  简单来说,状态转换函数使用当前的状态及交易作为输入,计算出下一个状态。
σ t + 1 ≡ γ ( σ t , T ) (1) \sigma _{t+1 }\equiv \gamma ( \sigma _t,T) \tag{1} σt+1γ(σt,T)(1)

-以太坊状态转换函数-
  在深入了解以太坊中的节点如何执行交易之前,我们先聊聊一笔交易是如何被验证的。

交易验证

  在执行交易之前,节点会先验证该交易是否满足一些基本(固有)规则。如果连这些基本规则都通过不了,节点就不会执行该交易。
  这些交易的固有规则如下:

  1. 满足 RLP 编码格式
  2. 具备合法签名
  3. 具备合法 nonce (与交易发送方的当前 nonce 值相同)
  4. 执行交易的固有成本(intrinsic cost)小于该交易设置的 gas 上限交易
  5. 发送方的账户余额大于等于交易所需的预付款

   还有一条规则,它不属于交易固有规则——如果一系列已准备好打包到区块中的交易,加上这条交易之后,会使得所有交易的总 Gas Limit 超过区块的 Gas 上限,那么该笔交易就不能和那些交易一起打包到一个区块中。
  让我们展开每一条规则,来解释这些规则如何运作及为何存在。

交易必须符合合规的 RLP 编码

  这条规则可能最好直观理解。RLP (Recursive Length Prefix,又称为递归长度前缀编码)是一种用于序列化以太坊中的对象的编码方法;和其他方法相同,如果你不按照 RLP 对物件编码,则无法对该物件进行解码,你也就无法通过数据编码得到原始对象的信息。
  该规则的目的是确保以太坊客户端收到交易后,能够成功解码并执行。

交易必须具备合法签名

  假设你的以太坊账户中有很多以太币,现在有人试图发起一笔交易,从你的帐户把钱转走以据为己有,你怎么看?你绝对不想看到有人冒充你,并偷走你的钱,这就是为什么我们需要交易签名。
  以太坊采用非对称加密,确保只有实际控制者能够从账户发起交易。与此同时,这种密码学工具还能让其他人验证该交易的确是由账户的实际控制者发起。
  我不会展开讨论 ECDSA (以太坊选择的非对称密码算法)的细节,因为我们只需要知道最基础的概念就行。
  在非对称密码学中,公钥和私钥是成对存在的。私钥应该完全保密,而公钥可以分享给任何人;私钥可以用来进行签名,这个签名可以用对应的公钥加以验证。在以太坊上签署一笔由你发起的交易,就相当于为在一封你写的信上签名,不同之处在于密码学签名比手写签名还要难伪造得多!
  在以太坊上,账户地址根据个人的公钥来生成。当发送一笔交易时,私钥被用来签署交易(还记得 v 、r 、s 这几个包含在交易里的值吗?),接着所有节点就能确定这笔交易是不是真的由关联账户的私钥所有者签署的。
  不具备合法签名的交易没有任何执行的意义,因此必须有合法签名就成了交易的固有规则之一。

交易 nonce 和账户 nonce 必须匹配

  在以太坊中,账户 nonce 值代表该账户发送的交易数量(如果是合约账户,则 nonce 值指的是账户所创建的合约数量)。如果没有 nonce ,同一笔交易可能被错误地执行多次(也就是所谓的 “重放攻击”)。考虑到以太坊的分布式特性,不同的节点可能会试图把同一笔交易打包进不同的区块,将重复的交易上链。假设一笔你把钱转给某人的交易被误打包了两次,导致你重复转了两次钱,你心里一定很不是滋味。
  每当用户创建一笔新的交易,他们必须设置能匹配当前账户 nonce 值的交易 nonce 值,当执行交易时,节点会检查交易 nonce 是否匹配账户 nonce 。
  如果因为某些原因,导致同一笔交易被重复提交给节点,此时,因为账户 nonce 值已经增加,所以重复提交的交易会被视为不合法。
  以太坊强制要求交易 nonce 值与账户 nonce 值匹配,这么做除了能避免重放攻击,还能确保一笔交易只会执行及改变状态一次。

交易的固有成本必须小于该交易设置的 gas 上限

  在前一篇博文中,我们说明了为什么使用以太坊需要付费,以及 gas 的概念。总的来说,每一笔交易都有与之关联的 gas ——发送一笔交易的成本包含两部分:固有成本和执行成本。
  执行成本根据该交易需要使用多少以太坊虚拟机(EVM)的资源来运算而定,执行一笔交易所需的操作越多,则它的执行成本就越高。
  固有成本由交易的负载( payload )决定,交易负载分为以下三种负载:

  • 如果该交易是为了创建智能合约,则负载就是创建智能合约的 EVM 代码
  • 如果该交易是为了调用智能合约的函数,则负载就是执行消息的输入数据
  • 如果该交易只是单纯在两个账户间转账,则负载为空

  假设 Nzeros 代表交易负载中,字节为 0 的字节总数;Nnonzeros 代表交易负载中,字节不为 0 的字节总数。可以通过下列公式计算出该交易的固有成本(黄皮书 6.2 章,方程式 54、55 和 56):
  固有成本 = Gtransaction + Gtxdatazero * Nzeros + Gtxdatanonzero * Nnonzeros + Gtxcreate
  在黄皮书的附录 G 中,可以看到一份创建和执行交易的相关成本的费用表。与固有成本相关的内容如下:

  • Gtransaction = 21,000 Wei
  • Gtxcreate = 32,000 Wei
  • Gtxdatazero = 4 Wei
  • Gtxdatanonzero = 68 Wei (在伊斯坦布尔升级时会改为 16 wei)

  当我们了解固有成本是什么,就能理解为什么一旦交易的固有成本高于 Gas 限制,则该交易就会被视为非法。Gas Limit 规定了一笔交易在执行时,能够消耗掉的 Gas 上限;如果还没开始执行该交易前,我们就知道它的固有成本高于 Gas 上限,那我们就没有理由执行这笔交易。
(可以看成:一个交易中。我设定的gas上线是10gas,当我执行合约时候,合约需要的成本是12gas,那么就是固有成本高于交易中的愿意交付的gas,这样就不会进行相关的操作。)

交易发送方的账户余额必须大于等于交易所需的预付款(交易所需的gas+转账数目:相当于手续费和转账数目)

  交易预付款指的是在交易执行前,从交易发送方账户,预先扣除的 Gas 数量。
  我们可以通过下面的公式算出交易预付款:
预付款 = gasLimit * gasPrice + value

   一笔交易的 Gas Limit,指的是交易发送方愿意花在执行该交易上的 Gas 最大值;Gas Price 指的是每一单位 Gas 的单价;交易 Value 指的是发给消息接收者的 Wei 的数量(例如转账金额),或是投入要创建的合约中的准备金。如果要进一步了解什么是 Gas ,以及为什么执行交易要耗费 Gas,可以看我们前一篇博文。
  因为交易预付款在交易执行前就会先扣除,所以一旦交易发送方的账户余额少于预扣额,这笔交易就没有执行的必要了。

交易的 Gas Limit 必须小于等于区块的 Gas 上限

  这条规则不属于固有规则,不过这是节点在选择要打包的交易时,需要遵守的基本要求。区块 Gas 上限是能够 “装在” 该区块中的交易所用总 Gas 数的上限。
  当节点在选择要打包的交易时,节点必须确保加入这笔交易后,区块里的交易所用总 Gas 数不会超过区块 Gas 上限。对于要被打包的交易来说,其 Gas Limit 加上其他交易的 Gas Limit 总和,必须小于等于区块 Gas 上限。当然,如果有一笔交易不能被打包进入当前区块,它还是有机会被后面的区块打包的。

执行交易

  验证完交易之后,是时候执行它了。在以太坊中,执行交易会改变状态——好几笔交易被打包进一个区块,每个区块就相当一个交易列表;当交易被顺序执行后,会输出新的合法状态。
  交易按照以下步骤执行:

  • 将发送者账户 nonce 值加 1;
  • 从发送者账户扣除交易预付额( gasLimit * gasPrice );
  • 确定该交易能够用于执行的 gas 值(gasLimit - intrinsic cost);
  • 执行该交易包含的操作(转账、调用或创建智能合约);
  • 通过 SELFDESTRUCT 和 SSTORE 函数对发送者退款;
  • 退还交易发送者任何未使用的 gas;
  • 向受益人账户(通常属于挖出包含该交易的区块的矿工)转入挖矿收益。
      增加发送者账户的 nonce 值
      每当发送一笔交易,发送者账户 nonce 就会增加。这个操作在交易执行之初就会完成,如果交易执行失败,则账户 nonce 值回滚。
      从发送者账户扣除交易预付额
      我们会从发送者账户余额里扣除交易预付额,这个机制很简单——由发送者为自愿付出的执行交易成本(gasLimit * gasPrice)付费。
      计算可用于执行交易的 gas
      交易的 gas 总额(gas limit)扣掉固有成本后,剩下的就是可用于执行交易的 gas 。
      执行该交易所包含的操作
      执行交易还涉及 EVM 的操作列表,其中唯一完全不需要 EVM 操作的交易——就是普通转账。
      每一项 EVM 操作都有对应的 gas 成本;在交易执行过程中,每做了一项 EVM 操作,就会从可用 gas 中扣掉对应的 gas 成本。直到下列两种情况中的一种出现才停止:
  • 可用 gas 被耗尽,执行失败
  • 执行结束后可用 gas 还有剩,或是刚好为零
      通过 SELFDESTRUCT 和 SSTORE 函数对发送者退款
      在以太坊中,SELFDESTRUCT 操作码用于销毁不再需要的智能合约。每销毁一个合约,执行者能够收取 24,000 Wei 。
      同样的,当使用 SSTORE 操作码写入 0 (有效删除值)的时候,操作者每写入一个 0 ,就能收取 1500 Wei 。
      关于退款,有件有趣的事情是,退款也有上限。该上限确保矿工可以算出执行交易所需的计算时间的上界。(更多关于 gas 费用和退款的详细说明,可以在以太坊的设计合理性一文中找到)。
      还有一个重点是,必须在交易所包含的操作都执行结束后,才会进行退款。因此任何应该返还的 gas 都不会被交易执行过程所消耗,从而避免了可能出现的永远不会耗尽 gas 的交易
      退还交易发送者任何未使用的 gas
      如果用于交易的预付款超过交易所使用的 gas,则发送方有权在执行交易后收回剩余的 gas。
      向受益人账户支付矿工费用
       执行交易所使用的所有 Gas 被视为交易手续费,由矿工获得。这种机制激励矿工持续出块,并在网络安全层面永续合作。

结语

   在本文中,我们详细讨论了交易的验证及执行(黄皮书第 6 章内容)。在第 7 、8 章会介绍更多类型的交易(合约创建及调用),我后会继续更新关于这几个章节的博文。
   我相信,想要彻底了解交易验证及执行的细节,最好的方法就是阅读随便一个实现协议的以太坊客户端源码。作为 Besu 的贡献者,我很熟悉它的实现,所以我建议即使你不是非常精通 Java ,你还是可以看看它的源码。可以从这两个部分开始读起:

MainnetTransactionValidation.javaMainnetTransactionProcessor.java

原文链接https://www.lucassaldanha.com/transaction-execution-ethereum-yellow-paper-walkthrough-4-7/

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值