ZYCORD 文档
简体中文
Zycord文档架构

架构

白皮书的工程篇伴读 — 参考节点如何实现这套设计,以及其背后的各项决定。是解释性的,不是规范性的。

本文档不是规范性的

协议是参数文件与黄金 向量,外加各项具名规则,无论它们在何处被定义; 线路规范承载着对等层的各项要求。本 页解释那个接口面,并记录其背后的各项决定。凡是它与规范性接口不一致之处, 以规范性接口为准,而本文会被更正。 完整的工程篇伴读是 docs/ARCHITECTURE.md。

两个谓词,两个引擎#

一份凭证从头到尾的一生:

 wallet                      network                        every full node
+------------+   gossip   +--------------+               +---------------------+
| build cert | ---------> | cert topic   | ------------> | STATELESS PIPELINE  |
| (reads,    |            | (TLS gossip) |               | V1..V8, batch sigs, |
| writes,    |            +--------------+               | native re-exec      |
| sigs, seq, |                                           |  -> mark VALID      |
| deposit)   |                                           +----------+----------+
+------------+                                                      | mempool
                                                                    v
                            miner (any node)              +---------------------+
                          +-------------------+  block    | FOLD (sequential)   |
                          | assemble ordered  | --------> | F-rules per cert:   |
                          | hash list + bodies|  gossip   | APPLY / SKIP / DROP |
                          | + RandomX solve   |           |  -> new state       |
                          +-------------------+           +---------------------+

有效性是无状态且可并行的:它在每个节点上对每份凭证运行一次, 在区块之前进行,且与区块无关。可应用性是有状态且串行的:它 在凭证被提交的位置上、在折叠内部运行。矿工不运行任何执行;它 对自己已经见过被验证通过的那些哈希进行排序,并求解工作量证明。折叠是一个在内存工作集上的紧凑循环 — 比较、相加、写入。

工程原则#

原则
P1折叠是神圣的。状态转换函数住在一个纯粹的包里,没有 I/O,没有时钟,没有 goroutine,没有 map 遍历,也没有浮点数。它是唯一一处其缺陷在事后无法修补的代码。节点里的其他一切都是可替换的管道。
P2确定性优先于性能。任何有不确定性风险的优化,在共识代码中一律被否决。性能属于无状态流水线,在那里它是安全的。
P3没有管理员密钥,没有特权 RPC。不存在任何一条代码路径,能让某把密钥去暂停、升级、铸币或重组。如果它不在折叠规则里,它就不存在。
P4共识面要小。core/ 不引入标准库以外的任何东西。节点的其余部分可以使用生态库;核心不可以。
P5规范优先。黄金向量就是协议。这份 Go 代码是一个参考实现;一个能通过这些向量的独立实现是对等实现,不是分叉。正是这一点使得维护者最终可以是无名之辈。
P6从 v0.1 起即可复现。钉死的 Go 工具链、-trimpath、钉死的依赖。信任从二进制文件转移到代码上,而这是一个匿名作者所能提供的唯一一种信任。
P7一个二进制文件,由高度把关。机器、抵押相关操作和定序器注册都编译进每一个发行版,并在其启用高度之下被拒绝 — Validate 要求 h1_vm ≥ h1_bond,且要求 h1_vm 落在一个纪元边界上。一个时代之所以到来,是因为链走到了某个数字,绝不是因为有人请求运营者升级。

P3 是一位怀疑的读者最该用力去按的一条,而国库正是该按下去的地方: 创世区块里根本没有密钥,也没有任何花费路径,所以没有什么特权可供 持有、委托或窃取。Era 2 的那套 5 选 3 法定人数是由一次未来的硬分叉钉定的 — 与任何其他共识变更是同一套社会机制,要经受同样的否决, 而且即便如此,它也只能移动恰好一个存储单元。一套只有在网络同意把它写进去时才会出现、 出现之后只掌管一个存储单元的法定人数,是一条花费规则。而管理员密钥是那种在任何人表示同意之前就已存在、 且触达一切的东西。

密码学原语#

角色选择为什么
签名Ed25519支持批量验证(通往 GPU 的入口匝道),没有可锻性,密钥极小。在创世时就钉死了严格规则:公钥和 R 必须采用规范编码,公钥必须无挠且不属于低阶,验证必须是无余因子的。
哈希BLAKE3凭证 id 与区块 id、地址、状态根。快到足以按转发线速做哈希;对纪元状态根而言又对并行友好。
工作量证明RandomX为 CPU 优化。它是整棵代码树里唯一的 cgo,藏在一个构建标签之后,不带该标签的构建里根本没有它。pow_engine 位于共识根之内,所以一个持有错误引擎的二进制文件会拒绝启动,而不是去接受错误的证明。
域分离强制每一次哈希都是 blake3(tag ‖ payload)。签名同时覆盖链 id 和共识根,这就同时消灭了跨网络的重放和跨同一网络两次化身的重放。

