VIEW THIS AS

Auto mode follows the Route Engine until you choose a viewpoint.

YOU ARE HERE

ROUTE CHECK

CONNECTED TO

WHAT NEXT

Use the canonical route for this room, or HELP if you are unsure.

区块链虚拟机:让两个验证者重放同一组输入

Three students in school uniforms work through open books at a classroom table, with textbooks and stationery nearby and study notes on the whiteboard behind them.

English version · 区块链学习导航 · 上一篇

区块链学习 · 文章 BC036

三名学生在教室桌旁一起阅读书本,身边放有课本和文具。
学习场景配图;不代表照片中的学生正在学习区块链。

区块链虚拟机为不同实现提供共同的指令含义。在规则版本、起始状态、有序输入和允许环境固定时,独立验证者应推导出相同的规定结果。它们不会靠多数投票来决定加法指令是什么意思。

难点往往在于确认输入确实相同。两台机器可能运行名称一样的代码,却使用不同字节、状态快照、交易顺序或区块上下文。本篇建立一份重放记录,在把结果差异当成谜题之前,先让这些差异显现出来。

选择适合你的阅读路线

本篇目录

先看重放资料清单和两次调用的课堂例子,再检查四种不匹配情形,以及协议输入与计算机外部环境的边界。最后写出简短诊断,指出第一处分歧,而不只是比较最终数字。

关于栈指令、类型和线性内存基础,可阅读已有的 WebAssembly 数学基础课程。本篇有不同任务:解释区块链验证者为何需要可复现的状态转移,以及怎样定义有意义的比较。

返回目录 · 下一节

1. 虚拟机是一份执行规范

虚拟机定义抽象执行环境,其指令规定如何解释数值、访问允许的数据及改变机器状态。实现可以使用不同语言、运行于不同硬件,同时努力复现这些规定效果。

以太坊 EVM 概述将执行描述为根据定义好的输入产生确定性输出的状态转移过程。课堂模型借用这一思想,但不声称复现完整 EVM 或以太坊区块处理的所有部分。

确定性是一项有条件的陈述。“结果相同”依赖相关输入和规则相同。它不表示无论状态、调用者、参数或区块上下文如何,每次调用某函数都必然返回同一个固定值。

它也不表示所有实现耗时相同,或在宿主内存中使用相同布局。协议约束可观察执行结果及适用核算,而不是执行检查的计算机的每一项物理细节。

返回目录 · 下一节

2. 比较输出前,先定义重放资料包

本篇所说的“重放资料包”,是复现某次课堂执行所需的全部给定信息。它是一份教学检查清单,并非新的交易格式。

  • 规则版本:所采用的指令及状态转移语义。
  • 起始状态:第一次调用前的持久记录。
  • 可执行程序身份:实际解释的精确程序,而不只是显示名称。
  • 有序调用:每一步的调用者、操作、参数及先后次序。
  • 允许环境:共享上下文输入,例如给定区块号。
  • 资源假设:足以完成给定成功路径的允许执行预算。

遗漏其中一个字段,就可能把两个不同计算误当成同一个测试。发现不一致时,应先对齐资料包,再检查实现。

真实系统需要比这份清单更详细的表示与验证。账户状态、代码和交易字段必须来自可信且正确识别的数据。清单可以组织证据,但不会自动认证证据本身。

返回目录 · 下一节

3. 课堂程序只有一个持久计数器

虚构程序名为 ArchiveCounter-C1。初始持久记录为 counter=10,允许调用者为 K。它提供两个操作:add(n) 存储 counter+n,multiply(n) 存储 counter×n。

给定规则版本 R1 先检查调用者是否为 K,再检查给定区块号是否至少为 50。两项通过后,使用非负课堂整数完成所请求操作并存储结果。下面所有数值均在允许范围内,因此不涉及溢出。

任一前置条件失败,计数器保持不变,并返回课堂拒绝结果。我们不模拟费用、账户 nonce、事件日志或真实交易的失败语义。这些被有意排除,使持久计数器成为唯一需要计算的状态变量。

