想象你走进一座没有围墙的数字城市——以太坊。这里没有市长,没有物业,任何人都能落户、摆摊、做生意。你在街上看到的“市民”分为两种:一种揣着钥匙,能自己走路办事,叫外部账户;另一种是立在路边的自动售货机,投币就自动出货,没有人的性格,只有铁的规则,叫合约账户。这座城市的每一笔买卖,都被记录在一本人人持有的公共档案里;而每一份“办事单”要真正生效,都得先加满油、再送进城市中央那一台名叫EVM的引擎里,由它按部就班地执行。这篇文章不堆术语,只带你“逛城”:先认账户,再看交易,然后付Gas、查档案,最后钻进引擎内部,看懂字节码是怎么被一步步消化掉的。逛完你就明白,所谓智能合约,不过是一台永远按规则运行的自动售货机。
对AI从业者来说,理解以太坊与EVM是进入Web3、链上数据分析和去中心化AI应用开发的第一块入门砖,能帮你读懂链上执行的底层逻辑与费用构成。
目录
可视化演示
动态演示拆解以太坊的核心机制,通过基础/核心/应用/扩展四种模式展示关键概念与交互流程。
本节配有交互动效(进入视口后自动加载,约 3KB)。
拆解以太坊:以太坊账 以太坊账户模型与状态结构 Gas机制与交易费用计算 核心机制可视化 拆解以太坊整体架构与关键组件
账户模型:城市里的“人”与“自动售货机”
想象一座向全世界开放的数字城市——以太坊。街上走着两类“居民”:一类是揣着钥匙的市民,能自己走路、主动办事、签名转账;另一类是立在街角的自动售货机,没有腿、没有意志,但只要投币符合条件,就会自动吐出商品。你没法让售货机“主动”来帮你搬东西,却可以把钱和指令塞进去,等它默默按规则执行。这种“有主动能力的居民”与“只会按程序反应的机器”之间的对比,正是理解以太坊账户模型的钥匙。
在以太坊里,“市民”和“售货机”统称为账户(Account)。外部账户(EOA)由一对公私钥控制,谁持有私钥谁就能支配它;合约账户则由一段字节码控制,一旦部署就按代码运行,任何人都无法篡改它的行为。两者都有余额(balance)和nonce(交易计数),合约账户额外保存代码(code)与存储(storage)。当你要转账或调用合约时,必须用私钥对交易签名,因为私钥是唯一能证明“你是这个账户所有者”的凭证。
你可能会好奇:地址是怎么来的?它不是随机生成的,而是由公钥经哈希运算推导而出。地址 = 公钥做Keccak256哈希后,取最后20字节。这个过程是单向的:从地址无法反推公钥,更无法反推私钥,但任何人都可以用公钥验证签名。
市政厅不需要记住你的长相,只需拿你的钥匙指纹现场算出门牌号,对上了就放行。这就是地址生成的巧妙之处——公钥可以公开,私钥必须藏好,谁有钥匙谁就是主人。
- 哈希公钥对公钥进行Keccak256单向哈希,得到64字节的摘要,把“长钥匙”压缩成“短指纹”。
- 截取后20字节从哈希第12字节开始,截取到末尾共20字节,就是通用的0x地址。
交易与消息:办事单的流转
市民要办事,得先填一张“办事单”。单子上写明:这是第几号申请(nonce)、每升油愿意出多少钱(gasPrice)、最多加多少油(gasLimit)、办什么事(to、value、data),最后签上自己的名字。单子填好后投进窗口,由城市系统广播给全网,等待被装进一个“包裹”(区块)。
这个“办事单”在以太坊里就叫交易(Transaction)。交易只能由外部账户发起,合约账户无法主动“发单”。交易的结构包含六个核心字段:nonce、gasPrice、gasLimit、to、value、data。其中to字段为目标地址;如果to为空,则表示这是一笔“创建合约”的交易。合约之间也可以相互沟通,这种内部调用叫消息调用(message call),它不产生独立的Gas字段,而是消耗父交易剩余的Gas——就像一台售货机内部机械臂,不能自己出门加油,只能花掉顾客投币产生的“额度”。
一笔交易的完整生命周期可以概括为四步:构造与签名、广播进内存池、矿工打包进区块、全节点在EVM中执行并更新状态。有趣的是,部署合约时,新合约的地址并非由你指定,而是由“发起者地址 + 发起者交易计数nonce”经RLP编码和Keccak256哈希后,取后20字节得到。
自动售货机的“出厂编号”由“申请人地址+申请序号”唯一确定。谁在第几号窗口申请,机器编号就是对应的哈希,绝不会撞号。
- RLP编码把发送者地址和nonce按RLP规则编码成一段唯一的字节串。
- Keccak256哈希对这段字节串求哈希,打乱并压缩成64字节摘要。
- 截取后20字节取哈希第12字节到末尾,得到合约地址。
Gas机制:燃油经济学
城市里跑车要加油。烧油不只是为了驱动车辆,更是为了防止有人开着车满城乱撞、刷存在感把道路堵死。你跑得越远、载得越重,油耗就越高。以太坊设计了同样的机制:每一笔操作都要消耗“燃油”——Gas。算术运算只需3个单位,读写临时内存只需几到几十个单位,而往持久存储里写一个数据(SSTORE)要花约20000个单位,几乎是算术的几千倍。
为什么存储这么贵?因为链上数据需要被全世界每个节点永久保存,存得越多,整座城市的“档案负担”越大。所以以太坊用价格杠杆鼓励大家少写存储。当你发起交易时,需要设定两个关键参数:gasLimit(这趟最多加多少油)和gasPrice(每升油愿意出多少钱)。矿工按gasPrice排序优先打包高油价的交易,就像加油站高峰期,愿意出更高价的车先进站。
交易结束后,实际费用按如下方式结算:
就像打出租车:路程决定耗油量,单价决定每升油多少钱。半路抛锚或出事故(交易失败),已经跑过的路程照常计费,绝不会因为没到达目的地就免单。
- 耗油量(gasUsed)交易执行过程中,每个操作码消耗的Gas累加之和。
- 油价(gasPrice)你为每个Gas单位支付的单价,由市场供需决定。
- 总费用两者相乘得到最终从账户扣除的金额。
状态存储:档案馆的索引系统
城市的档案馆每天新增海量档案,管理员不能每次确认“某人现在有多少钱”都翻遍整栋楼。于是她设计了一棵神奇的目录树:把每一份档案都做指纹哈希,两两配对、层层向上,最终汇总成一个“总指纹”——根哈希。任何人只要知道这个总指纹,就能快速验证任意一份档案是否被篡改,而无需下载全部档案。
以太坊的“世界状态”正是这样一棵树。它是一棵全局唯一的Patricia Merkle树,把所有账户的余额、nonce、代码哈希和存储根都挂在这棵树上。每个区块头里只保存一个stateRoot字段,相当于整个档案馆的“总印章”。当一个节点同步到新区块时,它会重新执行交易、重算状态树,如果算出的根与区块头不一致,说明数据被篡改或执行出错,这个区块会被拒绝。
合约账户内部的数据也存在一棵独立的存储树中。每棵存储树由无数个256位的“存储槽”(slot)组成,每个槽位都可以存一个256位数字。你可以把存储槽想象成售货机里一个个货道:编号固定,每个货道里放什么由合约代码决定。读存储很便宜,写存储极贵——因为一旦写入,就意味着全世界的档案馆都要为这个货道永久腾出空间。
所谓“防篡改”,靠的不是管理员盯得紧,而是数学上的蝴蝶效应——任何一粒灰尘落在哪片叶子上,都会让总印章彻底变脸。
- 叶子哈希每个账户数据先做一次哈希,得到固定长度的“指纹”。
- 配对拼接相邻两个指纹首尾相连成一个更长的字节串。
- 递归哈希对拼接结果再次哈希,得到父节点;层层向上直到根节点。
EVM执行引擎:城市的心脏
所有办事单最终会被送进市政大厅中央的一台精密机器——EVM(以太坊虚拟机)。它不认得城市风景,不关心天气和心情,只认一条条由字节码翻译成的机械指令。这台机器不是摆设,它运行在全世界每一个以太坊节点中。无论你在北京、东京还是纽约执行同一笔交易,得到的最终状态都完全一致——这种“确定性”是区块链共识的基础。
EVM是一个基于堆栈的虚拟机(Stack Machine)。它有三块截然不同的工作区:堆栈(Stack)是临时操作台,深度上限1024,所有算术都在这里进行,先进后出,像一摞盘子;内存(Memory)是临时草稿纸,按字节寻址,只存在于当前交易执行期间,交易结束后即被清空;存储(Storage)是永久账本,数据会写到链上,由全世界共同保存,因此也最贵。
EVM的执行流程可以理解为一个循环:取指令(PC计数器指向当前指令)→ 解码操作码 → 扣减Gas → 执行操作 → 更新PC并进入下一条。每次扣Gas前,EVM都会检查剩余Gas是否足够:
这台机器每走一步都先看油箱,油不够就立即停车并把一切恢复原状。正是这种“油尽回滚”的规则,保证了全世界数万个节点在各自机器上跑出完全一致的最终状态。
- 本步成本每个操作码都有明码标价,如PUSH是3,SSTORE是20000。
- 扣减余额用当前剩余Gas减去本步成本。
- 回滚判断如果结果为负,说明油不够,触发整笔交易回滚。
字节码与编译:从图纸到机器指令
售货机出厂前,工程师先画图纸(Solidity源码),再翻译成机器能执行的内部动作序列(字节码),最后装到街上通电试运行(部署)。以太坊的智能合约开发也遵循同样的流程。用Solidity写好的合约,交给solc编译器,会生成两段字节码:初始化代码(init code)和运行时代码(runtime code)。部署交易执行的是init code,它的职责是“装修”——把runtime code作为返回值写到链上,从此永久保存。
字节码由一系列单字节操作码(opcode)组成。比如0x60代表PUSH1(压入一个1字节数值)、0x01代表ADD(加法)、0x55代表SSTORE(写入存储)。整个字节码就是一张“机械动作清单”,EVM逐条读取、逐条执行。由于操作码种类有限、行为确定,合约能做什么、不能做什么,其实在你部署的那一刻就被这张清单锁死了。
当用户调用合约函数时,调用数据(calldata)的前4个字节叫“函数选择器”(function selector),它告诉EVM“你该执行清单里的哪一段动作”。选择器的计算方式是对函数签名(如 transfer(address,uint256))做Keccak256,取前4字节:
你发给售货机的每一枚代币,机器看一眼“编号前缀”就知道该掉出哪种饮料。而合约一旦部署,这道“编号规则”就永远刻在链上,谁也不能偷偷改装机器。
- 构造签名把函数名和参数类型写成规范字符串,如
transfer(address,uint256)。 - Keccak256哈希对签名串求哈希,得到64字节摘要。
- 截取前4字节取哈希前4字节作为函数选择器。
AI 应用场景
场景一:智能合约安全审计助手
阶段:代码分析与特征提取(模型推理)
解决的问题:人工审计一份合约往往需要数天,容易漏掉隐蔽的漏洞。AI可以扫描源码和字节码特征(如SSTORE调用位置、CALL的深度),快速标记重入漏洞、整数溢出、Gas陷阱等风险点,把审计时间从几天压缩到几小时。
具体案例:某安全公司用大语言模型读取Solidity源码,再结合EVM字节码反汇编结果,自动生成审计报告并给出高危函数定位。分析师只需复核AI标记的少数位置,效率提升近10倍。
场景二:链上交易行为分析
阶段:数据工程与特征工程
解决的问题:公链上每时每刻产生海量交易,传统SQL无法高效处理非结构化的事件日志和嵌套调用关系。AI工程师需要理解交易结构、Gas消耗和消息调用链,才能把原始数据清洗成可训练的特征。
具体案例:一个量化团队在做MEV(矿工可提取价值)策略时,用聚类算法分析Gas Price分布与交易排队模式,预测抢跑交易的黄金时机。他们先按“外部账户→合约账户→内部消息”的结构解析交易,再训练预测模型。
场景三:去中心化AI推理与Gas预估
阶段:推理上链与成本预估
解决的问题:AI模型跑在链上成本极高,工程师需要精确计算每个操作的Gas才能决策“哪部分上链、哪部分留链下”。理解EVM的存储成本是优化的关键。
具体案例:一个去中心化AI项目要把轻量逻辑回归模型部署到链上,他们通过改写合约、用事件日志代替存储、合并SSTORE操作,把单次推理的Gas费从几百万单位降到几十万单位。
Python 演示
"""迷你EVM风格栈执行器: 演示指令执行与Gas消耗。""" class StackMachine: """用栈模拟EVM的核心执行,记录每步Gas变化。""" def __init__(self, initial_gas=30000): self.stack = [] self.gas_left = initial_gas self.storage = {} self.logs = [] def _consume(self, cost): """扣除Gas,不足则回滚。""" if self.gas_left < cost: raise RuntimeError("out of gas, transaction reverted!") self.gas_left -= cost def push(self, value): """模拟PUSH指令,消耗3 Gas。""" self._consume(3) self.stack.append(value) self.logs.append( f"PUSH {value} stack={self.stack} gas={self.gas_left}" ) def add(self): """模拟ADD指令,消耗3 Gas。""" self._consume(3) b = self.stack.pop() a = self.stack.pop() self.stack.append(a + b) self.logs.append( f"ADD {a}+{b}={a + b} stack={self.stack} gas={self.gas_left}" ) def sstore(self): """模拟SSTORE指令,消耗20000 Gas,写入持久存储。""" self._consume(20000) slot = self.stack.pop() value = self.stack.pop() self.storage[slot] = value self.logs.append( f"SSTORE storage[{slot}]={value} gas={self.gas_left}" ) if __name__ == "__main__": machine = StackMachine() machine.push(1) machine.push(2) machine.add() machine.push(0) machine.sstore() for line in machine.logs: print(line) print("final storage:", machine.storage)
def _consume(self, cost):定义扣Gas方法,所有指令执行前都要先查油表。raise RuntimeError("out of gas, transaction reverted!")油量不足时抛异常,模拟EVM整笔回滚。self.stack.append(value)模拟PUSH:把数字压入栈顶。self.stack.pop()弹出栈顶操作数,模拟算术指令取数。self.storage[slot] = value写持久存储,对应EVM最贵的SSTORE指令。if __name__ == "__main__":保证脚本可直接运行并输出日志。
常见误区
误区我一直以为GasPrice就是交易手续费,填1 Gwei就感觉特别便宜,结果转账时发现扣的钱比想象中多得多。
正解GasPrice只是每单位燃料的单价,总费用=耗油量×单价。真正决定手续费高低的,是你到底执行了多少操作。普通转账约21000 Gas,而一次SSTORE就可能烧掉20000 Gas。所以GasPrice低并不等于便宜,还要看操作复杂度。
为何常见:主流钱包界面只展示一个“矿工费”总额,很少拆解GasUsed和GasPrice,用户自然把单价误认为总价。
误区我以为部署合约时能像注册用户名一样,给自己的合约挑一个好看的地址,结果发现地址完全不由我决定。
正解合约地址是“发送者地址+nonce”的RLP编码经Keccak256哈希后取后20字节,是可预测但不可挑选的。只有通过CREATE2操作码,才能把盐值(salt)纳入计算,提前算出并锁定地址。
为何常见:用户只见过部署完成后的地址,从未接触生成过程,容易按现实世界的“注册”直觉去理解。
误区EVM和JVM、V8差不多吧?为什么不能直接在链上打断点、单步调试、查看变量?它应该有完整的调试器。
正解EVM是确定性的执行环境:不访问系统时钟、不做文件IO、没有随机数。所有节点必须对同一交易产出完全一致的结果,所以链上不存在“调试模式”。开发者只能通过Hardhat、Tenderly等模拟器预演执行过程,部署后再也不能修改。
为何常见:把EVM类比成普通编程语言的运行时,忽略了区块链“全网一致”的共识约束。
误区既然没用完的Gas会退回来,那我GasLimit随便设个几百万也没关系,反正不会多扣钱。
正解交易成功后,未消耗的Gas确实会退还。但如果Gas耗尽导致交易回滚,已消耗的Gas照扣不误,而且失败的交易同样要付钱给矿工。此外,GasLimit过高会占用区块资源,钱包通常不允许无限提高。
为何常见:只记住了“退还”两个字,没理解“退还的前提是交易执行到某一步且余额充足”。
误区以太坊应该就是把所有账户余额存进一张超大SQL表,查余额就SELECT一下。
正解账户状态被组织成一棵Patricia Merkle树,每个区块头只存一个根哈希。好处是轻节点无需下载全量数据也能验证某账户余额;任何篡改都会导致根哈希变化,被其他节点拒绝。这是传统数据库没有的密码学可验证性。
为何常见:传统后端开发的思维惯性,把链上状态等同于MySQL里的表,忽略了Merkle树在“可验证”上的独特价值。
面试题
Q1. 以太坊中由私钥控制、能主动发起交易的账户类型是?
- A.合约账户类型
- B.外部账户类型← 正确答案
- C.多签账户类型
- D.系统账户类型
答案:B。外部账户(EOA)由私钥控制,能主动签名发起交易;合约账户只能由代码触发执行,无法主动发起交易。多签和系统账户不是以太坊的基础账户分类。
Q2. 以太坊地址本质上是什么?
- A.公钥的本身形式
- B.公钥哈希截取段← 正确答案
- C.私钥哈希的数值
- D.随机数种子生成
答案:B。地址 = Keccak256(公钥) 的后20字节。它由公钥单向哈希而来,无法反推出私钥。选项A错在“本身”,C错在“私钥”,D错在“随机”。
Q3. 账户nonce字段的主要作用是什么?
- A.记录交易的笔数← 正确答案
- B.记录账户的余额
- C.计算交易的费用
- D.更新存储的键值
答案:A。nonce是外部账户已发出的交易计数,每笔交易递增一次,可防止同一笔签名交易被重复广播(重放攻击)。余额、费用、存储键值都与nonce无关。
Q4. 合约地址的正确生成方式是什么?
- A.部署者自行指定
- B.随机算法随机生成
- C.哈希发送者与nonce← 正确答案
- D.矿工集体投票确定
答案:C。合约地址 = Keccak256(RLP([发送者地址,nonce])) 的后20字节,因此可以预先推算。但普通用户不能“指定”地址,矿工也不参与地址生成。
Q5. GasLimit设高但交易成功后,未用Gas会怎样?
- A.全部被矿工扣走
- B.按比例退还一半
- C.退还到发送账户← 正确答案
- D.自动转入合约账户
答案:C。交易按实际耗油量(gasUsed)结算,未消耗的Gas退回发送者账户,不会转给合约或矿工。矿工只获得实际消耗的部分。
Q6. 以下哪个操作消耗的Gas最多?
- A.ADD 加法运算
- B.SSTORE 写存储← 正确答案
- C.PUSH1 压栈操作
- D.POP 弹出栈顶
答案:B。SSTORE写存储约20000 Gas,因为数据要被全网永久保存;而ADD、PUSH1、POP等算术与栈操作只需3 Gas。存储比计算贵几千倍。
Q7. 交易总费用的正确计算公式是?
- A.耗油量乘以油价← 正确答案
- B.上限乘以油价再加值
- C.耗油量加上交易值
- D.油价乘以转账金额
答案:A。交易费 = gasUsed × gasPrice。gasLimit只是上限,不是实际消耗;转账额(value)另算,不参与手续费计算。失败交易也按已消耗的Gas扣费。
Q8. 以下属于EVM三种数据区域的有?(选两项)
- A.堆栈Stack存储← 正确答案
- B.内存Memory区域← 正确答案
- C.磁盘Disk存储区
- D.寄存器Reg区域
答案:AB。EVM的三种数据区域是堆栈(Stack)、内存(Memory)和存储(Storage)。磁盘与寄存器是传统计算机的概念,EVM中没有这两种区域。
Q9. 关于失败的链上交易,哪些说法正确?(选两项)
- A.失败交易同样会扣费← 正确答案
- B.失败后状态不会回滚
- C.失败后所有状态回滚← 正确答案
- D.失败交易不消耗任何Gas
答案:AC。交易失败时,已消耗的Gas不退还(所以A对、D错);所有状态变更会回滚,像没发生过一样(所以C对、B错)。失败交易的费用付给了打包它的矿工。
Q10. 关于EVM字节码,哪两项正确?(选两项)
- A.字节码由CPU直接执行
- B.部署后代码可以修改
- C.函数选择器是哈希前段← 正确答案
- D.字节码由EVM执行← 正确答案
答案:CD。函数选择器是函数签名Keccak256哈希的前4字节,由EVM解释执行而非CPU直接执行。合约部署后代码不可变,这是区块链的共识规则。