正是拒绝挠元素让批量路径变得安全。一把混合阶的密钥并不是 低阶的,所以没有哪份封禁名单能拦住它,而它恰恰是带余因子的批量验证器与无余因子的单次验证器 发生分歧的地方。当密钥和 R 都落在素数阶子群里时,两者 可被证明是等价的 — 因此批量验证器可以带余因子,前提是它在批处理之前 施加同样的编码规则和挠规则。那份义务是这项选择的代价,而批量验证器还不存在。

规范编码与各类标识符#

所有共识对象都是 SSZ 容器:单一的规范字节编码,没有 map 顺序问题,没有可选字段的歧义,有便于低成本部分解析的固定偏移量,以及原生的 merkle 化。

cert_id       = blake3("zcd/certid/v1" || ssz(certificate with an empty signature list))
cert_exemplar = blake3("zcd/cert/v1"   || ssz(certificate))
block_id      = blake3("zcd/block/v1"  || ssz(header))

前两者是回答不同问题的不同摘要,而一个在该用其中之一的地方用了另一个的 实现,在金钱意义上是坏的。id 回答的是 这份授权是否已被计费;实例字节的哈希回答的是这些字节是否证明了 它。两者从不共用一个键。

签名位于 id 的原像之外,因为签名是一次随机化的展示: 签名者挑选 nonce,每一个 nonce 都会对同一个消息体产出另一个同样有效、同样完美规范的签名, 而没有哪个验证者能核查用的是哪一个。倘若它们在里面,一份授权就会 拥有无穷多个 id,每一个都可计费,每一个都能由其所需签名者中任意一位 越过其他人的权限而单独产出。

费用出价在 id 之内,而这是一项货币层面的决定

一个位于 id 之外的出价,会是一个任何位于传输途中的人都能改写的出价 — 被抬高以便通过基础费 烧掉签名者的余额,或者被清零以便让这份凭证进不了任何区块。一份 凭证付出什么,是它的签名者所授权内容的一部分,所以它要被哈希,也要被签名。日后一次 “为了转发灵活性而把费用移出被签名的消息体”的编码变更,看起来会像 一次优化,而它会是一条盗窃通道。

随之而来的规则是:解析,而不要验证两遍。解码本身就强制施行规范 形式,所以一个被解码出来的对象在构造上就是结构合法的,而各规则引擎从不 重新核查形状。

存储单元模型#

存储单元是一个槽位上的值。存储单元的值是以大端序存放在 32 字节中的 无符号 256 位整数。不存在的存储单元读出来是零 — 零就是不存在,而这是一项 共识要求,而不是实现上的图省事:它让状态根成为状态的函数, 而不是产生出该状态的那段历史的函数。

另外,协议还保有一份已花费地址注册表:一个由签名权限 已被烧毁的一次性地址构成的、永久性的共识集合。

Addr = version || blake3("zcd/addr/v1" || version || payload)[:31]
版本种类借记授权
0x01一次性用户地址所有者签名;任何进行借记的凭证还必须携带一个显式的 MARK_SPENT。它一旦应用,该地址之下的每一次读取和写入都将永远失败。
0x02持久用户地址所有者签名;可永久重复使用。永远不会进入已花费注册表。
0x03资产由该资产不可变的权限存储单元管辖。
0x00协议仅折叠可用:纪元信标、基础费存储单元、coinbase 成熟环、国库存储单元。
0x04保留 — 隐藏值存储单元(Era S)在 Era 0 中不可达。

0x04 是现在就保留下来,而不是日后再分配,因为一个隐藏 值必须凭其地址就能与普通值区分开:一个 Pedersen 承诺 和一个 256 位余额都是 32 字节,所以一个因错误或恶意而瞄准某个承诺槽位的带守卫增量, 会让折叠把一个整数加到一个曲线点编码上 — 这种算术 能通过每一项检查,并留下一个谁也永远花不掉的存储单元。这张表在创世时就被冻结了, 所以这个字节在此被占下并留作不可达。

注册表条目从不被压缩。已花费地址之下的存储单元值 可以在回滚视界之后被剪除,但那条把该地址记录为已花费的条目,正是 阻止它被复活的东西。它是只追加的共识状态,每个地址约 33 字节, 永远保留 — 这个协议诚实承认的尚未解决的问题,在结构上与每一种 作废符集合设计共有。

