Skip to main content

第二章 以太坊:执行层 (EL)

以太坊,一台去中心化的全球超级计算机,其使命是为去中心化应用(dApps)提供一个强大的计算平台。它的独特魅力在于,它不受任何单一可信第三方的控制,而是由遍布全球的全节点用户网络共同维护。这些全节点用户就像是一群无私的守护者,共同协作推动着这台计算机从一个状态跃迁到另一个状态。想象一下,这就像是一片云端上的共享计算资源,不是由谷歌或亚马逊等巨头所掌控,而是由成千上万的分布式志愿者共同运营。全节点,就是用户运行的以太坊软件客户端,这些客户端不仅执行交易,还保存着这台超级计算机的历史记录——也就是我们熟知的区块链。要成为这样一个全节点,用户需要运行两个关键的软件组件:一个是执行客户端,另一个是共识客户端。在本章,我们将深入探讨以太坊的执行层(Execution Layer 简称 EL),这是以太坊协议中负责处理交易执行和世界状态存储的部分,我们将以全节点的身份贯穿本章的讨论,因为这样能够让我们获得对以太坊最全面和深入的理解。

世界状态

想象一下,如果我们现在停止以太坊世界计算机。在这一特定时间点的快照就是世界状态。世界状态是一个数据库,它将地址映射到用户账户和应用程序账户(见图2.1)。更正式地说,以太坊的世界状态是一个数据库,它将以太坊地址映射到外部拥有账户(用户账户)和合约账户(应用程序账户)1。每当有新的事情出现时,世界状态就需要更新。如果一个外部拥有账户向另一个外部拥有账户发送以太币(ETH),那么这就会改变以太坊的世界状态。如果一个EOA与合约账户交互,这也会导致世界状态的变化。基于用户发起的所有交易,世界状态每12秒更新一次。

设想我们此刻暂停以太坊这台全球计算机,在这一瞬时的定格,我们所捕捉到的就是所谓的“世界状态”。世界状态是一个精心设计的数据库,它将特定的地址与用户账户和应用账户相关联(参见图2.1)。更精确地讲,以太坊的世界状态是一个将以太坊地址映射到外部所有者账户(即用户账户)和合约账户(即应用账户)的数据库。每当网络中发生新的事件,无论是交易还是智能合约的交互,世界状态都需随之更新。例如,当一个用户账户向另一个账户转账以太币时,这一行为即刻反映在以太坊的世界状态上。同样如果一个用户账户与合约账户进行交互,也会触发世界状态的更新,这种更新是动态的大约每12秒就会根据用户发起的所有交易进行一次,确保以太坊网络的账本始终保持最新状态。

图 2.1 以太坊世界状态

外部所有者账户(EOA)即用户账户,是用户可以创建交易的账户类型,它们由私钥进行控制。与现实世界的身份无关,EOA 并非与您的个人身份直接关联。以太坊软件利用公钥密码学技术为您生成一个虚拟身份,而非使用您的实际身份信息,公钥密码学在区块链技术中扮演着创建匿名身份的角色。这些外部所有者账户通过一对密钥进行管理,包括一个私钥和一个与之相关联的公钥,它们之间存在一种加密学上的联系。私钥的作用类似于密码,它们授权您访问以太坊账户,并允许您执行各种操作,比如发送交易或与应用程序进行交互。至关重要的是,私钥绝不应与他人共享。与密码不同,您需要对私钥的安全负全责。如果您不慎丢失、删除私钥或它们被盗,将无法恢复。这正是比特币界广为流传的格言所强调的:“非你之钥,非你之币。”

从技术角度来看,以太坊账户的私钥是一个256位的数字(介于1和2^256之间),你可以将其想象为一个庞大到令人难以置信的数字范围,以至于不会有两个人最终拥有相同的数字。对于你的计算机来说,一个256位的数字是一长串的二进制0和1,但这个二进制数字被转换成了对我们来说更易于管理的十六进制形式。十六进制是一种基数为16的数字系统,使用0到9以及A到F来表示,以压缩表示大的二进制数字,以下是以太坊账户的私钥以十六进制表示的例子:

a37efb76efceae747a746f64ef25f9f8f622f57d754f397705425dbe28f901b4

