本文介绍了以太坊虚拟机(EVM)的基础知识,包括gas的度量和作用、智能合约的工作原理、EVM的组成和如何维护全局状态,以及EVM架构的关键组件:堆栈(Stack)、内存(Memory)、存储(Storage)和调用数据(Calldata)。
大多数开发者每天都在使用以太坊,但从未思考过是什么在真正运行他们的代码。 在底层,每一个交易、每一次智能合约调用,以及每一次存储更新都由 以太坊虚拟机(EVM) 处理,这是一个存在于每个节点内部的沙盒计算机。 在这篇文章中,我们将剖析基本原理: 1. Gas 真正衡量的是什么,以及为什么它很重要 2. 智能合约是如何工作的,以及为什么它们是无需信任的 3. EVM 是什么,以及它是如何维护全局状态的 4. 其架构的关键组成部分:堆栈、内存、存储和 calldata 最后,你将对以太坊实际如何执行你的代码有一个清晰的心理模型,并且你将为本系列的下一部分做好准备,在那里我们将以字节级别解码 calldata 和 bytecode。
合作
我目前对区块链、智能合约和全栈系统方面的合作和开发项目持开放态度,如果你正在构建一些有趣的东西,请随时在 LinkedIn 上联系
以太坊中的 gas 是什么?
Gas 是用于衡量以太坊虚拟机(EVM)中操作所需的计算工作量的单位。 要在以太坊上执行交易,你必须支付称为 gas 的费用。 Gas 涵盖两件事:
- 执行交易的计算成本(例如,写入存储、调用合约)
- 将你的交易纳入区块的包含成本
EVM 中的每个操作,从简单的数学运算到存储数据,都会消耗特定数量的 gas。 Gas 费用较高的交易会被验证者优先考虑,这意味着你愿意支付的越多,你的交易就越有可能被包含在一个区块中。
Gas 费用通常以区块链的原生代币(以太坊上的 ETH)支付,并确保计算资源定价合理,防止垃圾邮件,并保持网络安全。
下表摘自 以太坊黄皮书,大致说明了特定 操作码 消耗多少 gas。
按回车键或单击以全尺寸查看图像
Gas 费用是用户为补偿以太坊网络完成的计算工作而支付的费用。 就像Gas为汽车提供动力一样,gas 为智能合约的执行和交易提供动力。 交易越复杂,所需的 gas 就越多。
Gas 费用还可以保护网络免受垃圾邮件和滥用,并激励开发人员编写高效、优化的代码。
Gas 以 Gwei 衡量,Gwei 是 ETH 的一个很小的单位
(1 ETH = 10 亿 (1,000,000,000) Gwei)。
智能合约是存储在区块链上的自执行程序。 当满足预定义的条件时,它会自动运行,无需中介,无需手动批准。 可以将其视为强制执行各方之间协议的数字逻辑。 智能合约通过依赖代码而不是中心化机构来实现参与者之间无需信任、透明的交易。 在以太坊上,大多数智能合约都是用 Solidity 编写的,这是一种专门为 EVM 设计的高级语言。 EVM 确保每个合约在每个节点上以相同的方式运行,从而在整个网络中维护单个全局状态。 它定义了将区块链从一个有效状态移动到下一个状态的规则,一次一个区块。 EVM 在一个 沙盒环境 中运行智能合约,这意味着字节码在完全隔离的情况下执行,无法访问宿主机的网络、文件系统或进程。 这确保了整个网络的安全性和确定性。 合约中的每个指令都有一个预定义的 gas 成本,并且当 EVM 处理每个指令时,它会跟踪消耗的总 gas。 为了运行这些指令,用户必须附加足够的原生货币(ETH)来支付作为 gas 费 支付的执行成本。
以太坊虚拟机(EVM) 是以太坊执行智能合约的核心组件。 它运行称为 操作码 的底层指令,这些指令在以太坊的正式规范(黄皮书)中定义。 虽然其中许多操作码类似于传统的虚拟机指令,但以太坊还包括自定义操作码,这些操作码支持链上逻辑、存储访问和智能合约行为。 每个以太坊执行客户端,如 Geth、Nethermind 等…(这些将在后面介绍)世界状态。 EVM 在以太坊中发挥着两个关键作用:区块处理 和 交易执行。
当用户发送交易以与合约交互(例如调用函数)时,EVM 会读取字节码,逐步按照指令执行,并在有足够的 gas 来支付计算费用时更新状态。 Gas 限制确保合约不会永远运行,并且每个操作都有成本。
免费加入 Medium 以获取此作者的更新。 EVM 作为 基于堆栈的虚拟机 运行,由几个重要的组件组成:
其中一些组件(如堆栈和内存)仅在执行期间存在,而另一些组件(如存储和代码)则在各个区块之间持续存在。 它们共同构成了以太坊上每个智能合约运行的隔离的、确定性的环境。
按回车键或单击以全尺寸查看图像
EVM 架构(来源:EVM Illustrated)
栈是 EVM 执行模型的核心部分。 它是一个易失性、后进先出(LIFO)的结构,用于在交易执行期间在操作码之间传递数据。 每个栈项都是 256 位(32 字节),栈最多可以容纳 1024 项。
它用于基本操作,如 PUSH、ADD、POP 等。 如果你尝试使用超过允许的栈空间,或者在执行后留下错误数量的项,则交易将因栈错误而 回滚。
栈不能存储复杂类型,如数组或映射,因此内存用于此目的。 并且像 EVM 中的所有其他东西一样,栈操作消耗 gas。
按回车键或单击以全尺寸查看图像
EVM 架构(来源:EVM Illustrated)
EVM 中的 内存 是 临时 和 线性的。 它仅在单个交易期间存在,并在之后被清除。 它用于存储不适合栈的数据,如数组、字符串或复杂计算中的中间值。
内存以 32 字节的 字 运行,并且可以使用诸如 MSTORE 和 MLOAD 之类的操作码访问。 虽然内存成本随着大小的增加而增加,但它仍然 比存储便宜,使其成为执行期间短寿命数据的首选。
按回车键或单击以全尺寸查看图像
EVM 架构(来源:EVM Illustrated)
存储 是以太坊的 持久状态。 它保存着超出单个交易的合约数据。 它将 256 位密钥映射到 256 位值,并存储诸如余额、变量和合约状态之类的内容。
与内存和栈不同,存储 不是易失性的。 它存储在一个称为 Merkle Patricia Trie 的特殊结构中,该结构允许以太坊有效地跟踪和验证全局状态。
存储写入在 gas 方面 很昂贵,因此仅在需要长期保存数据时才使用它。
按回车键或单击以全尺寸查看图像
EVM 架构(来源:EVM Illustrated)
Calldata 是 EVM 中的一个 只读 数据位置,用于保存外部函数调用的输入参数。 它与交易一起传递,并且在执行期间无法修改。
当交易被发送到智能合约时,交易中的 **data** 字段 告诉合约要运行哪个函数以及使用哪些参数。
data 字段的前 4 个字节 被称为 函数选择器。 此值告诉 EVM 要调用合约中的哪个函数。
(你可以在 4byte-directory 中搜索著名的选择器)
内存、存储和 Calldata 总结示例:
contract CalldataExample {
string public storedName;
function setName(string calldata _name) external {
// _name 是只读的,存在于 calldata 中
string memory tempName = _name; // 如果需要操作,复制到内存
storedName = tempName; // 保存到持久存储
}
}
这里发生了什么:
_name 通过 calldata 传入,它是只读的,不能直接更改。string memory tempName = _name; 将其复制到 内存,以便我们可以使用它。storedName = tempName; 存储在合约的 存储 中。
- 原文链接: medium.com/@andrey_obruc...
- 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!