允许环境给定区块号 50,两次调用均来自 K。有序列表为先 add(3),再 multiply(2)。执行预算足够,中间没有其他交易改变计数器。

这些是非金融记录。例子不部署合约、不生成密钥,也不授权任何真实更新。程序名称和 K 是符号化教学标识,并非链上地址或代码哈希。

返回目录 · 下一节

4. 验证者 N 重放有序调用

N 从给定持久计数器 10 开始。执行 add(3) 时,检查调用者 K 和区块号 50,两项均通过。它计算临时结果 10+3=13,并把 13 存为新的持久计数器。

第二次调用从更新后的状态开始,因此 multiply(2) 读取的是 13,而不是最初的 10。同样的前置检查通过,临时结果为 13×2=26,N 存储 counter=26。

轨迹包含三个持久检查点:调用前 10,第一次后 13,第二次后 26。记录中间的 13 很有价值,因为后续比较可以据此判断分歧出现在乘法之前还是之后。

只看最终数字会隐藏这些信息。在更丰富的系统中,即使另一个实现因不同原因也得到 26,最终计数器相同也不能证明所有中间效果一致。这里的完整轨迹足够小,可以直接核对。

返回目录 · 下一节

5. 验证者 P 独立得到相同结果

P 使用另一套实现,接收相同 R1 规则、ArchiveCounter-C1 程序、初始计数器 10、有序调用及区块号 50。

P 检查前置条件,推导 10+3=13,再推导 13×2=26。持久检查点与 N 一致。即使 P 的处理器较慢,或内部使用不同宿主语言对象,这仍是共同模型下的预期结果。

两者都不需要抄写对方答案,也不能在结果为 25 和 27 时取平均数 26。求平均并不是 R1 的组成部分。

一致结果为这个测试中的执行一致性提供证据,却不能证明所有可能输入都被正确处理。两套实现可能共享同一个缺陷,测试可能遗漏重要情形,两者也可能都使用了同一个错误资料包。更广泛测试和规范工作仍然必要。

返回目录 · 下一节

6. 不匹配一:起始状态不同

假设 P 从 counter=11 开始,其他给定字段均不变。P 计算 11+3=14,再计算 14×2=28;N 从 10 开始,仍得到 26。

第一处分歧发生在第一次调用之前:执行前状态不同。这不是加法或乘法不确定的证据,而是两者处理了不同起始记录。

实际调查应识别状态快照及其与所选父区块的关系。文件名叫“latest-state”,不能证明两台机器加载了相同内容;区块高度相同,也不能证明区块身份和状态相同。

识别错误初始状态后,不应通过手工改最终答案来掩盖差异。有意义的修复是恢复正确输入上下文后重放。本例示范诊断,并不是修改真实链数据库的操作流程。

返回目录 · 下一节

7. 不匹配二:调用相同,顺序不同

现在让 P 使用原始计数器 10,但交换调用顺序。先 multiply(2) 得到 20,再 add(3) 得到 23;N 按原顺序仍得到 26。

两次运行拥有相同的两个操作名与参数,却没有相同的有序输入。先加后乘与先乘后加不同,完整状态转移因此依赖次序。

所以,重放记录必须保存顺序,而不只是列出交易集合。一个包含 add(3) 和 multiply(2) 的集合,不能说明哪次调用观察到另一次的状态更新。

这不表示每一对操作都对顺序敏感。有些操作在特定条件下可以交换;关键是独立性或可交换性必须得到证明,不能仅因两笔交易各自格式正确就默认成立。

选择接受的交易顺序,与一致地执行该顺序,是相关但不同的任务。本篇从已给定顺序开始,并不替代决定节点跟随哪段有序历史的共识与分叉选择规则。

返回目录 · 下一节

8. 不匹配三:熟悉名称掩盖了不同代码