大多数以太坊钱包创建了一个被称为助记词组的东西,它可以用来推导出多个账户的私钥,我知道这听起来有些复杂,但实际上这意味着你不会经常遇到原始的私钥,而是会经常接触到助记词组。下面是一个助记词组的例子:

heart forest bird damp abandon soap bird holiday poverty expire grant keep

大多数以太坊钱包会使用一个种子短语,通过它可以派生出多个独立的账户,这意味着即你只需记住一个种子短语,就可以管理多个账户,简化了账户管理的复杂性。

在身份验证的对立面,我们有公钥的概念。专业术语中,公钥是通过椭圆曲线密码学(ECC)算法,基于私钥生成的坐标集合,ECC 算法具有不可逆的特性。我可以利用私钥生成公钥并展示给你,但你无法通过公钥来逆向推导出原始的私钥,这种单向性确保了使用公钥进行加密的安全性,因为只有对应的私钥才能解密信息。

公钥密码学技术赋予我们构建数字签名体系的能力,数字签名是一种利用私钥对交易进行加密的密码学签名方式,通过公钥我们可以对这些签名进行验证。数字签名体系的运作流程如下:

i. 软件自动生成用户的私钥。 ii. 软件根据私钥生成相应的公钥。 iii. 用户利用其私钥为交易生成数字签名。 iv. 网络通过公钥来验证交易的数字签名是否由相应的私钥所生成。

以太坊的交易被发送至以太坊地址,通俗来讲,这些地址是账户的识别码。按照技术定义,以太坊地址是由公钥的末端20字节转换成的42位十六进制数,并且以"0x"作为前缀。以下是一个示例地址的格式:

0x5e16Fa36555B428823d3Ed32aa7CbB07a92F301B

以太坊在其发展路线图中,展望了向一种名为账户抽象(Account Abstraction)的更直观、更易于用户操作的账户体验过渡的愿景,当你正在阅读这段文字时,这一创新可能已经成为现实。

图 2.2 外部账户

希望账户抽象化能让用户对自己的账户拥有更多的控制和灵活性,例如私钥的社交恢复功能。 外部拥有账户(EOA)具有以太坊地址和相关的账户状态2。EOA 包含两个字段:计数器(Nonce)和余额,以太坊账户中的计数器(Nonce)是一个递增的数字,显示账户进行了多少笔交易以及创建了多少个合约,计数器(Nonce)用于确定同一时段内交易的顺序,余额则是账户持有的以太币数量,我们可以推断追踪世界状态的部分目的是为了追踪当前 EOA 的交易计数和余额(图 2.2)。

合约账户

要理解合约账户,我们首先需要明白它们是做什么的,即智能合约。直观地说,智能合约是可以在以太坊上运行的程序,你可以将它们想象为专门为以太坊的独特属性设计的计算机程序。在这个背景下,智能合约吸引人之处在于我们知道智能合约的代码对每个人来说总是以相同的方式执行,使它们具有确定性,确定性意味着对于一个用户的相同输入将为另一个用户产生相同的结果,这就是所谓的协议或合约。进一步讲,智能合约是一组称为操作码(Opcodes)的指令集,操作码是由以太坊虚拟机(EVM)(稍后详述)处理的指令或命令。例如,EVM 的操作码包括 STOP(停止执行)、ADD(加法操作)和 MUL(乘法)。智能合约通常使用专为以太坊设计的高级编程语言编写,如 Solidity 和 Vyper,这些语言与我们熟知的 JavaScript 或 Python 有相似之处。其中,Solidity 因其流行度而成为我们讨论的焦点,Solidity 合约代码在部署之前会被编译成 EVM 字节码,EVM 字节码是以太坊世界计算机能够读取和执行的低级编程语言,这听起来可能很复杂,但它的意思是开发者使用更符合人类语境的高级语言(如 Solidity)编写代码,而 EVM 则以低级的“计算机”语言——EVM 字节码来读取和执行,字节码中的“字节”部分指的是每个操作码指令都是一个字节,即一小块数据3

图 2.3 合约账户