无状态有效性,以及计费法则#

V 规则在每一份凭证上运行,是并行的,发生在进入内存池之前以及区块 验证期间,且需要零状态:规范形式与链 id;每一个签名都要在 签名根之上验证通过;授权可仅凭该凭证本身推导出来;所声明的读取要等于 程序推导出的结果;以及退款去向要与该凭证自己所烧掉的东西相核对。

这个系统的计费法则只有一句话,而它就是要牢牢抓住的那件事:

一个签名,至多一次计费,且绝不在其签名者无从回避的位置上计费。

本规范为白皮书的词汇表添了一个词:丢弃,一种不计费的 非事件。一份凭证到达应用阶段时其保证金已被消耗,它会被丢弃 — 不计费,不标记为已见,可以针对一笔新鲜的保证金自由重新提交 — 因此诚实的用户不会 因为自己那个保证金存储单元上的竞争而损失任何东西。

仓库布局#

zycord/
  spec/       parameter sets, golden vectors, library images   <- THE PROTOCOL
  core/       consensus-critical; standard library only (P4)
    types/  crypto/  ssz/  u256/  state/  validity/  fold/  params/  genesis/
    cevm/          the certificate-adapted EVM; vendored interpreter, pure Go
    stdlib/        the pre-deployed library, its addresses and code hashes
    pow/randomx/   the mainnet engine: vendored C++, cgo, behind a build tag
  node/       storage/  chain/  verify/  mempool/  miner/  p2p/  sync/  rpc/  stratum/
  wallet/     key management, certificate builders (reference; not consensus)
  contracts/  the reference contracts, in Solidity
  sim/        simulator, fuzz harnesses, differential refold, chaos soak
  cmd/        zycordd, zcd
  desktop/    the wallet in a native window — a separate Go module
  docs/       architecture, protocol, operating guide, whitepaper

依赖箭头只朝内指 — node → core、 wallet → core,绝不反向 — 而 core/ 内部的任何东西 都不会伸到 core/ 和标准库之外。有一个例外,它被点名并被强制执行: core/pow/randomx 是整棵代码树里唯一的 cgo,而它只在那个构建 标签之下才会编译,所以每一个不带该标签的构建仍然是只依赖标准库的,也不需要 C 工具链。CI 会运行强制执行这一点的检查,因为第三方检查是靠 grep 模块路径来做的,而 cgo 一个模块路径都没有 — 这曾让这条规则在被补上之前无人执行。

它是怎么被测试的#

  • 黄金向量。每一条折叠规则、区块规则和有效性规则都有正例和反例,形式为 (pre-state, block) → (post-state | invalid, outcomes, fees)。这套测试集就是面向独立实现的兼容性契约。
  • 骚扰攻击测试集。重新打包已应用和已跳过的凭证、过期后打包、出价不足时打包、在提议者洗牌下的依赖链、烧毁与退款的循环、第三方贷记风暴、铸造上限的边界竞争。
  • 基于性质的测试。在提议者顺序置换下的折叠确定性、增量的可交换性、ABA 容忍度,以及守恒 — 其中包括在途保证金、成熟环和国库存储单元,因为一项遗漏了国库的守恒检查,会把每一个区块都报告成在凭空创造价值。
  • 差分测试。一个刻意写得朴素的第二份折叠实现,为显而易见而非为速度而写,与真正那份对拍模糊测试。出现分歧就是发布的阻断项。
  • 对抗性模拟。跳过风暴、保证金抽干竞争、往里塞丢弃项的矿工、重组酷刑、时间戳操纵、轻量日蚀转发场景。场景配置都已提交入库;每次运行都可由种子复现。
  • 并发,且是刻意的。每一个被不止一个 goroutine 触及的组件,都有一个本身就是并发的测试,其形态与进程实际使用的形态一致。在一个单 goroutine 的测试集上跑 -race 什么也测不到,却会报告成功。
  • 混沌浸泡测试。真实的节点进程通过真实的套接字,经由一个注入延迟、抖动、丢包和分区的代理进行通信,同时节点会被随机地以 SIGKILL 杀掉。正是这个面找出了整套 -race 测试集都漏掉的那个数据竞争。

创世是一件产物,不是一场仪式#

zcd genesis 会产出创世区块 — 空状态、已初始化的信标存储单元、 空的已花费注册表、没有任何形式的分配 — 以及它的 id。已宣布的 发布会提前数周就对代码标签、参数哈希、创世 id 和 发布时间作出承诺。任何人都可以在任何机器上、从源代码出发,用毫秒级的时间把这四样全部重建出来。 除此之外没有别的东西需要信任。