假设 P 的程序被错误标为 ArchiveCounter-C1,但把第二次给定调用实现为乘以 3,而非乘以 2。它先计算 10+3=13,再计算 13×3=39。

这里第一个检查点相同,第二个分歧;可执行行为不同。网站标签、源文件名或友好的合约名称相同,不足以证明可执行程序身份相同。

Solidity 0.8.30 入门文档区分合约代码与执行期间使用的数据。在 EVM 工作流程中,可执行字节码很重要;高级语言的源码描述本身,并不是虚拟机实际解释的精确对象。

可复现构建或源码验证流程有助于把可读源码与已部署代码联系起来,但这是另一项证据任务。本练习直接给定精确程序身份,并不声称比较名称或看到“源码已验证”标志就完成了这项核查。

返回目录 · 下一节

9. 不匹配四:环境不同

保持原程序、计数器和调用顺序,但给 P 提供区块号 49。按照 R1,两次调用都未通过区块号门槛,计数器保持 10。N 使用区块号 50,两次通过,最终为 26。

输入再次不同,规则在两种情形中都保持确定性。如果测试只记录调用者和参数,却遗漏允许环境,就漏掉了改变执行路径的重要值。

Solidity 0.8.30 全局变量参考列出了 block.number、block.timestamp、msg.sender 等上下文值。链上执行时,必须在正确执行上下文中解释它们,不能替换为验证者操作系统恰好提供的任意数值。

同一文档提醒,链下求值可以使用自行提供的上下文。因此,操作名和参数相同,并不自动让一次模拟调用成为某个真实区块的重放。

练习中的区块号门槛只是协议输入例子。它不能证明某个物理事件在某时发生,也不是会自行唤醒程序的定时服务。

返回目录 · 下一节

10. 不要把宿主计算机悄悄引入共识执行

设想修改程序:如果验证者本地时钟显示偶数秒,就加 1;否则加 2。两个验证者即使收到相同的已记录链输入,也可能因各自时钟不同而产生不同结果。

R1 不允许这种隐藏输入。为了复现结果,相关环境值必须来自规则明确的共享允许执行上下文。把本地时钟换成给定区块上下文值,是改变了设计问题,并不会让那个值成为绝对准确的现实世界时钟。

同样,如果要求每个验证者在执行期间自行访问网页,除非协议明确规定共享输入机制,否则会引入可用性、时机和响应差异。网页可能在两次请求之间变化,也可能向不同观察者返回不同内容。

固定版本的 Solidity 入门文档说明,EVM 执行与宿主网络和文件系统的直接访问隔离。外部事实需要通过适当机制进入系统;预言机课程会讨论这一边界。处理可复现,并不能自动证明输入的外部事实真实。

这是范围规则,不是说区块链软件从不使用网络。节点在合约执行环境之外交换交易和区块;虚拟机周边的网络通信,与共识关键合约代码中的无限制网页请求,不是一回事。

返回目录 · 下一节

11. 临时工作数据不会自动成为持久状态

课堂计算先产生临时结果 13、26,再进行存储。临时值和持久记录有不同生命周期,即使某一时刻内容是同一个整数。

Solidity 0.8.30 入门文档区分栈、内存、持久存储和瞬态存储。在 EVM 中,它们是规则不同的区域。本篇不需要完整布局,只需要养成一个习惯:询问哪种数据会跨越相关执行边界继续保留。

在模型中,只有明确的存储操作才改变持久计数器。如果实现用临时变量算出 13,却忘记存储,下一次调用就不能认为持久状态已经是 13。重放轨迹应同时显示计算值与写入的课堂记录。

不要把真实虚拟机中的所有临时数据等同为一类。调用帧内存与交易范围的瞬态存储,生命周期不同。也不能把玩具模型里的“存储”一词,当成对区块链提交或最终性的完整描述。

我们跟踪的是给定的成功应用状态更新。其所在区块是否继续被选中或获得最终性,属于链证据层,而不是局部变量生命周期问题。