合约账户是已经在以太坊上部署的智能合约,它们由自身的内部代码控制(见图2.3)。合约账户拥有一个以太坊地址和一个相关的账户状态,合约账户包含一个状态,该状态由四个字段组成:计数器(Nonce)、余额(Balance)、代码(Code)和存储(Storage)。计数器是一个递增的数字,显示这个合约账户创建了多少其他合约;余额是指合约账户持有的以太币(ETH)数量;代码是包含在账户中的智能合约代码,当创建合约账户时,智能合约的代码会被哈希处理,并永久存储在区块链上,这意味着它是不可变的。每当外部所有者账户(EOA)触发合约账户时,以太坊虚拟机(EVM)会调用这段代码并根据交易的输入来执行它。此外,合约账户还拥有存储功能,它详细记录了合约账户的所有数据,每当处理涉及合约账户的交易时,所产生的任何变化都会被永久性地记录在合约的存储空间中。

以太坊交易

以太坊交易主要分为两种类型:消息调用(Message Call),来自外部所有者账户(EOAs)的消息调用传达了对以太坊世界状态变更的期望,变更涉及两个字段:Value 和 Input Data。Value 字段是指当一个 EOA 向另一个 EOA 发送以太币(ETH)时,其本质是在请求调整各自账户在以太坊全局状态中的余额,无论接收方是另一个 EOA 还是一个合约账户。 Input Data 字段是一个包含交易信息的十六进制字符串,根据交易类型的不同输入数据有两种类型,EOA-EOA 交易可以在这个字段中包含任意数据,用于传递信息或对交易进行标记。尽管通常在 EOA-EOA 交易中,这个字段是留空的,而在 EOA-合约或合约-合约交易使用该字段来包含输入数据,这包括要调用的合约的代码以及对存储的任何预期变更。值得注意的是,尽管合约账户可以相互进行交互,但所有这些交互的初始交易必须源自一个 EOA。

第二种交易类型是合约创建,它请求在以太坊世界状态中添加一个新的合约账户,创建合约的交易被发送到一个空地址,因为这里没有接收方。合约创建完成后,其地址将基于发送者的地址和发送者账户中最新的计数器值生成,确保了地址的唯一性和可预测性。

以太币和交易费用

以太坊区块链的货币称为以太币,其符号是 ETH(发音类似于“牙齿”中的“-eth”),货币单位是 1以太币(ETH),它可以细分为最小单位——微以太币(wei)。

wei1
ada10^3
babbage10^6
shannon / gwei10^9
szabo10^12
finney10^15
ether10^18

实际上,大多数人只会接触到 wei、gwei 和 ether。Gwei 用于表示交易费用的单位,在以太坊网络中,wei 是最小的货币单位,而 gwei 则是一个更常用的单位,等于 1000000000000 wei,通常用于计算交易费用,Ether 则是以太坊的主要货币单位,1 ether 等于 1000000000000000000 wei。

以太坊团队决定进行一次以太币(ETH)的预售4,这次预售从2014年7月22日开始,持续了42天,直到2014年9月2日结束。在预售期间投资者可以用1个比特币(BTC)购买2000个 ETH,14天后这个比例变为了 1 BTC 兑换 1337 ETH,预售期间共有6000万个 ETH 供投资者购买,另外大约有1200万个 ETH 被留作创始人、早期贡献者和以太坊基金会的份额。以太坊基金会是一个致力于构建以太坊生态系统的非营利组织,这次预售总共筹集了大约1830万美元,在撰写本文时大约有1.2亿个 ETH 被发行。

ETH 的实际用途之一是支付交易费用,自2021年8月的伦敦升级(London Upgrade)和 EIP-1559 提案实施以来,以太坊的交易费用机制发生了变化,我强调这一点是因为在网上经常会看到一些描述2021年之前情况的解释。

要理解交易费用,首先需要区分 “gas 成本” 与 “gas 费用” 这两个概念。在以太坊网络中,计算操作是通过一个称为 “gas” 的单位来计量的,但请注意这里的 “gas” 并不是货币单位,gas 成本仅与所需的计算操作周期数量相关,在交易中每个计算指令(即操作码)都对应一定的 gas 成本,例如执行一次交易的标准成本是 21000 个 gas 单位,而与合约账户的交互通常需要更多的操作码指令,从而产生额外的 gas 成本。例如执行 ADD 操作需要消耗3个 gas,DIV 操作需要5个 gas,而查询账户余额的 BALANCE 操作则需要700个 gas,用户在发送交易时会设定一个“gas 限制”,这是他们愿意为交易支付的最大计算量,即 gas 单位的上限。

