Monero工作量证明的工作原理

Monero 的 PoW 算法 RandomX 通过随机代码执行和大内存访问来抵抗 ASIC 中心化。

Monero 的工作量证明被称为 RandomX

Monero 并不要求矿工反复运行同一个微小的哈希函数。它要求矿工在虚拟机上运行一个小型随机程序,在此过程中大量访问内存,然后对结果进行哈希。

比特币的工作量证明适合专用芯片,因为工作量从不改变。RandomX 的设计则相反。它试图让高效的挖矿看起来尽可能像普通的 CPU 工作负载。

简短版本

以下是最简短的实用总结:

  1. Monero 获取候选区块头部和一个 nonce。
  2. 它还使用一个较早的区块哈希作为中期密钥。
  3. 该密钥构建一个大型共享内存数据集。
  4. 候选区块输入被哈希,以生成一个特殊虚拟机的种子。
  5. 虚拟机执行整数运算、浮点运算、分支以及大量内存访问,运行 8 个链式程序
  6. 最终的机器状态被哈希成一个 256 位输出。
  7. 如果该输出低于网络目标,则区块有效。

有趣的部分不是最终的“是/否”规则。每个工作量证明系统都有这个。有趣的部分在于 Monero 如何让每次哈希尝试在普通 CPU 擅长而定制芯片讨厌的方面变得昂贵。

为什么 Monero 不使用简单的哈希

如果你的工作量证明只是“在新输入上反复运行这个固定函数,直到得到幸运输出”,硬件设计师的任务很明确:构建能够以最低成本和最快速度运行该精确函数的硅片。

这就是比特币在 SHA-256 ASIC 上发生的事情。

Monero 不想走那条路。早在 RandomX 之前,该项目就明确指出专用挖矿硬件会造成中心化压力。更少的制造商变得更加重要。大型矿场变得更加重要。普通用户变得不那么重要。

Monero 早期的答案是 CryptoNight 系列。后来,在 2019 年末,Monero 切换到了 RandomX,其发布说明将其描述为一种新的工作量证明,“基于随机指令,适用于 CPU。”

因此,设计目标从 让内存变得重要 转变为 让整个 CPU 变得重要

RandomX 背后的核心理念

RandomX 从一个观察出发:CPU 不仅仅是算术盒子。它们是灵活的机器,专为运行不断变化的代码并同时处理大量硬件特性而构建。

现代 CPU 拥有:

  • 多级缓存
  • 整数单元
  • 浮点单元
  • 分支处理
  • 乱序执行
  • 投机执行
  • 内存控制器

普通的密码学哈希函数并没有充分利用这些多样性。它们大多只是通过固定的管道推送数据。

RandomX 试图将工作量证明与这些更广泛的 CPU 优势绑定。其设计文档指出,工作量必须是 动态的。这意味着矿工不仅是在输入新数据,还在获取要运行的新代码。

这就是 RandomX 基于 随机代码执行 的原因。

矿工实际计算的是什么

在 Monero 层面,RandomX 接收两个重要输入:

  • 密钥 K
  • 哈希输入 H

对于 Monero,K 来自一个较早的区块哈希,称为 密钥区块。RandomX 参考 README 建议每 2048 个区块 更改一次此密钥,并带有 64 个区块的延迟,而 Monero 正是这样实现的。

这个细节很重要,因为矿工 不会 为每个 nonce 重建庞大的共享内存结构。他们只在密钥更改时重建一次,大约每 2.8 天一次。

H 是候选区块的哈希数据块,包含选定的 nonce。这部分是矿工反复更改的内容。

因此,你可以这样理解 Monero 挖矿:

  • 网络通过 K 为你提供一个中期环境
  • 你的候选区块通过 H 为你提供一个每次尝试的输入

环境变化缓慢。尝试不断变化。

第 1 步:从密钥构建缓存

RandomX 首先对密钥 K 运行 Argon2d

Argon2d 更常被称为密码哈希和密钥派生函数。它在这里有用的原因与在那里相同:它是 内存硬 的。它迫使机器以难以欺骗的方式访问大量内存。

在默认的 RandomX 参数下,这会生成一个 256 MiB 的缓存

该缓存是 RandomX 中两个大型内存结构中较小的一个。它不是矿工想要直接用于最大速度的结构。它是用来构建更大结构的基础。