返回目录 · 下一节

12. 规则版本也是输入约定的一部分

可执行指令只有在指定规则下才有明确含义。网络升级可能改变可用操作、核算方式或其他执行行为。若使用未说明的“当前”规则重放历史数据,比较本身就可能定义不清。

官方以太坊执行规范仓库跟踪执行层规则和网络升级。开发中的仓库内容本身,并不能证明其中每项变化都已在某网络生效。在据此提出主张前,应识别适用分叉版本与上下文。

本练习主要重放过程始终固定使用 R1。假设 R2 改变门槛或操作语义,它就定义了另一个测试。未检查变更内容之前,R1 下结果一致并不能证明 R2 下也一致。

版本标签也必须有明确引用。如果两份文档都叫“R1”,却含有不同指令,标签就失去作用。有效记录应标明实际规范、可执行资料、输入状态和有序调用。

返回目录 · 下一节

13. 练习:寻找第一处分歧

采用主要资料包:R1、ArchiveCounter-C1、计数器 10、调用者 K、区块号 50,先 add(3),再 multiply(2),预算足够且没有中间调用。

  1. N、P 应在每次调用后记录什么持久计数器值?
  2. P 改从 11 开始,最终得到 28。第一处分歧属于算术指令还是执行前状态?
  3. P 交换调用顺序,最终得到 23。完整输入是否相同?
  4. 两者第一次调用后都得到 13,但 P 随后乘以 3,得到 39。应检查资料包中的哪一部分?
  5. P 使用区块号 49,最终仍为 10。这证明 R1 不确定吗?
  6. 两套实现都返回 26,能证明程序正确代表真实档案流程,或所在区块已最终确定吗?

答案与理由

  1. 都应先记录 13,再记录 26。第二次调用读取已经更新的持久状态。
  2. 执行前状态。任何调用开始前,11 与 10 就已不同。
  3. 不相同。即使无序操作名与参数集合相同,有序序列仍然不同。
  4. 检查可执行程序身份及实际采用的操作语义。第一检查点相同可以缩小调查范围,却不能认证代码。
  5. 不能。一个允许环境输入不同,导致给定门槛按规则拒绝这两次课堂调用。
  6. 不能。一次执行一致并不证明应用事实、实现普遍正确或区块最终性;它们需要不同证据。

延伸:遗漏第一次持久写入

假设一个有缺陷的实现执行 add(3) 时算出 13,却让持久计数器仍为 10。下一次 multiply(2) 读取 10,存储 20;正确主轨迹最终为 26。

第一处分歧是第一次调用后缺失持久写入,而不是乘法算错。这正是轨迹必须记录状态转移、不能只记录临时计算器答案的原因。

返回目录 · 下一节

14. 常见问题

所有验证者都需要相同电脑吗?

不需要。它们需要在适用规则和输入下产生符合规范的行为。硬件速度与实现语言可以不同,而规定结果不变。

确定性等于安全吗?

本身不等于。确定性程序也可以稳定地计算错误应用结果,或执行不足的权限规则。确定性只是具有明确范围的一项性质。

代码可以使用区块时间戳吗?

如果执行规则提供该值,代码可以读取相应上下文。它不是验证者任意的本地时钟,也不能独立证明现实事件的发生时间。

EVM 与 WebAssembly 是同一种虚拟机吗?

不是,两者规范和环境不同。链接中的 WebAssembly 课程提供抽象机器基础;本篇重放练习关注区块链执行证据。

返回目录 · 下一节

15. 资料来源与后续阅读

资料于新加坡时间 2026 年 10 月 12 日核对。Solidity 引用固定为 0.8.30,不表示它是最新版本,也不把它当成所有生效网络规则的完整规范。

继续学习智能合约条件,了解更完整的应用状态机;再阅读存储、事件和索引,区分持久记录与观察接口。两个结果不一致时,先保留重放资料,找到第一个不同输入或状态转移,再提出原因。

返回目录