每消耗一个 gas 单位,便会产生相应的 gas 费用,这些费用以 ETH 的微小单位——gwei(即十亿分之一个 wei 或 0.0000000001 ETH)来计价。燃气费用分为两部分:基础费用和优先费,基础费用是网络根据算法动态调整的 gwei 成本,用户还可以选择支付额外的优先费,以加快其交易的确认速度。交易费用的总额是按照公式:gas 成本 × (基础费用 + 优先费)来计算的。比如:我打算向另一个以太坊账户发送 2 ETH,那么按照每笔交易 21000 的 gas 成本,当前每单位 gas 的基础费用是15 gwei,我选择支付 2 gwei 的优先费,那么总费用将是 21000 × (15 + 2) gwei = 357000 gwei,相当于 0.000357 ETH。因此我实际发送的金额将是 2.000357 ETH,用户可以设定他们愿意支付的最高费用,如果实际费用更低差额5将退还给他们,尽管大多数以太坊客户端和钱包软件能够自动计算这些费用,但用户也可以选择手动调整。

自伦敦升级实施 EIP-1559 以来,交易费用展现出一种引人入胜的经济现象,基础费用被销毁从而退出流通领域,而优先费用则支付给那些在下一章中将要讨论的验证者用户,其核心思想在于在理想状态下,费用的销毁速度应超过发放给验证者的奖励,这一机制有望减少以太币(ETH)的总体供应量,使其变得更加稀缺进而提升其价值,这一理念被广泛称为“超级稳健货币”论断。

交易结构

整合这些信息后,我们可以观察到以太坊交易包含以下关键信息:

  • From6:指明交易的发起方地址。
  • To:指明交易的接收方地址。
  • Digital Signature:基于发送方的私钥生成的签名,确保交易的安全性。
  • Nonce:记录账户发起的交易次数,确保交易的唯一性。
  • Value:指定要转移的以太币(ETH)金额。
  • Input Data:包括消息调用或合约创建时的输入参数。
  • Gas Limit:设定交易可消耗的最大 gas 量,以控制交易执行的成本和复杂度。
  • Max Priority Fee:你愿意为交易的优先处理支付的最高费用。
  • Max Fee:你愿意为整个交易支付的最高总费用。

以太坊虚拟机(EVM)

以太坊虚拟机(EVM)负责在以太坊上执行交易,虚拟机是对操作系统(OS)的模拟。设想我正在运行 Windows 操作系统,但同时也想在同一台计算机上运行 Linux 操作系统,一个选择是通过分区硬盘来安装两个系统,然而可以使用虚拟机软件在一个操作系统内模拟另一个操作系统,以太坊借鉴了这一概念并进行了创新。以太坊客户端开发者开发了如 Geth 或 Nethermind 等执行软件客户端,它们遵循统一的规范来执行交易。当运行这些软件时 EVM 在用户自己的硬件上被模拟,就像 Linux 或 Windows 操作系统一样,EVM 的任务是处理最新的交易以实现向一个新的世界状态的过渡,世界状态需要每12秒改变一次,在这些12秒的时间段内会有一个用户(即区块提议者),被选中来提出下一个状态,这一状态被封装在一个称为区块的数据结构中。

以太坊虚拟机(EVM)扮演着中央处理器(CPU)的角色,负责协调状态转换(参见图2.4),状态转换函数涉及一系列操作,这些操作使以太坊能够从一个世界状态过渡到另一个世界状态。EOA 账户之间的交易流程直接明了,处理起来相对简单,仅需满足一系列有效性条件,比如具备有效的签名、合法的随机数(Nonce)以及充足的 gas,然而涉及合约账户的交易则更为复杂,需要 EVM 提供的全部功能来执行。

图 2.4 以太坊虚拟机(EVM)