第 2 步:将缓存扩展为数据集

从该 256 MiB 缓存出发,RandomX 构建 数据集

默认数据集大小为:

  • 2,147,483,648 字节 基本大小
  • 33,554,368 字节 额外大小

总共约为 2080 MiB,略大于 2 GiB

这种看起来奇怪的尺寸是有意为之。它足够大,以至于会溢出片上内存进入 DRAM,而额外的非 2 的幂次尾部使得硬件设计者的工作更加麻烦。

数据集在哈希过程中是只读的。RandomX 利用它来强制产生常规的 DRAM 流量。设计文档指出,每次程序迭代会读取一个 64 字节的数据集项,并且在整个哈希结果过程中,这变成 16,384 次数据集读取

这给了 RandomX 一个主要瓶颈:内存访问,而不仅仅是计算。

第 3 步:从区块输入初始化暂存器

现在 RandomX 转向每个哈希的输入 H

它使用 Blake2b 计算 Hash512(H)。这个 64 字节的结果用于种子化一个基于 AES 的生成器,该生成器填充 暂存器

暂存器是虚拟机的工作内存。与大型数据集不同,它应该留在 CPU 缓存中,而不是 DRAM。

其默认大小为 2 MiB,拆分为模拟 CPU 缓存层级:

  • 16 KiB L1
  • 256 KiB L2
  • 2 MiB L3

这是 RandomX 最聪明的部分之一。它同时使用了两个截然不同的内存结构:

  • 大数据集 用于命中 DRAM
  • 较小的暂存器 用于表现得像缓存密集型代码

这样既可以给内存子系统施压,也可以给 CPU 核心施压。

第 4 步:生成随机程序

暂存器准备好后,RandomX 为其虚拟机生成一个程序。

这不是 C 程序或 JavaScript 程序。它是一个紧凑的虚拟机程序,拥有自己的指令集。

两个细节非常重要:

  1. 每条指令长度为 8 字节
  2. 任何 8 字节字都是有效指令。

第二个选择意义重大。这意味着 RandomX 可以通过用随机字节填充缓冲区来生成程序。没有缓慢的解析器,也没有复杂的语法检查。

每个程序包含 256 条指令

这些指令被选择为看起来像是真实 CPU 擅长的工作:

  • 整数运算
  • 64 位乘法
  • 浮点操作
  • 128 位向量操作
  • 内存加载与存储
  • 偶尔的分支

浮点部分并非装饰。RandomX 使用 IEEE 754 双精度运算,包括除法和平方根,并使用了所有四种标准舍入模式。这使得虚拟机更难被压缩成一个小型“主要是整数”的定制设计。

第 5 步:运行程序循环

虚拟机以 2048 次迭代 的循环执行包含 256 条指令的程序。

在每次迭代中,它:

  • 读写暂存器内存
  • 预取并加载数据集项
  • 混合整数和浮点寄存器状态
  • 在条件满足时执行低概率分支

设计文档指出,平均每次迭代从内存读取约 504 字节,并写入约 256 字节

这个数字暗示了 RandomX 实际在做什么。它不仅仅是“带有额外步骤的哈希”。它试图表现得像杂乱、混合的真实软件。

为什么要有分支

分支很容易被忽视,但它们很重要。

如果代码完全是直线型的,专用硬件可以更容易地优化掉更多部分。分支使得静态简化变得更加困难。

RandomX 谨慎地使用分支。分支被触发的概率约为 1/256,并且设计有意让这些分支通常被预测为“未触发”。这意味着在大多数情况下,它们对 CPU 来说是廉价的,同时仍然阻碍过度优化的硬件捷径。

重点不在于 RandomX 找到了某种神奇的分支预测技巧。它没有。重点是,即便只有很少的实际控制流,也能让工作负载看起来更像真实代码,而不是一个干净的硬件流水线。

为什么每个哈希有 8 个链式程序

单个随机程序是不够的。

如果矿工只需要运行一个随机程序,不诚实的矿工可以尝试先检查程序并跳过“坏”的程序。或者定制芯片设计者可以只支持对其硬件容易的程序子集。

RandomX 通过将 8 个程序 链式连接来阻止这一点。