现在让我们设想一个从外部账户(EOA)到合约账户的消息调用交易场景,在以太坊虚拟机(EVM)中,最关键的部分是输入数据字段,因为它包含了我们期望执行的函数及其所需的参数,Solidity 语言中输入数据字段遵循应用二进制接口(ABI)规范,这是一个标准,它指导智能合约如何解析输入数据,函数是智能合约能够执行的指令,而在 ABI 规范中每个函数都通过其方法标识(MethodID)来唯一标识,参数则代表了请求的变更,例如指定发送地址或转账金额。以下是一个示例展示了 ABI 格式7下输入数据字段的结构。

Function: transfer(address to, uint256 value) MethodID: 0xa9059cbb [0]:0000000000000000000000009f11260cb6427c20a019780d99d3a2d7ffe9a253 [1]:000000000000000000000000000000000000000000000000000000002c8650c0

我们从合约的代码中看到了 transfer 函数,它有自己的方法标识符(MethodID),以及涉及的参数,在这个例子中是接收方地址和值,参数是我们想要改变的具体内容。[0] 是接收方地址,而 [1] 是值。

在字节码中方法标识符(MethodID)位于前面,紧接着是两个参数:

0xa9059cbb0000000000000000000000009f11260cb6427c20a 019780d99d3a2d7ffe9a253000000000000000000000000000000000 000000000000000000000002c8650c0

在交易详情中,“输入数据”字段所包含的信息通常被称作调用数据(Call Data)。

以太坊虚拟机(EVM)通过以下步骤来解释和执行交易中的“输入数据”字段:

虚拟只读存储器(ROM):用于读取(但不会修改)相关合约账户的代码,这部分代码是存储在合约账户中的不可更改的指令集,它定义了合约的运作方式。

接下来的是机器状态,这指的是在处理交易过程中的临时状态,它包含:

程序计数器:程序计数器用于追踪接下来需要执行的合约代码中的指令。 堆栈:以太坊虚拟机采用基于堆栈的执行模型,堆栈遵循后进先出(LIFO)的原则,操作码被推入堆栈顶端,执行后随即弹出为下一个操作码腾出位置。 EVM 内存:这是在机器状态中用于记录变化的临时存储空间,交易处理完成后其中的数据将被丢弃。 Gas 计数器:负责监控 Gas 的消耗情况,一旦 Gas 耗尽将触发回滚操作,并撤销所有提议的状态更改。 交易处理过程中的临时机器状态所得到的计算结果,将引发合约账户中永久存储区域的更新。 存储:对存储的修改被永久性地记录,以体现由临时机器状态转变而来的全新世界状态。 每当一笔交易得到处理后,以太坊虚拟机(EVM)便会按顺序转向下一笔交易,如此循环往复。EVM 会依次执行所有待处理的交易,直至队列为空。在这一过程中,EVM 会产生一个执行有效载荷,该有效载荷概述了 EVM 状态转换函数的输出结果。

智能合约案例研究

智能合约有多种形式,我们可以通过两种常见的标准——ERC-20 和 ERC-721 来加以阐释,然而这并不意味着它们是智能合约的唯一形式。以太坊赋予开发者极大的自由度来创造各式各样的合约应用,因此无法一一列举。请注意我提到的这两个例子都以“ERC”为前缀,它代表“以太坊征求修改意见”,后面紧跟着一个数字。ERC 标准是构建以太坊应用的模板,它们为开发者提供了一套基础代码规范,便于他人借鉴、调整或扩展。这些广泛的模板非常有价值,因为即使是对 Solidity 只有基础了解的开发者(有时甚至是那些不太了解的人)也能利用这些模板快速启动新项目。本文讨论的两个 ERC 标准是针对代币的,但 ERC 标准的应用范围远不止于此。

ERC-20

ERC-20 代币标准定义了一种同质化代币,即每一枚代币都是完全相同的,彼此之间没有区别,类似于您手中的现金或银行卡上的金额,无论是用这张特定的美元还是欧元支付都无关紧要。智能合约开发者在 Solidity 中创建同质化代币时,可以参考 ERC-20 智能合约的标准模板。该模板详细规定了代币的关键信息(如名称、总供应量等)以及外部账户可以执行的函数(如转账、查询余额、查看名称和总供应量等)。部署 ERC-20 智能合约后,这些代码便成为智能合约账户中的程序代码,即 EVM 在虚拟只读存储器(ROM)中调用的不可更改的指令,指导合约如何执行。ERC-20 合约的存储部分则像一个数据库,将外部账户地址映射到合约内的代币余额。每当外部账户向合约发送交易并提供相应的输入数据时,比如进行代币转账,EVM 就会执行必要的代码来更新存储中的余额。简而言之,用户的一笔交易会导致应用程序中代币余额的变动。ERC-20 代币不仅用作货币,它们在后续章节中还将展示出更多巧妙的用途,例如在区块链社区中作为治理或投票的工具。

ERC-721

ERC-721 代币标准与 ERC-20 标准相似,但其核心区别在于 ERC-721 专为创建非同质化代币(NFTs)而设计。与 ERC-20 代币相比,ERC-721 代币各具特色,每个代币都拥有一个独特的标识符,通常是 TokenID 整数。NFT 可能还包含存储在链上的元数据,这进一步明确了其独特性。大多数 NFT 合约使用 TokenURI,这是一个指向存储在外部的元数据或与 NFT 相关联的图像的链接,这些存储服务可能包括点对点(P2P)的星际文件系统(IPFS)或亚马逊网络服务(AWS)等。虽然可以为单一物品创建合约,但通常 NFT 智能合约会构建一个包含相似类型但具有独特差异的物品集合,例如 10000 只各具特色的企鹅。智能合约开发者在创建 NFT 项目时,会详细规定代币的关键信息(如系列名称、总供应量等),以及外部账户可以执行的函数(如铸造代币、转账代币、查询余额、查询名称、查询供应量等)。NFT 项目通常在启动时提供铸造功能,之后 NFT 便可以在用户间进行买卖和转让,而合约的存储部分将记录这些交易和所有权的变更。

去中心化应用(dApps)

去中心化应用(dApps)在以太坊领域中,不仅指代智能合约本身,还包括它与非区块链技术的结合,以提升用户交互体验。这些非区块链技术以某种形式介入,优化了与合约账户的互动。大多数智能合约平台都配备了友好的用户界面,通常表现为常规网站。例如,Uniswap 的智能合约具备代币交换功能,而为了简化与合约的互动 Uniswap 设立了网站,使用户操作更加直观便捷。再如无聊猿游艇俱乐部(BAYC)的智能合约,它定义了铸造和转移无聊猿资产的功能,但这些猿猴形象的图像数据并未直接存储在合约中,而是采用了外部的点对点(P2P)文件存储系统。鉴于以太坊上的存储成本可能较为昂贵,为此类 dApp 扩展存储解决方案是合乎逻辑的。尽管“智能合约”和“dApp”这两个概念常被等同视之,但区分以太坊上的原生智能合约与通过整合非区块链服务而形成的 dApp,对于深入理解其运作机制具有重要意义。

备注

Footnotes

  1. 各种以太坊软件客户端在存储“原始”数据库时采取不同的方法。例如,Geth 执行客户端会将其存储为 LevelDB 数据库。

  2. 以太坊采用递归长度前缀(Recursive Length Prefix, RLP)编码技术来序列化信息,例如账户状态。这种技术将每个所需字段的值按顺序放入二进制形式的序列中。例如,外部所有者账户(EOA)的序列化会包括一个计数器(Nonce)值和一个余额(Balance)值,而合约账户的序列化则会包含计数器、余额、代码(Code)和存储(Storage)。以太坊在其他地方也使用这种技术,比如交易处理,共识层(Consensus Layer)则采用了其自己的版本,称为简单序列化(Simple Serialize, SSZ)。

  3. 一个二进制位组由八个位组成,在十六进制表示法中,这八个位被编码为两个字符。这种表示方法使得数据更加紧凑,便于阅读和处理。

  4. 参见 Buterin V (2014)《启动以太币销售》,可在此查阅:https://blog.ethereum.org/2014/07/22/launching-the-ether-sale

  5. 具体而言,用户可以设定他们愿意支付的最高优先费率或总费用

  6. 虽然从技术层面讲,这些信息并不直接嵌入交易本身,但大多数区块浏览器都能够轻松地推断出这些数据并将其展示出来。

  7. 这笔交易是真实有效的,您可以通过以下链接自行验证:https://etherscan.io/tx/0xa47c82ba29272d82e1de8eec0e287b4584db0c505846a