一个程序的输出状态成为下一个程序的种子。因此,一旦开始,你无法预先知道整个链。你要么完成整个链,要么丢弃你已经付出的工作。

这是 RandomX 最简洁的设计之一。它将“也许我只做容易的工作”变成了一种糟糕的策略。

第 6 步:将最终状态压缩为哈希

在 8 个程序中的最后一个完成后,RandomX 仍需将所有机器状态转化为一个最终的摘要。

它做两件事:

  1. 使用基于 AES 的哈希对整个暂存器生成指纹。
  2. 将其与虚拟机寄存器文件结合,并运行最终的 基于 Blake2b 的 256 位哈希

这个最终的 256 位结果就是 RandomX 的输出。

在 Monero 层面,矿工检查该结果是否低于当前的难度目标。如果是,则区块获胜。如果不是,矿工更改 nonce 并再次尝试。

快速模式与轻量模式

RandomX 有两种模式:

  • 快速模式,使用完整的 2080 MiB 数据集
  • 轻量模式,仅使用 256 MiB 缓存并动态计算数据集项

两种模式给出 相同的答案

验证必须与挖矿一致,但这两种模式的工作成本并不相同。

快速模式用于挖矿。轻量模式用于验证。参考 README 明确说明了这一点,这种分离是 RandomX 最优秀的设计选择之一。

如果每个验证者都需要超过 2 GiB 内存才能检查一个工作量证明,那会很麻烦。如果轻量模式便宜到可以竞争性地挖矿,那也很糟糕。RandomX 试图保持在中间位置:验证应该是可行的,但作为挖矿捷径不具有吸引力。

设计文档明确指出,轻量模式应该在内存-时间权衡上处于劣势。通俗地说,如果你节省了内存,你就应该用额外的工作来补偿这一优势。

为什么这对 CPU 友好

“CPU 友好”听起来可能像营销话术,因此值得具体说明。

RandomX 有利于 CPU,因为它依赖于优秀 CPU 已经具备的特性:

  • 大型缓存
  • 不错的 DRAM 带宽
  • 硬件 AES
  • 快速 64 位运算
  • 浮点硬件
  • 乱序执行
  • 动态代码生成

参考实现甚至包含针对 x86-64ARM64RISCV64 的 JIT 编译器,因此虚拟机程序可以即时翻译成本机机器码,而不是逐条解释执行。

这并不意味着 ASIC 永远不可能。没有任何诚实的东西应该声称这一点。它所做的只是让定制芯片的工作变得更加棘手。一个优秀的 RandomX ASIC 开始看起来不像一个整洁的固定功能哈希引擎,而更像“构建一个带有大量内存的奇怪且昂贵的 CPU”。这缩小了优势差距。

RandomX 试图为 Monero 带来什么

RandomX 主要关心的不是原始速度。而是 硬件经济学

Monero 希望工作量证明能够:

  • 让挖矿对普通硬件更开放
  • 减少固定功能 ASIC 带来的优势
  • 避免将共识绑定到少数硬件供应商
  • 保持验证相对廉价

这就是为什么如果你期待一个正常的挖矿哈希,RandomX 看起来如此奇怪。

它并不试图在比特币的意义上优雅。它试图在专业化方面变得别扭。

最简单的思考方式

如果比特币挖矿是“永远运行这一台小机器”,那么 Monero 挖矿就是“不断生成类似 CPU 的小型工作负载并承受内存流量”。

这是我所知道的最简短且仍然正确的心理模型。

在底层,RandomX 是一系列精心选择的堆叠:

  • Argon2d 用于构建缓存
  • 超过 2 GiB 的数据集用于强制 DRAM 访问
  • 缓存大小的暂存器用于锻炼本地内存
  • 带有随机程序的虚拟机
  • 整数、浮点、向量和分支指令
  • 8 个链式程序以防止轻松过滤程序
  • 面向矿工的快速模式和面向验证者的轻量模式

将这些组合起来,你就得到了今天的 Monero 工作量证明。

它并不简单。但重点很简单:让挖矿看起来像通用计算,从而让专用硬件几乎没有空间在网络中占据主导。

  • 原文链接: blog.alcazarsec.com/tech...
  • 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
点赞 0
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
alcazarsec
alcazarsec
江湖只有他的大名,没有他的介绍。